diff --git a/block-buffer/CHANGELOG.md b/block-buffer/CHANGELOG.md index fc5ff8c4..7315efce 100644 --- a/block-buffer/CHANGELOG.md +++ b/block-buffer/CHANGELOG.md @@ -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]) diff --git a/block-buffer/src/lib.rs b/block-buffer/src/lib.rs index 6f8ac67c..be7c3658 100644 --- a/block-buffer/src/lib.rs +++ b/block-buffer/src/lib.rs @@ -184,12 +184,18 @@ impl BlockBuffer { 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::().add(pos); + let buf_ptr = buf.as_mut_ptr().cast::().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)); } @@ -364,6 +370,7 @@ impl BlockBuffer { mut compress: impl FnMut(&Array), ) { assert!(suffix.len() <= BS::USIZE, "suffix is too long"); + let pos = self.get_pos(); let mut buf = self.pad_with_zeros(); buf[pos] = delim; @@ -422,3 +429,12 @@ impl Drop for BlockBuffer { #[cfg(feature = "zeroize")] impl ZeroizeOnDrop for BlockBuffer {} + +/// Resets the referenced buffer on drop. +struct ResetGuard<'a, BS: BlockSizes, K: BufferKind>(&'a mut BlockBuffer); + +impl Drop for ResetGuard<'_, BS, K> { + fn drop(&mut self) { + self.0.reset(); + } +} diff --git a/block-buffer/src/read.rs b/block-buffer/src/read.rs index f72aabd6..1bd016e2 100644 --- a/block-buffer/src/read.rs +++ b/block-buffer/src/read.rs @@ -110,8 +110,16 @@ impl ReadBuffer { } 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) }; @@ -180,3 +188,12 @@ impl Drop for ReadBuffer { #[cfg(feature = "zeroize")] impl zeroize::ZeroizeOnDrop for ReadBuffer {} + +/// Resets the referenced buffer on drop. +struct ResetGuard<'a, BS: BlockSizes>(&'a mut ReadBuffer); + +impl Drop for ResetGuard<'_, BS> { + fn drop(&mut self) { + self.0.reset(); + } +} diff --git a/block-buffer/tests/mod.rs b/block-buffer/tests/mod.rs index 9a4e5ca5..436b44c0 100644 --- a/block-buffer/tests/mod.rs +++ b/block-buffer/tests/mod.rs @@ -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::::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::::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(); +}