From d93d1d2f2e90d6a42d100073cf48e01ae5dc952c Mon Sep 17 00:00:00 2001 From: Red Bear OS Date: Mon, 27 Jul 2026 20:46:18 +0900 Subject: [PATCH] buffer_pool: add F001 regression tests Three unit tests for the BufferPool zero-fill fix: 1. recycled_buffer_is_fully_zeroed_after_shrink: writes a 'secret', truncates to 8 bytes, drops to recycle, verifies the entire recycled buffer is zero. Catches the original v.fill(0)+set_len regression where the tail bytes were uninitialized. 2. recycled_buffer_zeroed_at_known_nonzero_size: writes 0xAB across the full buffer, truncates, recycles, verifies all bytes are 0. Catches the same regression from a different angle. 3. fresh_buffer_is_zeroed: trivial check that a brand-new buffer from a fresh pool is already zeroed (sanity check). The netstack crate is redox-target-only (cannot run on host due to redox_syscall/redox-scheme dependencies), but these tests serve as documentation of the expected behavior and can run in the canonical build-redbear.sh target test pass. --- netstack/src/buffer_pool.rs | 61 +++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/netstack/src/buffer_pool.rs b/netstack/src/buffer_pool.rs index 85de46e41d..a17f4fd272 100644 --- a/netstack/src/buffer_pool.rs +++ b/netstack/src/buffer_pool.rs @@ -104,3 +104,64 @@ impl BufferPool { } } } + +#[cfg(test)] +mod tests { + use super::*; + use std::io::Write; + + #[test] + fn recycled_buffer_is_fully_zeroed_after_shrink() { + // Regression test for the F001 information-disclosure fix: + // a recycled buffer must be fully zeroed across its entire + // capacity, including the part that was previously a + // shorter logical length (the shrink-then-recycle path that + // originally leaked stale packet data into the new flow). + let mut pool = BufferPool::new(256); + + // Round 1: write a "secret" then shrink. + let mut buf = pool.get_buffer(); + buf.as_mut_slice() + .write_all(b"top secret password: hunter2") + .unwrap(); + buf.as_mut_slice().truncate(8); + + // Recycle. + drop(buf); + let buf = pool.get_buffer(); + + // Round 2: every byte must be zero. + let zeros = buf.as_ref().iter().all(|&b| b == 0); + assert!(zeros, "recycled buffer is not fully zeroed"); + } + + #[test] + fn recycled_buffer_zeroed_at_known_nonzero_size() { + // Make sure a recycled buffer that was previously written + // with nonzero data and resized to 8 bytes still shows all + // zeros across its new capacity. This catches the previous + // v.fill(0) + set_len(capacity) implementation that only zeroed + // the old logical length. + let mut pool = BufferPool::new(64); + + let mut buf = pool.get_buffer(); + for i in 0..buf.as_ref().len() { + buf.as_mut_slice()[i] = 0xAB; + } + buf.as_mut_slice().truncate(32); + + drop(buf); + let buf = pool.get_buffer(); + for i in 0..buf.as_ref().len() { + assert_eq!(buf.as_ref()[i], 0, "byte {i} not zeroed"); + } + } + + #[test] + fn fresh_buffer_is_zeroed() { + // First use of a buffer from a fresh pool must be all-zero. + let mut pool = BufferPool::new(128); + let buf = pool.get_buffer(); + assert!(buf.as_ref().iter().all(|&b| b == 0)); + } +}