diff --git a/boot/init/src/sys.rs b/boot/init/src/sys.rs index 2711ed6..8bc9c7b 100644 --- a/boot/init/src/sys.rs +++ b/boot/init/src/sys.rs @@ -22,6 +22,8 @@ pub fn mount( let target_c = cstr(target)?; let fstype_c = fstype.map(cstr).transpose()?; let data_c = data.map(cstr).transpose()?; + // SAFETY: every pointer is either a live CString for the call's duration + // or null (the optional fstype/data), exactly as mount(2) expects. let ret = unsafe { libc::mount( src_c.as_ptr(), @@ -51,6 +53,7 @@ pub fn is_block_dev(path: &str) -> bool { } pub fn sethostname(name: &str) -> Result<(), String> { + // SAFETY: name is a live &str; sethostname(2) copies name.len() bytes. let ret = unsafe { libc::sethostname(name.as_ptr() as *const libc::c_char, name.len()) }; if ret != 0 { return Err(format!( @@ -68,6 +71,7 @@ pub fn switch_root(newroot: &str) -> Result<(), String> { std::env::set_current_dir(newroot).map_err(|err| format!("chdir {newroot}: {err}"))?; mount(".", "/", None, libc::MS_MOVE, None)?; let dot = cstr(".")?; + // SAFETY: dot is a live CString for the duration of the call. if unsafe { libc::chroot(dot.as_ptr()) } != 0 { return Err(format!("chroot: {}", io::Error::last_os_error())); } @@ -83,6 +87,8 @@ pub fn exec_init(path: &str) -> String { let term_env = static_cstr("TERM=linux"); let argv = [path_c.as_ptr(), ptr::null()]; let envp = [path_env.as_ptr(), term_env.as_ptr(), ptr::null()]; + // SAFETY: path_c, argv, and envp are live and NUL-terminated for the call; + // execve only returns on failure. unsafe { libc::execve(path_c.as_ptr(), argv.as_ptr(), envp.as_ptr()) }; io::Error::last_os_error().to_string() } @@ -91,6 +97,8 @@ pub fn exec_init(path: &str) -> String { /// cpio carries the console node; this also covers cpios built without it. pub fn claim_console() { let console = static_cstr("/dev/console"); + // SAFETY: console is a live CString; the fd is validated (>=0) before any + // dup2/close and only closed when it is not already a std stream. unsafe { let fd = libc::open(console.as_ptr(), libc::O_RDWR); if fd >= 0 { @@ -112,10 +120,12 @@ pub fn fatal(msg: &str, debug: bool) -> ! { if debug { if let Ok(sh) = CString::new("/bin/sh") { let argv = [sh.as_ptr(), ptr::null()]; + // SAFETY: sh and argv are live and NUL-terminated for the call. unsafe { libc::execv(sh.as_ptr(), argv.as_ptr()) }; } eprintln!("sandbox-init: no /bin/sh in this initramfs (non-debug build?)"); } + // SAFETY: sync(2) and reboot(2) take no pointer arguments. unsafe { libc::sync(); libc::reboot(libc::RB_POWER_OFF); diff --git a/sdk/python/cocoonsandbox/conn.py b/sdk/python/cocoonsandbox/conn.py index 6b75aa7..9190f3a 100644 --- a/sdk/python/cocoonsandbox/conn.py +++ b/sdk/python/cocoonsandbox/conn.py @@ -64,6 +64,7 @@ def dial_agent(addr: str, sandbox_id: str, token: str, timeout: float) -> Conn: Upgrade, so nothing pools or proxies underneath the byte stream.""" host, port = addr.rsplit(":", 1) sock = socket.create_connection((host, int(port)), timeout=timeout) + reader = None try: request = ( f"GET /v1/sandboxes/{sandbox_id}/agent HTTP/1.1\r\n" @@ -91,5 +92,8 @@ def dial_agent(addr: str, sandbox_id: str, token: str, timeout: float) -> Conn: raise APIError("agent upgrade", code, body.strip() or status.strip()) return Conn(sock, reader) except Exception: + # makefile() holds a ref on the socket; close it too or the fd lingers. + if reader is not None: + reader.close() sock.close() raise diff --git a/sdk/python/cocoonsandbox/sandbox.py b/sdk/python/cocoonsandbox/sandbox.py index 2424787..33980bd 100644 --- a/sdk/python/cocoonsandbox/sandbox.py +++ b/sdk/python/cocoonsandbox/sandbox.py @@ -32,8 +32,11 @@ def __enter__(self): return self def __exit__(self, *exc): - with contextlib.suppress(Exception): + try: self.close() + except Exception: + if exc[0] is None: # a clean block surfaces a real release failure + raise def exec(self, *argv: str, cwd: str = "", env: dict | None = None, user: str = "", session: str = "", stdin: bytes = b"") -> str: