From 105ed59fa4998d14af890069a690c8f073e8698c Mon Sep 17 00:00:00 2001 From: adamiwaniec Date: Mon, 5 Oct 2026 15:12:25 -0500 Subject: [PATCH 1/2] fix: null-terminate path passed into shm_unlink prevents libc from reading past the allocation, causing leaks. this issue doesnt really cause problems until the heap has churned enough and the byte after the string ischanged to something other than 0 (possible to happen in normal runs but not often). reproduced the bug non-deterministically using the existing test suite, at least one leak happened in 5/10 runs. also removed the 'as *const i8' cast since CString::as_ptr returns this type already, which is caught by clippy as a now unnecessary cast. --- src/common/src/shm_buffer.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/common/src/shm_buffer.rs b/src/common/src/shm_buffer.rs index 3ea7c66..e70dfcb 100644 --- a/src/common/src/shm_buffer.rs +++ b/src/common/src/shm_buffer.rs @@ -78,8 +78,10 @@ impl Drop for ShmBuffer { unsafe { libc::munmap(self.shm_addr as *mut libc::c_void, self.shm_size); libc::close(self.shm_fd); - if self.is_creator { - libc::shm_unlink(self.shm_path.as_ptr() as *const i8); + if self.is_creator + && let Ok(path) = std::ffi::CString::new(self.shm_path.as_str()) + { + libc::shm_unlink(path.as_ptr()); } } } From f18310469e287b91af612315ea20861035f0af6c Mon Sep 17 00:00:00 2001 From: adamiwaniec Date: Mon, 5 Oct 2026 15:47:58 -0500 Subject: [PATCH 2/2] fix: reject wrapping offsets in ShmBuffer::at_offset at_offset added offset and size before comparing against shm_size, so a large offset wraps and passes the bounds check. debug builds panic on the overflow, release builds return a pointer outside the mapping. this issue cant possibly cause problems from normal use (offsets are never going to be large enough). but it lets a bad offset through instead of rejecting it to prevent future offset calculation bugs and attackers from utilizing this security vulnerability. use checked_add, and added a test that fails without the fix. --- src/common/src/shm_buffer.rs | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/common/src/shm_buffer.rs b/src/common/src/shm_buffer.rs index e70dfcb..6fd0f08 100644 --- a/src/common/src/shm_buffer.rs +++ b/src/common/src/shm_buffer.rs @@ -62,7 +62,8 @@ impl ShmBuffer { /// # Safety /// The caller must ensure the shm buffer is valid pub unsafe fn at_offset(&self, offset: u64, size: usize) -> Option<*mut u8> { - if offset + size as u64 > self.shm_size as u64 { + let end = offset.checked_add(size as u64)?; + if end > self.shm_size as u64 { return None; } Some((self.shm_addr + offset) as *mut u8) @@ -86,3 +87,23 @@ impl Drop for ShmBuffer { } } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn at_offset_bounds() { + let name = format!("/nixie-test-at-offset-{}", std::process::id()); + let buf = ShmBuffer::new(&name, 4096, true).expect("create shm"); + unsafe { + assert!(buf.at_offset(0, 4096).is_some()); + assert!(buf.at_offset(0, 4097).is_none()); + assert!(buf.at_offset(4096, 1).is_none()); + assert!( + buf.at_offset(u64::MAX, 16).is_none(), + "wrapping offset accepted" + ); + } + } +}