Linux Security Modules development
 help / color / mirror / Atom feed
* [PATCH v9 0/9] Implement LANDLOCK_ADD_RULE_NO_INHERIT
@ 2026-06-21  3:52 Justin Suess
  2026-06-21  3:52 ` [PATCH v9 1/9] landlock: Add and use landlock_walk_path_up() helper Justin Suess
                   ` (8 more replies)
  0 siblings, 9 replies; 10+ messages in thread
From: Justin Suess @ 2026-06-21  3:52 UTC (permalink / raw)
  To: linux-security-module, mic; +Cc: m, gnoack, gnoack3000, matthieu, Justin Suess

Hi,

This is version 9 of the LANDLOCK_ADD_RULE_NO_INHERIT series, which
implements a new flag to suppress inheritance of access rights and
flags from parent objects.

The main change this version is the rebase onto mic/next, now that
Tingmao Wang's "quiet flag" series, which earlier revisions depended
on has been merged (congrats!).  This series is therefore no longer
based on it.

The rest is consolidation and hardening: the three path-walk patches
were squashed into one to avoid unused function build failures,
ancestor sealing and the removal hooks were factored into helpers, and
the topology seal was broadened to also deny hard links and
same-directory renames of a protected inode.  The KUnit suite was
trimmed to the tests that exercise the skip branch through realistic
rule application, and a new EINVAL case rejects the flag on a
ruleset that handles no filesystem access.

Overall the code is larger; but mostly from test coverage and the
hardlink gap being closed.

Ancestor sealing:

The other big change is the clear documentation of the consistency
guarantees associated with this flag. One notable property of the
way ancestor sealing has been done in this series thus far, is that
that a concurrent rename of a path that is underneath the current
position during the upward ancestor sealing walk will result in
a partially undersealed and/or oversealed hierarchy.

To make this clearer, in seal_ancestors, we walk upwards inserting
blank rules / updating existing ones with the has_no_inherit_descendent
marker. So if /a/b/c/d is tagged with LANDLOCK_ADD_RULE_NO_INHERIT,
we walk upward inserting this marker on /a/b/c, /a/b, /a and /.

Say we are in the middle of marking /a/b/ and /a/b/c is moved to
/foo. The result is we continue to walk upward towards /,
but /foo is unsealed with no marker.

It is of my opinion (and this is not my call obviously) that this
inconsistency is unavoidable. Even if we check against the rename_lock
and serialize on it, nothing stops an outside process from moving
/a/b/c to /foo even AFTER the rule is fully inserted, resulting in
the same exact state. So protecting against this concurrent rename
at insertion time wouldn't actually achieve anything.

The only way to truly close this would be costly; moving sealing to
runtime, and having to track the movement of the sealed inode outside
the domain. That seems to me unacceptable and inconsistent with
Landlock's threat model.

Thus my judgement is that this topology sealing is sufficient as is;
outside processes can already tamper with inodes in a landlock ruleset,
by removing inodes referenced by the ruleset, bind mounts, and all sorts
of things.

It is clear that there is no guarantee the filesystem and thus the ruleset
at ruleset creation time and enforcement time will be consistent.

We handle the -ENOMEM case similarly, if we run into memory pressure,
and can't finish sealing, it is left to userspace to catch the error
and discard the potentially unsealed ruleset, or retry rule insertion.

The only difference now is both of these gotchas are now explicitly
documented in the code and user documentation.

Previous patch summary:

The new flag enables policies where a parent directory needs broader
access than its children. For example, a sandbox may permit read-write
access to /home/user but still prohibit writes to ~/.bashrc or
~/.ssh, even though they are nested beneath the parent. Today this is
not possible because access rights always propagate from parent to
child inodes.

When a rule is added with LANDLOCK_ADD_RULE_NO_INHERIT:

  * access rights on parent inodes are ignored for that inode and its
    descendants; and
  * operations that reparent, rename, or remove the tagged inode or
    its ancestors (via rename, rmdir, link) are denied up to the VFS
    root; and
  * parent flags do not propagate below a NO_INHERIT rule.

