Skip to content

Commit 0262a71

Browse files
authored
Merge pull request #115 from multikernel/issue-111-deny-toctou-onbehalf-open
fs_deny: close the deny-bypass TOCTOU and namespace vectors
2 parents 69e1825 + 20b4e69 commit 0262a71

8 files changed

Lines changed: 718 additions & 58 deletions

File tree

crates/sandlock-core/src/policy_fn.rs

Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -147,22 +147,22 @@ pub struct PolicyContext {
147147
ceiling: LivePolicy,
148148
restricted: HashSet<&'static str>,
149149
pid_overrides: Arc<RwLock<HashMap<u32, HashSet<IpAddr>>>>,
150-
denied_paths: Arc<RwLock<HashSet<String>>>,
150+
denied: Arc<crate::seccomp::state::DeniedSet>,
151151
}
152152

153153
impl PolicyContext {
154154
pub(crate) fn new(
155155
live: Arc<RwLock<LivePolicy>>,
156156
ceiling: LivePolicy,
157157
pid_overrides: Arc<RwLock<HashMap<u32, HashSet<IpAddr>>>>,
158-
denied_paths: Arc<RwLock<HashSet<String>>>,
158+
denied: Arc<crate::seccomp::state::DeniedSet>,
159159
) -> Self {
160160
Self {
161161
live,
162162
ceiling,
163163
restricted: HashSet::new(),
164164
pid_overrides,
165-
denied_paths,
165+
denied,
166166
}
167167
}
168168

@@ -246,16 +246,16 @@ impl PolicyContext {
246246
// ---- Filesystem restriction ----
247247

248248
/// Deny access to a path (and all children). Checked by the supervisor
249-
/// on openat/stat/access syscalls. Takes effect immediately.
249+
/// on openat/stat/access syscalls. Takes effect immediately. The file's
250+
/// inode identity is captured too, so the deny survives hardlinks and
251+
/// renames to a non-denied name.
250252
pub fn deny_path(&self, path: &str) {
251-
let mut denied = self.denied_paths.write().unwrap();
252-
denied.insert(path.to_string());
253+
self.denied.deny(path);
253254
}
254255

255256
/// Remove a previously denied path.
256257
pub fn allow_path(&self, path: &str) {
257-
let mut denied = self.denied_paths.write().unwrap();
258-
denied.remove(path);
258+
self.denied.allow(path);
259259
}
260260

261261
// ---- Internal ----
@@ -337,14 +337,14 @@ pub(crate) fn spawn_policy_fn(
337337
live: Arc<RwLock<LivePolicy>>,
338338
ceiling: LivePolicy,
339339
pid_overrides: Arc<RwLock<HashMap<u32, HashSet<IpAddr>>>>,
340-
denied_paths: Arc<RwLock<HashSet<String>>>,
340+
denied: Arc<crate::seccomp::state::DeniedSet>,
341341
) -> tokio::sync::mpsc::UnboundedSender<PolicyEvent> {
342342
let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel::<PolicyEvent>();
343343

344344
std::thread::Builder::new()
345345
.name("sandlock-policy-fn".to_string())
346346
.spawn(move || {
347-
let mut ctx = PolicyContext::new(live, ceiling, pid_overrides, denied_paths);
347+
let mut ctx = PolicyContext::new(live, ceiling, pid_overrides, denied);
348348

349349
while let Some(pe) = rx.blocking_recv() {
350350
let verdict = callback(pe.event, &mut ctx);
@@ -389,8 +389,8 @@ mod tests {
389389
}));
390390
let ceiling = test_live();
391391
let pid_overrides = Arc::new(RwLock::new(HashMap::new()));
392-
let denied_paths = Arc::new(RwLock::new(HashSet::new()));
393-
let mut ctx = PolicyContext::new(live.clone(), ceiling, pid_overrides, denied_paths);
392+
let denied = Arc::new(crate::seccomp::state::DeniedSet::default());
393+
let mut ctx = PolicyContext::new(live.clone(), ceiling, pid_overrides, denied);
394394

395395
let ip: IpAddr = "127.0.0.1".parse().unwrap();
396396
ctx.grant_network(&[ip]).unwrap();
@@ -406,8 +406,8 @@ mod tests {
406406
}));
407407
let ceiling = test_live();
408408
let pid_overrides = Arc::new(RwLock::new(HashMap::new()));
409-
let denied_paths = Arc::new(RwLock::new(HashSet::new()));
410-
let mut ctx = PolicyContext::new(live.clone(), ceiling, pid_overrides, denied_paths);
409+
let denied = Arc::new(crate::seccomp::state::DeniedSet::default());
410+
let mut ctx = PolicyContext::new(live.clone(), ceiling, pid_overrides, denied);
411411

412412
// Try to grant an IP not in ceiling — should be silently ignored
413413
let foreign: IpAddr = "8.8.8.8".parse().unwrap();
@@ -420,8 +420,8 @@ mod tests {
420420
let live = Arc::new(RwLock::new(test_live()));
421421
let ceiling = test_live();
422422
let pid_overrides = Arc::new(RwLock::new(HashMap::new()));
423-
let denied_paths = Arc::new(RwLock::new(HashSet::new()));
424-
let mut ctx = PolicyContext::new(live, ceiling, pid_overrides, denied_paths);
423+
let denied = Arc::new(crate::seccomp::state::DeniedSet::default());
424+
let mut ctx = PolicyContext::new(live, ceiling, pid_overrides, denied);
425425

426426
ctx.restrict_network(&[]);
427427
let ip: IpAddr = "127.0.0.1".parse().unwrap();
@@ -433,8 +433,8 @@ mod tests {
433433
let live = Arc::new(RwLock::new(test_live()));
434434
let ceiling = test_live();
435435
let pid_overrides = Arc::new(RwLock::new(HashMap::new()));
436-
let denied_paths = Arc::new(RwLock::new(HashSet::new()));
437-
let mut ctx = PolicyContext::new(live.clone(), ceiling, pid_overrides, denied_paths);
436+
let denied = Arc::new(crate::seccomp::state::DeniedSet::default());
437+
let mut ctx = PolicyContext::new(live.clone(), ceiling, pid_overrides, denied);
438438

439439
ctx.restrict_max_memory(256 * 1024 * 1024);
440440
assert_eq!(live.read().unwrap().max_memory_bytes, 256 * 1024 * 1024);
@@ -445,8 +445,8 @@ mod tests {
445445
let live = Arc::new(RwLock::new(test_live()));
446446
let ceiling = test_live();
447447
let pid_overrides = Arc::new(RwLock::new(HashMap::new()));
448-
let denied_paths = Arc::new(RwLock::new(HashSet::new()));
449-
let ctx = PolicyContext::new(live, ceiling, pid_overrides.clone(), denied_paths);
448+
let denied = Arc::new(crate::seccomp::state::DeniedSet::default());
449+
let ctx = PolicyContext::new(live, ceiling, pid_overrides.clone(), denied);
450450

451451
let localhost: IpAddr = "127.0.0.1".parse().unwrap();
452452
ctx.restrict_pid_network(1234, &[localhost]);
@@ -462,8 +462,8 @@ mod tests {
462462
let live = Arc::new(RwLock::new(test_live()));
463463
let ceiling = test_live();
464464
let pid_overrides = Arc::new(RwLock::new(HashMap::new()));
465-
let denied_paths = Arc::new(RwLock::new(HashSet::new()));
466-
let ctx = PolicyContext::new(live, ceiling, pid_overrides.clone(), denied_paths);
465+
let denied = Arc::new(crate::seccomp::state::DeniedSet::default());
466+
let ctx = PolicyContext::new(live, ceiling, pid_overrides.clone(), denied);
467467

468468
let localhost: IpAddr = "127.0.0.1".parse().unwrap();
469469
ctx.restrict_pid_network(1234, &[localhost]);

crates/sandlock-core/src/sandbox.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1659,10 +1659,10 @@ impl Sandbox {
16591659

16601660
let mut policy_fn_state = PolicyFnState::new();
16611661

1662-
if let Ok(mut denied) = policy_fn_state.denied_paths.write() {
1663-
for path in &self.fs_denied {
1664-
denied.insert(path.to_string_lossy().into_owned());
1665-
}
1662+
for path in &self.fs_denied {
1663+
// Captures the path prefix and the file's inode identity, so
1664+
// the deny survives hardlinks/renames to a non-denied name.
1665+
policy_fn_state.denied.deny(&path.to_string_lossy());
16661666
}
16671667

16681668
if let Some(ref callback) = self.policy_fn {
@@ -1687,11 +1687,11 @@ impl Sandbox {
16871687
};
16881688
let ceiling = live.clone();
16891689
let live = std::sync::Arc::new(std::sync::RwLock::new(live));
1690-
let denied_paths = policy_fn_state.denied_paths.clone();
1690+
let denied = policy_fn_state.denied.clone();
16911691
let pid_overrides = net_state.pid_ip_overrides.clone();
16921692
policy_fn_state.live_policy = Some(live.clone());
16931693
let tx = crate::policy_fn::spawn_policy_fn(
1694-
callback.clone(), live, ceiling, pid_overrides, denied_paths,
1694+
callback.clone(), live, ceiling, pid_overrides, denied,
16951695
);
16961696
policy_fn_state.event_tx = Some(tx);
16971697
}

0 commit comments

Comments
 (0)