Document unsafe contracts and stop reusing poisoned state

This commit is contained in:
Samuel Onoja committed 2026-10-22 01:28:03 +01:00
1 parent a7fcd69897
commit f2e728b503
46 files changed
+377 -45

No files matched your search

+5
View File
@@ -17,6 +17,8 @@ fn random_cookie() -> io::Result<[u8; 16]> {
let mut cookie = [0_u8; 16];
let mut filled = 0;
while filled < cookie.len() {
// SAFETY: The remaining cookie slice is writable for exactly the byte count passed to
// getrandom.
let count = unsafe {
libc::getrandom(
cookie[filled..].as_mut_ptr().cast(),
@@ -45,6 +47,8 @@ fn random_cookie() -> io::Result<[u8; 16]> {
fn hostname() -> io::Result<Vec<u8>> {
let mut buffer = [0_u8; 256];
// SAFETY: buffer is writable for exactly buffer.len() bytes; termination is checked before
// interpreting its contents.
if unsafe { libc::gethostname(buffer.as_mut_ptr().cast(), buffer.len()) } != 0 {
return Err(io::Error::last_os_error());
}
@@ -66,6 +70,7 @@ fn field(output: &mut impl Write, bytes: &[u8]) -> io::Result<()> {
impl AuthorityFile {
pub fn create(runtime_directory: &Path, display: u32) -> io::Result<Self> {
let metadata = fs::symlink_metadata(runtime_directory)?;
// SAFETY: geteuid has no pointer arguments or caller-side preconditions.
let uid = unsafe { libc::geteuid() };
if !metadata.is_dir()
|| metadata.file_type().is_symlink()
+3
View File
@@ -117,6 +117,7 @@ fn satellite_command(
}
// PR_SET_PDEATHSIG follows the spawning thread; spawn from the compositor thread.
// SAFETY: getpid has no pointer arguments or caller-side preconditions.
let expected_parent = unsafe { libc::getpid() };
// SAFETY: this child-only hook performs only raw, non-allocating
// Linux/POSIX operations on prevalidated descriptors. All formatting and
@@ -203,6 +204,8 @@ impl OwnedProcessGroup {
if self.reaped {
return Observation::Exited;
}
// SAFETY: siginfo_t is a C record whose scalar and pointer storage permits zero
// initialization before waitid fills it.
let mut info: libc::siginfo_t = unsafe { std::mem::zeroed() };
// SAFETY: `info` is a live, correctly aligned local; P_PID with our own
// pid, and WNOWAIT|WNOHANG leave the status claimable by `wait`.
@@ -275,6 +275,8 @@ fn satellite_receives_exactly_the_owned_listeners_and_no_extra_descriptors() {
reap(satellite);
// Positive control: with the flag cleared in the forked child, the same descriptor is visible.
// SAFETY: F_GETFD only reads flags for the live sentinel descriptor and takes no pointer
// argument.
let flags = unsafe { libc::fcntl(sentinel_fd, libc::F_GETFD) };
assert_ne!(flags, -1);
assert_ne!(
@@ -290,6 +292,8 @@ fn satellite_receives_exactly_the_owned_listeners_and_no_extra_descriptors() {
("FAKE_SATELLITE_SENTINEL_TARGET", &sentinel_target),
]);
child::test_hook::set_in_child(move || {
// SAFETY: The forked child uses async-signal-safe fcntl with scalar flags; its descriptor
// table is independent of the parent.
if unsafe { libc::fcntl(sentinel_fd, libc::F_SETFD, 0) } == -1 {
return Err(std::io::Error::last_os_error());
}
@@ -307,6 +311,8 @@ fn satellite_receives_exactly_the_owned_listeners_and_no_extra_descriptors() {
);
reap(satellite);
// SAFETY: F_GETFD only reads flags for the live sentinel descriptor and takes no pointer
// argument.
let flags = unsafe { libc::fcntl(sentinel_fd, libc::F_GETFD) };
assert_ne!(
flags & libc::FD_CLOEXEC,
+1
View File
@@ -806,6 +806,7 @@ pub(crate) fn runtime_directory() -> Result<PathBuf, String> {
fn validate_runtime_directory(path: &std::path::Path) -> Result<(), String> {
let metadata = std::fs::symlink_metadata(path)
.map_err(|error| format!("XDG_RUNTIME_DIR is unusable ({}): {error}", path.display()))?;
// SAFETY: geteuid has no pointer arguments or caller-side preconditions.
let uid = unsafe { libc::geteuid() };
if !metadata.is_dir()
|| metadata.file_type().is_symlink()
+63 -34
View File
@@ -70,6 +70,8 @@ impl ReadinessSocket {
pub fn recv_ready(&self, satellite_pid: u32) -> io::Result<bool> {
let mut buffer = [0_u8; 512];
let mut control = ControlBuffer {
// SAFETY: cmsghdr contains only integer fields and permits an all-zero header before
// recvmsg fills it.
header: unsafe { std::mem::zeroed() },
payload: [0_u8; CONTROL_CAPACITY],
};
@@ -77,6 +79,8 @@ impl ReadinessSocket {
iov_base: buffer.as_mut_ptr().cast(),
iov_len: buffer.len(),
};
// SAFETY: msghdr permits zero/null fields; all active buffer pointers and lengths are
// assigned before recvmsg.
let mut message: libc::msghdr = unsafe { std::mem::zeroed() };
message.msg_iov = std::ptr::from_mut(&mut iov);
message.msg_iovlen = 1;
@@ -220,9 +224,10 @@ mod tests {
/// Connecting first means the senders can use plain `sendmsg`, whose
/// glibc export carries no destination address.
///
/// # Safety
/// Async-signal-safe, for use between `fork` and `_exit`.
unsafe fn connect_datagram(path: &Path) -> libc::c_int {
/// Uses only async-signal-safe operations between `fork` and `_exit`.
fn connect_datagram(path: &Path) -> libc::c_int {
// SAFETY: All socket address storage is initialized and correctly sized. The connected
// descriptor is either returned to its sole owner or closed before returning an error.
unsafe {
let fd = libc::socket(libc::AF_UNIX, libc::SOCK_DGRAM, 0);
if fd < 0 {
@@ -243,7 +248,8 @@ mod tests {
std::ptr::from_ref(&address).cast(),
// The fixed `sockaddr_un` length is what the kernel expects for
// AF_UNIX; `sun_path` is already zero-filled past the name.
libc::socklen_t::try_from(std::mem::size_of::<libc::sockaddr_un>()).unwrap(),
libc::socklen_t::try_from(std::mem::size_of::<libc::sockaddr_un>())
.expect("sockaddr_un fits socklen_t"),
) == -1
{
libc::close(fd);
@@ -264,17 +270,27 @@ mod tests {
}
fn send_from_child(path: &Path, payload: &[u8]) -> i32 {
// SAFETY: the child path performs only socket/bind-free sendto and
// _exit, which are async-signal-safe.
// SAFETY: the child uses only raw socket/connect/send/_exit calls with
// inherited buffers, without allocation or locks.
unsafe {
let pid = libc::fork();
assert!(pid >= 0, "fork must succeed");
if pid == 0 {
let Ok(client) = UnixDatagram::unbound() else {
let fd = connect_datagram(path);
if fd < 0 {
libc::_exit(70);
};
let sent = client.send_to(payload, path);
libc::_exit(if sent.is_ok() { 0 } else { 71 });
}
let sent = libc::send(
fd,
payload.as_ptr().cast(),
payload.len(),
libc::MSG_NOSIGNAL,
);
libc::_exit(if sent == payload.len() as isize {
0
} else {
71
});
}
let mut status = 0;
libc::waitpid(pid, std::ptr::from_mut(&mut status), 0);
@@ -287,6 +303,8 @@ mod tests {
/// The child stays alive so the receiver can name its PID, and the extra
/// descriptor must be released by the receiver rather than leaked.
fn send_with_descriptor_from_child(path: &Path, sentinel: &Path) -> i32 {
let name = std::ffi::CString::new(sentinel.as_os_str().as_encoded_bytes())
.expect("test sentinel pathname contains no NUL");
// SAFETY: the child performs only socket/open/sendmsg/_exit, all
// async-signal-safe.
unsafe {
@@ -299,10 +317,6 @@ mod tests {
}
// A descriptor for a known file, so the receiver can prove it
// closed exactly this one rather than guessing at a count.
let Ok(name) = std::ffi::CString::new(sentinel.as_os_str().as_encoded_bytes())
else {
libc::_exit(73);
};
let spare = libc::open(name.as_ptr(), libc::O_RDONLY);
if spare < 0 {
libc::_exit(72);
@@ -396,8 +410,9 @@ mod tests {
);
let _ = std::fs::remove_file(&sentinel);
// SAFETY: reaping a direct child that has finished sleeping.
let mut status = 0;
// SAFETY: The status pointer is writable and the PID names a direct child whose wait
// status belongs to this test.
unsafe { libc::waitpid(pid, std::ptr::from_mut(&mut status), 0) };
}
@@ -414,8 +429,9 @@ mod tests {
"a credential that is not the first control message must still count"
);
// SAFETY: reaping a direct child that has finished sleeping.
let mut status = 0;
// SAFETY: The status pointer is writable and the PID names a direct child whose wait
// status belongs to this test.
unsafe { libc::waitpid(pid, std::ptr::from_mut(&mut status), 0) };
}
@@ -426,8 +442,14 @@ mod tests {
sentinel: &Path,
count: usize,
) -> i32 {
// SAFETY: the child performs only socket/open/sendmsg/sleep/_exit, all
// async-signal-safe except sleep, which is a bounded wait before exit.
let name = std::ffi::CString::new(sentinel.as_os_str().as_encoded_bytes())
.expect("test sentinel pathname contains no NUL");
assert!(
count <= 64,
"descriptor flood must fit the aligned control buffer"
);
// SAFETY: path allocation and bounds checks finish before fork. The child
// uses only async-signal-safe socket/open/sendmsg/sleep/_exit operations.
unsafe {
let pid = libc::fork();
assert!(pid >= 0, "fork must succeed");
@@ -436,10 +458,6 @@ mod tests {
if fd < 0 {
libc::_exit(70);
}
let Ok(name) = std::ffi::CString::new(sentinel.as_os_str().as_encoded_bytes())
else {
libc::_exit(73);
};
let sentinel_fd = libc::open(name.as_ptr(), libc::O_RDONLY);
if sentinel_fd < 0 {
libc::_exit(72);
@@ -455,12 +473,13 @@ mod tests {
};
// A control buffer large enough to *send* the flood; the
// receiver's much smaller buffer is what truncates it.
let mut control = [0_u8; 1024];
// cmsghdr storage also guarantees alignment for CMSG_FIRSTHDR.
let mut control: [libc::cmsghdr; 64] = std::mem::zeroed();
let mut message: libc::msghdr = std::mem::zeroed();
message.msg_iov = std::ptr::from_mut(&mut iov);
message.msg_iovlen = 1;
message.msg_control = control.as_mut_ptr().cast();
message.msg_controllen = control.len() as _;
message.msg_controllen = std::mem::size_of_val(&control) as _;
let header = libc::CMSG_FIRSTHDR(&message);
if header.is_null() {
libc::_exit(74);
@@ -493,6 +512,8 @@ mod tests {
/// Send `count` malformed datagrams, each carrying a descriptor.
fn send_malformed_storm_from_child(path: &Path, sentinel: &Path, count: usize) -> i32 {
let name = std::ffi::CString::new(sentinel.as_os_str().as_encoded_bytes())
.expect("test sentinel pathname contains no NUL");
// SAFETY: the child performs only socket/open/sendmsg/_exit, all
// async-signal-safe.
unsafe {
@@ -503,10 +524,6 @@ mod tests {
if fd < 0 {
libc::_exit(70);
}
let Ok(name) = std::ffi::CString::new(sentinel.as_os_str().as_encoded_bytes())
else {
libc::_exit(73);
};
let sentinel_fd = libc::open(name.as_ptr(), libc::O_RDONLY);
if sentinel_fd < 0 {
libc::_exit(72);
@@ -573,8 +590,9 @@ mod tests {
let before = open_fd_count();
// Far more descriptors than the receiver's 128-byte payload can hold.
let pid = send_descriptors_beyond_capacity_from_child(&path, &sentinel, 64);
// SAFETY: reaping a direct child that has finished sleeping.
let mut status = 0;
// SAFETY: The status pointer is writable and the PID names a direct child whose wait
// status belongs to this test.
unsafe { libc::waitpid(pid, std::ptr::from_mut(&mut status), 0) };
assert!(
libc::WIFEXITED(status) && libc::WEXITSTATUS(status) == 0,
@@ -688,8 +706,9 @@ mod tests {
);
let _ = std::fs::remove_file(&ready_sentinel);
// SAFETY: reaping a direct child that has finished sleeping.
let mut ready_status = 0;
// SAFETY: The status pointer is writable and the PID names a direct child whose wait
// status belongs to this test.
unsafe { libc::waitpid(ready_pid, std::ptr::from_mut(&mut ready_status), 0) };
}
@@ -856,15 +875,24 @@ mod tests {
let socket = ReadinessSocket::create(&runtime, 5).unwrap();
let path = socket.path().to_owned();
// SAFETY: The child uses only raw socket/send/usleep/_exit calls with inherited
// stack storage, without allocation or returning to the test harness.
let pid = unsafe {
let pid = libc::fork();
assert!(pid >= 0);
if pid == 0 {
let fd = connect_datagram(&path);
if fd < 0 {
libc::_exit(70);
}
let payload = b"RELOADING=1\nSTATUS=starting";
for _ in 0..500 {
if let Ok(client) = UnixDatagram::unbound()
&& client
.send_to(b"RELOADING=1\nSTATUS=starting", &path)
.is_ok()
if libc::send(
fd,
payload.as_ptr().cast(),
payload.len(),
libc::MSG_NOSIGNAL,
) == payload.len() as isize
{
libc::_exit(0);
}
@@ -889,6 +917,7 @@ mod tests {
std::thread::sleep(std::time::Duration::from_millis(2));
}
// SAFETY: status is writable and pid names the direct child created by this test.
unsafe {
let mut status = 0;
libc::waitpid(pid, std::ptr::from_mut(&mut status), 0);
+1
View File
@@ -59,6 +59,7 @@ impl Drop for CreatedPath {
fn validate_shared_directory(path: &Path) -> io::Result<()> {
let metadata = fs::symlink_metadata(path)?;
// SAFETY: geteuid has no pointer arguments or caller-side preconditions.
let uid = unsafe { libc::geteuid() };
if !metadata.is_dir()
|| metadata.file_type().is_symlink()