From 2973e4bc9386a4cbee36d2e51d6d87998acb9981 Mon Sep 17 00:00:00 2001 From: Sean Kim Date: Sun, 19 Apr 2026 01:35:29 +0000 Subject: [PATCH 1/2] reject absolute path for openat, readlinkat, symlinkat, unlinkat syscalls --- src/rawposix/src/fs_calls.rs | 51 ++++++++++++++++++++++++++++++------ 1 file changed, 43 insertions(+), 8 deletions(-) diff --git a/src/rawposix/src/fs_calls.rs b/src/rawposix/src/fs_calls.rs index 0c1c35ee3..3c47ef7e2 100644 --- a/src/rawposix/src/fs_calls.rs +++ b/src/rawposix/src/fs_calls.rs @@ -122,6 +122,17 @@ pub extern "C" fn openat_syscall( Ok(p) => p, Err(_) => return syscall_error(Errno::EINVAL, "openat", "invalid path"), }; + + // Reject absolute paths — the host kernel ignores dirfd for absolute paths, + // which would allow a guest to escape the cage sandbox. + if raw_path.to_bytes().starts_with(b"/") { + return syscall_error( + Errno::EACCES, + "openat", + "absolute path bypasses dirfd sandbox", + ); + } + let c_path = match CString::new(raw_path) { Ok(c) => c, Err(_) => return syscall_error(Errno::EINVAL, "openat", "invalid path"), @@ -2108,16 +2119,19 @@ pub extern "C" fn readlinkat_syscall( let raw_path = match get_cstr(path_arg) { Ok(p) => p, - Err(_) => { - return syscall_error( - Errno::EINVAL, - "readlinkat", - "invalid - path", - ) - } + Err(_) => return syscall_error(Errno::EINVAL, "readlinkat", "invalid path"), }; + // Reject absolute paths — the host kernel ignores dirfd for absolute paths, + // which would allow a guest to read arbitrary host filesystem paths. + if raw_path.to_bytes().starts_with(b"/") { + return syscall_error( + Errno::EACCES, + "readlinkat", + "absolute path bypasses dirfd sandbox", + ); + } + unsafe { libc::readlinkat(kernel_fd, raw_path.as_ptr() as *const c_char, buf, buflen) } }; @@ -2328,6 +2342,17 @@ pub extern "C" fn unlinkat_syscall( // For this case, we pass the provided pathname directly. let pathname = pathname_arg; let tmp_cstr = get_cstr(pathname).unwrap(); + + // Reject absolute paths — the host kernel ignores dirfd for absolute paths, + // which would allow a guest to escape the cage sandbox. + if tmp_cstr.to_bytes().starts_with(b"/") { + return syscall_error( + Errno::EACCES, + "unlinkat", + "absolute path bypasses dirfd sandbox", + ); + } + c_path = CString::new(tmp_cstr).unwrap(); vfd.underfd as i32 }; @@ -4791,11 +4816,21 @@ pub extern "C" fn symlinkat_syscall( // Use the raw (untranslated) linkpath here intentionally — sc_convert_path_to_host // resolves relative to CWD, but symlinkat interprets linkpath relative to dirfd. // The kernel handles that resolution, so we pass the original guest pointer. + // However, we must reject absolute paths because the host kernel ignores dirfd + // for absolute paths, which would allow a guest to escape the cage sandbox. let raw_linkpath = match get_cstr(linkpath_arg) { Ok(p) => p, Err(_) => return syscall_error(Errno::EINVAL, "symlinkat", "invalid linkpath"), }; + if raw_linkpath.to_bytes().starts_with(b"/") { + return syscall_error( + Errno::EACCES, + "symlinkat", + "absolute path bypasses dirfd sandbox", + ); + } + unsafe { libc::symlinkat( target.as_ptr() as *const libc::c_char, From cf78efc490187ef3fa10c0436eb77b45c89b0d37 Mon Sep 17 00:00:00 2001 From: Sean Kim Date: Sun, 19 Apr 2026 01:42:05 +0000 Subject: [PATCH 2/2] function fail fix --- src/rawposix/src/fs_calls.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/rawposix/src/fs_calls.rs b/src/rawposix/src/fs_calls.rs index 3c47ef7e2..b0f73600d 100644 --- a/src/rawposix/src/fs_calls.rs +++ b/src/rawposix/src/fs_calls.rs @@ -125,7 +125,7 @@ pub extern "C" fn openat_syscall( // Reject absolute paths — the host kernel ignores dirfd for absolute paths, // which would allow a guest to escape the cage sandbox. - if raw_path.to_bytes().starts_with(b"/") { + if raw_path.as_bytes().starts_with(b"/") { return syscall_error( Errno::EACCES, "openat", @@ -2124,7 +2124,7 @@ pub extern "C" fn readlinkat_syscall( // Reject absolute paths — the host kernel ignores dirfd for absolute paths, // which would allow a guest to read arbitrary host filesystem paths. - if raw_path.to_bytes().starts_with(b"/") { + if raw_path.as_bytes().starts_with(b"/") { return syscall_error( Errno::EACCES, "readlinkat", @@ -2345,7 +2345,7 @@ pub extern "C" fn unlinkat_syscall( // Reject absolute paths — the host kernel ignores dirfd for absolute paths, // which would allow a guest to escape the cage sandbox. - if tmp_cstr.to_bytes().starts_with(b"/") { + if tmp_cstr.as_bytes().starts_with(b"/") { return syscall_error( Errno::EACCES, "unlinkat", @@ -4823,7 +4823,7 @@ pub extern "C" fn symlinkat_syscall( Err(_) => return syscall_error(Errno::EINVAL, "symlinkat", "invalid linkpath"), }; - if raw_linkpath.to_bytes().starts_with(b"/") { + if raw_linkpath.as_bytes().starts_with(b"/") { return syscall_error( Errno::EACCES, "symlinkat",