diff --git a/Cargo.lock b/Cargo.lock index 7bdc1855..67e357bf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1815,9 +1815,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.51.0" +version = "1.54.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bd1c4c0fc4a7ab90fc15ef6daaa3ec3b893f004f915f2392557ed23237820cd" +checksum = "b52fd3255258d26fac7264ab5a76b53c13367136dc011d3d6ad4f8ed070396a3" dependencies = [ "bytes", "libc", diff --git a/crates/sandlock-core/Cargo.toml b/crates/sandlock-core/Cargo.toml index 039f846e..21d9275b 100644 --- a/crates/sandlock-core/Cargo.toml +++ b/crates/sandlock-core/Cargo.toml @@ -14,7 +14,7 @@ libc = "0.2.191" log = "0.4" syscalls = { version = "0.8", default-features = false } nix = { version = "0.29", features = ["dir", "user"] } -tokio = { version = "1", features = ["rt", "net", "time", "sync", "macros", "io-util", "fs"] } +tokio = { version = "1.53.3", features = ["rt", "net", "time", "sync", "macros", "io-util", "fs"] } tokio-util = "0.7" serde = { version = "1", features = ["derive"] } thiserror = "2" diff --git a/crates/sandlock-core/src/control.rs b/crates/sandlock-core/src/control.rs index 78394730..db87befa 100644 --- a/crates/sandlock-core/src/control.rs +++ b/crates/sandlock-core/src/control.rs @@ -224,7 +224,8 @@ fn peer_cred(fd: RawFd) -> Option { fn into_async(fd: ControlFd) -> Option> { fd.set_nonblocking().ok()?; - AsyncFd::new(fd).ok() + // SAFETY: the AsyncFd owns the ControlFd, which closes only on drop. + unsafe { AsyncFd::register(fd) }.ok() } async fn accept(listener: &AsyncFd) -> std::io::Result { @@ -311,7 +312,8 @@ async fn control_loop( if peer_cred(stream.as_raw_fd()).map(|c| c.uid) != Some(my_uid) { continue; } - let Ok(stream) = AsyncFd::new(stream).map(ControlStream) else { + // SAFETY: the AsyncFd owns the ControlFd, which closes only on drop. + let Ok(stream) = unsafe { AsyncFd::register(stream) }.map(ControlStream) else { continue; }; let _ = tokio::time::timeout( diff --git a/crates/sandlock-core/src/controller.rs b/crates/sandlock-core/src/controller.rs index 9096ec32..428c7767 100644 --- a/crates/sandlock-core/src/controller.rs +++ b/crates/sandlock-core/src/controller.rs @@ -112,8 +112,13 @@ impl MainExit { let Some(fd) = &self.pipe else { return Err(std::io::Error::other("main's exit has no status pipe")); }; - let ready = - tokio::io::unix::AsyncFd::with_interest(fd.as_raw_fd(), tokio::io::Interest::READABLE)?; + // SAFETY: the AsyncFd owns its own dup of the pipe. + let ready = unsafe { + tokio::io::unix::AsyncFd::register_with_interest( + fd.try_clone()?, + tokio::io::Interest::READABLE, + ) + }?; loop { if let Some(exit) = self.get() { return Ok(exit); diff --git a/crates/sandlock-core/src/netlink/proxy.rs b/crates/sandlock-core/src/netlink/proxy.rs index a7ef4091..72dcba46 100644 --- a/crates/sandlock-core/src/netlink/proxy.rs +++ b/crates/sandlock-core/src/netlink/proxy.rs @@ -46,7 +46,8 @@ pub fn spawn_responder( } async fn responder_loop(fd: OwnedFd, reply_pid: u32) -> std::io::Result<()> { - let async_fd = AsyncFd::with_interest(fd, Interest::READABLE)?; + // SAFETY: the AsyncFd owns the fd. + let async_fd = unsafe { AsyncFd::register_with_interest(fd, Interest::READABLE) }?; let mut buf = vec![0u8; RECV_BUF]; loop { diff --git a/crates/sandlock-core/src/network/send_engine.rs b/crates/sandlock-core/src/network/send_engine.rs index 398b9156..cba834bb 100644 --- a/crates/sandlock-core/src/network/send_engine.rs +++ b/crates/sandlock-core/src/network/send_engine.rs @@ -168,8 +168,11 @@ async fn push_until_done( flags: i32, mut offset: usize, ) -> Result { - let afd = tokio::io::unix::AsyncFd::with_interest(dup_fd, tokio::io::Interest::WRITABLE) - .map_err(|_| libc::EIO)?; + // SAFETY: the AsyncFd owns the fd. + let afd = unsafe { + tokio::io::unix::AsyncFd::register_with_interest(dup_fd, tokio::io::Interest::WRITABLE) + } + .map_err(|_| libc::EIO)?; loop { let mut guard = afd.writable().await.map_err(|_| libc::EIO)?; let ret = send_materialized_at( diff --git a/crates/sandlock-core/src/reaper.rs b/crates/sandlock-core/src/reaper.rs index 95f6ef7d..02657612 100644 --- a/crates/sandlock-core/src/reaper.rs +++ b/crates/sandlock-core/src/reaper.rs @@ -31,7 +31,11 @@ impl Reaper { /// The reaper exits once the session has no process left. pub async fn exit(&self) { - if let Ok(fd) = tokio::io::unix::AsyncFd::new(self.pidfd.as_raw_fd()) { + let Ok(pidfd) = self.pidfd.try_clone() else { + return; + }; + // SAFETY: the AsyncFd owns its own dup of the pidfd. + if let Ok(fd) = unsafe { tokio::io::unix::AsyncFd::register(pidfd) } { let _ = fd.readable().await; } } diff --git a/crates/sandlock-core/src/sandbox.rs b/crates/sandlock-core/src/sandbox.rs index 5742e9ec..8db65cf1 100644 --- a/crates/sandlock-core/src/sandbox.rs +++ b/crates/sandlock-core/src/sandbox.rs @@ -3587,11 +3587,13 @@ async fn wait_child_exit_via_pidfd( ) -> crate::result::ExitStatus { use crate::result::ExitStatus; - let async_fd = - match tokio::io::unix::AsyncFd::with_interest(pidfd, tokio::io::Interest::READABLE) { - Ok(fd) => fd, - Err(_) => return wait_child_exit_blocking(pid).await, - }; + // SAFETY: the AsyncFd owns the pidfd. + let async_fd = match unsafe { + tokio::io::unix::AsyncFd::register_with_interest(pidfd, tokio::io::Interest::READABLE) + } { + Ok(fd) => fd, + Err(_) => return wait_child_exit_blocking(pid).await, + }; loop { // pidfd becomes readable when the process exits; no data is read. diff --git a/crates/sandlock-core/src/seccomp/notif.rs b/crates/sandlock-core/src/seccomp/notif.rs index 55ce3262..54c77b24 100644 --- a/crates/sandlock-core/src/seccomp/notif.rs +++ b/crates/sandlock-core/src/seccomp/notif.rs @@ -3369,13 +3369,16 @@ pub async fn supervisor( ) { // Register the notif fd with the Tokio IO driver so we can wait for // readiness via epoll instead of a dedicated blocking thread. - let async_fd = match tokio::io::unix::AsyncFd::with_interest( - Arc::new(notif_fd), - tokio::io::Interest::READABLE, - ) { + // SAFETY: the AsyncFd holds a strong reference, so the fd outlives it. + let async_fd = match unsafe { + tokio::io::unix::AsyncFd::register_with_interest( + Arc::new(notif_fd), + tokio::io::Interest::READABLE, + ) + } { Ok(fd) => fd, Err(err) => { - let _ = startup.send(Err(err)); + let _ = startup.send(Err(err.into())); return; } }; @@ -3499,18 +3502,20 @@ pub(crate) fn spawn_pid_watcher( pidfd: Arc, ) { tokio::spawn(async move { - let async_fd = - match tokio::io::unix::AsyncFd::with_interest(pidfd, tokio::io::Interest::READABLE) { - Ok(f) => f, - Err(_) => { - // AsyncFd registration failed (extremely unusual); - // fall back to immediate cleanup so we don't leak the - // index entry. The process is still alive, so its slot - // stays held until `release_exited_slots` sees it die. - cleanup_pid(&ctx, key).await; - return; - } - }; + // SAFETY: the AsyncFd holds a strong reference, so the fd outlives it. + let async_fd = match unsafe { + tokio::io::unix::AsyncFd::register_with_interest(pidfd, tokio::io::Interest::READABLE) + } { + Ok(f) => f, + Err(_) => { + // AsyncFd registration failed (extremely unusual); + // fall back to immediate cleanup so we don't leak the + // index entry. The process is still alive, so its slot + // stays held until `release_exited_slots` sees it die. + cleanup_pid(&ctx, key).await; + return; + } + }; // pidfd becomes readable when the process exits; we don't // read any data, so `readable()` is just an await point. let _ = async_fd.readable().await; diff --git a/crates/sandlock-oci/Cargo.toml b/crates/sandlock-oci/Cargo.toml index 2738081a..a9075d23 100644 --- a/crates/sandlock-oci/Cargo.toml +++ b/crates/sandlock-oci/Cargo.toml @@ -28,7 +28,7 @@ http = ["sandlock-core/http"] sandlock-core = { version = "0.9.1", path = "../sandlock-core", default-features = false } anyhow = "1" clap = { version = "4", features = ["derive"] } -tokio = { version = "1", features = ["rt-multi-thread", "macros", "net", "io-util"] } +tokio = { version = "1.53.3", features = ["rt-multi-thread", "macros", "net", "io-util"] } serde = { version = "1", features = ["derive"] } serde_json = "1" libc = "0.2" diff --git a/crates/sandlock-oci/src/supervisor.rs b/crates/sandlock-oci/src/supervisor.rs index 47aba11f..284b58ae 100644 --- a/crates/sandlock-oci/src/supervisor.rs +++ b/crates/sandlock-oci/src/supervisor.rs @@ -813,7 +813,9 @@ fn exit_watcher(pid: i32) -> Option