sandbox_landlock/
ruleset.rs1use sandbox_core::{Result, SandboxError};
7use std::path::PathBuf;
8
9const LANDLOCK_CREATE_RULESET_VERSION: u32 = 1;
11
12const LANDLOCK_ACCESS_FS_EXECUTE: u64 = 1;
14const LANDLOCK_ACCESS_FS_WRITE_FILE: u64 = 1 << 1;
15const LANDLOCK_ACCESS_FS_READ_FILE: u64 = 1 << 2;
16const LANDLOCK_ACCESS_FS_READ_DIR: u64 = 1 << 3;
17const LANDLOCK_ACCESS_FS_REMOVE_DIR: u64 = 1 << 4;
18const LANDLOCK_ACCESS_FS_REMOVE_FILE: u64 = 1 << 5;
19const LANDLOCK_ACCESS_FS_MAKE_CHAR: u64 = 1 << 6;
20const LANDLOCK_ACCESS_FS_MAKE_DIR: u64 = 1 << 7;
21const LANDLOCK_ACCESS_FS_MAKE_REG: u64 = 1 << 8;
22const LANDLOCK_ACCESS_FS_MAKE_SOCK: u64 = 1 << 9;
23const LANDLOCK_ACCESS_FS_MAKE_FIFO: u64 = 1 << 10;
24const LANDLOCK_ACCESS_FS_MAKE_BLOCK: u64 = 1 << 11;
25const LANDLOCK_ACCESS_FS_MAKE_SYM: u64 = 1 << 12;
26
27const ALL_ACCESS_FS: u64 = LANDLOCK_ACCESS_FS_EXECUTE
28 | LANDLOCK_ACCESS_FS_WRITE_FILE
29 | LANDLOCK_ACCESS_FS_READ_FILE
30 | LANDLOCK_ACCESS_FS_READ_DIR
31 | LANDLOCK_ACCESS_FS_REMOVE_DIR
32 | LANDLOCK_ACCESS_FS_REMOVE_FILE
33 | LANDLOCK_ACCESS_FS_MAKE_CHAR
34 | LANDLOCK_ACCESS_FS_MAKE_DIR
35 | LANDLOCK_ACCESS_FS_MAKE_REG
36 | LANDLOCK_ACCESS_FS_MAKE_SOCK
37 | LANDLOCK_ACCESS_FS_MAKE_FIFO
38 | LANDLOCK_ACCESS_FS_MAKE_BLOCK
39 | LANDLOCK_ACCESS_FS_MAKE_SYM;
40
41const READ_ACCESS: u64 = LANDLOCK_ACCESS_FS_READ_FILE | LANDLOCK_ACCESS_FS_READ_DIR;
42const WRITE_ACCESS: u64 = LANDLOCK_ACCESS_FS_WRITE_FILE
43 | LANDLOCK_ACCESS_FS_REMOVE_DIR
44 | LANDLOCK_ACCESS_FS_REMOVE_FILE
45 | LANDLOCK_ACCESS_FS_MAKE_CHAR
46 | LANDLOCK_ACCESS_FS_MAKE_DIR
47 | LANDLOCK_ACCESS_FS_MAKE_REG
48 | LANDLOCK_ACCESS_FS_MAKE_SOCK
49 | LANDLOCK_ACCESS_FS_MAKE_FIFO
50 | LANDLOCK_ACCESS_FS_MAKE_BLOCK
51 | LANDLOCK_ACCESS_FS_MAKE_SYM;
52const EXEC_ACCESS: u64 = LANDLOCK_ACCESS_FS_EXECUTE;
53
54#[derive(Debug, Clone, Default)]
56pub struct LandlockConfig {
57 pub read_paths: Vec<PathBuf>,
59 pub write_paths: Vec<PathBuf>,
61 pub exec_paths: Vec<PathBuf>,
63}
64
65#[repr(C)]
67struct LandlockRulesetAttr {
68 handled_access_fs: u64,
69 handled_access_net: u64,
70}
71
72#[repr(C)]
73struct LandlockPathBeneathAttr {
74 allowed_access: u64,
75 parent_fd: i32,
76}
77
78const LANDLOCK_RULE_PATH_BENEATH: u32 = 1;
79
80impl LandlockConfig {
81 pub fn is_available() -> bool {
83 let ret = unsafe {
84 libc::syscall(
85 libc::SYS_landlock_create_ruleset,
86 std::ptr::null::<libc::c_void>(),
87 0usize,
88 LANDLOCK_CREATE_RULESET_VERSION,
89 )
90 };
91 if ret >= 0 {
92 unsafe {
93 libc::close(ret as i32);
94 }
95 return true;
96 }
97 let errno = std::io::Error::last_os_error().raw_os_error().unwrap_or(0);
98 errno != libc::ENOSYS
99 }
100
101 pub fn apply(&self) -> Result<()> {
104 if !Self::is_available() {
105 return Err(SandboxError::FeatureNotAvailable(
106 "Landlock is not available on this kernel (requires Linux 5.13+)".to_string(),
107 ));
108 }
109
110 let attr = LandlockRulesetAttr {
112 handled_access_fs: ALL_ACCESS_FS,
113 handled_access_net: 0,
114 };
115
116 let ruleset_fd = unsafe {
117 libc::syscall(
118 libc::SYS_landlock_create_ruleset,
119 &attr as *const LandlockRulesetAttr,
120 std::mem::size_of::<LandlockRulesetAttr>(),
121 0u32,
122 )
123 };
124
125 if ruleset_fd < 0 {
126 return Err(SandboxError::Landlock(format!(
127 "landlock_create_ruleset failed: {}",
128 std::io::Error::last_os_error()
129 )));
130 }
131
132 let ruleset_fd = ruleset_fd as i32;
133
134 let result = self.add_all_rules(ruleset_fd);
136
137 if result.is_err() {
138 unsafe {
139 libc::close(ruleset_fd);
140 }
141 return result;
142 }
143
144 unsafe {
146 if libc::prctl(libc::PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) != 0 {
147 libc::close(ruleset_fd);
148 return Err(SandboxError::Landlock(format!(
149 "PR_SET_NO_NEW_PRIVS failed: {}",
150 std::io::Error::last_os_error()
151 )));
152 }
153
154 let ret = libc::syscall(libc::SYS_landlock_restrict_self, ruleset_fd, 0u32);
155
156 libc::close(ruleset_fd);
157
158 if ret < 0 {
159 return Err(SandboxError::Landlock(format!(
160 "landlock_restrict_self failed: {}",
161 std::io::Error::last_os_error()
162 )));
163 }
164 }
165
166 Ok(())
167 }
168
169 fn add_all_rules(&self, ruleset_fd: i32) -> Result<()> {
170 for path in &self.read_paths {
171 self.add_path_rule(ruleset_fd, path, READ_ACCESS)?;
172 }
173 for path in &self.write_paths {
174 self.add_path_rule(ruleset_fd, path, READ_ACCESS | WRITE_ACCESS)?;
175 }
176 for path in &self.exec_paths {
177 self.add_path_rule(ruleset_fd, path, READ_ACCESS | EXEC_ACCESS)?;
178 }
179 Ok(())
180 }
181
182 fn add_path_rule(&self, ruleset_fd: i32, path: &PathBuf, access: u64) -> Result<()> {
183 use std::os::unix::io::IntoRawFd;
184
185 let file = std::fs::File::open(path).map_err(|e| {
186 SandboxError::Landlock(format!(
187 "Failed to open path for landlock rule {}: {}",
188 path.display(),
189 e
190 ))
191 })?;
192
193 let fd = file.into_raw_fd();
194
195 let attr = LandlockPathBeneathAttr {
196 allowed_access: access,
197 parent_fd: fd,
198 };
199
200 let ret = unsafe {
201 libc::syscall(
202 libc::SYS_landlock_add_rule,
203 ruleset_fd,
204 LANDLOCK_RULE_PATH_BENEATH,
205 &attr as *const LandlockPathBeneathAttr,
206 0u32,
207 )
208 };
209
210 unsafe {
211 libc::close(fd);
212 }
213
214 if ret < 0 {
215 return Err(SandboxError::Landlock(format!(
216 "landlock_add_rule failed for {}: {}",
217 path.display(),
218 std::io::Error::last_os_error()
219 )));
220 }
221
222 Ok(())
223 }
224}
225
226#[cfg(test)]
227mod tests {
228 use super::*;
229
230 #[test]
231 fn test_landlock_config_default() {
232 let config = LandlockConfig::default();
233 assert!(config.read_paths.is_empty());
234 assert!(config.write_paths.is_empty());
235 assert!(config.exec_paths.is_empty());
236 }
237
238 #[test]
239 fn test_landlock_availability_check() {
240 let _ = LandlockConfig::is_available();
242 }
243}