These parent-directory restrictions help mitigate sandbox-restart
attacks: a sandboxed process could otherwise move a protected
directory before exit, causing the next sandbox instance to apply its
policy to the wrong path.

Changes since v8:

  1. Rebased onto mic/next now that Tingmao Wang's "quiet flag" series
     has been merged; this series is no longer based on it.
  2. Squashed the three landlock_walk_path_up() patches (add helper,
     use in is_access_to_paths_allowed(), use in
     collect_domain_accesses()) into a single patch.
  3. landlock_walk_path_up() now advances to the mount root before
     reporting an internal mount point, so the ancestor walk can
     follow_up() into the parent mount instead of stopping at a
     disconnected root. The sealing loop in landlock_append_fs_rule()
     handles this LANDLOCK_WALK_INTERNAL case explicitly.
  4. Added a preparatory patch moving log_fs_change_topology_dentry()
     above current_check_refer_path(), and reordered the user-API
     patch ahead of "Return inserted rule from landlock_insert_rule()".
  5. Extracted ancestor sealing into a seal_ancestors() helper and the
     unlink/rmdir hooks into a current_check_remove() helper, removing
     the duplicated subject-lookup/deny blocks and the out_unlock goto.
  6. Topology-change enforcement in current_check_refer_path() now
     covers hard links (link) and same-directory renames in addition
     to reparenting renames; deny_no_inherit_topology_change() short-
     circuits and logs on the first sealed layer.
  7. NO_INHERIT on a ruleset that handles no filesystem access is now
     rejected with -EINVAL, with matching base_test coverage.
  8. Selftests gained same-directory-rename and hard-link denial
     assertions, REFER handling in the layered test, and two rejection
     tests; the audit fixture dropped its redundant parent_is_logged
     variant.
  9. The KUnit suite was reduced from five tests to three, dropping the
     two cases that set the per-layer no_inherit bit synthetically and
     keeping those that exercise the skip branch through realistic rule
     application.
  10. Bumped the Landlock ABI to version 11 (updating base_test) and
      expanded the UAPI and userspace-api documentation (conservative
      seal, best-effort walk, threat model).

Changes since v7:

  1. Replaced the v7 "Move find_rule definition above
     landlock_append_fs_rule" preparatory patch with a new
     preparatory patch that makes landlock_insert_rule() return the
     inserted struct landlock_rule * via ERR_PTR(). The core
     implementation patch now tags ancestor rules directly from the
     return value, removing the find_rule() round trip after
     insertion.
  2. Folded the no_inherit / has_no_inherit_descendant bookkeeping
     into the existing struct layer_mask as a single per-layer
     no_inherit bit. The separate collected_rule_flags fields
     (no_inherit_masks, no_inherit_desc_masks) are gone;
     landlock_unmask_layers() now skips layers whose mask already has
     no_inherit set, and landlock_init_layer_masks() clears the new
     bit on initialization.
  3. has_no_inherit_descendant is now auto-set on the rule's own
     object when LANDLOCK_ADD_RULE_NO_INHERIT is passed, sealing it
     against topology changes without requiring a separate blank-rule
     insertion.
  4. Centralized flag validation in sys_landlock_add_rule(): a single
     mask check rejects unknown flags, and NO_INHERIT on any rule
     type other than path-beneath is rejected at the syscall entry
     point. The redundant per-rule-type NO_INHERIT check in
     add_rule_net_port() was removed.
  5. collect_domain_accesses() now takes a single struct path *
     instead of separate mnt_root/dir parameters, matching
     is_access_to_paths_allowed(). The disconnected-directory stop
     condition was tightened with an explicit !d_unhashed() check at
     the mount root.
  6. deny_no_inherit_topology_change() dropped its override_layers
     accumulator (it was always 0 in practice) and now just
     OR-collects sealed layers.
  7. Selftest coverage in fs_test.c was reorganized around fixtures
     and variants: the v7 layout1 tests collapse into a
     layout1_no_inherit fixture with five variants and three shared
     tests; the four v7 layout4 mount tests collapse into a single
     variant + test; and a new audit_no_inherit fixture replaces the
     ad hoc audit case. Net change: 705 added lines in v7 -> 419
     added lines in v8, with equivalent coverage.
  8. The single KUnit test was expanded into five focused tests
     covering propagation, skip, both-set, multi-layer, and
     sequential-walk behavior of the per-layer no_inherit bit.
  9. UAPI and userspace-api documentation reworded for clarity. The
     new EINVAL case (NO_INHERIT on unsupported rule types) is
     documented in the syscall kernel-doc.
  10. Various commit messages reworded; switch arms in
      is_access_to_paths_allowed() reordered so the fast path comes
      first.

