feat(fast_io,daemon): Landlock LSM defense-in-depth for daemon receiver (SEC-1.p)#4702
Open
oferchen wants to merge 1 commit into
Open
feat(fast_io,daemon): Landlock LSM defense-in-depth for daemon receiver (SEC-1.p)#4702oferchen wants to merge 1 commit into
oferchen wants to merge 1 commit into
Conversation
…er (SEC-1.p)
Layers a kernel-enforced allowlist above the SEC-1 *at* syscall chain in the
daemon receiver path. Once `restrict_to_module_paths` succeeds, every
filesystem syscall the connection thread issues outside the module root is
rejected by the kernel with EACCES, regardless of which API the caller used.
The sandbox is inherited by the name converter and pre/post-xfer-exec hook
processes, closing the "missed call site / future regression" gap that pure
call-site routing cannot guarantee on its own.
- `crates/fast_io/Cargo.toml`: optional `landlock = "0.4"` dep behind a new
`landlock` Cargo feature (Linux-only, mirrors the `iouring-send-zc` opt-in
pattern).
- `crates/fast_io/src/landlock.rs`: real implementation using
`Ruleset::default()` + `set_compatibility(BestEffort)` so the helper
requests `AccessFs::from_all(ABI::V3)` unconditionally and the crate
silently drops REFER (v1) and TRUNCATE (v1/v2) on older kernels. The
returned `LandlockOutcome` carries the `RulesetStatus` so callers can log
the actual enforcement level.
- `crates/fast_io/src/landlock_stub.rs`: identical public surface for non-
Linux targets and feature-disabled builds; every call short-circuits to
`Unavailable` so cross-platform callers compile without `#[cfg]` branching.
- `crates/daemon/Cargo.toml`: depend on `fast_io` (stub on non-Linux, real
Landlock dep on Linux) so the wire-in stays target-agnostic.
- `crates/daemon/src/daemon/sections/module_access/transfer.rs`:
- `validate_client_paths_in_module` rejects client-supplied `--temp-dir`
/ `--partial-dir` / `--backup-dir` paths that resolve outside the
module root (audit-recommended REJECT over allowlist widening).
- `engage_landlock_sandbox` engages the kernel allowlist immediately
after `apply_module_privilege_restrictions` returns. Probe failure or
setup error is logged but does not abort the connection; the SEC-1
*at* chain remains the primary defense.
- `SECURITY.md`: SEC-1.p entry documenting kernel-version matrix and the
reject-vs-widen decision.
NOTE: `Cargo.lock` requires `cargo update -p landlock` to register the new
optional crate before `--locked` CI jobs will pass. Local cargo is
deliberately not run from this agent per the consolidate-cargo standing
rule; the lock update lands in a follow-up commit before merge.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Wires Linux Landlock LSM into the daemon receiver path as defense-in-depth above the SEC-1
*atsyscall chain. Implements the audit recommendation from PR #4699. Afterapply_module_privilege_restrictionsreturns, the receiver thread engages a kernel-enforced allowlist over the module root: every filesystem syscall the thread (and any process inherited from it) issues outside that root is rejected by the kernel withEACCESregardless of which API the caller used. The sandbox is inherited by the name converter and pre/post-xfer-exec hook processes, closing the missed-call-site / future-regression gap that pure call-site routing cannot guarantee on its own.crates/fast_io/Cargo.tomladds the optionallandlock = "0.4"dep behind a newlandlockCargo feature, Linux-only, mirroring theiouring-send-zcopt-in pattern at lines 110-117 of the same file.crates/fast_io/src/landlock.rsbuilds the ruleset withRuleset::default()+set_compatibility(CompatLevel::BestEffort)and requestsAccessFs::from_all(ABI::V3)unconditionally so the crate silently drops REFER (v1) and TRUNCATE (v1/v2) on older kernels. ReturnsLandlockOutcome::{Enforced(RulesetStatus), Unavailable, Error}so the daemon can log the actual ABI tier the kernel honoured.crates/fast_io/src/landlock_stub.rsships the same public surface for non-Linux targets and feature-disabled builds; every call short-circuits toUnavailableso the wire-in stays target-agnostic.crates/daemon/Cargo.tomldepends onfast_io(stub on non-Linux, real Landlock dep on Linux) so the call site intransfer.rscompiles unconditionally.crates/daemon/src/daemon/sections/module_access/transfer.rsadds two helpers:validate_client_paths_in_modulerejects client-supplied--temp-dir/--partial-dir/--backup-dirpaths that resolve outside the module root. Per the audit's recommendation in section 10, this is REJECT over widening the allowlist; widening the writable surface to honour an attacker-supplied prefix undermines the whole point of the sandbox and rsync's own chroot mode behaves the same way.engage_landlock_sandboxengages the kernel allowlist immediately afterapply_module_privilege_restrictionsreturns. Probe failure or setup error is logged at WARN but does not abort the connection; the SEC-1*atchain remains the primary defense even when Landlock is unavailable.SECURITY.mdadds a SEC-1.p entry documenting the kernel-version matrix and the reject-vs-widen decision.Kernel-version downgrade strategy implemented
LandlockOutcome::Unavailable; SEC-1*athelpers remain the sole defense. Logged once per connection at INFO.set_compatibility(CompatLevel::BestEffort)lets the crate strip rights the kernel does not understand, so the same call site works on every supported kernel without runtime branching.Audit recommendation alignment
module_access/transfer.rsimmediately afterapply_module_privilege_restrictions, exactly as PR docs(design): SEC-1.p Landlock LSM as defense-in-depth for daemon receiver #4699 prescribed.iouring-send-zc.Coordination
*atchain (PRs feat(fast_io): expose openat2_supported() probe helper (SEC-1.d) #4643, feat(fast_io): add DirSandbox + thread through receiver pipeline (SEC-1.e) #4650, feat(fast_io,engine): replace lstat/symlink_metadata with fstatat(AT_SYMLINK_NOFOLLOW) (SEC-1.f) #4668, feat(fast_io,transfer): replace remove_file/remove_dir with unlinkat (SEC-1.g) #4671, feat(fast_io): mkdirat/symlinkat/linkat sandbox helpers (SEC-1.h) #4683, feat(fast_io): fchmodat/fchownat/utimensat sandbox helpers (SEC-1.i) #4690, feat(fast_io): renameat sandbox helper (SEC-1.j) #4693 - all merged).docs/design/sec-1-p-landlock-defense-in-depth-2026-05-22.md). That PR may still be open at the time this one is filed; both should land before SECURITY.md's SEC-1 status table is bumped to FIXED.Lock-file requirement
Cargo.lockrequirescargo update -p landlock(orcargo generate-lockfile) to register the new optional crate and its transitive deps before--lockedCI jobs will pass. The lock update was deliberately not run from this branch per the consolidate-cargo standing rule on agents; it should be added in a follow-up commit before merge.Test plan
fmt+clippypasses on the lock-updated branch.nextest (stable)passes on Linux with the daemon's forwardedlandlockfeature.crates/fast_io/src/landlock.rs::testscover the four scenarios called out by the audit:is_supportedagreement withABI::query, in-module write succeeds, out-of-module write fails withPermissionDenied, empty allowlist denies all writes, and a second restrict call does not relax the sandbox.oc-rsyncdon a kernel >= 6.2, push from a client with--temp-dir=/tmp, observe the daemon log linerejected --temp-dir='/tmp' ... outside module rootand the wire-protocol@ERRORreply.