Skip to content

Commit b48d072

Browse files
authored
inout: fix Miri Stacked Borrows violation in PaddedInOutBuf (#1556)
1 parent 34a6ebc commit b48d072

4 files changed

Lines changed: 75 additions & 52 deletions

File tree

‎.github/workflows/inout.yml‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,3 +68,14 @@ jobs:
6868
- run: cargo test
6969
- run: cargo test --features block-padding
7070
- run: cargo test --all-features
71+
72+
miri:
73+
runs-on: ubuntu-latest
74+
env:
75+
MIRIFLAGS: "-Zmiri-symbolic-alignment-check -Zmiri-strict-provenance"
76+
steps:
77+
- uses: actions/checkout@v7
78+
- uses: dtolnay/rust-toolchain@nightly
79+
with:
80+
components: miri
81+
- run: cargo miri test --all-features

‎inout/CHANGELOG.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77
## 0.2.3 (UNRELEASED)
88
### Fixed
99
- List of safety pre-conditions in `InOutBufReserved::from_raw` docs ([#1502])
10+
- Miri Stacked Borrows violation in `PaddedInOutBuf` ([#1556])
1011

1112
[#1502]: https://github.com/RustCrypto/utils/pull/1502
13+
[#1556]: https://github.com/RustCrypto/utils/pull/1556
1214

1315
## 0.2.2 (2025-12-27)
1416
### Changed

‎inout/src/reserved.rs‎

Lines changed: 31 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -174,91 +174,70 @@ impl<'inp, 'out> InOutBufReserved<'inp, 'out, u8> {
174174
P: Padding,
175175
BS: ArraySize,
176176
{
177-
let bs = BS::USIZE;
178-
let blocks_len = self.in_len / bs;
179-
180177
use block_padding::PaddedData;
181178
let (blocks, tail_block) = match P::pad_detached(self.get_in()) {
182179
PaddedData::Pad { blocks, tail_block } => (blocks, Some(tail_block)),
183180
PaddedData::NoPad { blocks } => (blocks, None),
184181
PaddedData::Error => return Err(PadError),
185182
};
186183

187-
assert_eq!(blocks.len(), blocks_len);
188-
189-
let out_len = self.out_len;
190-
let (in_ptr, out_ptr) = self.into_raw();
191-
192-
let blocks = unsafe {
193-
InOutBuf::from_raw(
194-
in_ptr.cast::<Array<u8, BS>>(),
195-
out_ptr.cast::<Array<u8, BS>>(),
196-
blocks_len,
197-
)
198-
};
199-
200-
let Some(tail_block) = tail_block else {
201-
let tail_inout = None;
202-
return Ok(PaddedInOutBuf { blocks, tail_inout });
203-
};
204-
205-
let blocks_byte_len = blocks_len * bs;
206-
let reserve_len = out_len - blocks_byte_len;
207-
if reserve_len < tail_block.len() {
208-
return Err(PadError);
209-
}
210-
// SAFETY: we checked that the out buffer has enough bytes in reserve
211-
let tail_out: &mut Array<u8, BS> = unsafe {
212-
let tail_out_ptr = out_ptr.add(blocks_byte_len);
213-
&mut *(tail_out_ptr.cast())
214-
};
184+
assert_eq!(blocks.len(), self.in_len / BS::USIZE);
215185

216-
let tail_inout = Some((tail_block, tail_out));
217-
218-
Ok(PaddedInOutBuf { blocks, tail_inout })
186+
Ok(PaddedInOutBuf {
187+
in_ptr: blocks.as_ptr(),
188+
out_ptr: self.out_ptr.cast(),
189+
blocks_len: blocks.len(),
190+
tail_block,
191+
_pd: PhantomData,
192+
})
219193
}
220194
}
221195

222196
/// Variant of [`InOutBuf`] with optional padded tail block.
223197
#[cfg(feature = "block-padding")]
224-
#[allow(clippy::type_complexity)]
225198
pub struct PaddedInOutBuf<'inp, 'out, BS: ArraySize> {
226-
blocks: InOutBuf<'inp, 'out, Array<u8, BS>>,
227-
tail_inout: Option<(Array<u8, BS>, &'out mut Array<u8, BS>)>,
199+
/// Pointer to the input blocks.
200+
in_ptr: *const Array<u8, BS>,
201+
/// Pointer to the output blocks (including tail block if present).
202+
out_ptr: *mut Array<u8, BS>,
203+
/// Number of full blocks (i.e. excluding the tail block if present).
204+
blocks_len: usize,
205+
/// Padded input tail.
206+
tail_block: Option<Array<u8, BS>>,
207+
_pd: PhantomData<(&'inp u8, &'out mut u8)>,
228208
}
229209

230210
#[cfg(feature = "block-padding")]
231211
impl<'out, BS: ArraySize> PaddedInOutBuf<'_, 'out, BS> {
232212
/// Get full blocks.
233213
#[inline(always)]
234214
pub fn get_blocks(&mut self) -> InOutBuf<'_, '_, Array<u8, BS>> {
235-
self.blocks.reborrow()
215+
unsafe { InOutBuf::from_raw(self.in_ptr, self.out_ptr, self.blocks_len) }
236216
}
237217

238218
/// Get padded tail block.
239219
///
240220
/// Most padding implementations always return `Some`.
241221
#[inline(always)]
242222
pub fn get_tail_block(&mut self) -> Option<InOut<'_, '_, Array<u8, BS>>> {
243-
self.tail_inout.as_mut().map(|(in_block, out_block)| {
244-
let in_block = &*in_block;
245-
let out_block = &mut **out_block;
246-
InOut::from((in_block, out_block))
223+
// SAFETY: if `tail` is `Some`, then `out_ptr` points to a buffer with length equal
224+
// to at least `blocks_len + 1` blocks, so it satisfies the safety conditions
225+
// required by `InOut::from_raw`.
226+
self.tail_block.as_ref().map(|in_block| unsafe {
227+
let tail_out_ptr = self.out_ptr.add(self.blocks_len);
228+
InOut::from_raw(in_block, tail_out_ptr)
247229
})
248230
}
249231

250232
/// Convert buffer into output slice.
251233
#[inline(always)]
252234
pub fn into_out(self) -> &'out [u8] {
253-
let total_blocks = if self.tail_inout.is_some() {
254-
self.blocks.len() + 1
255-
} else {
256-
self.blocks.len()
257-
};
258-
let res_len = BS::USIZE * total_blocks;
259-
let (_, out_ptr) = self.blocks.into_raw();
260-
// SAFETY: `res_len` is always valid for the output buffer since
261-
// it's checked during type construction
262-
unsafe { slice::from_raw_parts(out_ptr as *const u8, res_len) }
235+
let mut bytes_len = BS::USIZE * self.blocks_len;
236+
// If tail is present, add its length to the resulting buffer length.
237+
if self.tail_block.is_some() {
238+
bytes_len += BS::USIZE;
239+
}
240+
// SAFETY: `out_ptr` points to a valid memory with size of `bytes_len`
241+
unsafe { slice::from_raw_parts(self.out_ptr.cast(), bytes_len) }
263242
}
264243
}

‎inout/tests/reserved.rs‎

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
//! Tests for `InOutBufReserved`
2+
#![cfg(feature = "block-padding")]
3+
use hybrid_array::sizes::U16;
4+
use inout::{
5+
InOutBufReserved,
6+
block_padding::{Iso7816, Padding},
7+
};
8+
9+
#[test]
10+
fn padded_inout_buf_reserved() {
11+
let mut buf = [0xFF; 48];
12+
let inout_buf = InOutBufReserved::from_mut_slice(&mut buf, 46).unwrap();
13+
let mut padded_blocks = inout_buf.into_padded_blocks::<Iso7816, U16>().unwrap();
14+
15+
assert_eq!(padded_blocks.get_blocks().len(), 2);
16+
padded_blocks
17+
.get_blocks()
18+
.get_out()
19+
.iter()
20+
.flatten()
21+
.for_each(|x| assert_eq!(*x, 0xFF));
22+
23+
let Some(tail) = padded_blocks.get_tail_block() else {
24+
panic!("expected a tail block");
25+
};
26+
let arr_unpadded = Iso7816::unpad(tail.get_in()).unwrap();
27+
assert_eq!(arr_unpadded, &[0xFF; 14]);
28+
29+
let out = padded_blocks.into_out();
30+
assert_eq!(out, &[0xFF; 48]);
31+
}

0 commit comments

Comments
 (0)