Changes since v6:

  1. The main implementation of NO_INHERIT was split into smaller more
     reviewable patches, separating the landlock_walk_path_up
     implementation, usages of landlock_walk_path_up, and the find_rule
     move to separate patches
  2. A small issue regarding disconnected directory handling, where rules
     inserted with NO_INHERIT only had protection up to a disconnected
     directory instead of the mountpoint was fixed. In practice, this
     isn't a problem at the current time since landlock forbids the mount
     syscall needed to move a mountpoint with MS_MOVE. However, for
     future-proofing in the case landlock allows some mount operations,
     restrictions on parent directories now apply to the real root.

Changes since v5:

  1. Retain existing documentation for path traversal in
     is_access_to_paths_allowed.
  2. Change conditional for path walk in is_access_to_paths_allowed
     removing possibility of infinite loop and renamed constant.
  3. Remove (now) redundant mnt_root parameter from
     collect_domain_accesses.
  4. Change path parameter to a dentry for
     deny_no_inherit_topology_change because only the dentry was needed.
  5. Remove duplicated tree diagram comment from selftests.
  6. Minor documentation fixes.

  Credit to Tingmao Wang for pointing out 1, 2, 3, 4, and 6.

Changes since v4:

  1. Trimmed 120 lines from core implementation in fs.c.
  2. Centralized path traversal logic with a helper function
     landlock_walk_path_up.
  3. Fixed bug in test on applying LANDLOCK_ADD_RULE_NO_INHERIT on
     a file, giving it valid access rights.
  4. Restructured commits to allow independent builds.
  5. Adds userspace API documentation for the flag.

Changes since v3:

  1. Trimmed core implementation in fs.c by removing redundant functions.
  2. Fixed placement/inclusion of prototypes.
  3. Added 4 new selftests for bind mount cases.
  4. Protections now apply up to the VFS root instead of the mountpoint
     root.

Links:

v1:
  https://lore.kernel.org/linux-security-module/20251105180019.1432367-1-utilityemal77@gmail.com/
v2:
  https://lore.kernel.org/linux-security-module/20251120222346.1157004-1-utilityemal77@gmail.com/
v3:
  https://lore.kernel.org/linux-security-module/20251126122039.3832162-1-utilityemal77@gmail.com/
v4:
  https://lore.kernel.org/linux-security-module/20251207015132.800576-1-utilityemal77@gmail.com/
v5:
  https://lore.kernel.org/linux-security-module/20251214170548.408142-1-utilityemal77@gmail.com/
v6:
  https://lore.kernel.org/linux-security-module/20260118000000.000000-1-utilityemal77@gmail.com/
v7:
  https://lore.kernel.org/linux-security-module/20260412193214.87072-1-utilityemal77@gmail.com/
v8:
  https://lore.kernel.org/linux-security-module/20260529015210.500291-1-utilityemal77@gmail.com/

