feat(jailer): add Landlock LSM sandboxing via --landlock flag#5771
feat(jailer): add Landlock LSM sandboxing via --landlock flag#5771pavitrabhalla wants to merge 11 commits intofirecracker-microvm:mainfrom
Conversation
Add opt-in `--landlock` flag to the jailer that uses the Linux Landlock LSM (kernel >= 5.13) as a defense-in-depth mechanism. When enabled, a Landlock ruleset granting full filesystem access within the jail directory is prepared before pivot_root (capturing the inode) and enforced right before exec, so restrictions are inherited by the jailed Firecracker process. If Firecracker escapes the pivot_root chroot via a kernel exploit, Landlock independently prevents access to files outside the jail directory. The flag fails loudly if the kernel does not support Landlock, since the user explicitly opted in. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Signed-off-by: Pavitra Bhalla <pavitra@superserve.ai>
Break up method chain closures that exceeded the 100-character line limit enforced by rustfmt. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Signed-off-by: Pavitra Bhalla <pavitra@superserve.ai>
Document the new --landlock CLI flag under the [Unreleased] Added section of CHANGELOG.md. Signed-off-by: Pavitra Bhalla <pavitra@superserve.ai>
The ArgVals struct initializer in env.rs tests did not include the landlock field added by the parent commit, causing a compilation error. Signed-off-by: Pavitra Bhalla <pavitra@superserve.ai>
Normalize markdown list formatting in CHANGELOG.md. Signed-off-by: Pavitra Bhalla <pavitra@superserve.ai>
Clippy's assertions_on_result_states lint forbids assert!(x.is_ok()) and assert!(x.is_err()); use .unwrap() and .unwrap_err() instead. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Signed-off-by: Pavitra Bhalla <pavitra@superserve.ai>
Signed-off-by: Pavitra Bhalla <pavitra@superserve.ai>
Signed-off-by: Pavitra Bhalla <pavitra@superserve.ai>
src/jailer/src/landlock.rs
Outdated
| /// Returns [`JailerError::Landlock`] if the kernel does not support Landlock (kernel < 5.13), | ||
| /// if `jail_dir` cannot be opened, or if any ruleset syscall fails. | ||
| pub fn prepare_ruleset(jail_dir: &Path) -> Result<RulesetCreated, JailerError> { | ||
| let abi = ABI::V1; |
There was a problem hiding this comment.
Please don't pin to ABI::V1 but to the latest that you actually tested on a machine. Restricting only to ABI::V1 will limit the features to only the initial ones, whereas if you take the greatest version you'll get all the ones supported by the running system (i.e. best-effort). See the documentation. BTW, this version should regularly be incremented once the new version is tested on a kernel supporting it.
src/jailer/src/landlock.rs
Outdated
| let mut parts = release.split('.'); | ||
| let major: i32 = parts.next().and_then(|s| s.parse().ok()).unwrap_or(0); | ||
| let minor: i32 = parts.next().and_then(|s| s.parse().ok()).unwrap_or(0); | ||
| major > 5 || (major == 5 && minor >= 13) |
There was a problem hiding this comment.
Please don't check kernel version. Landlock may be available/backported to older kernels. Landlock provides a dedicated interface to check which ABI version is supported by the running kernel. You should use LandlockStatus instead, but keep in mind that there are only a few valid use case to check this support (testing makes sense here) because the normal use of the Rust crate automatically follows a best-effort approach.
src/jailer/src/main.rs
Outdated
| .takes_value(false) | ||
| .help("Print the binary version number."), | ||
| ) | ||
| .arg(Argument::new("landlock").takes_value(false).help( |
There was a problem hiding this comment.
This option should probably reflect the goal, not (only) the mechanism. Landlock is gaining new feature over time and the current option doesn't reflect the related description.
…tation - Use ABI::V4 instead of V1 for best-effort support on newer kernels - Add official Landlock docs link to module comment - Replace uname() kernel version check with CompatLevel::HardRequirement - Rename --landlock flag to --landlock-restrict-fs to reflect the goal Signed-off-by: Pavitra Bhalla <pavitra@superserve.ai> Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Pavitra Bhalla <pavitra@superserve.ai> Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
@l0kod Thanks for the feedback. I updated everything as per your comments. |
Summary
This PR implements the feature requested in #5513 — adding opt-in Landlock LSM support to the Firecracker jailer as a defense-in-depth mechanism.
What it does
Adds a
--landlockflag to the jailer. When passed:pivot_root: a Landlock ruleset is created and aPathFdis opened on the jail directory, capturing its inodeexec:restrict_self()is called, enforcing the ruleset on the jailed Firecracker processThe ruleset grants all filesystem access rights within the jail directory and denies everything outside. This means if Firecracker escapes the
pivot_rootchroot via a kernel exploit, Landlock independently prevents access to files outside the jail — enforced by a separate LSM path in the kernel.The flag is opt-in — existing deployments are completely unaffected.
Why Landlock on top of chroot?
pivot_root+ chroot lives in the kernel's VFS layer and can be bypassed by a guest-triggered kernel exploit. Landlock is enforced by a separate LSM subsystem and independently blocks filesystem access, so it provides defense-in-depth even ifpivot_rootis defeated. The inode-based rules (not path-based) also survive thepivot_rootboundary.Verification
Tested on kernel 6.8.0-1048-gcp (Landlock V1/V2/V3 supported).
straceconfirms the correct syscalls are made:Without
--landlock— no Landlock syscalls:With
--landlock— all 4 Landlock syscalls succeed:parent_fd=3is thePathFdopened beforepivot_root— confirming the inode reference survives the chroot boundary.Changes
src/jailer/src/landlock.rs(new):prepare_ruleset()andenforce()src/jailer/src/env.rs: wires Landlock intorun()— before chroot and before execsrc/jailer/src/main.rs:--landlockCLI flag +JailerError::Landlockvariantsrc/jailer/Cargo.toml:landlock = "0.4"dependencytests/framework/jailer.py:landlockparameter inJailerContextCHANGELOG.md: entry under[Unreleased] → AddedLicense Acceptance
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
PR Checklist
tools/devtool checkbuild --allto verify that the PR passes build checks on all supported architectures.tools/devtool checkstyleto verify that the PR passes the automated style checks.CHANGELOG.md.TODO.rust-vmm.