From a1d7b961e7ae1b1c940811e6f39e65f1d1120c4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D1=80=D1=82=D1=91=D0=BC=20=D0=9F=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=BE=D0=B2=20=5BArtyom=20Pavlov=5D?= Date: Mon, 8 Jun 2026 16:54:59 +0300 Subject: [PATCH 1/3] block-buffer: fix exception safety --- block-buffer/src/lib.rs | 20 ++++++++++++++++++-- block-buffer/src/read.rs | 18 ++++++++++++++++-- block-buffer/tests/mod.rs | 32 ++++++++++++++++++++++++++++++++ 3 files changed, 66 insertions(+), 4 deletions(-) diff --git a/block-buffer/src/lib.rs b/block-buffer/src/lib.rs index 6f8ac67c..e8a6f2fe 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 we may temporarily break the 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..6c7138bb 100644 --- a/block-buffer/src/read.rs +++ b/block-buffer/src/read.rs @@ -110,8 +110,13 @@ 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; + + 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 +185,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(); +} From f545dfc1d41ed96d96f9a7eff5794f7171de75a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D1=80=D1=82=D1=91=D0=BC=20=D0=9F=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=BE=D0=B2=20=5BArtyom=20Pavlov=5D?= Date: Mon, 8 Jun 2026 17:00:03 +0300 Subject: [PATCH 2/3] Update changelog --- block-buffer/CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) 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]) From cdb0690966351fa558986d73e62dccb1fa3362f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D1=80=D1=82=D1=91=D0=BC=20=D0=9F=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=BE=D0=B2=20=5BArtyom=20Pavlov=5D?= Date: Mon, 8 Jun 2026 17:08:27 +0300 Subject: [PATCH 3/3] tweak docs --- block-buffer/src/lib.rs | 6 +++--- block-buffer/src/read.rs | 3 +++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/block-buffer/src/lib.rs b/block-buffer/src/lib.rs index e8a6f2fe..be7c3658 100644 --- a/block-buffer/src/lib.rs +++ b/block-buffer/src/lib.rs @@ -189,9 +189,9 @@ impl BlockBuffer { 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 we may temporarily break the invariant, but we reset the buffer - // immediately after `compress` using `Drop` impl of `ResetGuard`, so this code - // is safe even if `compress` panics. + // 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 = buf.as_mut_ptr().cast::().add(pos); ptr::copy_nonoverlapping(left.as_ptr(), buf_ptr, left.len()); diff --git a/block-buffer/src/read.rs b/block-buffer/src/read.rs index 6c7138bb..1bd016e2 100644 --- a/block-buffer/src/read.rs +++ b/block-buffer/src/read.rs @@ -113,6 +113,9 @@ impl ReadBuffer { 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]);