Skip to content

Commit 99a0aa7

Browse files
senamakelmedullabot
andcommitted
refactor(jail): extract availability check and ruleset application
Extracted the backend availability check into a separate function to allow testing fallback behaviour, and pulled the ruleset application logic into a reusable helper to simplify the pre_exec closure. This makes the backend selection testable with a mock availability predicate and reduces duplication in the landlock backend. Auto-committed-on: dragonfly Co-authored-by: Medulla <medulla@tinyhumans.ai>
1 parent 2f6f274 commit 99a0aa7

4 files changed

Lines changed: 66 additions & 9 deletions

File tree

‎crates/tinybox-jail/src/detect.rs‎

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,26 +28,30 @@ impl JailBackend for UnsupportedBackend {
2828
/// Picks the strongest available backend, returning an unsupported backend
2929
/// when no OS sandbox works.
3030
pub fn pick_backend() -> Arc<dyn JailBackend> {
31+
pick_backend_with(is_available)
32+
}
33+
34+
fn pick_backend_with(mut is_available: impl FnMut(&str) -> bool) -> Arc<dyn JailBackend> {
3135
#[cfg(target_os = "linux")]
3236
{
3337
let lb = super::linux::LandlockBackend::new();
34-
if lb.is_available() {
38+
if is_available(lb.name()) {
3539
log::info!("[cwd_jail] backend=landlock");
3640
return Arc::new(lb);
3741
}
3842
}
3943
#[cfg(target_os = "macos")]
4044
{
4145
let sb = super::macos::SeatbeltBackend::new();
42-
if sb.is_available() {
46+
if is_available(sb.name()) {
4347
log::info!("[cwd_jail] backend=seatbelt");
4448
return Arc::new(sb);
4549
}
4650
}
4751
#[cfg(target_os = "windows")]
4852
{
4953
let ac = super::windows::AppContainerBackend::new();
50-
if ac.is_available() {
54+
if is_available(ac.name()) {
5155
log::info!("[cwd_jail] backend=appcontainer");
5256
return Arc::new(ac);
5357
}
@@ -56,6 +60,22 @@ pub fn pick_backend() -> Arc<dyn JailBackend> {
5660
Arc::new(UnsupportedBackend)
5761
}
5862

63+
fn is_available(name: &str) -> bool {
64+
#[cfg(target_os = "linux")]
65+
if name == "landlock" {
66+
return super::linux::LandlockBackend::new().is_available();
67+
}
68+
#[cfg(target_os = "macos")]
69+
if name == "seatbelt" {
70+
return super::macos::SeatbeltBackend::new().is_available();
71+
}
72+
#[cfg(target_os = "windows")]
73+
if name == "appcontainer" {
74+
return super::windows::AppContainerBackend::new().is_available();
75+
}
76+
false
77+
}
78+
5979
#[cfg(test)]
6080
#[path = "detect_tests.rs"]
6181
mod tests;

‎crates/tinybox-jail/src/detect_tests.rs‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,10 @@ fn unsupported_backend_reports_and_rejects_spawns() {
1717
fn backend_selection_returns_a_named_backend() {
1818
assert!(!pick_backend().name().is_empty());
1919
}
20+
21+
#[test]
22+
fn backend_selection_falls_back_when_no_backend_is_available() {
23+
let backend = pick_backend_with(|_| false);
24+
assert_eq!(backend.name(), "unsupported");
25+
assert!(!backend.is_available());
26+
}

‎crates/tinybox-jail/src/linux.rs‎

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -91,12 +91,13 @@ impl JailBackend for LandlockBackend {
9191

9292
// SAFETY: the child callback only applies this prebuilt ruleset.
9393
unsafe {
94-
cmd.pre_exec(move || match ruleset.take() {
95-
Some(ruleset) => match ruleset.restrict_self() {
96-
Ok(_) => Ok(()),
97-
Err(_) => Err(std::io::Error::from_raw_os_error(5)),
98-
},
99-
None => Err(std::io::Error::from_raw_os_error(22)),
94+
cmd.pre_exec(move || {
95+
apply_ruleset(ruleset.take(), |ruleset| {
96+
ruleset
97+
.restrict_self()
98+
.map(|_| ())
99+
.map_err(|_| std::io::Error::from_raw_os_error(5))
100+
})
100101
});
101102
}
102103

@@ -110,6 +111,17 @@ impl JailBackend for LandlockBackend {
110111
}
111112
}
112113

114+
#[cfg(feature = "landlock")]
115+
fn apply_ruleset<T>(
116+
ruleset: Option<T>,
117+
restrict: impl FnOnce(T) -> std::io::Result<()>,
118+
) -> std::io::Result<()> {
119+
match ruleset {
120+
Some(ruleset) => restrict(ruleset),
121+
None => Err(std::io::Error::from_raw_os_error(22)),
122+
}
123+
}
124+
113125
#[cfg(test)]
114126
#[path = "linux_tests.rs"]
115127
mod tests;

‎crates/tinybox-jail/src/linux_tests.rs‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,24 @@
33
use super::*;
44
use std::path::Path;
55

6+
#[cfg(feature = "landlock")]
7+
#[test]
8+
fn apply_ruleset_handles_success_failure_and_missing_ruleset() {
9+
assert!(apply_ruleset(Some(()), |_| Ok(())).is_ok());
10+
assert_eq!(
11+
apply_ruleset(Some(()), |_| Err(std::io::Error::other("failed")))
12+
.err()
13+
.map(|error| error.kind()),
14+
Some(std::io::ErrorKind::Other)
15+
);
16+
assert_eq!(
17+
apply_ruleset::<()>(None, |_| Ok(()))
18+
.err()
19+
.map(|error| error.raw_os_error()),
20+
Some(Some(22))
21+
);
22+
}
23+
624
#[test]
725
fn backend_reports_name_and_availability() {
826
let backend = LandlockBackend::new();

0 commit comments

Comments
 (0)