Example usage:

  # LL_FS_RO="/a/b/c" LL_FS_RW="/" LL_FS_NO_INHERIT="/a/b/c"
    landlock-sandboxer sh
  # touch /a/b/c/fi                    # denied; / RW does not inherit
  # rmdir /a/b/c                       # denied by ancestor protections
  # mv /a /bad                         # denied
  # mkdir /a/good; touch /a/good/fi    # allowed; unrelated path

All tests added by this series, and all other existing landlock tests,
are passing. This patch was also validated through checkpatch.pl --strict
with no complaints.

Special thanks to Tingmao Wang and Mickaël Salaün for your valuable
feedback.

Thank you for your time and review.

Regards,
Justin Suess

Justin Suess (9):
  landlock: Add and use landlock_walk_path_up() helper
  landlock: Add LANDLOCK_ADD_RULE_NO_INHERIT user API
  landlock: Return inserted rule from landlock_insert_rule()
  landlock: Move log_fs_change_topology_dentry() above
    current_check_refer_path()
  landlock: Implement LANDLOCK_ADD_RULE_NO_INHERIT
  landlock: Add documentation for LANDLOCK_ADD_RULE_NO_INHERIT
  samples/landlock: Add LANDLOCK_ADD_RULE_NO_INHERIT to
    landlock-sandboxer
  selftests/landlock: Add selftests for LANDLOCK_ADD_RULE_NO_INHERIT
  landlock: Add KUnit tests for LANDLOCK_ADD_RULE_NO_INHERIT

 Documentation/userspace-api/landlock.rst     |  44 ++
 include/uapi/linux/landlock.h                |  35 ++
 samples/landlock/sandboxer.c                 |  16 +-
 security/landlock/access.h                   |   6 +
 security/landlock/fs.c                       | 366 ++++++++++++----
 security/landlock/net.c                      |   6 +-
 security/landlock/ruleset.c                  | 235 ++++++++--
 security/landlock/ruleset.h                  |  20 +-
 security/landlock/syscalls.c                 |  27 +-
 tools/testing/selftests/landlock/base_test.c |  60 ++-
 tools/testing/selftests/landlock/fs_test.c   | 425 +++++++++++++++++++
 11 files changed, 1100 insertions(+), 140 deletions(-)


base-commit: 1c236e7fe740a009ad8dd40a5ee0602ec402fffe
-- 
2.54.0


^ permalink raw reply	[flat|nested] 10+ messages in thread

end of thread, other threads:[~2026-06-21  3:52 UTC | newest]

Thread overview: 10+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-06-21  3:52 [PATCH v9 0/9] Implement LANDLOCK_ADD_RULE_NO_INHERIT Justin Suess
2026-06-21  3:52 ` [PATCH v9 1/9] landlock: Add and use landlock_walk_path_up() helper Justin Suess
2026-06-21  3:52 ` [PATCH v9 2/9] landlock: Add LANDLOCK_ADD_RULE_NO_INHERIT user API Justin Suess
2026-06-21  3:52 ` [PATCH v9 3/9] landlock: Return inserted rule from landlock_insert_rule() Justin Suess
2026-06-21  3:52 ` [PATCH v9 4/9] landlock: Move log_fs_change_topology_dentry() above current_check_refer_path() Justin Suess
2026-06-21  3:52 ` [PATCH v9 5/9] landlock: Implement LANDLOCK_ADD_RULE_NO_INHERIT Justin Suess
2026-06-21  3:52 ` [PATCH v9 6/9] landlock: Add documentation for LANDLOCK_ADD_RULE_NO_INHERIT Justin Suess
2026-06-21  3:52 ` [PATCH v9 7/9] samples/landlock: Add LANDLOCK_ADD_RULE_NO_INHERIT to landlock-sandboxer Justin Suess
2026-06-21  3:52 ` [PATCH v9 8/9] selftests/landlock: Add selftests for LANDLOCK_ADD_RULE_NO_INHERIT Justin Suess
2026-06-21  3:52 ` [PATCH v9 9/9] landlock: Add KUnit tests " Justin Suess

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox