1#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
11pub struct Rights(u64);
12
13impl Rights {
14 pub const NONE: Self = Self(0);
15 pub const EXECUTE: Self = Self(1 << 0);
16 pub const WRITE_FILE: Self = Self(1 << 1);
17 pub const READ_FILE: Self = Self(1 << 2);
18 pub const READ_DIR: Self = Self(1 << 3);
19 pub const ABI1: Self = Self((1 << 13) - 1);
21 pub const REFER: Self = Self(1 << 13);
22 pub const TRUNCATE: Self = Self(1 << 14);
23 pub const IOCTL_DEV: Self = Self(1 << 15);
24 pub const ALL: Self = Self(u64::MAX);
26 pub const READ: Self = Self(Self::READ_FILE.0 | Self::EXECUTE.0);
28 pub const READ_TREE: Self = Self(Self::READ.0 | Self::READ_DIR.0);
30 pub const FILE: Self = Self(
32 Self::EXECUTE.0
33 | Self::WRITE_FILE.0
34 | Self::READ_FILE.0
35 | Self::TRUNCATE.0
36 | Self::IOCTL_DEV.0,
37 );
38
39 pub const fn bits(self) -> u64 {
40 self.0
41 }
42
43 pub const fn is_empty(self) -> bool {
44 self.0 == 0
45 }
46
47 pub fn handled(abi: u32) -> Self {
49 let mut rights = Self::ABI1;
50 if abi >= 2 {
51 rights |= Self::REFER;
52 }
53 if abi >= 3 {
54 rights |= Self::TRUNCATE;
55 }
56 if abi >= 5 {
57 rights |= Self::IOCTL_DEV;
58 }
59 rights
60 }
61}
62
63impl std::ops::BitOr for Rights {
64 type Output = Self;
65
66 fn bitor(self, other: Self) -> Self {
67 Self(self.0 | other.0)
68 }
69}
70
71impl std::ops::BitOrAssign for Rights {
72 fn bitor_assign(&mut self, other: Self) {
73 self.0 |= other.0;
74 }
75}
76
77impl std::ops::BitAnd for Rights {
78 type Output = Self;
79
80 fn bitand(self, other: Self) -> Self {
81 Self(self.0 & other.0)
82 }
83}
84
85impl std::ops::BitAndAssign for Rights {
86 fn bitand_assign(&mut self, other: Self) {
87 self.0 &= other.0;
88 }
89}
90
91impl std::ops::Not for Rights {
92 type Output = Self;
93
94 fn not(self) -> Self {
95 Self(!self.0)
96 }
97}
98
99#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
101pub enum Open {
102 Following,
104 Unlinked,
107 Directory,
110}
111
112impl Open {
113 #[cfg(unix)]
116 pub fn name(self, relative: &std::path::Path) -> Result<std::ffi::CString, std::ffi::NulError> {
117 use std::os::unix::ffi::OsStrExt;
118 let mut name = relative.as_os_str().as_bytes().to_vec();
119 if self == Self::Directory {
120 name.extend_from_slice(b"/.");
121 }
122 std::ffi::CString::new(name)
123 }
124}
125
126#[cfg(target_os = "linux")]
127pub use system::*;
128
129#[cfg(target_os = "linux")]
130mod system {
131 use std::ffi::CStr;
132 use std::io;
133 use std::os::fd::{FromRawFd, OwnedFd, RawFd};
134
135 use super::Rights;
136
137 const CREATE_RULESET_VERSION: u32 = 1 << 0;
138 const RULE_PATH_BENEATH: i32 = 1;
139
140 #[repr(C)]
141 struct RulesetAttr {
142 handled_access_fs: u64,
143 }
144
145 #[repr(C, packed)]
146 struct PathBeneathAttr {
147 allowed_access: u64,
148 parent_fd: i32,
149 }
150
151 pub fn abi() -> io::Result<u32> {
153 let version = unsafe {
154 libc::syscall(
155 libc::SYS_landlock_create_ruleset,
156 std::ptr::null::<RulesetAttr>(),
157 0usize,
158 CREATE_RULESET_VERSION,
159 )
160 };
161 if version < 1 {
162 return Err(io::Error::last_os_error());
163 }
164 Ok(version as u32)
165 }
166
167 pub fn create_ruleset(handled: Rights) -> io::Result<OwnedFd> {
169 let attr = RulesetAttr {
170 handled_access_fs: handled.bits(),
171 };
172 let fd = unsafe {
173 libc::syscall(
174 libc::SYS_landlock_create_ruleset,
175 &attr as *const RulesetAttr,
176 std::mem::size_of::<RulesetAttr>(),
177 0u32,
178 )
179 };
180 if fd < 0 {
181 return Err(io::Error::last_os_error());
182 }
183 Ok(unsafe { OwnedFd::from_raw_fd(fd as i32) })
185 }
186
187 pub fn add_rule(ruleset: RawFd, fd: RawFd, rights: Rights, handled: Rights) -> io::Result<()> {
192 let mut stat: libc::stat = unsafe { std::mem::zeroed() };
193 let directory = unsafe { libc::fstat(fd, &mut stat) } == 0
194 && stat.st_mode & libc::S_IFMT == libc::S_IFDIR;
195 let mut rights = rights & handled;
196 if !directory {
197 rights &= Rights::FILE;
198 }
199 if rights.is_empty() {
200 return Ok(());
201 }
202 let attr = PathBeneathAttr {
203 allowed_access: rights.bits(),
204 parent_fd: fd,
205 };
206 let added = unsafe {
207 libc::syscall(
208 libc::SYS_landlock_add_rule,
209 ruleset,
210 RULE_PATH_BENEATH,
211 &attr as *const PathBeneathAttr,
212 0u32,
213 )
214 };
215 if added != 0 {
216 return Err(io::Error::last_os_error());
217 }
218 Ok(())
219 }
220
221 pub fn open_beneath(directory: RawFd, name: &CStr) -> io::Result<OwnedFd> {
225 let mut how: libc::open_how = unsafe { std::mem::zeroed() };
227 how.flags = (libc::O_PATH | libc::O_CLOEXEC) as u64;
228 how.resolve = libc::RESOLVE_BENEATH | libc::RESOLVE_NO_SYMLINKS;
229 let fd = unsafe {
230 libc::syscall(
231 libc::SYS_openat2,
232 directory,
233 name.as_ptr(),
234 &how as *const libc::open_how,
235 std::mem::size_of::<libc::open_how>(),
236 )
237 };
238 if fd < 0 {
239 return Err(io::Error::last_os_error());
240 }
241 Ok(unsafe { OwnedFd::from_raw_fd(fd as i32) })
243 }
244
245 pub fn restrict_self(ruleset: RawFd) -> io::Result<()> {
248 unsafe {
249 if libc::prctl(libc::PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) != 0
250 || libc::syscall(libc::SYS_landlock_restrict_self, ruleset, 0u32) != 0
251 {
252 return Err(io::Error::last_os_error());
253 }
254 }
255 Ok(())
256 }
257}