Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions block-buffer/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## 0.12.1 (UNRELEASED)
### Fixed
- Exception safety bug ([#1487])

[#1487]: https://github.com/RustCrypto/utils/pull/1487

## 0.12.0 (2026-02-24)
### Added
- `BlockSizes` trait implemented for sizes bigger than `U0` and smaller than `U256` ([#1455])
Expand Down
20 changes: 18 additions & 2 deletions block-buffer/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -184,12 +184,18 @@ impl<BS: BlockSizes, K: BufferKind> BlockBuffer<BS, K> {
if pos != 0 {
let (left, right) = input.split_at(rem);
input = right;

let g = ResetGuard(self);
let buf = &mut g.0.buffer;
// SAFETY: length of `left` is equal to number of remaining bytes in `buffer`,
// so we can copy data into it and process `buffer` as fully initialized block.
// Note that this code can temporarily break the eager buffer invariant,
// but we reset the buffer immediately after `compress` using `Drop` impl of
// `ResetGuard`, so this code is safe even if `compress` panics.
let block = unsafe {
let buf_ptr = self.buffer.as_mut_ptr().cast::<u8>().add(pos);
let buf_ptr = buf.as_mut_ptr().cast::<u8>().add(pos);
ptr::copy_nonoverlapping(left.as_ptr(), buf_ptr, left.len());
self.buffer.assume_init_ref()
buf.assume_init_ref()
};
compress(slice::from_ref(block));
}
Expand Down Expand Up @@ -364,6 +370,7 @@ impl<BS: BlockSizes> BlockBuffer<BS, Eager> {
mut compress: impl FnMut(&Array<u8, BS>),
) {
assert!(suffix.len() <= BS::USIZE, "suffix is too long");

let pos = self.get_pos();
let mut buf = self.pad_with_zeros();
buf[pos] = delim;
Expand Down Expand Up @@ -422,3 +429,12 @@ impl<BS: BlockSizes, K: BufferKind> Drop for BlockBuffer<BS, K> {

#[cfg(feature = "zeroize")]
impl<BS: BlockSizes, K: BufferKind> ZeroizeOnDrop for BlockBuffer<BS, K> {}

/// Resets the referenced buffer on drop.
struct ResetGuard<'a, BS: BlockSizes, K: BufferKind>(&'a mut BlockBuffer<BS, K>);

impl<BS: BlockSizes, K: BufferKind> Drop for ResetGuard<'_, BS, K> {
fn drop(&mut self) {
self.0.reset();
}
}
21 changes: 19 additions & 2 deletions block-buffer/src/read.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,8 +110,16 @@ impl<BS: BlockSizes> ReadBuffer<BS> {
}
assert!(read_len < BS::USIZE);

gen_block(&mut self.buffer);
read_fn(&self.buffer[..read_len]);
let g = ResetGuard(self);
let buf = &mut g.0.buffer;

// Note that generated block is likely to break the `ReadBuffer` invariant.
// We restore it using `set_pos_unchecked` below and in case if one of the closures
// panic the buffer gets reset by the guard.
gen_block(buf);
read_fn(&buf[..read_len]);

core::mem::forget(g);

// We checked that `read_len` satisfies the `set_pos_unchecked` safety contract
unsafe { self.set_pos_unchecked(read_len) };
Expand Down Expand Up @@ -180,3 +188,12 @@ impl<BS: BlockSizes> Drop for ReadBuffer<BS> {

#[cfg(feature = "zeroize")]
impl<BS: BlockSizes> zeroize::ZeroizeOnDrop for ReadBuffer<BS> {}

/// Resets the referenced buffer on drop.
struct ResetGuard<'a, BS: BlockSizes>(&'a mut ReadBuffer<BS>);

impl<BS: BlockSizes> Drop for ResetGuard<'_, BS> {
fn drop(&mut self) {
self.0.reset();
}
}
32 changes: 32 additions & 0 deletions block-buffer/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -344,3 +344,35 @@ fn test_read_serialize() {
let buf = Array([4, 0, 0, 1]);
assert!(Buf::deserialize(&buf).is_err());
}

#[test]
fn eager_buffer_exception_safety() {
let mut buf = EagerBuffer::<U4>::default();

let res = std::panic::catch_unwind(core::panic::AssertUnwindSafe(|| {
buf.digest_blocks(b"ab", |_| {});
buf.digest_blocks(b"cd", |_| panic!());
}));

assert!(res.is_err());
let _ = buf.get_pos();
}

#[test]
fn read_buffer_exception_safety() {
let mut buf = ReadBuffer::<U4>::default();

let res = std::panic::catch_unwind(core::panic::AssertUnwindSafe(|| {
buf.write_block(
1,
|block| {
block[0] = 0xFF;
panic!();
},
|_| {},
);
}));

assert!(res.is_err());
let _ = buf.get_pos();
}