Skip to content

fix(landlock): tolerate missing read/exec grant paths instead of aborting#146

Closed
congwang-mk wants to merge 1 commit into
mainfrom
fix/landlock-tolerate-missing-read-paths
Closed

fix(landlock): tolerate missing read/exec grant paths instead of aborting#146
congwang-mk wants to merge 1 commit into
mainfrom
fix/landlock-tolerate-missing-read-paths

Conversation

@congwang-mk

Copy link
Copy Markdown
Contributor

Problem

A Landlock read/exec grant that names a path absent on the host made add_path_rule hard-fail the O_PATH open, which aborted confinement and took the whole sandbox launch down with it:

confinement failed: landlock error: open path "/lib64" failed: No such file or directory

/lib64 is an x86-64 glibc multilib convention. On RISC-V glibc (loader at /lib/ld-linux-riscv64-lp64d.so.1) and on musl there is no /lib64 at all, so a base policy that lists the standard library dirs cannot be portable across distros/arches. Concretely, every sandlock-cli test that grants -r /lib64 fails on RISC-V (11 failures), and this is the same /lib64 sharp edge previously worked around in the alpine/musl policy.

Fix

add_path_rule now skips a read/exec grant whose path does not exist, with a warning, instead of failing. One base policy can then list /lib, /lib64, /usr/lib, /usr/lib64, ... and each host takes whatever subset it actually has.

Scoped narrowly:

  • ENOENT only. Any other open error (EACCES, ...) still hard-fails.
  • Read/exec grants only ((access & !READ_ACCESS) == 0). A missing write target stays a hard error, since a caller naming a write path means it to exist.
  • The skip is warned, not silent, and a genuine typo still surfaces as the loud runtime failure of a program that cannot find its files.

Why it is security-neutral

Landlock is default-deny with allow-only rules; there are no deny rules to weaken. add_path_rule only ever widens access, so skipping a grant only ever narrows it. For a nonexistent path there is nothing beneath it to grant, so the reachable-file set is identical. Adversarial cases resolve in the safe (deny) direction:

  • Path appears later / TOCTOU: the process was never granted it, so it stays denied.
  • Dangling symlink: O_PATH follows to a missing target -> ENOENT -> skipped; the grant would have been for the missing target anyway.
  • Attacker deletes a required dir before launch: worst case the process fails to run (DoS), not a confinement bypass.

The only behavioral change is launch-time: a missing grant no longer aborts the launch; the process runs confined by a strict subset of the intended grants. It can only do less, never more.

Tests

  • New unit test add_path_rule_skips_missing_read_but_fails_missing_write pins the contract (both branches return before the ruleset fd is touched, so it needs no Landlock kernel support).
  • x86 unaffected: all 22 cli_test cases and all landlock unit tests pass (/lib64 exists there, so the skip branch is not even exercised). On RISC-V the previously-failing tests now pass.

…ting

A Landlock read/exec grant that names a path absent on the host (e.g.
/lib64 on RISC-V glibc or musl, where the loader lives under /lib) made
add_path_rule hard-fail the O_PATH open, which aborted confinement and
took the whole sandbox launch down with it. One base policy therefore
could not portably list the standard library dirs across distros and
arches, and the CLI tests that grant /lib64 all failed on RISC-V.

Skip such a grant with a warning rather than failing. This is
security-neutral: Landlock is default-deny with allow-only rules, so an
absent subtree grants nothing and skipping it leaves confinement at
least as strict (a path that appears later, or a dangling symlink,
resolves in the deny direction). Scope the tolerance to ENOENT on
read/exec grants only: other open errors (EACCES, ...) and missing
WRITE targets stay hard errors, since a caller naming a write path
means it to exist.

Add a unit test pinning the contract: a read grant for a missing path
is skipped, a write grant for a missing path still errors.
@congwang-mk

Copy link
Copy Markdown
Contributor Author

Actually we have fs_read_if_exists().

@congwang-mk congwang-mk deleted the fix/landlock-tolerate-missing-read-paths branch July 15, 2026 21:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant