feat(core): Landlock fs_read axis with loader/system base allow-list (#31)#54
Merged
Conversation
#31) WHAT: LandlockSandbox::apply now governs reads, not just writes. When fs_read is restricted (Only), the ruleset read-allows the granted read roots PLUS BASE_READ_PATHS (the dynamic linker, shared libs, ld.so.cache, locale, name-resolution config, /proc/self and /dev essentials) and denies everything else. Reads use ReadFile|ReadDir only — Execute is left ungoverned this increment so libraries can be mmap-exec'd without an execute allow-list. When fs_read is All, reads stay ambient (nothing to confine, no base list needed). A scope_roots() helper factors the existing-path resolution shared by both axes. WHY (#31 checklist item 1): closes the read half of the ADR-0001 L3 gap — a PERMITTED external program can no longer read user data outside fs_read (e.g. grep -f /etc/shadow is denied: /etc is NOT granted wholesale, only /etc/ld.so.cache as a single file), while still being able to load libc and run. apply() stays fail-closed (ADR 0006 D4). The base list is tuned for a glibc/FHS layout (the CI target); non-existent paths are skipped so extra entries are harmless, and a musl/Nix layout may need more (documented). TEST (Linux+feature kernel tests, self-skip without Landlock): fs_read in-scope read succeeds / out-of-scope is EACCES; a REAL dynamically-linked binary (cat) still loads under read confinement and reads the in-scope file but is denied the out-of-scope one (proves base-list adequacy + real enforcement); fs_read: All leaves reads ambient. All prior write-axis tests still pass. Verified on a 6.8 kernel; just check green. Part of #31 — exec axis (block find -exec curl via exec-Caveat path resolution) and net (netns/Landlock-net) remain. Composes with #53 (engine wiring): once both land, restricting fs_read confines the spawned interior's reads. NOTE: intended_sandbox_kind in #53 currently triggers on fs_write Only; a tiny follow-up will also trigger on fs_read Only. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HMGPEApE4XfwgMhgFbRn6c
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.
Part of #31 (checklist item 1) — adds the Landlock
fs_readaxis with a loader/system base allow-list, closing the read half of the ADR-0001 L3 gap. Open for review (risk:high), like #53.What
LandlockSandbox::applynow governs reads as well as writes:fs_readis restricted (Only), the ruleset read-allows the granted read roots plusBASE_READ_PATHS(the dynamic linker, shared libs,/etc/ld.so.cache, locale, name-resolution config,/proc/self,/devessentials) and denies everything else.ReadFile | ReadDironly —Executeis left ungoverned this increment so libraries can bemmap-exec'd without an execute allow-list (the exec axis is a follow-up).fs_readisAll, reads stay ambient (nothing to confine).scope_roots()helper shares existing-path resolution across both axes;apply()stays fail-closed (ADR 0006 D4).Why
A permitted program can no longer read user data outside
fs_read— e.g.grep -f /etc/shadowis denied because/etcis not granted wholesale (only/etc/ld.so.cacheas a single file) — while still loading libc and running.Testing (Linux +
linux-landlock, self-skips without Landlock)fs_readin-scope read succeeds; out-of-scope isEACCES(kernel).cat) still loads under read confinement and reads the in-scope file, but is denied the out-of-scope one — proves the base list lets binaries start and that reads are really confined.fs_read: Allleaves reads ambient.just checkgreen.Apple Silicon / macOS
Core change behind
cfg(all(target_os="linux", feature="linux-landlock"))— macOS builds unaffected (honestNone). No engine change here; composes with #53.Scope / follow-ups (still #31)
find -exec curl/awk system()via exec-Caveatpath resolution (only granted program paths + loader/libs executable).fs_readconfines the spawned interior's reads. A tiny follow-up will makeintended_sandbox_kind(in feat(shell): wire the Landlock fs_write L3 boundary into the engine (#35) #53) also trigger onfs_read: Only(today it triggers onfs_write: Only).Test plan
just checkgreen (fmt + clippy--all-features&--no-default-features+ workspace tests; the--all-featuresrun executed the new read-axis enforcement on a Landlock-capable kernel).🤖 Generated with Claude Code