Document unsafe contracts and stop reusing poisoned state
This commit is contained in:
1 parent
a7fcd69897
commit
f2e728b503
46 files changed
+377
-45
No files matched your search
@@ -22,6 +22,7 @@ impl Deadline {
|
||||
if fd < 0 {
|
||||
return Err(io::Error::last_os_error().into());
|
||||
}
|
||||
// SAFETY: timerfd_create succeeded above; this fresh descriptor transfers to one OwnedFd.
|
||||
let fd = unsafe { OwnedFd::from_raw_fd(fd) };
|
||||
let token = handle.insert_source(
|
||||
Generic::new(fd.try_clone()?, Interest::READ, Mode::Level),
|
||||
@@ -113,11 +114,14 @@ mod tests {
|
||||
assert_eq!(unsafe { libc::poll(&raw mut pollfd, 1, 500) }, 1);
|
||||
let mut counter = 0_u64;
|
||||
assert_eq!(
|
||||
// SAFETY: counter is writable for the requested eight bytes and deadline owns the
|
||||
// timer descriptor.
|
||||
unsafe { libc::read(deadline.fd.as_raw_fd(), (&raw mut counter).cast(), 8) },
|
||||
8
|
||||
);
|
||||
assert_eq!(counter, 1);
|
||||
deadline.update(None).unwrap();
|
||||
// SAFETY: pollfd is a live writable pollfd and the entry count is one.
|
||||
assert_eq!(unsafe { libc::poll(&raw mut pollfd, 1, 0) }, 0);
|
||||
}
|
||||
}
|
||||
@@ -187,8 +187,12 @@ mod tests {
|
||||
.unwrap()
|
||||
.last()
|
||||
.expect("an EGL device");
|
||||
// SAFETY: The native display comes from a live enumerated EGLDevice and its lifetime is
|
||||
// retained by the EGL objects created here.
|
||||
let display = unsafe { EGLDisplay::new(device).unwrap() };
|
||||
let context = EGLContext::new(&display).unwrap();
|
||||
// SAFETY: The fresh EGL context is exclusively owned by this renderer; this test issues
|
||||
// GL work only through its renderer methods.
|
||||
let mut renderer = unsafe { GlesRenderer::new(context).unwrap() };
|
||||
let output = Output::new(
|
||||
"capture".into(),
|
||||
|
||||
@@ -139,8 +139,12 @@ mod tests {
|
||||
use smithay::backend::renderer::element::{Id, Kind};
|
||||
use smithay::backend::renderer::{Color32F, ExportMem};
|
||||
let device = EGLDevice::enumerate().unwrap().last().expect("EGL device");
|
||||
// SAFETY: The native display comes from a live enumerated EGLDevice and its lifetime is
|
||||
// retained by the EGL objects created here.
|
||||
let display = unsafe { EGLDisplay::new(device).unwrap() };
|
||||
let context = EGLContext::new(&display).unwrap();
|
||||
// SAFETY: The fresh EGL context is exclusively owned by this renderer; this test issues
|
||||
// GL work only through its renderer methods.
|
||||
let mut renderer = unsafe { GlesRenderer::new(context).unwrap() };
|
||||
let output = |name: &str| {
|
||||
Output::new(
|
||||
|
||||
@@ -278,6 +278,7 @@ mod tests {
|
||||
runner.reconcile(&[config], SessionPolicy::Desktop);
|
||||
assert!(runner.0.is_empty());
|
||||
let deadline = Instant::now() + Duration::from_secs(2);
|
||||
// SAFETY: kill with signal zero only probes an integer PID and has no pointer arguments.
|
||||
while unsafe { libc::kill(pid as i32, 0) } == 0 {
|
||||
assert!(
|
||||
Instant::now() < deadline,
|
||||
|
||||
@@ -306,6 +306,8 @@ mod tests {
|
||||
queue.process(&1, |_| failures += 1);
|
||||
// SAFETY: F_GETFD observes descriptor validity without modifying it.
|
||||
assert_eq!(unsafe { libc::fcntl(fd_removed, libc::F_GETFD) }, -1);
|
||||
// SAFETY: F_GETFD observes an integer descriptor without modifying it or dereferencing
|
||||
// caller memory.
|
||||
assert_ne!(unsafe { libc::fcntl(fd_expired, libc::F_GETFD) }, -1);
|
||||
queue.remove_if(
|
||||
|entry| entry.deadline <= now + MAX_WAIT,
|
||||
@@ -315,6 +317,8 @@ mod tests {
|
||||
|entry| entry.deadline <= now + MAX_WAIT + Duration::from_secs(1),
|
||||
|_| failures += 1,
|
||||
);
|
||||
// SAFETY: F_GETFD observes an integer descriptor without modifying it or dereferencing
|
||||
// caller memory.
|
||||
assert_eq!(unsafe { libc::fcntl(fd_expired, libc::F_GETFD) }, -1);
|
||||
assert_eq!(failures, 2);
|
||||
assert!(queue.deadline().is_none());
|
||||
|
||||
@@ -195,6 +195,7 @@ fn private_directory() -> Result<PathBuf, String> {
|
||||
}
|
||||
Ok(metadata) => {
|
||||
use std::os::unix::fs::MetadataExt;
|
||||
// SAFETY: geteuid has no pointer arguments or caller-side preconditions.
|
||||
if !metadata.is_dir() || metadata.uid() != unsafe { libc::geteuid() } {
|
||||
return Err("Refusing to stage screenshots in an untrusted directory".into());
|
||||
}
|
||||
|
||||
@@ -541,6 +541,8 @@ pub(crate) mod tests {
|
||||
assert_eq!(after, before, "rejected snapshot wrote private pixels");
|
||||
}
|
||||
// Restore the client's colored buffer for subsequent renderer checks.
|
||||
// SAFETY: before was copied from this checked SHM layout, so the destination has
|
||||
// before.len() writable bytes and does not overlap the owned backup.
|
||||
unsafe {
|
||||
std::ptr::copy_nonoverlapping(
|
||||
before.as_ptr(),
|
||||
|
||||
@@ -1547,6 +1547,7 @@ mod tests {
|
||||
|
||||
let mut events = smithay::reexports::calloop::EventLoop::try_new().unwrap();
|
||||
let mut state = crate::startup_tests::state(&mut events);
|
||||
// SAFETY: gettid has no pointer arguments or caller-side preconditions.
|
||||
let children_path = format!("/proc/self/task/{}/children", unsafe { libc::gettid() });
|
||||
assert!(
|
||||
std::fs::read_to_string(&children_path)
|
||||
@@ -1585,6 +1586,8 @@ mod tests {
|
||||
for pid in children {
|
||||
let mut status = 0;
|
||||
assert_eq!(
|
||||
// SAFETY: status is a live writable integer; waitpid uses only a child PID and
|
||||
// WNOHANG.
|
||||
unsafe { libc::waitpid(pid, &mut status, libc::WNOHANG) },
|
||||
-1
|
||||
);
|
||||
|
||||
@@ -214,6 +214,8 @@ fn pidfd(pid: u32) -> io::Result<OwnedFd> {
|
||||
if fd < 0 {
|
||||
Err(io::Error::last_os_error())
|
||||
} else {
|
||||
// SAFETY: pidfd_open succeeded above and returned a fresh descriptor whose sole ownership
|
||||
// transfers here.
|
||||
Ok(unsafe { OwnedFd::from_raw_fd(fd as i32) })
|
||||
}
|
||||
}
|
||||
@@ -260,6 +262,7 @@ fn credentials(stream: &UnixStream) -> io::Result<libc::ucred> {
|
||||
}
|
||||
pub(crate) fn inspect(pid: u32) -> io::Result<(Owner, OwnedFd, Vec<String>)> {
|
||||
use std::os::unix::fs::MetadataExt;
|
||||
// SAFETY: geteuid has no pointer arguments or caller-side preconditions.
|
||||
if std::fs::metadata(format!("/proc/{pid}"))?.uid() != unsafe { libc::geteuid() } {
|
||||
return Err(io::Error::other("process belongs to another user"));
|
||||
}
|
||||
@@ -395,6 +398,7 @@ mod tests {
|
||||
let (peer, _) = monitor.verify(&left, false).unwrap();
|
||||
assert_eq!(peer.owner.pid, std::process::id());
|
||||
let (parent, _) = monitor.verify(&left, true).unwrap();
|
||||
// SAFETY: getppid has no pointer arguments or caller-side preconditions.
|
||||
assert_eq!(parent.owner.pid, unsafe { libc::getppid() } as u32);
|
||||
let mut child = std::process::Command::new("sleep")
|
||||
.arg("0.05")
|
||||
|
||||
@@ -230,6 +230,8 @@ if os.environ['FERESE_CLIENT_RESTART'] == '1':
|
||||
rows[1].split_whitespace().next()
|
||||
);
|
||||
let child_pid = owner.borrow().child.as_ref().unwrap().id();
|
||||
// SAFETY: kill has no pointer arguments and the supervisor retains the unreaped child
|
||||
// owning this PID.
|
||||
unsafe {
|
||||
libc::kill(child_pid as i32, libc::SIGKILL);
|
||||
}
|
||||
|
||||
@@ -197,7 +197,11 @@ fn renderer() -> GlesRenderer {
|
||||
.into_iter()
|
||||
.find(|device| device.is_software() == software)
|
||||
.expect("the requested EGL device");
|
||||
// SAFETY: The native display comes from a live enumerated EGLDevice and its lifetime is
|
||||
// retained by the EGL objects created here.
|
||||
let display = unsafe { EGLDisplay::new(device).unwrap() };
|
||||
// SAFETY: The fresh EGL context is exclusively owned by this renderer; this test issues GL
|
||||
// work only through its renderer methods.
|
||||
unsafe { GlesRenderer::new(EGLContext::new(&display).unwrap()).unwrap() }
|
||||
}
|
||||
|
||||
@@ -207,6 +211,8 @@ fn window_corner_shader_timing_sample() {
|
||||
let mut renderer = renderer();
|
||||
let driver = renderer
|
||||
.with_context(|gl| {
|
||||
// SAFETY: The current renderer context returns a live NUL-terminated GL_RENDERER
|
||||
// string, copied before leaving with_context.
|
||||
unsafe { std::ffi::CStr::from_ptr(gl.GetString(0x1f01).cast()) }
|
||||
.to_string_lossy()
|
||||
.into_owned()
|
||||
|
||||
@@ -282,6 +282,9 @@ impl RenderElement<GlesRenderer> for BlurRenderElement {
|
||||
// clean captured scene. Recapturing a partly repainted framebuffer
|
||||
// would feed previous blurred pixels back into the blur.
|
||||
if self.capture_dirty.load(Ordering::Relaxed) {
|
||||
// SAFETY: with_context makes this frame context current. GL writes into live local
|
||||
// scalars, and the capture texture belongs to this renderer with the allocated
|
||||
// framebuffer bounds.
|
||||
let error = frame.with_context(|gl| unsafe {
|
||||
gl.GetError();
|
||||
let mut framebuffer = 0;
|
||||
@@ -690,8 +693,12 @@ mod tests {
|
||||
.unwrap()
|
||||
.last()
|
||||
.expect("an EGL device");
|
||||
// SAFETY: The native display comes from a live enumerated EGLDevice and its lifetime is
|
||||
// retained by the EGL objects created here.
|
||||
let display = unsafe { EGLDisplay::new(device).unwrap() };
|
||||
let context = EGLContext::new(&display).unwrap();
|
||||
// SAFETY: The fresh EGL context is exclusively owned by this renderer; this test issues
|
||||
// GL work only through its renderer methods.
|
||||
let mut renderer = unsafe { GlesRenderer::new(context).unwrap() };
|
||||
let program = renderer
|
||||
.compile_custom_pixel_shader(
|
||||
@@ -755,8 +762,12 @@ mod tests {
|
||||
.unwrap()
|
||||
.last()
|
||||
.expect("an EGL device");
|
||||
// SAFETY: The native display comes from a live enumerated EGLDevice and its lifetime is
|
||||
// retained by the EGL objects created here.
|
||||
let display = unsafe { EGLDisplay::new(device).unwrap() };
|
||||
let context = EGLContext::new(&display).unwrap();
|
||||
// SAFETY: The fresh EGL context is exclusively owned by this renderer; this test issues
|
||||
// GL work only through its renderer methods.
|
||||
let mut renderer = unsafe { GlesRenderer::new(context).unwrap() };
|
||||
for source in [super::ROUNDED_TEXTURE_SHADER, super::BLUR_SHADER] {
|
||||
renderer
|
||||
@@ -789,8 +800,12 @@ mod tests {
|
||||
.unwrap()
|
||||
.last()
|
||||
.expect("an EGL device");
|
||||
// SAFETY: The native display comes from a live enumerated EGLDevice and its lifetime is
|
||||
// retained by the EGL objects created here.
|
||||
let display = unsafe { EGLDisplay::new(device).unwrap() };
|
||||
let context = EGLContext::new(&display).unwrap();
|
||||
// SAFETY: The fresh EGL context is exclusively owned by this renderer; this test issues
|
||||
// GL work only through its renderer methods.
|
||||
let mut renderer = unsafe { GlesRenderer::new(context).unwrap() };
|
||||
let program = renderer
|
||||
.compile_custom_texture_shader(
|
||||
|
||||
@@ -258,8 +258,14 @@ mod tests {
|
||||
.unwrap()
|
||||
.last()
|
||||
.expect("an EGL device");
|
||||
// SAFETY: The native display comes from a live enumerated EGLDevice and its lifetime is
|
||||
// retained by the EGL objects created here.
|
||||
let display = unsafe { EGLDisplay::new(device).unwrap() };
|
||||
// SAFETY: The fresh EGL context is exclusively owned by this renderer; this test issues
|
||||
// GL work only through its renderer methods.
|
||||
let mut first = unsafe { GlesRenderer::new(EGLContext::new(&display).unwrap()).unwrap() };
|
||||
// SAFETY: The fresh EGL context is exclusively owned by this renderer; this test issues
|
||||
// GL work only through its renderer methods.
|
||||
let mut second = unsafe { GlesRenderer::new(EGLContext::new(&display).unwrap()).unwrap() };
|
||||
let first_id = first.context_id().erased();
|
||||
let second_id = second.context_id().erased();
|
||||
|
||||
@@ -175,8 +175,12 @@ mod tests {
|
||||
.unwrap()
|
||||
.last()
|
||||
.expect("an EGL device");
|
||||
// SAFETY: The native display comes from a live enumerated EGLDevice and its lifetime is
|
||||
// retained by the EGL objects created here.
|
||||
let display = unsafe { EGLDisplay::new(device).unwrap() };
|
||||
let mut renderer =
|
||||
// SAFETY: The fresh EGL context is exclusively owned by this renderer; this test
|
||||
// issues GL work only through its renderer methods.
|
||||
unsafe { GlesRenderer::new(EGLContext::new(&display).unwrap()).unwrap() };
|
||||
let program = renderer
|
||||
.compile_custom_pixel_shader(
|
||||
|
||||
@@ -99,8 +99,12 @@ mod tests {
|
||||
use smithay::backend::egl::{EGLContext, EGLDevice, EGLDisplay};
|
||||
use smithay::output::{Mode, PhysicalProperties, Subpixel};
|
||||
let device = EGLDevice::enumerate().unwrap().last().expect("EGL device");
|
||||
// SAFETY: The native display comes from a live enumerated EGLDevice and its lifetime is
|
||||
// retained by the EGL objects created here.
|
||||
let display = unsafe { EGLDisplay::new(device).unwrap() };
|
||||
let context = EGLContext::new(&display).unwrap();
|
||||
// SAFETY: The fresh EGL context is exclusively owned by this renderer; this test issues
|
||||
// GL work only through its renderer methods.
|
||||
let mut renderer = unsafe { GlesRenderer::new(context).unwrap() };
|
||||
let output = Output::new(
|
||||
"snapshot-test".into(),
|
||||
|
||||
@@ -269,12 +269,18 @@ fn launched_clients_unblock_termination_signals_and_keep_private_sockets() {
|
||||
}
|
||||
|
||||
// Child setup must not unblock the compositor's own signalfd mask.
|
||||
// SAFETY: sigset_t is writable C storage initialized before pthread_sigmask fills its
|
||||
// contents.
|
||||
let mut mask = unsafe { std::mem::zeroed() };
|
||||
assert_eq!(
|
||||
// SAFETY: mask is writable sigset_t storage; a null input set queries the mask without
|
||||
// changing it.
|
||||
unsafe { libc::pthread_sigmask(libc::SIG_BLOCK, std::ptr::null(), &mut mask) },
|
||||
0
|
||||
);
|
||||
// SAFETY: pthread_sigmask initialized mask above and SIGINT/SIGTERM are valid signal numbers.
|
||||
assert_eq!(unsafe { libc::sigismember(&mask, libc::SIGINT) }, 1);
|
||||
// SAFETY: pthread_sigmask initialized mask above and SIGINT/SIGTERM are valid signal numbers.
|
||||
assert_eq!(unsafe { libc::sigismember(&mask, libc::SIGTERM) }, 1);
|
||||
drop(signals);
|
||||
}
|
||||
@@ -308,6 +314,8 @@ fn explicit_client_exit_is_reaped_without_stopping_the_compositor() {
|
||||
|
||||
let mut status = 0;
|
||||
assert_eq!(
|
||||
// SAFETY: status is a live writable integer and pid names this fixture child; WNOHANG
|
||||
// cannot block.
|
||||
unsafe { libc::waitpid(pid as i32, &mut status, libc::WNOHANG) },
|
||||
-1
|
||||
);
|
||||
|
||||
@@ -343,7 +343,11 @@ fn lock_scene_draws_the_pointer_and_delivers_client_cursor_frames() {
|
||||
.set_location((20.0, 20.0).into());
|
||||
|
||||
let device = EGLDevice::enumerate().unwrap().last().expect("EGL device");
|
||||
// SAFETY: The native display comes from a live enumerated EGLDevice and its lifetime is
|
||||
// retained by the EGL objects created here.
|
||||
let display = unsafe { EGLDisplay::new(device).unwrap() };
|
||||
// SAFETY: The fresh EGL context is exclusively owned by this renderer; this test issues GL
|
||||
// work only through its renderer methods.
|
||||
let mut renderer = unsafe { GlesRenderer::new(EGLContext::new(&display).unwrap()).unwrap() };
|
||||
let mut texture =
|
||||
Offscreen::<GlesTexture>::create_buffer(&mut renderer, Fourcc::Abgr8888, (320, 240).into())
|
||||
@@ -507,8 +511,12 @@ fn capture_privacy_pixels_and_policy_transitions() {
|
||||
let id = state.windows.ids()[&secret];
|
||||
assert!(state.window_content_ready(&secret));
|
||||
let device = EGLDevice::enumerate().unwrap().last().expect("EGL device");
|
||||
// SAFETY: The native display comes from a live enumerated EGLDevice and its lifetime is
|
||||
// retained by the EGL objects created here.
|
||||
let display = unsafe { EGLDisplay::new(device).unwrap() };
|
||||
let context = EGLContext::new(&display).unwrap();
|
||||
// SAFETY: The fresh EGL context is exclusively owned by this renderer; this test issues GL
|
||||
// work only through its renderer methods.
|
||||
let mut renderer = unsafe { GlesRenderer::new(context).unwrap() };
|
||||
let mut display_texture =
|
||||
Offscreen::<GlesTexture>::create_buffer(&mut renderer, Fourcc::Abgr8888, (320, 240).into())
|
||||
@@ -1107,8 +1115,12 @@ fn resize_dependencies_protocol_and_pixels() {
|
||||
"sampling mutated authoritative state"
|
||||
);
|
||||
let device = EGLDevice::enumerate().unwrap().last().expect("EGL device");
|
||||
// SAFETY: The native display comes from a live enumerated EGLDevice and its lifetime is
|
||||
// retained by the EGL objects created here.
|
||||
let display = unsafe { EGLDisplay::new(device).unwrap() };
|
||||
let context = EGLContext::new(&display).unwrap();
|
||||
// SAFETY: The fresh EGL context is exclusively owned by this renderer; this test issues GL
|
||||
// work only through its renderer methods.
|
||||
let mut renderer = unsafe { GlesRenderer::new(context).unwrap() };
|
||||
let mut display_texture =
|
||||
Offscreen::<GlesTexture>::create_buffer(&mut renderer, Fourcc::Abgr8888, (320, 240).into())
|
||||
|
||||
@@ -290,11 +290,15 @@ impl Engine {
|
||||
|
||||
pub(crate) fn arm_clock(&self, next: Option<Timestamp>) -> io::Result<()> {
|
||||
let Some(fd) = &self.clock else { return Ok(()) };
|
||||
// SAFETY: itimerspec contains only integer timespec fields, for which zero is a valid
|
||||
// disarmed timer.
|
||||
let mut timer: libc::itimerspec = unsafe { std::mem::zeroed() };
|
||||
if let Some(next) = next {
|
||||
timer.it_value.tv_sec = next.as_second();
|
||||
timer.it_value.tv_nsec = i64::from(next.subsec_nanosecond());
|
||||
}
|
||||
// SAFETY: fd retains the timer descriptor and timer is fully initialized; the optional
|
||||
// previous-value pointer may be null.
|
||||
let result = unsafe {
|
||||
libc::timerfd_settime(
|
||||
fd.as_raw_fd(),
|
||||
@@ -316,12 +320,16 @@ pub(crate) fn init(
|
||||
state: &mut Ferese,
|
||||
candidate: Candidate,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
// SAFETY: timerfd_create takes scalar arguments and its result is checked before ownership is
|
||||
// adopted.
|
||||
let clock = unsafe {
|
||||
libc::timerfd_create(libc::CLOCK_REALTIME, libc::TFD_CLOEXEC | libc::TFD_NONBLOCK)
|
||||
};
|
||||
if clock < 0 {
|
||||
return Err(io::Error::last_os_error().into());
|
||||
}
|
||||
// SAFETY: timerfd_create returned a fresh nonnegative descriptor whose ownership transfers to
|
||||
// OwnedFd.
|
||||
let clock = unsafe { OwnedFd::from_raw_fd(clock) };
|
||||
let source = clock.try_clone()?;
|
||||
state.theme_engine.clock = Some(clock);
|
||||
@@ -330,6 +338,8 @@ pub(crate) fn init(
|
||||
|_, _, state| {
|
||||
if let Some(clock) = &state.theme_engine.clock {
|
||||
let mut expirations = 0u64;
|
||||
// SAFETY: expirations is writable for the requested eight bytes and clock owns
|
||||
// the descriptor.
|
||||
unsafe { libc::read(clock.as_raw_fd(), (&mut expirations as *mut u64).cast(), 8) };
|
||||
}
|
||||
state.refresh_theme();
|
||||
@@ -354,6 +364,8 @@ pub(crate) fn init(
|
||||
use std::io::BufRead;
|
||||
use std::os::unix::process::CommandExt;
|
||||
let mut command = crate::process::command("gsettings");
|
||||
// SAFETY: The pre_exec hook only invokes async-signal-safe prctl and constructs an OS
|
||||
// error on failure, without allocation or locks.
|
||||
unsafe {
|
||||
command.pre_exec(|| {
|
||||
if libc::prctl(libc::PR_SET_PDEATHSIG, libc::SIGTERM) == -1 {
|
||||
|
||||
@@ -14,6 +14,8 @@ pub(super) fn load(path: &Path) -> Result<Pixels, String> {
|
||||
// them here, after decode has dropped its temporaries, on the worker only.
|
||||
// The returned pixels have their own mapping and are unaffected by trimming.
|
||||
#[cfg(all(target_os = "linux", target_env = "gnu"))]
|
||||
// SAFETY: glibc malloc_trim is thread-safe and does not invalidate live pixel mappings or
|
||||
// allocations.
|
||||
unsafe {
|
||||
libc::malloc_trim(0);
|
||||
}
|
||||
|
||||
@@ -488,8 +488,12 @@ mod tests {
|
||||
let mut events = EventLoop::try_new().unwrap();
|
||||
let mut state = crate::startup_tests::state(&mut events);
|
||||
let display =
|
||||
// SAFETY: The native display comes from a live enumerated EGLDevice and its lifetime
|
||||
// is retained by the EGL objects created here.
|
||||
unsafe { EGLDisplay::new(EGLDevice::enumerate().unwrap().last().unwrap()).unwrap() };
|
||||
let context = EGLContext::new(&display).unwrap();
|
||||
// SAFETY: The fresh EGL context is exclusively owned by this renderer; this test issues
|
||||
// GL work only through its renderer methods.
|
||||
let mut renderer = unsafe { GlesRenderer::new(context).unwrap() };
|
||||
let output = Output::new(
|
||||
"nested".into(),
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in new issue
Block a user