Skip to content

Commit 0846c55

Browse files
committed
feat(sandbox): Landlock TCP port restriction in Platform mode
When Platform mode is active, apply Landlock ABI v4 network rules to restrict TCP connect to only the proxy port (default 3128). This makes the loopback CONNECT proxy mandatory at the kernel level. Graceful degradation: handle_access(ConnectTcp) and add_rule(NetPort) failures are caught independently -- filesystem sandboxing continues even if TCP port restriction is unavailable (ABI < v4). Only the network restriction degrades, not the entire sandbox. Also fixes rules_applied underflow via saturating_sub. Ref: NVIDIA#899 Signed-off-by: Ladislav Smola <lsmola@redhat.com>
1 parent 7158b3d commit 0846c55

1 file changed

Lines changed: 53 additions & 4 deletions

File tree

crates/openshell-sandbox/src/sandbox/linux/landlock.rs

Lines changed: 53 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@
33

44
//! Landlock filesystem sandboxing.
55
6-
use crate::policy::{LandlockCompatibility, SandboxPolicy};
6+
use crate::policy::{LandlockCompatibility, NetworkMode, SandboxPolicy};
77
use landlock::{
8-
ABI, Access, AccessFs, CompatLevel, Compatible, PathBeneath, PathFd, PathFdError, Ruleset,
9-
RulesetAttr, RulesetCreatedAttr,
8+
ABI, Access, AccessFs, AccessNet, CompatLevel, Compatible, NetPort, PathBeneath, PathFd,
9+
PathFdError, Ruleset, RulesetAttr, RulesetCreatedAttr,
1010
};
1111
use miette::{IntoDiagnostic, Result};
1212
use std::path::{Path, PathBuf};
@@ -184,6 +184,27 @@ pub fn prepare(policy: &SandboxPolicy, workdir: Option<&str>) -> Result<Option<P
184184
.handle_access(access_all)
185185
.into_diagnostic()?;
186186

187+
// Platform mode: declare intent to handle TCP connect access.
188+
// This is independent of filesystem sandboxing -- if it fails
189+
// (ABI < v4), we degrade only the network restriction, not the
190+
// entire sandbox. The add_rule() call below handles the fallback.
191+
let mut tcp_handled = false;
192+
if matches!(policy.network.mode, NetworkMode::Platform) {
193+
match ruleset.handle_access(AccessNet::ConnectTcp) {
194+
Ok(r) => {
195+
ruleset = r;
196+
tcp_handled = true;
197+
}
198+
Err(e) => {
199+
tracing::warn!(
200+
error = %e,
201+
"Landlock handle_access(ConnectTcp) failed (ABI v4 required). \
202+
TCP port restriction will not be applied."
203+
);
204+
}
205+
}
206+
}
207+
187208
let mut ruleset = ruleset.create().into_diagnostic()?;
188209
let mut rules_applied: usize = 0;
189210

@@ -207,6 +228,34 @@ pub fn prepare(policy: &SandboxPolicy, workdir: Option<&str>) -> Result<Option<P
207228
}
208229
}
209230

231+
if tcp_handled {
232+
let proxy_port = policy
233+
.network
234+
.proxy
235+
.as_ref()
236+
.and_then(|p| p.http_addr)
237+
.map_or(3128_u16, |addr| addr.port());
238+
239+
match ruleset.add_rule(NetPort::new(proxy_port, AccessNet::ConnectTcp)) {
240+
Ok(r) => {
241+
ruleset = r;
242+
debug!(
243+
port = proxy_port,
244+
"Landlock allow TCP connect (proxy port only)"
245+
);
246+
rules_applied += 1;
247+
}
248+
Err(e) => {
249+
tracing::warn!(
250+
error = %e,
251+
"Landlock TCP port restriction unavailable. \
252+
Network enforcement degraded: agent can bypass proxy via direct \
253+
connect(). Upgrade to RHEL 9.6+ for kernel-enforced TCP port restriction."
254+
);
255+
}
256+
}
257+
}
258+
210259
if rules_applied == 0 {
211260
return Err(miette::miette!(
212261
"Landlock ruleset has zero valid paths — all {} path(s) failed to open. \
@@ -215,7 +264,7 @@ pub fn prepare(policy: &SandboxPolicy, workdir: Option<&str>) -> Result<Option<P
215264
));
216265
}
217266

218-
let skipped = total_paths - rules_applied;
267+
let skipped = total_paths.saturating_sub(rules_applied);
219268
openshell_ocsf::ocsf_emit!(
220269
openshell_ocsf::ConfigStateChangeBuilder::new(crate::ocsf_ctx())
221270
.severity(openshell_ocsf::SeverityId::Informational)

0 commit comments

Comments
 (0)