|
1 | | -use std::ffi::CString; |
2 | | -use std::os::unix::io::RawFd; |
3 | 1 | use std::path::{Path, PathBuf}; |
4 | 2 |
|
| 3 | +use crate::sys::fs::openat2_in_root; |
| 4 | + |
5 | 5 | /// Collapse `..` components clamping at `/` (pivot_root semantics). |
6 | 6 | /// Always returns an absolute path under `/`. |
7 | 7 | pub fn confine(virtual_path: &str) -> PathBuf { |
@@ -36,86 +36,6 @@ pub fn to_virtual_path(chroot_root: &Path, host_path: &Path) -> Option<PathBuf> |
36 | 36 | .map(|rel| PathBuf::from("/").join(rel)) |
37 | 37 | } |
38 | 38 |
|
39 | | -// ============================================================ |
40 | | -// openat2(RESOLVE_IN_ROOT) based resolution |
41 | | -// ============================================================ |
42 | | - |
43 | | -/// openat2 syscall number, sourced from the `syscalls` crate via `arch`. |
44 | | -const SYS_OPENAT2: libc::c_long = crate::arch::SYS_OPENAT2; |
45 | | - |
46 | | -/// RESOLVE_IN_ROOT — treat the dirfd as the filesystem root for resolution. |
47 | | -const RESOLVE_IN_ROOT: u64 = 0x10; |
48 | | - |
49 | | -/// Kernel `struct open_how` for openat2(). |
50 | | -#[repr(C)] |
51 | | -struct OpenHow { |
52 | | - flags: u64, |
53 | | - mode: u64, |
54 | | - resolve: u64, |
55 | | -} |
56 | | - |
57 | | -fn last_errno(fallback: i32) -> i32 { |
58 | | - std::io::Error::last_os_error() |
59 | | - .raw_os_error() |
60 | | - .unwrap_or(fallback) |
61 | | -} |
62 | | - |
63 | | -/// Open a path confined within `chroot_root` using `openat2(RESOLVE_IN_ROOT)`. |
64 | | -/// |
65 | | -/// The kernel handles symlink resolution, `..` traversal, and prevents |
66 | | -/// escapes above the root — eliminating TOCTOU races and edge cases |
67 | | -/// inherent in userspace path walking. |
68 | | -pub fn openat2_in_root( |
69 | | - chroot_root: &Path, |
70 | | - path: &str, |
71 | | - flags: i32, |
72 | | - mode: u32, |
73 | | -) -> Result<RawFd, i32> { |
74 | | - let c_root = |
75 | | - CString::new(chroot_root.to_str().unwrap_or("")).map_err(|_| libc::EINVAL)?; |
76 | | - let root_fd = unsafe { |
77 | | - libc::open( |
78 | | - c_root.as_ptr(), |
79 | | - libc::O_RDONLY | libc::O_DIRECTORY | libc::O_CLOEXEC, |
80 | | - ) |
81 | | - }; |
82 | | - if root_fd < 0 { |
83 | | - return Err(last_errno(libc::EIO)); |
84 | | - } |
85 | | - |
86 | | - let rel_path = path.strip_prefix('/').unwrap_or(path); |
87 | | - // Empty path means the root itself — use "." |
88 | | - let rel_path = if rel_path.is_empty() { "." } else { rel_path }; |
89 | | - let c_path = CString::new(rel_path).map_err(|_| { |
90 | | - unsafe { libc::close(root_fd) }; |
91 | | - libc::EINVAL |
92 | | - })?; |
93 | | - |
94 | | - let how = OpenHow { |
95 | | - flags: flags as u64, |
96 | | - mode: mode as u64, |
97 | | - resolve: RESOLVE_IN_ROOT, |
98 | | - }; |
99 | | - |
100 | | - let fd = unsafe { |
101 | | - libc::syscall( |
102 | | - SYS_OPENAT2, |
103 | | - root_fd, |
104 | | - c_path.as_ptr(), |
105 | | - &how as *const OpenHow, |
106 | | - std::mem::size_of::<OpenHow>(), |
107 | | - ) |
108 | | - } as i32; |
109 | | - |
110 | | - unsafe { libc::close(root_fd) }; |
111 | | - |
112 | | - if fd < 0 { |
113 | | - Err(last_errno(libc::ENOENT)) |
114 | | - } else { |
115 | | - Ok(fd) |
116 | | - } |
117 | | -} |
118 | | - |
119 | 39 | /// Resolve a virtual path within the chroot using `openat2(RESOLVE_IN_ROOT)`. |
120 | 40 | /// |
121 | 41 | /// The kernel resolves all symlinks and `..` components, keeping the result |
|
0 commit comments