* [PATCH v8 01/10] landlock: Add landlock_walk_path_up() helper
From: Justin Suess @ 2026-05-29 1:52 UTC (permalink / raw)
To: gnoack3000, mic
Cc: linux-kernel, linux-security-module, Justin Suess, Tingmao Wang
In-Reply-To: <20260529015210.500291-1-utilityemal77@gmail.com>
In preparation for centralizing path-walk logic, add
landlock_walk_path_up(), which moves @path one step toward the VFS
root. Its return value indicates whether the new position is an
internal mount point, the real root, or neither (i.e. the caller
should continue walking).
No functional change intended.
Cc: Tingmao Wang <m@maowtm.org>
Signed-off-by: Justin Suess <utilityemal77@gmail.com>
---
Notes:
v7..v8 changes:
* Reworded commit message; no code changes.
security/landlock/fs.c | 32 ++++++++++++++++++++++++++++++++
1 file changed, 32 insertions(+)
diff --git a/security/landlock/fs.c b/security/landlock/fs.c
index 3b71f569a8f9..8e75583c3ca7 100644
--- a/security/landlock/fs.c
+++ b/security/landlock/fs.c
@@ -320,6 +320,38 @@ static struct landlock_object *get_inode_object(struct inode *const inode)
LANDLOCK_ACCESS_FS_RESOLVE_UNIX)
/* clang-format on */
+/**
+ * enum landlock_walk_result - Result codes for landlock_walk_path_up()
+ * @LANDLOCK_WALK_CONTINUE: Path is now neither the real root nor an internal mount point.
+ * @LANDLOCK_WALK_STOP_REAL_ROOT: Path has reached the real VFS root.
+ * @LANDLOCK_WALK_INTERNAL: Path has reached an internal mount point.
+ */
+enum landlock_walk_result {
+ LANDLOCK_WALK_CONTINUE,
+ LANDLOCK_WALK_STOP_REAL_ROOT,
+ LANDLOCK_WALK_INTERNAL,
+};
+
+static enum landlock_walk_result landlock_walk_path_up(struct path *const path)
+{
+ struct dentry *old;
+
+ while (path->dentry == path->mnt->mnt_root) {
+ if (!follow_up(path))
+ return LANDLOCK_WALK_STOP_REAL_ROOT;
+ }
+ old = path->dentry;
+ if (unlikely(IS_ROOT(old))) {
+ if (likely(path->mnt->mnt_flags & MNT_INTERNAL))
+ return LANDLOCK_WALK_INTERNAL;
+ path->dentry = dget(path->mnt->mnt_root);
+ } else {
+ path->dentry = dget_parent(old);
+ }
+ dput(old);
+ return LANDLOCK_WALK_CONTINUE;
+}
+
/*
* @path: Should have been checked by get_path_from_fd().
*/
--
2.53.0
^ permalink raw reply related
* [PATCH v8 00/10] Implement LANDLOCK_ADD_RULE_NO_INHERIT
From: Justin Suess @ 2026-05-29 1:51 UTC (permalink / raw)
To: gnoack3000, mic; +Cc: linux-kernel, linux-security-module, Justin Suess
Hi,
This is version 8 of the LANDLOCK_ADD_RULE_NO_INHERIT series, which
implements a new flag to suppress inheritance of access rights and
flags from parent objects.
This version is mostly cleanup: a preparatory patch was replaced to
avoid needing to move find_rule(), the no_inherit bookkeeping was
folded into the existing per-layer mask, syscall flag validation was
centralized, and the selftest coverage was reorganized around
fixtures and variants to cut roughly 290 lines of near-duplicate test
code while keeping equivalent coverage.
Behavior of the flag is identical to the previous version.
This series remains rebased on v9 of Tingmao Wang's "quiet flag"
series.
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 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/
quiet-flag v6:
https://lore.kernel.org/linux-security-module/cover.1765040503.git.m@maowtm.org/
quiet-flag v7:
https://lore.kernel.org/linux-security-module/cover.1766330134.git.m@maowtm.org/
quiet-flag v8:
https://lore.kernel.org/linux-security-module/cover.1775490344.git.m@maowtm.org/
quiet-flag v9:
https://lore.kernel.org/linux-security-module/cover.1779843375.git.m@maowtm.org/
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.
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 (10):
landlock: Add landlock_walk_path_up() helper
landlock: Use landlock_walk_path_up() in is_access_to_paths_allowed()
landlock: Use landlock_walk_path_up() in collect_domain_accesses()
landlock: Add LANDLOCK_ADD_RULE_NO_INHERIT user API
landlock: Return inserted rule from landlock_insert_rule()
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 | 18 ++
include/uapi/linux/landlock.h | 24 ++
samples/landlock/sandboxer.c | 13 +-
security/landlock/access.h | 4 +
security/landlock/fs.c | 290 ++++++++++++++------
security/landlock/net.c | 8 +-
security/landlock/ruleset.c | 280 ++++++++++++++++---
security/landlock/ruleset.h | 20 +-
security/landlock/syscalls.c | 14 +-
tools/testing/selftests/landlock/fs_test.c | 419 +++++++++++++++++++++++++++++
10 files changed, 966 insertions(+), 124 deletions(-)
--
2.53.0
Justin Suess (10):
landlock: Add landlock_walk_path_up() helper
landlock: Use landlock_walk_path_up() in is_access_to_paths_allowed()
landlock: Use landlock_walk_path_up() in collect_domain_accesses()
landlock: Add LANDLOCK_ADD_RULE_NO_INHERIT user API
landlock: Return inserted rule from landlock_insert_rule()
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 | 18 +
include/uapi/linux/landlock.h | 24 ++
samples/landlock/sandboxer.c | 13 +-
security/landlock/access.h | 4 +
security/landlock/fs.c | 290 ++++++++++----
security/landlock/net.c | 8 +-
security/landlock/ruleset.c | 280 ++++++++++++--
security/landlock/ruleset.h | 20 +-
security/landlock/syscalls.c | 14 +-
tools/testing/selftests/landlock/fs_test.c | 419 +++++++++++++++++++++
10 files changed, 966 insertions(+), 124 deletions(-)
base-commit: fe7832557561ed6312563368854d5f8df1fa55e3
prerequisite-patch-id: e3aaf6d74feae4e831f7ecf033987028f2b9fa89
prerequisite-patch-id: c0fe2c5da8481b5712e4289ed969e5374a8d3d14
prerequisite-patch-id: 7af9880bb7747f3b4e1dc38c405ea84256ffb853
prerequisite-patch-id: 707ec2e5bb927ab78302e2500ca9c4ed0af74c26
prerequisite-patch-id: 4fc670726f25b501ed244ad6177e24ec833642bc
prerequisite-patch-id: 90971f3bce38e63ab6c829c1daf34e0f343003dd
prerequisite-patch-id: cf58f275348749ab5adf03ccba5fd6f11a349fce
prerequisite-patch-id: 34a66721c66f731e7aa45a7ccab6eacb24e0218e
prerequisite-patch-id: 3d5eb906e6e923a85b9b9eeb12ed3bc3b69ee366
--
2.53.0
^ permalink raw reply
* Re: [PATCH 00/11] hornet: security, tooling and selftest fixes
From: Paul Moore @ 2026-05-29 1:39 UTC (permalink / raw)
To: Blaise Boscaccy
Cc: Jonathan Corbet, Shuah Khan, James Morris, Serge E. Hallyn,
Eric Biggers, Fan Wu, James.Bottomley, linux-security-module
In-Reply-To: <20260528030915.2654994-1-bboscaccy@linux.microsoft.com>
On Wed, May 27, 2026 at 11:09 PM Blaise Boscaccy
<bboscaccy@linux.microsoft.com> wrote:
>
> Patch 1 closes a TOCTOU race in signature verification. Map
> contents were hashed at the program-load hook and re-hashed at
> the program-run hook, leaving a window in which a sufficiently
> privileged attacker could mutate a map between the two checks
> and run a program whose maps no longer matched what was signed.
> The fix records the verified hashes on the prog at load time
> and, in security_bpf_prog, checks them against
> prog->aux->used_maps — the same map set the verifier and
> runtime resolve against — so the verified and executed sets
> cannot diverge. The per-map index in the signature format is no
> longer needed and is dropped; the check becomes a subset test.
> Reported by Eric Biggers.
>
> Patches 2-3 fix two counting bugs in the same area: duplicate maps
> could satisfy the required hash count, and an off-by-one capped
> accepted maps at MAX_USED_MAPS.
>
> Patches 4-11 are in response to sashiko feedback found here:
> https://sashiko.dev/#/patchset/20260507191416.2984054-1-bboscaccy%40linux.microsoft.com
>
> They provide some correctness fixes in the hornet tooling along with
> making the selftest behave under cross-compilation and skip cleanly
> when signing keys / bpftool / vmlinux BTF are unavailable, instead of
> breaking the global selftest build.
>
> Blaise Boscaccy (11):
> hornet: fix TOCTOU in signed program verification
> hornet: invert map set check logic
> hornet: fix off-by-one bug in max used maps check
> selftests: hornet: handle cross compilation and test skipping
> hornet: gen_sig: fix off-by-one check for used maps
> hornet: gen_sig: fix error string allocations
> hornet: gen_sig: check for bad allocations
> hornet: gen_sig: fix missing command line switches
> hornet: scripts: set a non-zero error code for usage
> hornet: scripts: harden scripts to handle trailing whitespace
> hornet: scripts: Improve argument handling and error messages
>
> Documentation/admin-guide/LSM/Hornet.rst | 39 +++---
> scripts/hornet/extract-insn.sh | 24 ++--
> scripts/hornet/extract-map.sh | 25 ++--
> scripts/hornet/extract-skel.sh | 35 ++++--
> scripts/hornet/gen_sig.c | 61 ++++++----
> scripts/hornet/write-sig.sh | 10 +-
> security/hornet/hornet.asn1 | 1 -
> security/hornet/hornet_lsm.c | 148 ++++-------------------
> tools/testing/selftests/hornet/Makefile | 114 +++++++++++++----
> 9 files changed, 235 insertions(+), 222 deletions(-)
Aside from a possible (?) typo in patch 5/11, this patchset looks okay
to me so I'm going to merge it to lsm/dev-staging now with the idea of
moving it to lsm/dev once Blaise provides some clarity on patch-5.
--
paul-moore.com
^ permalink raw reply
* Re: [PATCH v9 1/9] landlock: Add a place for flags to layer rules
From: Justin Suess @ 2026-05-28 22:01 UTC (permalink / raw)
To: Tingmao Wang
Cc: Mickaël Salaün, Günther Noack, Jan Kara,
Abhinav Saxena, linux-security-module
In-Reply-To: <725de048a1756253dd2164c8f39b038e4e0ebdb5.1779843375.git.m@maowtm.org>
On Wed, May 27, 2026 at 02:01:11AM +0100, Tingmao Wang wrote:
> To avoid unnecessarily increasing the size of struct landlock_layer, we
> make the layer level a u8 and use the space to store the flags struct.
>
> struct layer_access_masks is renamed to struct layer_masks, and a new
> field is added to track whether a quiet flag rule is seen for each
> layer. Through use of bitfields, this does not increase the size of the
> struct.
>
> Cc: Justin Suess <utilityemal77@gmail.com>
> Assisted-by: GitHub Copilot:claude-opus-4.7 copilot-review
> Signed-off-by: Tingmao Wang <m@maowtm.org>
> Co-developed-by: Justin Suess <utilityemal77@gmail.com>
> Signed-off-by: Justin Suess <utilityemal77@gmail.com>
> ---
>
> Changes in v9:
> - Move a hunk from patch 2 to here
> - Fix comment and format
> - Renamed struct layer_access_masks to struct layer_masks, and moved the
> content of struct collected_rule_flags into this struct, getting rid
> of the extra struct collected_rule_flags and function parameters.
> This is following a discussion in [3]. The flag is now initialized in
> landlock_init_layer_masks as false.
> - Thus also removed now unnecessary layer_mask_t
>
> Changes in v8:
> - Rebase on top of mic/next
> - Add Co-developed-by: Justin Suess for handling this rebase initially
> - layer_mask_t was removed in [1] but we still need it for the
> collected_rule_flags. Rather than using raw u16, I've chosen to
> re-define it back in ruleset.h (it was in access.h).
>
> Changes in v7:
> - Take rule_flags separately from landlock_request in
> is_access_to_paths_allowed to avoid writing to the landlock_request
> variable if CONFIG_AUDIT is disabled (to enable compiler elision).
> - Due to the above change, we don't need rule_flags in landlock_request in
> this commit anymore (will be added later).
>
> Changes in v6:
> - Rebased to include the revised disconnected directory handling changes
> (without the "reverting" behaviour)
>
> Changes in v5:
> - Move rule_flags into landlock_request. This lets us get rid of the
> extra parameters to is_access_to_paths_allowed (and later on,
> landlock_log_denial), and thus less code changes.
>
> Changes in v3:
> - Comment changes, move local variables, simplify if branch
>
> Changes in v2:
> - Comment changes
> - Rebased to include disconnected directory handling changes on mic/next
> and add backing up of collected_rule_flags.
>
> [1]: https://lore.kernel.org/all/20260125195853.109967-1-gnoack3000@gmail.com/
> [2]: https://lore.kernel.org/all/20251221194301.247484-1-utilityemal77@gmail.com/
> [3]: https://lore.kernel.org/all/20260524.eFiz4hahrami@digikod.net/
>
> security/landlock/access.h | 35 +++++++--
> security/landlock/audit.c | 20 ++---
> security/landlock/audit.h | 2 +-
> security/landlock/domain.c | 19 ++---
> security/landlock/domain.h | 2 +-
> security/landlock/fs.c | 147 +++++++++++++++++++-----------------
> security/landlock/limits.h | 3 +
> security/landlock/net.c | 2 +-
> security/landlock/ruleset.c | 33 +++++---
> security/landlock/ruleset.h | 17 ++++-
> 10 files changed, 170 insertions(+), 110 deletions(-)
>
> diff --git a/security/landlock/access.h b/security/landlock/access.h
> index c19d5bc13944..3b8ba6c1300d 100644
> --- a/security/landlock/access.h
> +++ b/security/landlock/access.h
> @@ -62,18 +62,37 @@ static_assert(sizeof(typeof_member(union access_masks_all, masks)) ==
> sizeof(typeof_member(union access_masks_all, all)));
>
> /**
> - * struct layer_access_masks - A boolean matrix of layers and access rights
> + * struct layer_mask - The unfulfilled access rights and rule flags for
> + * a layer.
> *
> - * This has a bit for each combination of layer numbers and access rights.
> - * During access checks, it is used to represent the access rights for each
> - * layer which still need to be fulfilled. When all bits are 0, the access
> - * request is considered to be fulfilled.
> + * During access checks, @access is used to represent the access rights
> + * for each layer which still need to be fulfilled. When all bits in
> + * @access is 0, the access request is allowed by this layer.
> + *
> + * @quiet is used to store whether we have encountered a rule with the
> + * quiet flag for this layer, which will be used to control audit logging.
> + */
> +struct layer_mask {
> + access_mask_t access:LANDLOCK_NUM_ACCESS_MAX;
> +#ifdef CONFIG_AUDIT
> + bool quiet:1;
> +#endif /* CONFIG_AUDIT */
> +};
From landlock-test-tools/docker-run.sh:
Warning: security/landlock/access.h:79 struct member 'access' not described in 'layer_mask'
Warning: security/landlock/access.h:79 struct member 'quiet' not described in 'layer_mask'
Warning: security/landlock/access.h:79 struct member 'access' not described in 'layer_mask'
Warning: security/landlock/access.h:79 struct member 'quiet' not described in 'layer_mask
Probably worth adding these.
Tested this out as a basis of my draft for next version of
the no inherit series and it works great, handles addition
of more flags and passes selftests with no issues.
Tested-by: Justin Suess <utilityemal77@gmail.com>
> [...]
^ permalink raw reply
* Re: [PATCH 05/11] hornet: gen_sig: fix off-by-one check for used maps
From: Paul Moore @ 2026-05-28 21:22 UTC (permalink / raw)
To: Blaise Boscaccy
Cc: Jonathan Corbet, Shuah Khan, James Morris, Serge E. Hallyn,
Eric Biggers, Fan Wu, James.Bottomley, linux-security-module
In-Reply-To: <20260528030915.2654994-6-bboscaccy@linux.microsoft.com>
On Wed, May 27, 2026 at 11:09 PM Blaise Boscaccy
<bboscaccy@linux.microsoft.com> wrote:
>
> A logic bug limited the maximum number of used maps to
> MAX_USED_MAPS-1.
Should this be MAX_HASHES-1 and not MAX_USED_MAPS-1?
> Signed-off-by: Blaise Boscaccy <bboscaccy@linux.microsoft.com>
> ---
> scripts/hornet/gen_sig.c | 4 ++--
> 1 file changed, 2 insertions(+), 2 deletions(-)
>
> diff --git a/scripts/hornet/gen_sig.c b/scripts/hornet/gen_sig.c
> index b4f983ab24bcd..4e8caad22f381 100644
> --- a/scripts/hornet/gen_sig.c
> +++ b/scripts/hornet/gen_sig.c
> @@ -317,11 +317,11 @@ int main(int argc, char **argv)
> data_path = optarg;
> break;
> case 'A':
> - hashes[hash_count].file = optarg;
> - if (++hash_count >= MAX_HASHES) {
> + if (hash_count >= MAX_HASHES) {
> usage(argv[0]);
> return EXIT_FAILURE;
> }
> + hashes[hash_count++].file = optarg;
> break;
> default:
> usage(argv[0]);
> --
> 2.53.0
--
paul-moore.com
^ permalink raw reply
* [PATCH] landlock: fix LANDLOCK_SCOPE_SIGNAL bypass via F_SETOWN to invoker's pgid
From: hexlabsecurity @ 2026-05-28 21:21 UTC (permalink / raw)
To: mic@digikod.net
Cc: gnoack@google.com, linux-security-module@vger.kernel.org,
stable@vger.kernel.org
From 22a0086b44beaaef01883e047dd4a8b8bc3153e9 Mon Sep 17 00:00:00 2001
From: Bryam Vargas <hexlabsecurity@proton.me>
Date: Thu, 28 May 2026 01:30:00 -0500
Subject: [PATCH] landlock: fix LANDLOCK_SCOPE_SIGNAL bypass via F_SETOWN to
invoker's pgid
A Landlock-restricted process can bypass LANDLOCK_SCOPE_SIGNAL on the
SIGIO delivery path and deliver arbitrary signals (including SIGKILL via
F_SETSIG) to non-Landlocked targets that share its pgid, by exploiting a
producer-side cache-vs-live evaluation gap.
The SIGIO path in hook_file_send_sigiotask() consults a cached subject
stored in landlock_file(file)->fown_subject at fcntl(F_SETOWN) time
(via hook_file_set_fowner()), instead of evaluating the live Landlock
domain of the invoking task at signal-send time. The capture is gated
by control_current_fowner(), which returns false (skipping capture)
when pid_task(fown->pid, fown->pid_type) is in current's thread group.
This is correct for PIDTYPE_TGID / PIDTYPE_PID, where the target is a
single thread or thread-group leader sharing current's cred. It is
unsafe for PIDTYPE_PGID and PIDTYPE_SID: when current is at the head
of its pgid hlist -- the default placement after fork(),
hlist_add_head_rcu() in kernel/fork.c -- pid_task(pgid, PIDTYPE_PGID)
resolves to current itself, same_thread_group(current, current) is
true, the capture is skipped, and fown_subject.domain stays NULL.
hook_file_send_sigiotask() then short-circuits at
"if (!subject->domain) return 0;", allowing the kernel to fan the
signal out to every member of the group, including tasks outside
current's Landlock domain that the SCOPE_SIGNAL contract is supposed
to protect.
The direct kill() path (hook_task_kill) is unaffected: it evaluates
current's live domain on every call. Only the cached SIGIO path is
broken.
Repro (ordinary unprivileged user; sandbox active in the child):
int pfd[2]; pipe(pfd);
landlock_create_ruleset(&{.scoped = LANDLOCK_SCOPE_SIGNAL},
sizeof(attr), 0);
prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);
landlock_restrict_self(rfd, 0);
fcntl(pfd[0], F_SETSIG, SIGKILL);
fcntl(pfd[0], F_SETOWN, -getpgrp()); /* PIDTYPE_PGID */
fcntl(pfd[0], F_SETFL, O_ASYNC);
write(pfd[1], "X", 1); /* trigger SIGIO */
/* every pgid member receives SIGKILL, including non-sandboxed
* parent / supervisor / sibling workers */
Tighten control_current_fowner() to apply the thread-group exemption
only when the target identifies a SINGLE task whose Landlock cred is
necessarily shared with current (PIDTYPE_TGID, PIDTYPE_PID). For
PIDTYPE_PGID and PIDTYPE_SID, always capture the current Landlock
subject so the consumer's scope check runs against every member of
the group at delivery time.
Empirically A/B-verified on a 6.12.90 lab kernel (same .config, only
the patch hunk differs): pre-fix build exits with "BUG PRESENT --
SCOPE_SIGNAL BYPASSED", post-fix build exits with "SANDBOX HELD".
hook_task_kill's direct-kill enforcement and the intra-thread-group
F_SETOWN cases continue to work post-patch.
Reported-by: Bryam Vargas <hexlabsecurity@proton.me>
Signed-off-by: Bryam Vargas <hexlabsecurity@proton.me>
---
security/landlock/fs.c | 12 ++++++++++++
1 file changed, 12 insertions(+)
diff --git a/security/landlock/fs.c b/security/landlock/fs.c
index c1ecfe239032..edaa52572cbd 100644
--- a/security/landlock/fs.c
+++ b/security/landlock/fs.c
@@ -1909,6 +1909,18 @@ static bool control_current_fowner(struct fown_struct *const fown)
if (!p)
return true;
+ /*
+ * For PIDTYPE_PGID and PIDTYPE_SID, signal delivery fans out to
+ * every member of the group at SIGIO time. Even when pid_task()
+ * resolves to current itself (e.g., current is the pgid hlist
+ * head post-fork), non-current members of the group are still
+ * valid targets that must be checked by hook_file_send_sigiotask().
+ * Always capture the current subject for those types so the
+ * consumer scope check runs against the live fown_subject.
+ */
+ if (fown->pid_type == PIDTYPE_PGID || fown->pid_type == PIDTYPE_SID)
+ return true;
+
return !same_thread_group(p, current);
}
--
2.43.0
^ permalink raw reply related
* [PATCH v5 8/8] lsm: Remove security_sb_mount and security_move_mount
From: Song Liu @ 2026-05-28 18:26 UTC (permalink / raw)
To: linux-security-module, linux-fsdevel, selinux, apparmor
Cc: paul, jmorris, serge, viro, brauner, jack, john.johansen,
stephen.smalley.work, omosnace, mic, gnoack, takedakn,
penguin-kernel, herton, kernel-team, Song Liu
In-Reply-To: <20260528182607.3150386-1-song@kernel.org>
Now that all LSMs have been converted to granular mount hooks and
fs/namespace.c calls the new hooks, remove the old hooks:
- security_sb_mount(): removed from lsm_hook_defs.h, security.h,
security.c.
- security_move_mount(): removed from lsm_hook_defs.h, security.h,
security.c, and bpf_lsm.c.
Code generated with the assistance of Claude, reviewed by human.
Reviewed-by: Stephen Smalley <stephen.smalley.work@gmail.com>
Tested-by: Stephen Smalley <stephen.smalley.work@gmail.com> # for selinux only
Signed-off-by: Song Liu <song@kernel.org>
---
include/linux/lsm_hook_defs.h | 4 ----
include/linux/security.h | 16 ---------------
kernel/bpf/bpf_lsm.c | 2 --
security/security.c | 38 -----------------------------------
4 files changed, 60 deletions(-)
diff --git a/include/linux/lsm_hook_defs.h b/include/linux/lsm_hook_defs.h
index 98f0fe382665..c870260bf402 100644
--- a/include/linux/lsm_hook_defs.h
+++ b/include/linux/lsm_hook_defs.h
@@ -69,8 +69,6 @@ LSM_HOOK(int, 0, sb_remount, struct super_block *sb, void *mnt_opts)
LSM_HOOK(int, 0, sb_kern_mount, const struct super_block *sb)
LSM_HOOK(int, 0, sb_show_options, struct seq_file *m, struct super_block *sb)
LSM_HOOK(int, 0, sb_statfs, struct dentry *dentry)
-LSM_HOOK(int, 0, sb_mount, const char *dev_name, const struct path *path,
- const char *type, unsigned long flags, void *data)
LSM_HOOK(int, 0, sb_umount, struct vfsmount *mnt, int flags)
LSM_HOOK(int, 0, sb_pivotroot, const struct path *old_path,
const struct path *new_path)
@@ -79,8 +77,6 @@ LSM_HOOK(int, 0, sb_set_mnt_opts, struct super_block *sb, void *mnt_opts,
LSM_HOOK(int, 0, sb_clone_mnt_opts, const struct super_block *oldsb,
struct super_block *newsb, unsigned long kern_flags,
unsigned long *set_kern_flags)
-LSM_HOOK(int, 0, move_mount, const struct path *from_path,
- const struct path *to_path)
LSM_HOOK(int, 0, mount_bind, const struct path *from, const struct path *to,
bool recurse)
LSM_HOOK(int, 0, mount_new, struct fs_context *fc, const struct path *mp,
diff --git a/include/linux/security.h b/include/linux/security.h
index b1b3da51a88d..f1dcfc569cf2 100644
--- a/include/linux/security.h
+++ b/include/linux/security.h
@@ -373,8 +373,6 @@ int security_sb_remount(struct super_block *sb, void *mnt_opts);
int security_sb_kern_mount(const struct super_block *sb);
int security_sb_show_options(struct seq_file *m, struct super_block *sb);
int security_sb_statfs(struct dentry *dentry);
-int security_sb_mount(const char *dev_name, const struct path *path,
- const char *type, unsigned long flags, void *data);
int security_sb_umount(struct vfsmount *mnt, int flags);
int security_sb_pivotroot(const struct path *old_path, const struct path *new_path);
int security_sb_set_mnt_opts(struct super_block *sb,
@@ -385,7 +383,6 @@ int security_sb_clone_mnt_opts(const struct super_block *oldsb,
struct super_block *newsb,
unsigned long kern_flags,
unsigned long *set_kern_flags);
-int security_move_mount(const struct path *from_path, const struct path *to_path);
int security_mount_bind(const struct path *from, const struct path *to,
bool recurse);
int security_mount_new(struct fs_context *fc, const struct path *mp,
@@ -825,13 +822,6 @@ static inline int security_sb_statfs(struct dentry *dentry)
return 0;
}
-static inline int security_sb_mount(const char *dev_name, const struct path *path,
- const char *type, unsigned long flags,
- void *data)
-{
- return 0;
-}
-
static inline int security_sb_umount(struct vfsmount *mnt, int flags)
{
return 0;
@@ -859,12 +849,6 @@ static inline int security_sb_clone_mnt_opts(const struct super_block *oldsb,
return 0;
}
-static inline int security_move_mount(const struct path *from_path,
- const struct path *to_path)
-{
- return 0;
-}
-
static inline int security_mount_bind(const struct path *from,
const struct path *to, bool recurse)
{
diff --git a/kernel/bpf/bpf_lsm.c b/kernel/bpf/bpf_lsm.c
index aa228372cfb4..77371ca25d09 100644
--- a/kernel/bpf/bpf_lsm.c
+++ b/kernel/bpf/bpf_lsm.c
@@ -350,7 +350,6 @@ BTF_ID(func, bpf_lsm_release_secctx)
BTF_ID(func, bpf_lsm_sb_alloc_security)
BTF_ID(func, bpf_lsm_sb_eat_lsm_opts)
BTF_ID(func, bpf_lsm_sb_kern_mount)
-BTF_ID(func, bpf_lsm_sb_mount)
BTF_ID(func, bpf_lsm_sb_remount)
BTF_ID(func, bpf_lsm_sb_set_mnt_opts)
BTF_ID(func, bpf_lsm_sb_show_options)
@@ -382,7 +381,6 @@ BTF_ID(func, bpf_lsm_task_setscheduler)
BTF_ID(func, bpf_lsm_userns_create)
BTF_ID(func, bpf_lsm_bdev_alloc_security)
BTF_ID(func, bpf_lsm_bdev_setintegrity)
-BTF_ID(func, bpf_lsm_move_mount)
BTF_ID(func, bpf_lsm_mount_bind)
BTF_ID(func, bpf_lsm_mount_new)
BTF_ID(func, bpf_lsm_mount_remount)
diff --git a/security/security.c b/security/security.c
index b7ec0ec7af26..bc55ee588c59 100644
--- a/security/security.c
+++ b/security/security.c
@@ -1065,29 +1065,6 @@ int security_sb_statfs(struct dentry *dentry)
return call_int_hook(sb_statfs, dentry);
}
-/**
- * security_sb_mount() - Check permission for mounting a filesystem
- * @dev_name: filesystem backing device
- * @path: mount point
- * @type: filesystem type
- * @flags: mount flags
- * @data: filesystem specific data
- *
- * Check permission before an object specified by @dev_name is mounted on the
- * mount point named by @nd. For an ordinary mount, @dev_name identifies a
- * device if the file system type requires a device. For a remount
- * (@flags & MS_REMOUNT), @dev_name is irrelevant. For a loopback/bind mount
- * (@flags & MS_BIND), @dev_name identifies the pathname of the object being
- * mounted.
- *
- * Return: Returns 0 if permission is granted.
- */
-int security_sb_mount(const char *dev_name, const struct path *path,
- const char *type, unsigned long flags, void *data)
-{
- return call_int_hook(sb_mount, dev_name, path, type, flags, data);
-}
-
/**
* security_sb_umount() - Check permission for unmounting a filesystem
* @mnt: mounted filesystem
@@ -1167,21 +1144,6 @@ int security_sb_clone_mnt_opts(const struct super_block *oldsb,
}
EXPORT_SYMBOL(security_sb_clone_mnt_opts);
-/**
- * security_move_mount() - Check permissions for moving a mount
- * @from_path: source mount point
- * @to_path: destination mount point
- *
- * Check permission before a mount is moved.
- *
- * Return: Returns 0 if permission is granted.
- */
-int security_move_mount(const struct path *from_path,
- const struct path *to_path)
-{
- return call_int_hook(move_mount, from_path, to_path);
-}
-
/**
* security_mount_bind() - Check permissions for a bind mount
* @from: source path
--
2.53.0-Meta
^ permalink raw reply related
* [PATCH v5 7/8] vfs: Replace security_sb_mount/security_move_mount with granular hooks
From: Song Liu @ 2026-05-28 18:26 UTC (permalink / raw)
To: linux-security-module, linux-fsdevel, selinux, apparmor
Cc: paul, jmorris, serge, viro, brauner, jack, john.johansen,
stephen.smalley.work, omosnace, mic, gnoack, takedakn,
penguin-kernel, herton, kernel-team, Song Liu
In-Reply-To: <20260528182607.3150386-1-song@kernel.org>
Replace the monolithic security_sb_mount() call in path_mount() and
security_move_mount() in vfs_move_mount() with the new granular mount
hooks:
- do_loopback(): call security_mount_bind()
- do_new_mount(): call security_mount_new()
- do_remount(): call security_mount_remount()
- do_reconfigure_mnt(): call security_mount_reconfigure()
- do_move_mount_old(): call security_mount_move()
- do_change_type(): call security_mount_change_type()
- vfs_move_mount(): replace security_move_mount() with
security_mount_move()
The new hooks are called at the individual operation level with
appropriate context (resolved paths, fs_context), rather than at
the top of path_mount() with raw string arguments.
Code generated with the assistance of Claude, reviewed by human.
Reviewed-by: Stephen Smalley <stephen.smalley.work@gmail.com>
Tested-by: Stephen Smalley <stephen.smalley.work@gmail.com> # for selinux only
Signed-off-by: Song Liu <song@kernel.org>
---
fs/namespace.c | 41 ++++++++++++++++++++++++++++++-----------
1 file changed, 30 insertions(+), 11 deletions(-)
diff --git a/fs/namespace.c b/fs/namespace.c
index fe919abd2f01..43f22c5e2bf4 100644
--- a/fs/namespace.c
+++ b/fs/namespace.c
@@ -2888,6 +2888,10 @@ static int do_change_type(const struct path *path, int ms_flags)
if (!type)
return -EINVAL;
+ err = security_mount_change_type(path, ms_flags);
+ if (err)
+ return err;
+
guard(namespace_excl)();
err = may_change_propagation(mnt);
@@ -3006,6 +3010,10 @@ static int do_loopback(const struct path *path, const char *old_name,
if (err)
return err;
+ err = security_mount_bind(&old_path, path, recurse);
+ if (err)
+ return err;
+
if (mnt_ns_loop(old_path.dentry))
return -EINVAL;
@@ -3328,7 +3336,8 @@ static void mnt_warn_timestamp_expiry(const struct path *mountpoint,
* superblock it refers to. This is triggered by specifying MS_REMOUNT|MS_BIND
* to mount(2).
*/
-static int do_reconfigure_mnt(const struct path *path, unsigned int mnt_flags)
+static int do_reconfigure_mnt(const struct path *path, unsigned int mnt_flags,
+ unsigned long flags)
{
struct super_block *sb = path->mnt->mnt_sb;
struct mount *mnt = real_mount(path->mnt);
@@ -3343,6 +3352,10 @@ static int do_reconfigure_mnt(const struct path *path, unsigned int mnt_flags)
if (!can_change_locked_flags(mnt, mnt_flags))
return -EPERM;
+ ret = security_mount_reconfigure(path, mnt_flags, flags);
+ if (ret)
+ return ret;
+
/*
* We're only checking whether the superblock is read-only not
* changing it, so only take down_read(&sb->s_umount).
@@ -3366,7 +3379,7 @@ static int do_reconfigure_mnt(const struct path *path, unsigned int mnt_flags)
* on it - tough luck.
*/
static int do_remount(const struct path *path, int sb_flags,
- int mnt_flags, void *data)
+ int mnt_flags, void *data, unsigned long flags)
{
int err;
struct super_block *sb = path->mnt->mnt_sb;
@@ -3393,6 +3406,9 @@ static int do_remount(const struct path *path, int sb_flags,
fc->oldapi = true;
err = parse_monolithic_mount_data(fc, data);
+ if (!err)
+ err = security_mount_remount(fc, path, mnt_flags, flags,
+ data);
if (!err) {
down_write(&sb->s_umount);
err = -EPERM;
@@ -3708,6 +3724,10 @@ static int do_move_mount_old(const struct path *path, const char *old_name)
if (err)
return err;
+ err = security_mount_move(&old_path, path);
+ if (err)
+ return err;
+
return do_move_mount(&old_path, path, 0);
}
@@ -3786,7 +3806,7 @@ static int do_new_mount_fc(struct fs_context *fc, const struct path *mountpoint,
*/
static int do_new_mount(const struct path *path, const char *fstype,
int sb_flags, int mnt_flags,
- const char *name, void *data)
+ const char *name, void *data, unsigned long flags)
{
struct file_system_type *type;
struct fs_context *fc;
@@ -3830,6 +3850,9 @@ static int do_new_mount(const struct path *path, const char *fstype,
err = parse_monolithic_mount_data(fc, data);
if (!err && !mount_capable(fc))
err = -EPERM;
+
+ if (!err)
+ err = security_mount_new(fc, path, mnt_flags, flags, data);
if (!err)
err = do_new_mount_fc(fc, path, mnt_flags);
@@ -4080,7 +4103,6 @@ int path_mount(const char *dev_name, const struct path *path,
const char *type_page, unsigned long flags, void *data_page)
{
unsigned int mnt_flags = 0, sb_flags;
- int ret;
/* Discard magic */
if ((flags & MS_MGC_MSK) == MS_MGC_VAL)
@@ -4093,9 +4115,6 @@ int path_mount(const char *dev_name, const struct path *path,
if (flags & MS_NOUSER)
return -EINVAL;
- ret = security_sb_mount(dev_name, path, type_page, flags, data_page);
- if (ret)
- return ret;
if (!may_mount())
return -EPERM;
if (flags & SB_MANDLOCK)
@@ -4141,9 +4160,9 @@ int path_mount(const char *dev_name, const struct path *path,
SB_I_VERSION);
if ((flags & (MS_REMOUNT | MS_BIND)) == (MS_REMOUNT | MS_BIND))
- return do_reconfigure_mnt(path, mnt_flags);
+ return do_reconfigure_mnt(path, mnt_flags, flags);
if (flags & MS_REMOUNT)
- return do_remount(path, sb_flags, mnt_flags, data_page);
+ return do_remount(path, sb_flags, mnt_flags, data_page, flags);
if (flags & MS_BIND)
return do_loopback(path, dev_name, flags & MS_REC);
if (flags & (MS_SHARED | MS_PRIVATE | MS_SLAVE | MS_UNBINDABLE))
@@ -4152,7 +4171,7 @@ int path_mount(const char *dev_name, const struct path *path,
return do_move_mount_old(path, dev_name);
return do_new_mount(path, type_page, sb_flags, mnt_flags, dev_name,
- data_page);
+ data_page, flags);
}
int do_mount(const char *dev_name, const char __user *dir_name,
@@ -4545,7 +4564,7 @@ static inline int vfs_move_mount(const struct path *from_path,
{
int ret;
- ret = security_move_mount(from_path, to_path);
+ ret = security_mount_move(from_path, to_path);
if (ret)
return ret;
--
2.53.0-Meta
^ permalink raw reply related
* [PATCH v5 6/8] tomoyo: Convert from sb_mount to granular mount hooks
From: Song Liu @ 2026-05-28 18:26 UTC (permalink / raw)
To: linux-security-module, linux-fsdevel, selinux, apparmor
Cc: paul, jmorris, serge, viro, brauner, jack, john.johansen,
stephen.smalley.work, omosnace, mic, gnoack, takedakn,
penguin-kernel, herton, kernel-team, Song Liu
In-Reply-To: <20260528182607.3150386-1-song@kernel.org>
Replace tomoyo_sb_mount() with granular mount hooks. Each hook
reconstructs the MS_* flags expected by tomoyo_mount_permission()
using the original flags parameter where available.
Key changes:
- mount_bind: passes the pre-resolved source path to
tomoyo_mount_acl() via a new dev_path parameter, instead of
re-resolving dev_name via kern_path(). This eliminates a TOCTOU
vulnerability.
- mount_new, mount_remount, mount_reconfigure: use the original
mount(2) flags for policy matching.
- mount_move: passes pre-resolved paths for both source and
destination.
- mount_change_type: passes raw ms_flags directly.
Also removes the unused data_page parameter from
tomoyo_mount_permission().
Code generated with the assistance of Claude, reviewed by human.
Acked-by: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
Signed-off-by: Song Liu <song@kernel.org>
---
security/tomoyo/common.h | 2 +-
security/tomoyo/mount.c | 31 +++++++----
security/tomoyo/tomoyo.c | 109 +++++++++++++++++++++++++++++++++++----
3 files changed, 121 insertions(+), 21 deletions(-)
diff --git a/security/tomoyo/common.h b/security/tomoyo/common.h
index d098cf8aae61..9241034cfede 100644
--- a/security/tomoyo/common.h
+++ b/security/tomoyo/common.h
@@ -1013,7 +1013,7 @@ int tomoyo_mkdev_perm(const u8 operation, const struct path *path,
const unsigned int mode, unsigned int dev);
int tomoyo_mount_permission(const char *dev_name, const struct path *path,
const char *type, unsigned long flags,
- void *data_page);
+ const struct path *dev_path);
int tomoyo_open_control(const u8 type, struct file *file);
int tomoyo_path2_perm(const u8 operation, const struct path *path1,
const struct path *path2);
diff --git a/security/tomoyo/mount.c b/security/tomoyo/mount.c
index 322dfd188ada..82ffe7d02814 100644
--- a/security/tomoyo/mount.c
+++ b/security/tomoyo/mount.c
@@ -70,6 +70,7 @@ static bool tomoyo_check_mount_acl(struct tomoyo_request_info *r,
* @dir: Pointer to "struct path".
* @type: Name of filesystem type.
* @flags: Mount options.
+ * @dev_path: Pre-resolved device/source path. Maybe NULL.
*
* Returns 0 on success, negative value otherwise.
*
@@ -78,11 +79,11 @@ static bool tomoyo_check_mount_acl(struct tomoyo_request_info *r,
static int tomoyo_mount_acl(struct tomoyo_request_info *r,
const char *dev_name,
const struct path *dir, const char *type,
- unsigned long flags)
+ unsigned long flags,
+ const struct path *dev_path)
__must_hold_shared(&tomoyo_ss)
{
struct tomoyo_obj_info obj = { };
- struct path path;
struct file_system_type *fstype = NULL;
const char *requested_type = NULL;
const char *requested_dir_name = NULL;
@@ -134,13 +135,23 @@ static int tomoyo_mount_acl(struct tomoyo_request_info *r,
need_dev = 1;
}
if (need_dev) {
- /* Get mount point or device file. */
- if (!dev_name || kern_path(dev_name, LOOKUP_FOLLOW, &path)) {
+ if (dev_path) {
+ /* Use pre-resolved path to avoid TOCTOU issues. */
+ obj.path1 = *dev_path;
+ path_get(&obj.path1);
+ } else if (!dev_name) {
error = -ENOENT;
goto out;
+ } else {
+ struct path path;
+
+ if (kern_path(dev_name, LOOKUP_FOLLOW, &path)) {
+ error = -ENOENT;
+ goto out;
+ }
+ obj.path1 = path;
}
- obj.path1 = path;
- requested_dev_name = tomoyo_realpath_from_path(&path);
+ requested_dev_name = tomoyo_realpath_from_path(&obj.path1);
if (!requested_dev_name) {
error = -ENOENT;
goto out;
@@ -173,7 +184,7 @@ static int tomoyo_mount_acl(struct tomoyo_request_info *r,
if (fstype)
put_filesystem(fstype);
kfree(requested_type);
- /* Drop refcount obtained by kern_path(). */
+ /* Drop refcount obtained by kern_path() or path_get(). */
if (obj.path1.dentry)
path_put(&obj.path1);
return error;
@@ -186,13 +197,13 @@ static int tomoyo_mount_acl(struct tomoyo_request_info *r,
* @path: Pointer to "struct path".
* @type: Name of filesystem type. Maybe NULL.
* @flags: Mount options.
- * @data_page: Optional data. Maybe NULL.
+ * @dev_path: Pre-resolved device/source path. Maybe NULL.
*
* Returns 0 on success, negative value otherwise.
*/
int tomoyo_mount_permission(const char *dev_name, const struct path *path,
const char *type, unsigned long flags,
- void *data_page)
+ const struct path *dev_path)
{
struct tomoyo_request_info r;
int error;
@@ -236,7 +247,7 @@ int tomoyo_mount_permission(const char *dev_name, const struct path *path,
if (!type)
type = "<NULL>";
idx = tomoyo_read_lock();
- error = tomoyo_mount_acl(&r, dev_name, path, type, flags);
+ error = tomoyo_mount_acl(&r, dev_name, path, type, flags, dev_path);
tomoyo_read_unlock(idx);
return error;
}
diff --git a/security/tomoyo/tomoyo.c b/security/tomoyo/tomoyo.c
index c66e02ed8ee3..c93d000acc95 100644
--- a/security/tomoyo/tomoyo.c
+++ b/security/tomoyo/tomoyo.c
@@ -6,6 +6,8 @@
*/
#include <linux/lsm_hooks.h>
+#include <linux/fs_context.h>
+#include <uapi/linux/mount.h>
#include <uapi/linux/lsm.h>
#include "common.h"
@@ -399,20 +401,102 @@ static int tomoyo_path_chroot(const struct path *path)
}
/**
- * tomoyo_sb_mount - Target for security_sb_mount().
+ * tomoyo_mount_bind - Target for security_mount_bind().
*
- * @dev_name: Name of device file. Maybe NULL.
- * @path: Pointer to "struct path".
- * @type: Name of filesystem type. Maybe NULL.
- * @flags: Mount options.
- * @data: Optional data. Maybe NULL.
+ * @from: Pointer to "struct path".
+ * @to: Pointer to "struct path".
+ * @recurse: Whether recursive bind mount or not.
*
* Returns 0 on success, negative value otherwise.
*/
-static int tomoyo_sb_mount(const char *dev_name, const struct path *path,
- const char *type, unsigned long flags, void *data)
+static int tomoyo_mount_bind(const struct path *from, const struct path *to,
+ bool recurse)
{
- return tomoyo_mount_permission(dev_name, path, type, flags, data);
+ unsigned long flags = MS_BIND | (recurse ? MS_REC : 0);
+
+ return tomoyo_mount_permission(NULL, to, NULL, flags, from);
+}
+
+/**
+ * tomoyo_mount_new - Target for security_mount_new().
+ *
+ * @fc: Pointer to "struct fs_context".
+ * @mp: Pointer to "struct path".
+ * @mnt_flags: Mount options.
+ * @flags: Original mount options.
+ * @data: Optional data. Maybe NULL.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int tomoyo_mount_new(struct fs_context *fc, const struct path *mp,
+ int mnt_flags, unsigned long flags, void *data)
+{
+ /* Use original MS_* flags for policy matching */
+ return tomoyo_mount_permission(fc->source, mp, fc->fs_type->name,
+ flags, NULL);
+}
+
+/**
+ * tomoyo_mount_remount - Target for security_mount_remount().
+ *
+ * @fc: Pointer to "struct fs_context".
+ * @mp: Pointer to "struct path".
+ * @mnt_flags: Mount options.
+ * @flags: Original mount options.
+ * @data: Optional data. Maybe NULL.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int tomoyo_mount_remount(struct fs_context *fc, const struct path *mp,
+ int mnt_flags, unsigned long flags, void *data)
+{
+ /* Use original MS_* flags for policy matching */
+ return tomoyo_mount_permission(NULL, mp, NULL, flags, NULL);
+}
+
+/**
+ * tomoyo_mount_reconfigure - Target for security_mount_reconfigure().
+ *
+ * @mp: Pointer to "struct path".
+ * @mnt_flags: Mount options.
+ * @flags: Original mount options.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int tomoyo_mount_reconfigure(const struct path *mp,
+ unsigned int mnt_flags,
+ unsigned long flags)
+{
+ /* Use original MS_* flags for policy matching */
+ return tomoyo_mount_permission(NULL, mp, NULL, flags, NULL);
+}
+
+/**
+ * tomoyo_mount_change_type - Target for security_mount_change_type().
+ *
+ * @mp: Pointer to "struct path".
+ * @ms_flags: Mount options.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int tomoyo_mount_change_type(const struct path *mp, int ms_flags)
+{
+ return tomoyo_mount_permission(NULL, mp, NULL, ms_flags, NULL);
+}
+
+/**
+ * tomoyo_mount_move - Target for security_mount_move().
+ *
+ * @from_path: Pointer to "struct path".
+ * @to_path: Pointer to "struct path".
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int tomoyo_mount_move(const struct path *from_path,
+ const struct path *to_path)
+{
+ return tomoyo_mount_permission(NULL, to_path, NULL, MS_MOVE,
+ from_path);
}
/**
@@ -576,7 +660,12 @@ static struct security_hook_list tomoyo_hooks[] __ro_after_init = {
LSM_HOOK_INIT(path_chmod, tomoyo_path_chmod),
LSM_HOOK_INIT(path_chown, tomoyo_path_chown),
LSM_HOOK_INIT(path_chroot, tomoyo_path_chroot),
- LSM_HOOK_INIT(sb_mount, tomoyo_sb_mount),
+ LSM_HOOK_INIT(mount_bind, tomoyo_mount_bind),
+ LSM_HOOK_INIT(mount_new, tomoyo_mount_new),
+ LSM_HOOK_INIT(mount_remount, tomoyo_mount_remount),
+ LSM_HOOK_INIT(mount_reconfigure, tomoyo_mount_reconfigure),
+ LSM_HOOK_INIT(mount_change_type, tomoyo_mount_change_type),
+ LSM_HOOK_INIT(mount_move, tomoyo_mount_move),
LSM_HOOK_INIT(sb_umount, tomoyo_sb_umount),
LSM_HOOK_INIT(sb_pivotroot, tomoyo_sb_pivotroot),
LSM_HOOK_INIT(socket_bind, tomoyo_socket_bind),
--
2.53.0-Meta
^ permalink raw reply related
* [PATCH v5 5/8] landlock: Convert from sb_mount to granular mount hooks
From: Song Liu @ 2026-05-28 18:26 UTC (permalink / raw)
To: linux-security-module, linux-fsdevel, selinux, apparmor
Cc: paul, jmorris, serge, viro, brauner, jack, john.johansen,
stephen.smalley.work, omosnace, mic, gnoack, takedakn,
penguin-kernel, herton, kernel-team, Song Liu
In-Reply-To: <20260528182607.3150386-1-song@kernel.org>
Replace hook_sb_mount() with granular mount hooks. Landlock denies
all mount operations for sandboxed processes regardless of flags,
so all new hooks share a common hook_mount_deny() helper. The
mount_move hook reuses hook_move_mount().
Code generated with the assistance of Claude, reviewed by human.
Signed-off-by: Song Liu <song@kernel.org>
---
security/landlock/fs.c | 41 ++++++++++++++++++++++++++++++++++++-----
1 file changed, 36 insertions(+), 5 deletions(-)
diff --git a/security/landlock/fs.c b/security/landlock/fs.c
index c1ecfe239032..7377f22a165e 100644
--- a/security/landlock/fs.c
+++ b/security/landlock/fs.c
@@ -1416,9 +1416,7 @@ static void log_fs_change_topology_dentry(
* inherit these new constraints. Anyway, for backward compatibility reasons,
* a dedicated user space option would be required (e.g. as a ruleset flag).
*/
-static int hook_sb_mount(const char *const dev_name,
- const struct path *const path, const char *const type,
- const unsigned long flags, void *const data)
+static int hook_mount_deny(const struct path *const path)
{
size_t handle_layer;
const struct landlock_cred_security *const subject =
@@ -1432,6 +1430,35 @@ static int hook_sb_mount(const char *const dev_name,
return -EPERM;
}
+static int hook_mount_bind(const struct path *const from,
+ const struct path *const to, bool recurse)
+{
+ return hook_mount_deny(to);
+}
+
+static int hook_mount_new(struct fs_context *fc, const struct path *const mp,
+ int mnt_flags, unsigned long flags, void *data)
+{
+ return hook_mount_deny(mp);
+}
+
+static int hook_mount_remount(struct fs_context *fc, const struct path *mp,
+ int mnt_flags, unsigned long flags, void *data)
+{
+ return hook_mount_deny(mp);
+}
+
+static int hook_mount_reconfigure(const struct path *const mp,
+ unsigned int mnt_flags, unsigned long flags)
+{
+ return hook_mount_deny(mp);
+}
+
+static int hook_mount_change_type(const struct path *const mp, int ms_flags)
+{
+ return hook_mount_deny(mp);
+}
+
static int hook_move_mount(const struct path *const from_path,
const struct path *const to_path)
{
@@ -1950,8 +1977,12 @@ static struct security_hook_list landlock_hooks[] __ro_after_init = {
LSM_HOOK_INIT(inode_free_security_rcu, hook_inode_free_security_rcu),
LSM_HOOK_INIT(sb_delete, hook_sb_delete),
- LSM_HOOK_INIT(sb_mount, hook_sb_mount),
- LSM_HOOK_INIT(move_mount, hook_move_mount),
+ LSM_HOOK_INIT(mount_bind, hook_mount_bind),
+ LSM_HOOK_INIT(mount_new, hook_mount_new),
+ LSM_HOOK_INIT(mount_remount, hook_mount_remount),
+ LSM_HOOK_INIT(mount_reconfigure, hook_mount_reconfigure),
+ LSM_HOOK_INIT(mount_change_type, hook_mount_change_type),
+ LSM_HOOK_INIT(mount_move, hook_move_mount),
LSM_HOOK_INIT(sb_umount, hook_sb_umount),
LSM_HOOK_INIT(sb_remount, hook_sb_remount),
LSM_HOOK_INIT(sb_pivotroot, hook_sb_pivotroot),
--
2.53.0-Meta
^ permalink raw reply related
* [PATCH v5 4/8] selinux: Convert from sb_mount to granular mount hooks
From: Song Liu @ 2026-05-28 18:26 UTC (permalink / raw)
To: linux-security-module, linux-fsdevel, selinux, apparmor
Cc: paul, jmorris, serge, viro, brauner, jack, john.johansen,
stephen.smalley.work, omosnace, mic, gnoack, takedakn,
penguin-kernel, herton, kernel-team, Song Liu
In-Reply-To: <20260528182607.3150386-1-song@kernel.org>
Replace selinux_mount() with granular mount hooks, preserving the
same permission checks:
- mount_bind, mount_new, mount_change_type: FILE__MOUNTON
- mount_remount, mount_reconfigure: FILESYSTEM__REMOUNT
- mount_move: FILE__MOUNTON (reuses selinux_move_mount)
The flags and data parameters are unused by SELinux.
Code generated with the assistance of Claude, reviewed by human.
Reviewed-by: Stephen Smalley <stephen.smalley.work@gmail.com>
Tested-by: Stephen Smalley <stephen.smalley.work@gmail.com>
Signed-off-by: Song Liu <song@kernel.org>
---
security/selinux/hooks.c | 49 ++++++++++++++++++++++++++++------------
1 file changed, 35 insertions(+), 14 deletions(-)
diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c
index 0f704380a8c8..c8de175bde04 100644
--- a/security/selinux/hooks.c
+++ b/security/selinux/hooks.c
@@ -2802,19 +2802,37 @@ static int selinux_sb_statfs(struct dentry *dentry)
return superblock_has_perm(cred, dentry->d_sb, FILESYSTEM__GETATTR, &ad);
}
-static int selinux_mount(const char *dev_name,
- const struct path *path,
- const char *type,
- unsigned long flags,
- void *data)
+static int selinux_mount_bind(const struct path *from, const struct path *to,
+ bool recurse)
{
- const struct cred *cred = current_cred();
+ return path_has_perm(current_cred(), to, FILE__MOUNTON);
+}
- if (flags & MS_REMOUNT)
- return superblock_has_perm(cred, path->dentry->d_sb,
- FILESYSTEM__REMOUNT, NULL);
- else
- return path_has_perm(cred, path, FILE__MOUNTON);
+static int selinux_mount_new(struct fs_context *fc, const struct path *mp,
+ int mnt_flags, unsigned long flags, void *data)
+{
+ return path_has_perm(current_cred(), mp, FILE__MOUNTON);
+}
+
+static int selinux_mount_remount(struct fs_context *fc, const struct path *mp,
+ int mnt_flags, unsigned long flags,
+ void *data)
+{
+ return superblock_has_perm(current_cred(), fc->root->d_sb,
+ FILESYSTEM__REMOUNT, NULL);
+}
+
+static int selinux_mount_reconfigure(const struct path *mp,
+ unsigned int mnt_flags,
+ unsigned long flags)
+{
+ return superblock_has_perm(current_cred(), mp->dentry->d_sb,
+ FILESYSTEM__REMOUNT, NULL);
+}
+
+static int selinux_mount_change_type(const struct path *mp, int ms_flags)
+{
+ return path_has_perm(current_cred(), mp, FILE__MOUNTON);
}
static int selinux_move_mount(const struct path *from_path,
@@ -7558,13 +7576,16 @@ static struct security_hook_list selinux_hooks[] __ro_after_init = {
LSM_HOOK_INIT(sb_kern_mount, selinux_sb_kern_mount),
LSM_HOOK_INIT(sb_show_options, selinux_sb_show_options),
LSM_HOOK_INIT(sb_statfs, selinux_sb_statfs),
- LSM_HOOK_INIT(sb_mount, selinux_mount),
+ LSM_HOOK_INIT(mount_bind, selinux_mount_bind),
+ LSM_HOOK_INIT(mount_new, selinux_mount_new),
+ LSM_HOOK_INIT(mount_remount, selinux_mount_remount),
+ LSM_HOOK_INIT(mount_reconfigure, selinux_mount_reconfigure),
+ LSM_HOOK_INIT(mount_change_type, selinux_mount_change_type),
+ LSM_HOOK_INIT(mount_move, selinux_move_mount),
LSM_HOOK_INIT(sb_umount, selinux_umount),
LSM_HOOK_INIT(sb_set_mnt_opts, selinux_set_mnt_opts),
LSM_HOOK_INIT(sb_clone_mnt_opts, selinux_sb_clone_mnt_opts),
- LSM_HOOK_INIT(move_mount, selinux_move_mount),
-
LSM_HOOK_INIT(dentry_init_security, selinux_dentry_init_security),
LSM_HOOK_INIT(dentry_create_files_as, selinux_dentry_create_files_as),
--
2.53.0-Meta
^ permalink raw reply related
* [PATCH v5 3/8] apparmor: Convert from sb_mount to granular mount hooks
From: Song Liu @ 2026-05-28 18:26 UTC (permalink / raw)
To: linux-security-module, linux-fsdevel, selinux, apparmor
Cc: paul, jmorris, serge, viro, brauner, jack, john.johansen,
stephen.smalley.work, omosnace, mic, gnoack, takedakn,
penguin-kernel, herton, kernel-team, Song Liu
In-Reply-To: <20260528182607.3150386-1-song@kernel.org>
Replace AppArmor's monolithic apparmor_sb_mount() with granular
mount hooks.
Key changes:
- mount_bind: uses the pre-resolved struct path from VFS instead of
re-resolving dev_name via kern_path(), eliminating a TOCTOU
vulnerability. aa_bind_mount() now takes a struct path instead of
a string for the source.
- mount_new, mount_remount: receive the original mount(2) flags and
data parameters for policy matching via match_mnt_flags() and
AA_MNT_CONT_MATCH data matching.
- mount_reconfigure: handles MS_REMOUNT|MS_BIND (mount attribute
reconfiguration) which was previously handled as a remount.
- mount_move: reuses apparmor_move_mount() which already handles
pre-resolved paths.
- mount_change_type: propagation type changes.
aa_move_mount_old() is removed since move mounts now go through
security_mount_move() with pre-resolved struct path pointers for
both the old mount(2) and new move_mount(2) APIs.
Code generated with the assistance of Claude, reviewed by human.
Signed-off-by: Song Liu <song@kernel.org>
---
security/apparmor/include/mount.h | 5 +-
security/apparmor/lsm.c | 100 +++++++++++++++++++++++-------
security/apparmor/mount.c | 37 ++---------
3 files changed, 83 insertions(+), 59 deletions(-)
diff --git a/security/apparmor/include/mount.h b/security/apparmor/include/mount.h
index 46834f828179..088e2f938cc1 100644
--- a/security/apparmor/include/mount.h
+++ b/security/apparmor/include/mount.h
@@ -31,16 +31,13 @@ int aa_remount(const struct cred *subj_cred,
int aa_bind_mount(const struct cred *subj_cred,
struct aa_label *label, const struct path *path,
- const char *old_name, unsigned long flags);
+ const struct path *old_path, bool recurse);
int aa_mount_change_type(const struct cred *subj_cred,
struct aa_label *label, const struct path *path,
unsigned long flags);
-int aa_move_mount_old(const struct cred *subj_cred,
- struct aa_label *label, const struct path *path,
- const char *old_name);
int aa_move_mount(const struct cred *subj_cred,
struct aa_label *label, const struct path *from_path,
const struct path *to_path);
diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c
index 4415bca5889c..b0de7f316f51 100644
--- a/security/apparmor/lsm.c
+++ b/security/apparmor/lsm.c
@@ -13,6 +13,7 @@
#include <linux/mm.h>
#include <linux/mman.h>
#include <linux/mount.h>
+#include <linux/fs_context.h>
#include <linux/namei.h>
#include <linux/ptrace.h>
#include <linux/ctype.h>
@@ -698,34 +699,83 @@ static int apparmor_uring_sqpoll(void)
}
#endif /* CONFIG_IO_URING */
-static int apparmor_sb_mount(const char *dev_name, const struct path *path,
- const char *type, unsigned long flags, void *data)
+static int apparmor_mount_bind(const struct path *from, const struct path *to,
+ bool recurse)
{
struct aa_label *label;
int error = 0;
bool needput;
- flags &= ~AA_MS_IGNORE_MASK;
+ label = __begin_current_label_crit_section(&needput);
+ if (!unconfined(label))
+ error = aa_bind_mount(current_cred(), label, to, from,
+ recurse);
+ __end_current_label_crit_section(label, needput);
+ return error;
+}
+
+static int apparmor_mount_new(struct fs_context *fc, const struct path *mp,
+ int mnt_flags, unsigned long flags, void *data)
+{
+ struct aa_label *label;
+ int error = 0;
+ bool needput;
+
+ /* flags and data are from the original mount(2) call */
label = __begin_current_label_crit_section(&needput);
- if (!unconfined(label)) {
- if (flags & MS_REMOUNT)
- error = aa_remount(current_cred(), label, path, flags,
- data);
- else if (flags & MS_BIND)
- error = aa_bind_mount(current_cred(), label, path,
- dev_name, flags);
- else if (flags & (MS_SHARED | MS_PRIVATE | MS_SLAVE |
- MS_UNBINDABLE))
- error = aa_mount_change_type(current_cred(), label,
- path, flags);
- else if (flags & MS_MOVE)
- error = aa_move_mount_old(current_cred(), label, path,
- dev_name);
- else
- error = aa_new_mount(current_cred(), label, dev_name,
- path, type, flags, data);
- }
+ if (!unconfined(label))
+ error = aa_new_mount(current_cred(), label, fc->source,
+ mp, fc->fs_type->name, flags, data);
+ __end_current_label_crit_section(label, needput);
+
+ return error;
+}
+
+static int apparmor_mount_remount(struct fs_context *fc, const struct path *mp,
+ int mnt_flags, unsigned long flags,
+ void *data)
+{
+ struct aa_label *label;
+ int error = 0;
+ bool needput;
+
+ /* flags and data are from the original mount(2) call */
+ label = __begin_current_label_crit_section(&needput);
+ if (!unconfined(label))
+ error = aa_remount(current_cred(), label, mp, flags, data);
+ __end_current_label_crit_section(label, needput);
+
+ return error;
+}
+
+static int apparmor_mount_reconfigure(const struct path *mp,
+ unsigned int mnt_flags,
+ unsigned long flags)
+{
+ struct aa_label *label;
+ int error = 0;
+ bool needput;
+
+ /* flags are from the original mount(2) call */
+ label = __begin_current_label_crit_section(&needput);
+ if (!unconfined(label))
+ error = aa_remount(current_cred(), label, mp, flags, NULL);
+ __end_current_label_crit_section(label, needput);
+
+ return error;
+}
+
+static int apparmor_mount_change_type(const struct path *mp, int ms_flags)
+{
+ struct aa_label *label;
+ int error = 0;
+ bool needput;
+
+ label = __begin_current_label_crit_section(&needput);
+ if (!unconfined(label))
+ error = aa_mount_change_type(current_cred(), label, mp,
+ ms_flags);
__end_current_label_crit_section(label, needput);
return error;
@@ -1655,8 +1705,12 @@ static struct security_hook_list apparmor_hooks[] __ro_after_init = {
LSM_HOOK_INIT(capget, apparmor_capget),
LSM_HOOK_INIT(capable, apparmor_capable),
- LSM_HOOK_INIT(move_mount, apparmor_move_mount),
- LSM_HOOK_INIT(sb_mount, apparmor_sb_mount),
+ LSM_HOOK_INIT(mount_bind, apparmor_mount_bind),
+ LSM_HOOK_INIT(mount_new, apparmor_mount_new),
+ LSM_HOOK_INIT(mount_remount, apparmor_mount_remount),
+ LSM_HOOK_INIT(mount_reconfigure, apparmor_mount_reconfigure),
+ LSM_HOOK_INIT(mount_move, apparmor_move_mount),
+ LSM_HOOK_INIT(mount_change_type, apparmor_mount_change_type),
LSM_HOOK_INIT(sb_umount, apparmor_sb_umount),
LSM_HOOK_INIT(sb_pivotroot, apparmor_sb_pivotroot),
diff --git a/security/apparmor/mount.c b/security/apparmor/mount.c
index 523570aa1a5a..38b40e16014f 100644
--- a/security/apparmor/mount.c
+++ b/security/apparmor/mount.c
@@ -418,25 +418,17 @@ int aa_remount(const struct cred *subj_cred,
}
int aa_bind_mount(const struct cred *subj_cred,
- struct aa_label *label, const struct path *path,
- const char *dev_name, unsigned long flags)
+ struct aa_label *label, const struct path *path,
+ const struct path *old_path, bool recurse)
{
struct aa_profile *profile;
char *buffer = NULL, *old_buffer = NULL;
- struct path old_path;
+ unsigned long flags = MS_BIND | (recurse ? MS_REC : 0);
int error;
AA_BUG(!label);
AA_BUG(!path);
-
- if (!dev_name || !*dev_name)
- return -EINVAL;
-
- flags &= MS_REC | MS_BIND;
-
- error = kern_path(dev_name, LOOKUP_FOLLOW|LOOKUP_AUTOMOUNT, &old_path);
- if (error)
- return error;
+ AA_BUG(!old_path);
buffer = aa_get_buffer(false);
old_buffer = aa_get_buffer(false);
@@ -445,12 +437,11 @@ int aa_bind_mount(const struct cred *subj_cred,
goto out;
error = fn_for_each_confined(label, profile,
- match_mnt(subj_cred, profile, path, buffer, &old_path,
+ match_mnt(subj_cred, profile, path, buffer, old_path,
old_buffer, NULL, flags, NULL, false));
out:
aa_put_buffer(buffer);
aa_put_buffer(old_buffer);
- path_put(&old_path);
return error;
}
@@ -514,24 +505,6 @@ int aa_move_mount(const struct cred *subj_cred,
return error;
}
-int aa_move_mount_old(const struct cred *subj_cred, struct aa_label *label,
- const struct path *path, const char *orig_name)
-{
- struct path old_path;
- int error;
-
- if (!orig_name || !*orig_name)
- return -EINVAL;
- error = kern_path(orig_name, LOOKUP_FOLLOW, &old_path);
- if (error)
- return error;
-
- error = aa_move_mount(subj_cred, label, &old_path, path);
- path_put(&old_path);
-
- return error;
-}
-
int aa_new_mount(const struct cred *subj_cred, struct aa_label *label,
const char *dev_name, const struct path *path,
const char *type, unsigned long flags, void *data)
--
2.53.0-Meta
^ permalink raw reply related
* [PATCH v5 2/8] apparmor: Remove redundant MS_MGC_MSK stripping in apparmor_sb_mount
From: Song Liu @ 2026-05-28 18:26 UTC (permalink / raw)
To: linux-security-module, linux-fsdevel, selinux, apparmor
Cc: paul, jmorris, serge, viro, brauner, jack, john.johansen,
stephen.smalley.work, omosnace, mic, gnoack, takedakn,
penguin-kernel, herton, kernel-team, Song Liu
In-Reply-To: <20260528182607.3150386-1-song@kernel.org>
path_mount() already strips the magic number from flags before
calling security_sb_mount(), so this check in apparmor_sb_mount()
is a no-op. Remove it.
Code generated with the assistance of Claude, reviewed by human.
Signed-off-by: Song Liu <song@kernel.org>
---
security/apparmor/lsm.c | 4 ----
1 file changed, 4 deletions(-)
diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c
index 3491e9f60194..4415bca5889c 100644
--- a/security/apparmor/lsm.c
+++ b/security/apparmor/lsm.c
@@ -705,10 +705,6 @@ static int apparmor_sb_mount(const char *dev_name, const struct path *path,
int error = 0;
bool needput;
- /* Discard magic */
- if ((flags & MS_MGC_MSK) == MS_MGC_VAL)
- flags &= ~MS_MGC_MSK;
-
flags &= ~AA_MS_IGNORE_MASK;
label = __begin_current_label_crit_section(&needput);
--
2.53.0-Meta
^ permalink raw reply related
* [PATCH v5 1/8] lsm: Add granular mount hooks
From: Song Liu @ 2026-05-28 18:26 UTC (permalink / raw)
To: linux-security-module, linux-fsdevel, selinux, apparmor
Cc: paul, jmorris, serge, viro, brauner, jack, john.johansen,
stephen.smalley.work, omosnace, mic, gnoack, takedakn,
penguin-kernel, herton, kernel-team, Song Liu
In-Reply-To: <20260528182607.3150386-1-song@kernel.org>
Add the new granular mount hook declarations and implementations
to the LSM framework:
mount_bind - bind mount (pre-resolved source path)
mount_new - new filesystem mount (with fs_context)
mount_remount - filesystem remount (with fs_context)
mount_reconfigure - mount flag reconfiguration (MS_REMOUNT|MS_BIND)
mount_move - move mount (pre-resolved paths)
mount_change_type - propagation type changes
These hooks are added alongside the existing security_sb_mount() and
security_move_mount() hooks, which remain in place until all LSMs
are converted.
Code generated with the assistance of Claude, reviewed by human.
Reviewed-by: Stephen Smalley <stephen.smalley.work@gmail.com>
Tested-by: Stephen Smalley <stephen.smalley.work@gmail.com> # for selinux only
Signed-off-by: Song Liu <song@kernel.org>
---
include/linux/lsm_hook_defs.h | 12 ++++
include/linux/security.h | 50 +++++++++++++++++
kernel/bpf/bpf_lsm.c | 7 +++
security/security.c | 101 ++++++++++++++++++++++++++++++++++
4 files changed, 170 insertions(+)
diff --git a/include/linux/lsm_hook_defs.h b/include/linux/lsm_hook_defs.h
index 2b8dfb35caed..98f0fe382665 100644
--- a/include/linux/lsm_hook_defs.h
+++ b/include/linux/lsm_hook_defs.h
@@ -81,6 +81,18 @@ LSM_HOOK(int, 0, sb_clone_mnt_opts, const struct super_block *oldsb,
unsigned long *set_kern_flags)
LSM_HOOK(int, 0, move_mount, const struct path *from_path,
const struct path *to_path)
+LSM_HOOK(int, 0, mount_bind, const struct path *from, const struct path *to,
+ bool recurse)
+LSM_HOOK(int, 0, mount_new, struct fs_context *fc, const struct path *mp,
+ int mnt_flags, unsigned long flags, void *data)
+LSM_HOOK(int, 0, mount_remount, struct fs_context *fc,
+ const struct path *mp, int mnt_flags, unsigned long flags,
+ void *data)
+LSM_HOOK(int, 0, mount_reconfigure, const struct path *mp,
+ unsigned int mnt_flags, unsigned long flags)
+LSM_HOOK(int, 0, mount_move, const struct path *from_path,
+ const struct path *to_path)
+LSM_HOOK(int, 0, mount_change_type, const struct path *mp, int ms_flags)
LSM_HOOK(int, -EOPNOTSUPP, dentry_init_security, struct dentry *dentry,
int mode, const struct qstr *name, const char **xattr_name,
struct lsm_context *cp)
diff --git a/include/linux/security.h b/include/linux/security.h
index 41d7367cf403..b1b3da51a88d 100644
--- a/include/linux/security.h
+++ b/include/linux/security.h
@@ -386,6 +386,17 @@ int security_sb_clone_mnt_opts(const struct super_block *oldsb,
unsigned long kern_flags,
unsigned long *set_kern_flags);
int security_move_mount(const struct path *from_path, const struct path *to_path);
+int security_mount_bind(const struct path *from, const struct path *to,
+ bool recurse);
+int security_mount_new(struct fs_context *fc, const struct path *mp,
+ int mnt_flags, unsigned long flags, void *data);
+int security_mount_remount(struct fs_context *fc, const struct path *mp,
+ int mnt_flags, unsigned long flags, void *data);
+int security_mount_reconfigure(const struct path *mp, unsigned int mnt_flags,
+ unsigned long flags);
+int security_mount_move(const struct path *from_path,
+ const struct path *to_path);
+int security_mount_change_type(const struct path *mp, int ms_flags);
int security_dentry_init_security(struct dentry *dentry, int mode,
const struct qstr *name,
const char **xattr_name,
@@ -854,6 +865,45 @@ static inline int security_move_mount(const struct path *from_path,
return 0;
}
+static inline int security_mount_bind(const struct path *from,
+ const struct path *to, bool recurse)
+{
+ return 0;
+}
+
+static inline int security_mount_new(struct fs_context *fc,
+ const struct path *mp, int mnt_flags,
+ unsigned long flags, void *data)
+{
+ return 0;
+}
+
+static inline int security_mount_remount(struct fs_context *fc,
+ const struct path *mp, int mnt_flags,
+ unsigned long flags, void *data)
+{
+ return 0;
+}
+
+static inline int security_mount_reconfigure(const struct path *mp,
+ unsigned int mnt_flags,
+ unsigned long flags)
+{
+ return 0;
+}
+
+static inline int security_mount_move(const struct path *from_path,
+ const struct path *to_path)
+{
+ return 0;
+}
+
+static inline int security_mount_change_type(const struct path *mp,
+ int ms_flags)
+{
+ return 0;
+}
+
static inline int security_path_notify(const struct path *path, u64 mask,
unsigned int obj_type)
{
diff --git a/kernel/bpf/bpf_lsm.c b/kernel/bpf/bpf_lsm.c
index c5c925f00202..aa228372cfb4 100644
--- a/kernel/bpf/bpf_lsm.c
+++ b/kernel/bpf/bpf_lsm.c
@@ -382,6 +382,13 @@ BTF_ID(func, bpf_lsm_task_setscheduler)
BTF_ID(func, bpf_lsm_userns_create)
BTF_ID(func, bpf_lsm_bdev_alloc_security)
BTF_ID(func, bpf_lsm_bdev_setintegrity)
+BTF_ID(func, bpf_lsm_move_mount)
+BTF_ID(func, bpf_lsm_mount_bind)
+BTF_ID(func, bpf_lsm_mount_new)
+BTF_ID(func, bpf_lsm_mount_remount)
+BTF_ID(func, bpf_lsm_mount_reconfigure)
+BTF_ID(func, bpf_lsm_mount_move)
+BTF_ID(func, bpf_lsm_mount_change_type)
BTF_SET_END(sleepable_lsm_hooks)
BTF_SET_START(untrusted_lsm_hooks)
diff --git a/security/security.c b/security/security.c
index 4e999f023651..b7ec0ec7af26 100644
--- a/security/security.c
+++ b/security/security.c
@@ -1182,6 +1182,107 @@ int security_move_mount(const struct path *from_path,
return call_int_hook(move_mount, from_path, to_path);
}
+/**
+ * security_mount_bind() - Check permissions for a bind mount
+ * @from: source path
+ * @to: destination mount point
+ * @recurse: whether this is a recursive bind mount
+ *
+ * Check permission before a bind mount is performed. Called with the
+ * source path already resolved, eliminating TOCTOU issues with
+ * string-based dev_name in security_sb_mount().
+ *
+ * Return: Returns 0 if permission is granted.
+ */
+int security_mount_bind(const struct path *from, const struct path *to,
+ bool recurse)
+{
+ return call_int_hook(mount_bind, from, to, recurse);
+}
+
+/**
+ * security_mount_new() - Check permissions for a new mount
+ * @fc: filesystem context with parsed options
+ * @mp: mount point path
+ * @mnt_flags: mount flags (MNT_*)
+ * @flags: original mount flags (MS_*, used by AppArmor/Tomoyo)
+ * @data: filesystem specific data (used by AppArmor)
+ *
+ * Check permission before a new filesystem is mounted. Called after
+ * mount options are parsed, providing access to the fs_context.
+ *
+ * Return: Returns 0 if permission is granted.
+ */
+int security_mount_new(struct fs_context *fc, const struct path *mp,
+ int mnt_flags, unsigned long flags, void *data)
+{
+ return call_int_hook(mount_new, fc, mp, mnt_flags, flags, data);
+}
+
+/**
+ * security_mount_remount() - Check permissions for a remount
+ * @fc: filesystem context with parsed options
+ * @mp: mount point path
+ * @mnt_flags: mount flags (MNT_*)
+ * @flags: original mount flags (MS_*, used by AppArmor/Tomoyo)
+ * @data: filesystem specific data (used by AppArmor)
+ *
+ * Check permission before a filesystem is remounted. Called after
+ * mount options are parsed, providing access to the fs_context.
+ *
+ * Return: Returns 0 if permission is granted.
+ */
+int security_mount_remount(struct fs_context *fc, const struct path *mp,
+ int mnt_flags, unsigned long flags, void *data)
+{
+ return call_int_hook(mount_remount, fc, mp, mnt_flags, flags, data);
+}
+
+/**
+ * security_mount_reconfigure() - Check permissions for mount reconfiguration
+ * @mp: mount point path
+ * @mnt_flags: new mount flags (MNT_*)
+ * @flags: original mount flags (MS_*, used by AppArmor/Tomoyo)
+ *
+ * Check permission before mount flags are reconfigured (MS_REMOUNT|MS_BIND).
+ *
+ * Return: Returns 0 if permission is granted.
+ */
+int security_mount_reconfigure(const struct path *mp, unsigned int mnt_flags,
+ unsigned long flags)
+{
+ return call_int_hook(mount_reconfigure, mp, mnt_flags, flags);
+}
+
+/**
+ * security_mount_move() - Check permissions for moving a mount
+ * @from_path: source mount path
+ * @to_path: destination mount point path
+ *
+ * Check permission before a mount is moved.
+ *
+ * Return: Returns 0 if permission is granted.
+ */
+int security_mount_move(const struct path *from_path,
+ const struct path *to_path)
+{
+ return call_int_hook(mount_move, from_path, to_path);
+}
+
+/**
+ * security_mount_change_type() - Check permissions for propagation changes
+ * @mp: mount point path
+ * @ms_flags: propagation flags (MS_SHARED, MS_PRIVATE, etc.)
+ *
+ * Check permission before mount propagation type is changed.
+ *
+ * Return: Returns 0 if permission is granted.
+ */
+int security_mount_change_type(const struct path *mp, int ms_flags)
+{
+ return call_int_hook(mount_change_type, mp, ms_flags);
+}
+
/**
* security_path_notify() - Check if setting a watch is allowed
* @path: file path
--
2.53.0-Meta
^ permalink raw reply related
* [PATCH v5 0/8] lsm: Replace security_sb_mount with granular mount hooks
From: Song Liu @ 2026-05-28 18:25 UTC (permalink / raw)
To: linux-security-module, linux-fsdevel, selinux, apparmor
Cc: paul, jmorris, serge, viro, brauner, jack, john.johansen,
stephen.smalley.work, omosnace, mic, gnoack, takedakn,
penguin-kernel, herton, kernel-team, Song Liu
This series replaces the monolithic security_sb_mount() hook with
per-operation mount hooks, addressing two main issues:
1. TOCTOU: security_sb_mount() receives dev_name as a string, which
LSMs like AppArmor and Tomoyo re-resolve via kern_path(). The new
hooks pass pre-resolved struct path pointers where possible (bind
mount, move mount), eliminating the double-resolution.
2. Conflation: security_sb_mount() handles bind, new mount, remount,
move, propagation changes, and mount reconfiguration through a
single hook, requiring LSMs to dispatch on flags internally. The
new hooks are called at the operation level with appropriate
context.
The new hooks are:
mount_bind - bind mount (pre-resolved source path)
mount_new - new filesystem mount (with fs_context)
mount_remount - filesystem remount (with fs_context)
mount_reconfigure - mount flag reconfiguration (MS_REMOUNT|MS_BIND)
mount_move - move mount (pre-resolved paths)
mount_change_type - propagation type changes
mount_new and mount_remount are called after parse_monolithic_mount_data(),
so LSMs have access to the fs_context with parsed mount options. They also
receive the original mount(2) flags and data pointer for LSMs (AppArmor,
Tomoyo) that need them for policy matching.
The series also replaces security_move_mount() with the new mount_move
hook, unifying the old mount(2) MS_MOVE path with the move_mount(2)
syscall path.
All existing LSM behaviors are preserved:
AppArmor: same policy matching, TOCTOU fixed for bind/move
SELinux: same permission checks (FILE__MOUNTON, FILESYSTEM__REMOUNT)
Landlock: same deny-all for sandboxed processes
Tomoyo: same policy matching, TOCTOU fixed for bind/move, unused
data_page parameter removed
This work is inspired by earlier discussions:
[1] https://lore.kernel.org/bpf/20251127005011.1872209-1-song@kernel.org/
[2] https://lore.kernel.org/linux-security-module/20250708230504.3994335-1-song@kernel.org/
Changes v4 => v5:
1. Restructure series: add new hooks in security/ first, then convert
individual LSMs, then replace old hooks with new hooks in
fs/namespace.c (single patch), then remove old hooks. This keeps
all fs/namespace.c changes in one patch. (Christian Brauner)
2. Rebase.
v4: https://lore.kernel.org/linux-security-module/20260515200158.4081915-1-song@kernel.org/
Changes v3 => v4:
1. Move LSM_HOOK_INIT(move_mount, ...) removal from patch 7/7 to each
per-LSM conversion patch (3/7, 4/7, 5/7). (Paul Moore)
2. Add kdoc comments to tomoyo mount hook functions and rename
tomoyo_move_mount to tomoyo_mount_move in patch 6/7. (Tetsuo Handa)
3. Add Acked-by from Tetsuo Handa to patch 6/7.
v3: https://lore.kernel.org/linux-security-module/20260509015208.3853132-1-song@kernel.org/
Changes v2 => v3:
1. Rebase.
2. Move security_mount_move() call in vfs_move_mount() from patch 7/7
to patch 1/7. (Paul Moore)
v2: https://lore.kernel.org/linux-security-module/20260430000315.918964-1-song@kernel.org/
Changes v1 => v2:
1. Rebase.
2. Add Reviewed-by and Tested-by from Stephen Smalley.
v1: https://lore.kernel.org/linux-security-module/20260318184400.3502908-1-song@kernel.org/
Song Liu (8):
lsm: Add granular mount hooks
apparmor: Remove redundant MS_MGC_MSK stripping in apparmor_sb_mount
apparmor: Convert from sb_mount to granular mount hooks
selinux: Convert from sb_mount to granular mount hooks
landlock: Convert from sb_mount to granular mount hooks
tomoyo: Convert from sb_mount to granular mount hooks
vfs: Replace security_sb_mount/security_move_mount with granular hooks
lsm: Remove security_sb_mount and security_move_mount
fs/namespace.c | 41 +++++++---
include/linux/lsm_hook_defs.h | 14 +++-
include/linux/security.h | 56 +++++++++++---
kernel/bpf/bpf_lsm.c | 7 +-
security/apparmor/include/mount.h | 5 +-
security/apparmor/lsm.c | 102 ++++++++++++++++++-------
security/apparmor/mount.c | 37 ++--------
security/landlock/fs.c | 41 ++++++++--
security/security.c | 119 +++++++++++++++++++++++-------
security/selinux/hooks.c | 49 ++++++++----
security/tomoyo/common.h | 2 +-
security/tomoyo/mount.c | 31 +++++---
security/tomoyo/tomoyo.c | 109 ++++++++++++++++++++++++---
13 files changed, 457 insertions(+), 156 deletions(-)
--
2.53.0-Meta
^ permalink raw reply
* [PATCH 6.12.y] landlock: Fix TCP handling of short AF_UNSPEC addresses
From: Maximilian Heyne @ 2026-05-28 12:14 UTC (permalink / raw)
To: stable
Cc: Maximilian Heyne, Matthieu Buffet, Mickaël Salaün,
Günther Noack, Paul Moore, James Morris, Serge E. Hallyn,
Konstantin Meskhidze, linux-security-module, linux-kernel
From: Matthieu Buffet <matthieu@buffet.re>
[ Upstream commit e4d82cbce2258f454634307fdabf33aa46b61ab0 ]
current_check_access_socket() treats AF_UNSPEC addresses as
AF_INET ones, and only later adds special case handling to
allow connect(AF_UNSPEC), and on IPv4 sockets
bind(AF_UNSPEC+INADDR_ANY).
This would be fine except AF_UNSPEC addresses can be as
short as a bare AF_UNSPEC sa_family_t field, and nothing
more. The AF_INET code path incorrectly enforces a length of
sizeof(struct sockaddr_in) instead.
Move AF_UNSPEC edge case handling up inside the switch-case,
before the address is (potentially incorrectly) treated as
AF_INET.
Fixes: fff69fb03dde ("landlock: Support network rules with TCP bind and connect")
Signed-off-by: Matthieu Buffet <matthieu@buffet.re>
Link: https://lore.kernel.org/r/20251027190726.626244-4-matthieu@buffet.re
Signed-off-by: Mickaël Salaün <mic@digikod.net>
[ There was a conflict due to missing commit 9f74411a40ce ("landlock:
Log TCP bind and connect denials") ]
Signed-off-by: Maximilian Heyne <mheyne@amazon.de>
---
Backporting this because landlock/net_test deterministically fails as
the selftest from the patch series "Fix TCP short AF_UNSPEC handling"
(https://lore.kernel.org/all/20251027190726.626244-1-matthieu@buffet.re/)
has been backported to 6.12 but not this patch due to conflicts.
---
security/landlock/net.c | 118 +++++++++++++++++++++++-----------------
1 file changed, 67 insertions(+), 51 deletions(-)
diff --git a/security/landlock/net.c b/security/landlock/net.c
index 104b6c01fe503..53d479893475f 100644
--- a/security/landlock/net.c
+++ b/security/landlock/net.c
@@ -72,6 +72,61 @@ static int current_check_access_socket(struct socket *const sock,
switch (address->sa_family) {
case AF_UNSPEC:
+ if (access_request == LANDLOCK_ACCESS_NET_CONNECT_TCP) {
+ /*
+ * Connecting to an address with AF_UNSPEC dissolves
+ * the TCP association, which have the same effect as
+ * closing the connection while retaining the socket
+ * object (i.e., the file descriptor). As for dropping
+ * privileges, closing connections is always allowed.
+ *
+ * For a TCP access control system, this request is
+ * legitimate. Let the network stack handle potential
+ * inconsistencies and return -EINVAL if needed.
+ */
+ return 0;
+ } else if (access_request == LANDLOCK_ACCESS_NET_BIND_TCP) {
+ /*
+ * Binding to an AF_UNSPEC address is treated
+ * differently by IPv4 and IPv6 sockets. The socket's
+ * family may change under our feet due to
+ * setsockopt(IPV6_ADDRFORM), but that's ok: we either
+ * reject entirely or require
+ * %LANDLOCK_ACCESS_NET_BIND_TCP for the given port, so
+ * it cannot be used to bypass the policy.
+ *
+ * IPv4 sockets map AF_UNSPEC to AF_INET for
+ * retrocompatibility for bind accesses, only if the
+ * address is INADDR_ANY (cf. __inet_bind). IPv6
+ * sockets always reject it.
+ *
+ * Checking the address is required to not wrongfully
+ * return -EACCES instead of -EAFNOSUPPORT or -EINVAL.
+ * We could return 0 and let the network stack handle
+ * these checks, but it is safer to return a proper
+ * error and test consistency thanks to kselftest.
+ */
+ if (sock->sk->__sk_common.skc_family == AF_INET) {
+ const struct sockaddr_in *const sockaddr =
+ (struct sockaddr_in *)address;
+
+ if (addrlen < sizeof(struct sockaddr_in))
+ return -EINVAL;
+
+ if (sockaddr->sin_addr.s_addr !=
+ htonl(INADDR_ANY))
+ return -EAFNOSUPPORT;
+ } else {
+ if (addrlen < SIN6_LEN_RFC2133)
+ return -EINVAL;
+ else
+ return -EAFNOSUPPORT;
+ }
+ } else {
+ WARN_ON_ONCE(1);
+ }
+ /* Only for bind(AF_UNSPEC+INADDR_ANY) on IPv4 socket. */
+ fallthrough;
case AF_INET:
if (addrlen < sizeof(struct sockaddr_in))
return -EINVAL;
@@ -90,57 +145,18 @@ static int current_check_access_socket(struct socket *const sock,
return 0;
}
- /* Specific AF_UNSPEC handling. */
- if (address->sa_family == AF_UNSPEC) {
- /*
- * Connecting to an address with AF_UNSPEC dissolves the TCP
- * association, which have the same effect as closing the
- * connection while retaining the socket object (i.e., the file
- * descriptor). As for dropping privileges, closing
- * connections is always allowed.
- *
- * For a TCP access control system, this request is legitimate.
- * Let the network stack handle potential inconsistencies and
- * return -EINVAL if needed.
- */
- if (access_request == LANDLOCK_ACCESS_NET_CONNECT_TCP)
- return 0;
-
- /*
- * For compatibility reason, accept AF_UNSPEC for bind
- * accesses (mapped to AF_INET) only if the address is
- * INADDR_ANY (cf. __inet_bind). Checking the address is
- * required to not wrongfully return -EACCES instead of
- * -EAFNOSUPPORT.
- *
- * We could return 0 and let the network stack handle these
- * checks, but it is safer to return a proper error and test
- * consistency thanks to kselftest.
- */
- if (access_request == LANDLOCK_ACCESS_NET_BIND_TCP) {
- /* addrlen has already been checked for AF_UNSPEC. */
- const struct sockaddr_in *const sockaddr =
- (struct sockaddr_in *)address;
-
- if (sock->sk->__sk_common.skc_family != AF_INET)
- return -EINVAL;
-
- if (sockaddr->sin_addr.s_addr != htonl(INADDR_ANY))
- return -EAFNOSUPPORT;
- }
- } else {
- /*
- * Checks sa_family consistency to not wrongfully return
- * -EACCES instead of -EINVAL. Valid sa_family changes are
- * only (from AF_INET or AF_INET6) to AF_UNSPEC.
- *
- * We could return 0 and let the network stack handle this
- * check, but it is safer to return a proper error and test
- * consistency thanks to kselftest.
- */
- if (address->sa_family != sock->sk->__sk_common.skc_family)
- return -EINVAL;
- }
+ /*
+ * Checks sa_family consistency to not wrongfully return
+ * -EACCES instead of -EINVAL. Valid sa_family changes are
+ * only (from AF_INET or AF_INET6) to AF_UNSPEC.
+ *
+ * We could return 0 and let the network stack handle this
+ * check, but it is safer to return a proper error and test
+ * consistency thanks to kselftest.
+ */
+ if (address->sa_family != sock->sk->__sk_common.skc_family &&
+ address->sa_family != AF_UNSPEC)
+ return -EINVAL;
id.key.data = (__force uintptr_t)port;
BUILD_BUG_ON(sizeof(port) > sizeof(id.key.data));
--
2.50.1
Amazon Web Services Development Center Germany GmbH
Tamara-Danz-Str. 13
10243 Berlin
Geschaeftsfuehrung: Christof Hellmis, Andreas Stieger
Eingetragen am Amtsgericht Charlottenburg unter HRB 257764 B
Sitz: Berlin
Ust-ID: DE 365 538 597
^ permalink raw reply related
* [PATCH 11/11] hornet: scripts: Improve argument handling and error messages
From: Blaise Boscaccy @ 2026-05-28 3:08 UTC (permalink / raw)
To: Jonathan Corbet, Shuah Khan, Paul Moore, James Morris,
Serge E. Hallyn, Eric Biggers, Fan Wu, James.Bottomley,
Blaise Boscaccy, linux-security-module
In-Reply-To: <20260528030915.2654994-1-bboscaccy@linux.microsoft.com>
Spaces in file names may have caused invocation errors. Additionally
add helpful error messages if we are unable to extract the requested
payload.
Signed-off-by: Blaise Boscaccy <bboscaccy@linux.microsoft.com>
---
| 14 +++++++++++---
| 15 ++++++++++++---
| 25 ++++++++++++++++++++++---
3 files changed, 45 insertions(+), 9 deletions(-)
--git a/scripts/hornet/extract-insn.sh b/scripts/hornet/extract-insn.sh
index 3e7bed049acb6..e0c7a4ae967f3 100755
--- a/scripts/hornet/extract-insn.sh
+++ b/scripts/hornet/extract-insn.sh
@@ -21,7 +21,15 @@ EXPECTED_ARGS=1
if [ $ARGC -ne $EXPECTED_ARGS ] ; then
usage
-else
- printf $(gcc -E $1 | grep "opts_insn" | \
- awk -F"=" '{print $2}' | sed 's/[[:space:];]*$//' | sed 's/\"//g')
fi
+
+HEADER="$1"
+STR=$(gcc -E "$HEADER" | \
+ sed -n 's/.*char opts_insn[[:space:]]*\[\][^=]*=[[:space:]]*"\(.*\)"[[:space:]]*;.*/\1/p')
+
+if [ -z "$STR" ]; then
+ echo "$(basename "$0"): no opts_insn[] declaration found in $HEADER" >&2
+ exit 1
+fi
+
+printf '%b' "$STR"
--git a/scripts/hornet/extract-map.sh b/scripts/hornet/extract-map.sh
index 1d92ebe1a04b5..2d68bb473d889 100755
--- a/scripts/hornet/extract-map.sh
+++ b/scripts/hornet/extract-map.sh
@@ -21,7 +21,16 @@ EXPECTED_ARGS=1
if [ $ARGC -ne $EXPECTED_ARGS ] ; then
usage
-else
- printf $(gcc -E $1 | grep "opts_data" | \
- awk -F"=" '{print $2}' | sed 's/[[:space:];]*$//' | sed 's/\"//g')
fi
+
+# See extract-insn.sh for the rationale behind the sed/printf '%b' shape.
+HEADER="$1"
+STR=$(gcc -E "$HEADER" | \
+ sed -n 's/.*char opts_data[[:space:]]*\[\][^=]*=[[:space:]]*"\(.*\)"[[:space:]]*;.*/\1/p')
+
+if [ -z "$STR" ]; then
+ echo "$(basename "$0"): no opts_data[] declaration found in $HEADER" >&2
+ exit 1
+fi
+
+printf '%b' "$STR"
--git a/scripts/hornet/extract-skel.sh b/scripts/hornet/extract-skel.sh
index e115f4b7fdf74..c980f245f4a39 100755
--- a/scripts/hornet/extract-skel.sh
+++ b/scripts/hornet/extract-skel.sh
@@ -21,7 +21,26 @@ EXPECTED_ARGS=2
if [ $ARGC -ne $EXPECTED_ARGS ] ; then
usage
-else
- printf $(gcc -E $1 | grep "static const char opts_$2" | \
- awk -F"=" '{print $2}' | sed 's/[[:space:];]*$//' | sed 's/\"//g')
fi
+
+# See extract-insn.sh for the rationale behind the sed/printf '%b' shape.
+HEADER="$1"
+FIELD="$2"
+
+# Reject anything that could escape into the sed pattern below.
+case "$FIELD" in
+ *[!A-Za-z0-9_]*|"")
+ echo "$(basename "$0"): invalid field name '$FIELD'" >&2
+ exit 1
+ ;;
+esac
+
+STR=$(gcc -E "$HEADER" | \
+ sed -n "s/.*char opts_${FIELD}[[:space:]]*\[\][^=]*=[[:space:]]*\"\(.*\)\"[[:space:]]*;.*/\1/p")
+
+if [ -z "$STR" ]; then
+ echo "$(basename "$0"): no opts_${FIELD}[] declaration found in $HEADER" >&2
+ exit 1
+fi
+
+printf '%b' "$STR"
--
2.53.0
^ permalink raw reply related
* [PATCH 10/11] hornet: scripts: harden scripts to handle trailing whitespace
From: Blaise Boscaccy @ 2026-05-28 3:08 UTC (permalink / raw)
To: Jonathan Corbet, Shuah Khan, Paul Moore, James Morris,
Serge E. Hallyn, Eric Biggers, Fan Wu, James.Bottomley,
Blaise Boscaccy, linux-security-module
In-Reply-To: <20260528030915.2654994-1-bboscaccy@linux.microsoft.com>
Trailing whitespace after the semicolon in the header files may have
caused the binary extracted payload to be corrupted due to a missing
anchor.
Signed-off-by: Blaise Boscaccy <bboscaccy@linux.microsoft.com>
---
| 2 +-
| 2 +-
| 2 +-
3 files changed, 3 insertions(+), 3 deletions(-)
--git a/scripts/hornet/extract-insn.sh b/scripts/hornet/extract-insn.sh
index e136932275aa5..3e7bed049acb6 100755
--- a/scripts/hornet/extract-insn.sh
+++ b/scripts/hornet/extract-insn.sh
@@ -23,5 +23,5 @@ if [ $ARGC -ne $EXPECTED_ARGS ] ; then
usage
else
printf $(gcc -E $1 | grep "opts_insn" | \
- awk -F"=" '{print $2}' | sed 's/;\+$//' | sed 's/\"//g')
+ awk -F"=" '{print $2}' | sed 's/[[:space:];]*$//' | sed 's/\"//g')
fi
--git a/scripts/hornet/extract-map.sh b/scripts/hornet/extract-map.sh
index 058ac1b32d743..1d92ebe1a04b5 100755
--- a/scripts/hornet/extract-map.sh
+++ b/scripts/hornet/extract-map.sh
@@ -23,5 +23,5 @@ if [ $ARGC -ne $EXPECTED_ARGS ] ; then
usage
else
printf $(gcc -E $1 | grep "opts_data" | \
- awk -F"=" '{print $2}' | sed 's/;\+$//' | sed 's/\"//g')
+ awk -F"=" '{print $2}' | sed 's/[[:space:];]*$//' | sed 's/\"//g')
fi
--git a/scripts/hornet/extract-skel.sh b/scripts/hornet/extract-skel.sh
index abc435e2bcd4e..e115f4b7fdf74 100755
--- a/scripts/hornet/extract-skel.sh
+++ b/scripts/hornet/extract-skel.sh
@@ -23,5 +23,5 @@ if [ $ARGC -ne $EXPECTED_ARGS ] ; then
usage
else
printf $(gcc -E $1 | grep "static const char opts_$2" | \
- awk -F"=" '{print $2}' | sed 's/;\+$//' | sed 's/\"//g')
+ awk -F"=" '{print $2}' | sed 's/[[:space:];]*$//' | sed 's/\"//g')
fi
--
2.53.0
^ permalink raw reply related
* [PATCH 09/11] hornet: scripts: set a non-zero error code for usage
From: Blaise Boscaccy @ 2026-05-28 3:08 UTC (permalink / raw)
To: Jonathan Corbet, Shuah Khan, Paul Moore, James Morris,
Serge E. Hallyn, Eric Biggers, Fan Wu, James.Bottomley,
Blaise Boscaccy, linux-security-module
In-Reply-To: <20260528030915.2654994-1-bboscaccy@linux.microsoft.com>
It was possible that build scripts may continue if arguments were
missing.
Signed-off-by: Blaise Boscaccy <bboscaccy@linux.microsoft.com>
---
| 10 +++++-----
| 10 +++++-----
| 10 +++++-----
scripts/hornet/write-sig.sh | 10 +++++-----
4 files changed, 20 insertions(+), 20 deletions(-)
--git a/scripts/hornet/extract-insn.sh b/scripts/hornet/extract-insn.sh
index 52338f057ff6b..e136932275aa5 100755
--- a/scripts/hornet/extract-insn.sh
+++ b/scripts/hornet/extract-insn.sh
@@ -8,11 +8,11 @@
# License as published by the Free Software Foundation.
function usage() {
- echo "Sample script for extracting instructions"
- echo "autogenerated eBPF lskel headers"
- echo ""
- echo "USAGE: header_file"
- exit
+ echo "Sample script for extracting instructions" >&2
+ echo "autogenerated eBPF lskel headers" >&2
+ echo "" >&2
+ echo "USAGE: header_file" >&2
+ exit 1
}
ARGC=$#
--git a/scripts/hornet/extract-map.sh b/scripts/hornet/extract-map.sh
index c309f505c6238..058ac1b32d743 100755
--- a/scripts/hornet/extract-map.sh
+++ b/scripts/hornet/extract-map.sh
@@ -8,11 +8,11 @@
# License as published by the Free Software Foundation.
function usage() {
- echo "Sample script for extracting instructions"
- echo "autogenerated eBPF lskel headers"
- echo ""
- echo "USAGE: header_file"
- exit
+ echo "Sample script for extracting instructions" >&2
+ echo "autogenerated eBPF lskel headers" >&2
+ echo "" >&2
+ echo "USAGE: header_file" >&2
+ exit 1
}
ARGC=$#
--git a/scripts/hornet/extract-skel.sh b/scripts/hornet/extract-skel.sh
index 6550a86b89917..abc435e2bcd4e 100755
--- a/scripts/hornet/extract-skel.sh
+++ b/scripts/hornet/extract-skel.sh
@@ -8,11 +8,11 @@
# License as published by the Free Software Foundation.
function usage() {
- echo "Sample script for extracting instructions and map data out of"
- echo "autogenerated eBPF lskel headers"
- echo ""
- echo "USAGE: header_file field"
- exit
+ echo "Sample script for extracting instructions and map data out of" >&2
+ echo "autogenerated eBPF lskel headers" >&2
+ echo "" >&2
+ echo "USAGE: header_file field" >&2
+ exit 1
}
ARGC=$#
diff --git a/scripts/hornet/write-sig.sh b/scripts/hornet/write-sig.sh
index 7eaabe3bab9aa..ad2b65761c282 100755
--- a/scripts/hornet/write-sig.sh
+++ b/scripts/hornet/write-sig.sh
@@ -8,11 +8,11 @@
# License as published by the Free Software Foundation.
function usage() {
- echo "Sample for rewriting an autogenerated eBPF lskel headers"
- echo "with a new signature"
- echo ""
- echo "USAGE: header_file sig"
- exit
+ echo "Sample for rewriting an autogenerated eBPF lskel headers" >&2
+ echo "with a new signature" >&2
+ echo "" >&2
+ echo "USAGE: header_file sig" >&2
+ exit 1
}
ARGC=$#
--
2.53.0
^ permalink raw reply related
* [PATCH 08/11] hornet: gen_sig: fix missing command line switches
From: Blaise Boscaccy @ 2026-05-28 3:08 UTC (permalink / raw)
To: Jonathan Corbet, Shuah Khan, Paul Moore, James Morris,
Serge E. Hallyn, Eric Biggers, Fan Wu, James.Bottomley,
Blaise Boscaccy, linux-security-module
In-Reply-To: <20260528030915.2654994-1-bboscaccy@linux.microsoft.com>
D was missing from the getopt list. Additionally, we were missing the
help option handler.
Signed-off-by: Blaise Boscaccy <bboscaccy@linux.microsoft.com>
---
scripts/hornet/gen_sig.c | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/scripts/hornet/gen_sig.c b/scripts/hornet/gen_sig.c
index fb9ae1934206a..19ae9af006853 100644
--- a/scripts/hornet/gen_sig.c
+++ b/scripts/hornet/gen_sig.c
@@ -295,7 +295,7 @@ int main(int argc, char **argv)
int i;
int opt;
- const char *short_opts = "C:K:P:O:A:Sh";
+ const char *short_opts = "C:K:P:O:D:A:Sh";
static const struct option long_opts[] = {
{"cert", required_argument, 0, 'C'},
@@ -332,6 +332,9 @@ int main(int argc, char **argv)
}
hashes[hash_count++].file = optarg;
break;
+ case 'h':
+ usage(argv[0]);
+ return EXIT_SUCCESS;
default:
usage(argv[0]);
return EXIT_FAILURE;
--
2.53.0
^ permalink raw reply related
* [PATCH 07/11] hornet: gen_sig: check for bad allocations
From: Blaise Boscaccy @ 2026-05-28 3:08 UTC (permalink / raw)
To: Jonathan Corbet, Shuah Khan, Paul Moore, James Morris,
Serge E. Hallyn, Eric Biggers, Fan Wu, James.Bottomley,
Blaise Boscaccy, linux-security-module
In-Reply-To: <20260528030915.2654994-1-bboscaccy@linux.microsoft.com>
There were a few sites where gen_sig failed to check for bad return
values after allocations. Error out appropriately as needed.
Signed-off-by: Blaise Boscaccy <bboscaccy@linux.microsoft.com>
---
scripts/hornet/gen_sig.c | 40 ++++++++++++++++++++++++++++++++++------
1 file changed, 34 insertions(+), 6 deletions(-)
diff --git a/scripts/hornet/gen_sig.c b/scripts/hornet/gen_sig.c
index 647bc3a257dd0..fb9ae1934206a 100644
--- a/scripts/hornet/gen_sig.c
+++ b/scripts/hornet/gen_sig.c
@@ -248,13 +248,25 @@ static int sha256(const char *path, unsigned char out[SHA256_LEN], unsigned int
return rc;
}
-static void add_hash(MAP_SET *set, unsigned char *buffer, int buffer_len)
+static int add_hash(MAP_SET *set, unsigned char *buffer, int buffer_len)
{
- HORNET_MAP *map = NULL;
+ HORNET_MAP *map;
map = HORNET_MAP_new();
- ASN1_OCTET_STRING_set(map->hash, buffer, buffer_len);
- sk_HORNET_MAP_push(set->maps, map);
+ if (!map)
+ return -1;
+
+ if (ASN1_OCTET_STRING_set(map->hash, buffer, buffer_len) != 1) {
+ HORNET_MAP_free(map);
+ return -1;
+ }
+
+ if (sk_HORNET_MAP_push(set->maps, map) <= 0) {
+ HORNET_MAP_free(map);
+ return -1;
+ }
+
+ return 0;
}
int main(int argc, char **argv)
@@ -353,13 +365,18 @@ int main(int argc, char **argv)
ERR(!si, "add signer failed");
set = MAP_SET_new();
+ ERR(!set, "alloc MAP_SET failed");
set->maps = sk_HORNET_MAP_new_null();
+ ERR(!set->maps, "alloc HORNET_MAP stack failed");
for (i = 0; i < hash_count; i++) {
if (sha256(hashes[i].file, hash_buffer, &hash_len) != 0) {
DIE("failed to hash input");
}
- add_hash(set, hash_buffer, hash_len);
+ if (add_hash(set, hash_buffer, hash_len) != 0) {
+ ERR_print_errors_fp(stderr);
+ DIE("failed to add hash to map set");
+ }
}
oid = OBJ_txt2obj("2.25.316487325684022475439036912669789383960", 1);
@@ -380,7 +397,18 @@ int main(int argc, char **argv)
b_out = bio_open_wr(out_path);
ERR(!b_out, "opening output path failed");
- i2d_CMS_bio_stream(b_out, cms_out, NULL, 0);
+ err = i2d_CMS_bio_stream(b_out, cms_out, NULL, 0);
+ ERR(!err, "writing CMS signature to %s failed", out_path);
+
+ /*
+ * File BIOs wrap stdio, which buffers writes; small payloads will
+ * report success from BIO_write even when the underlying file is
+ * full or otherwise un-writable. Force a flush and check it before
+ * the BIO is freed, otherwise gen_sig could exit successfully with
+ * a truncated or empty signature file (e.g. ENOSPC on /dev/full).
+ */
+ err = BIO_flush(b_out);
+ ERR(err <= 0, "flushing %s failed", out_path);
BIO_free(data_in);
BIO_free(b_out);
--
2.53.0
^ permalink raw reply related
* [PATCH 06/11] hornet: gen_sig: fix error string allocations
From: Blaise Boscaccy @ 2026-05-28 3:08 UTC (permalink / raw)
To: Jonathan Corbet, Shuah Khan, Paul Moore, James Morris,
Serge E. Hallyn, Eric Biggers, Fan Wu, James.Bottomley,
Blaise Boscaccy, linux-security-module
In-Reply-To: <20260528030915.2654994-1-bboscaccy@linux.microsoft.com>
The sha256 function was allocating/freeing it's own error strings,
which could case further errors to only return their error number.
Signed-off-by: Blaise Boscaccy <bboscaccy@linux.microsoft.com>
---
scripts/hornet/gen_sig.c | 3 ---
1 file changed, 3 deletions(-)
diff --git a/scripts/hornet/gen_sig.c b/scripts/hornet/gen_sig.c
index 4e8caad22f381..647bc3a257dd0 100644
--- a/scripts/hornet/gen_sig.c
+++ b/scripts/hornet/gen_sig.c
@@ -200,8 +200,6 @@ static int sha256(const char *path, unsigned char out[SHA256_LEN], unsigned int
return -2;
}
- ERR_load_crypto_strings();
-
rc = -3;
ctx = EVP_MD_CTX_new();
if (!ctx) {
@@ -247,7 +245,6 @@ static int sha256(const char *path, unsigned char out[SHA256_LEN], unsigned int
done:
EVP_MD_CTX_free(ctx);
fclose(f);
- ERR_free_strings();
return rc;
}
--
2.53.0
^ permalink raw reply related
* [PATCH 05/11] hornet: gen_sig: fix off-by-one check for used maps
From: Blaise Boscaccy @ 2026-05-28 3:08 UTC (permalink / raw)
To: Jonathan Corbet, Shuah Khan, Paul Moore, James Morris,
Serge E. Hallyn, Eric Biggers, Fan Wu, James.Bottomley,
Blaise Boscaccy, linux-security-module
In-Reply-To: <20260528030915.2654994-1-bboscaccy@linux.microsoft.com>
A logic bug limited the maximum number of used maps to
MAX_USED_MAPS-1.
Signed-off-by: Blaise Boscaccy <bboscaccy@linux.microsoft.com>
---
scripts/hornet/gen_sig.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/scripts/hornet/gen_sig.c b/scripts/hornet/gen_sig.c
index b4f983ab24bcd..4e8caad22f381 100644
--- a/scripts/hornet/gen_sig.c
+++ b/scripts/hornet/gen_sig.c
@@ -317,11 +317,11 @@ int main(int argc, char **argv)
data_path = optarg;
break;
case 'A':
- hashes[hash_count].file = optarg;
- if (++hash_count >= MAX_HASHES) {
+ if (hash_count >= MAX_HASHES) {
usage(argv[0]);
return EXIT_FAILURE;
}
+ hashes[hash_count++].file = optarg;
break;
default:
usage(argv[0]);
--
2.53.0
^ permalink raw reply related
* [PATCH 04/11] selftests: hornet: handle cross compilation and test skipping
From: Blaise Boscaccy @ 2026-05-28 3:08 UTC (permalink / raw)
To: Jonathan Corbet, Shuah Khan, Paul Moore, James Morris,
Serge E. Hallyn, Eric Biggers, Fan Wu, James.Bottomley,
Blaise Boscaccy, linux-security-module
In-Reply-To: <20260528030915.2654994-1-bboscaccy@linux.microsoft.com>
There were a few spots in the hornet selftest makefile where some host
resources were assumed to be used. Additionally add proper skip
detection for scenarios where the autogenerated signing keys don't
exist.
Signed-off-by: Blaise Boscaccy <bboscaccy@linux.microsoft.com>
---
tools/testing/selftests/hornet/Makefile | 114 ++++++++++++++++++------
1 file changed, 89 insertions(+), 25 deletions(-)
diff --git a/tools/testing/selftests/hornet/Makefile b/tools/testing/selftests/hornet/Makefile
index 316364f95f28c..460adab35e238 100644
--- a/tools/testing/selftests/hornet/Makefile
+++ b/tools/testing/selftests/hornet/Makefile
@@ -5,59 +5,123 @@ include ../../../scripts/Makefile.include
CLANG ?= clang
CFLAGS := -g -O2 -Wall
+TOOLSDIR := $(abspath ../../..)
BPFTOOL ?= $(TOOLSDIR)/bpf/bpftool/bpftool
SCRIPTSDIR := $(abspath ../../../../scripts/hornet)
-TOOLSDIR := $(abspath ../../..)
LIBDIR := $(TOOLSDIR)/lib
BPFDIR := $(LIBDIR)/bpf
TOOLSINCDIR := $(TOOLSDIR)/include
APIDIR := $(TOOLSINCDIR)/uapi
CERTDIR := $(abspath ../../../../certs)
-PKG_CONFIG ?= $(CROSS_COMPILE)pkg-config
+HOSTPKG_CONFIG ?= pkg-config
+
+SIGNING_KEY := $(CERTDIR)/signing_key.pem
+SIGNING_CERT := $(CERTDIR)/signing_key.x509
+
+VMLINUX_BTF_PATHS ?= $(if $(O),$(O)/vmlinux) \
+ $(if $(KBUILD_OUTPUT),$(KBUILD_OUTPUT)/vmlinux) \
+ ../../../../vmlinux \
+ /sys/kernel/btf/vmlinux \
+ /boot/vmlinux-$(shell uname -r)
+VMLINUX_BTF ?= $(abspath $(firstword $(wildcard $(VMLINUX_BTF_PATHS))))
+
+# The hornet selftest needs the kernel module signing key/cert (generated when
+# the kernel is built with CONFIG_MODULE_SIG=y), a bpftool binary, and a
+# vmlinux with BTF for trivial.bpf.o. If any of those are missing (cross-build
+# without artifacts, container CI, CONFIG_MODULE_SIG disabled, etc.) skip the
+# targets rather than failing the global selftests build.
+hornet_skip_reason :=
+ifeq ($(wildcard $(SIGNING_KEY)),)
+hornet_skip_reason := module signing key not found at $(SIGNING_KEY) (build the kernel with CONFIG_MODULE_SIG=y first)
+else ifeq ($(wildcard $(SIGNING_CERT)),)
+hornet_skip_reason := module signing cert not found at $(SIGNING_CERT)
+else ifeq ($(wildcard $(BPFTOOL)),)
+hornet_skip_reason := bpftool not found at $(BPFTOOL) (build it under tools/bpf/bpftool first)
+else ifeq ($(VMLINUX_BTF),)
+hornet_skip_reason := no vmlinux with BTF found; tried $(VMLINUX_BTF_PATHS) (build the kernel with CONFIG_DEBUG_INFO_BTF=y or set VMLINUX_BTF=)
+endif
+
+ifneq ($(hornet_skip_reason),)
+$(warning Skipping hornet selftests: $(hornet_skip_reason))
+TEST_GEN_PROGS :=
+TEST_GEN_FILES :=
+
+include ../lib.mk
+
+else
TEST_GEN_PROGS := loader
TEST_GEN_FILES := vmlinux.h loader.h trivial.bpf.o map.bin sig.bin insn.bin signed_loader.h
-$(TEST_GEN_PROGS): LDLIBS += -lbpf
-$(TEST_GEN_PROGS): $(TEST_GEN_FILES)
include ../lib.mk
-BPF_CFLAGS := -target bpf \
- -D__TARGET_ARCH_$(ARCH) \
- -I/usr/include/$(shell uname -m)-linux-gnu \
+define get_sys_includes
+$(shell $(1) $(2) -v -E - </dev/null 2>&1 \
+ | sed -n '/<...> search starts here:/,/End of search list./{ s| \(/.*\)|-idirafter \1|p }') \
+$(shell $(1) $(2) -dM -E - </dev/null | grep '__riscv_xlen ' | awk '{printf("-D__riscv_xlen=%d -D__BITS_PER_LONG=%d", $$3, $$3)}') \
+$(shell $(1) $(2) -dM -E - </dev/null | grep '__loongarch_grlen ' | awk '{printf("-D__BITS_PER_LONG=%d", $$3)}') \
+$(shell $(1) $(2) -dM -E - </dev/null | grep -E 'MIPS(EL|EB)|_MIPS_SZ(PTR|LONG) |_MIPS_SIM |_ABI(O32|N32|64) ' | awk '{printf("-D%s=%s ", $$2, $$3)}')
+endef
+
+ifneq ($(CROSS_COMPILE),)
+CLANG_TARGET_ARCH = --target=$(notdir $(CROSS_COMPILE:%-=%))
+endif
+CLANG_SYS_INCLUDES = $(call get_sys_includes,$(CLANG),$(CLANG_TARGET_ARCH))
+
+IS_LITTLE_ENDIAN := $(shell $(CC) -dM -E - </dev/null | \
+ grep 'define __BYTE_ORDER__ __ORDER_LITTLE_ENDIAN__')
+BPF_TARGET_ENDIAN := $(if $(IS_LITTLE_ENDIAN),--target=bpfel,--target=bpfeb)
+
+BPF_CFLAGS := $(BPF_TARGET_ENDIAN) \
+ -D__TARGET_ARCH_$(SRCARCH) \
+ $(CLANG_SYS_INCLUDES) \
$(KHDR_INCLUDES)
-vmlinux.h:
- $(BPFTOOL) btf dump file /sys/kernel/btf/vmlinux format c > vmlinux.h
+$(OUTPUT)/vmlinux.h: $(VMLINUX_BTF) $(BPFTOOL)
+ $(BPFTOOL) btf dump file $(VMLINUX_BTF) format c > $@
-trivial.bpf.o: trivial.bpf.c vmlinux.h
- $(CLANG) $(CFLAGS) $(BPF_CFLAGS) -c $< -o $@
+$(OUTPUT)/trivial.bpf.o: trivial.bpf.c $(OUTPUT)/vmlinux.h
+ $(CLANG) $(CFLAGS) $(BPF_CFLAGS) -I$(OUTPUT) -c $< -o $@
-loader.h: trivial.bpf.o
- $(BPFTOOL) gen skeleton -S -k $(CERTDIR)/signing_key.pem -i $(CERTDIR)/signing_key.x509 \
+$(OUTPUT)/loader.h: $(OUTPUT)/trivial.bpf.o
+ $(BPFTOOL) gen skeleton -S -k $(SIGNING_KEY) -i $(SIGNING_CERT) \
-L $< name trivial > $@
-insn.bin: loader.h
+$(OUTPUT)/insn.bin: $(OUTPUT)/loader.h
$(SCRIPTSDIR)/extract-insn.sh $< > $@
-map.bin: loader.h
+$(OUTPUT)/map.bin: $(OUTPUT)/loader.h
$(SCRIPTSDIR)/extract-map.sh $< > $@
$(OUTPUT)/gen_sig: ../../../../scripts/hornet/gen_sig.c
$(call msg,GEN_SIG,,$@)
- $(Q)$(CC) $(shell $(PKG_CONFIG) --cflags libcrypto 2> /dev/null) \
+ $(Q)$(HOSTCC) $(shell $(HOSTPKG_CONFIG) --cflags libcrypto 2> /dev/null) \
$< -o $@ \
- $(shell $(PKG_CONFIG) --libs libcrypto 2> /dev/null || echo -lcrypto)
+ $(shell $(HOSTPKG_CONFIG) --libs libcrypto 2> /dev/null || echo -lcrypto)
+
+$(OUTPUT)/sig.bin: $(OUTPUT)/insn.bin $(OUTPUT)/map.bin $(OUTPUT)/gen_sig
+ $(OUTPUT)/gen_sig --key $(SIGNING_KEY) --cert $(SIGNING_CERT) \
+ --data $(OUTPUT)/insn.bin --add $(OUTPUT)/map.bin --out $@
+
+$(OUTPUT)/signed_loader.h: $(OUTPUT)/sig.bin $(OUTPUT)/loader.h
+ $(SCRIPTSDIR)/write-sig.sh $(OUTPUT)/loader.h $(OUTPUT)/sig.bin > $@
+
+BPFOBJ := $(OUTPUT)/libbpf/libbpf.a
+
+$(OUTPUT)/libbpf:
+ $(Q)mkdir -p $@
-sig.bin: insn.bin map.bin $(OUTPUT)/gen_sig
- $(OUTPUT)/gen_sig --key $(CERTDIR)/signing_key.pem --cert $(CERTDIR)/signing_key.x509 \
- --data insn.bin --add map.bin --out sig.bin
+$(BPFOBJ): $(wildcard $(BPFDIR)/*.[ch] $(BPFDIR)/Makefile) \
+ $(APIDIR)/linux/bpf.h | $(OUTPUT)/libbpf
+ $(Q)$(MAKE) -C $(BPFDIR) OUTPUT=$(OUTPUT)/libbpf/ \
+ DESTDIR=$(OUTPUT) prefix= \
+ $(BPFOBJ) install_headers
-signed_loader.h: sig.bin
- $(SCRIPTSDIR)/write-sig.sh loader.h sig.bin > $@
+$(OUTPUT)/loader: loader.c $(OUTPUT)/signed_loader.h $(BPFOBJ)
+ $(CC) $(CFLAGS) -I$(LIBDIR) -I$(APIDIR) -I$(OUTPUT) \
+ $< $(BPFOBJ) -o $@ -lelf -lz
-loader: loader.c signed_loader.h
- $(CC) $(CFLAGS) -I$(LIBDIR) -I$(APIDIR) $< -o $@ -lbpf
+EXTRA_CLEAN = $(OUTPUT)/gen_sig $(OUTPUT)/libbpf
-EXTRA_CLEAN = $(OUTPUT)/gen_sig
+endif
--
2.53.0
^ permalink raw reply related
* [PATCH 03/11] hornet: fix off-by-one bug in max used maps check
From: Blaise Boscaccy @ 2026-05-28 3:08 UTC (permalink / raw)
To: Jonathan Corbet, Shuah Khan, Paul Moore, James Morris,
Serge E. Hallyn, Eric Biggers, Fan Wu, James.Bottomley,
Blaise Boscaccy, linux-security-module
In-Reply-To: <20260528030915.2654994-1-bboscaccy@linux.microsoft.com>
Sashiko correctly reported an off-by-one logic error checking against
the maximum number of used maps. Removing the index constraint allows
us to simplify the check logic.
Signed-off-by: Blaise Boscaccy <bboscaccy@linux.microsoft.com>
---
security/hornet/hornet_lsm.c | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/security/hornet/hornet_lsm.c b/security/hornet/hornet_lsm.c
index 35d9522d6bc72..eeb422db1092d 100644
--- a/security/hornet/hornet_lsm.c
+++ b/security/hornet/hornet_lsm.c
@@ -49,8 +49,7 @@ int hornet_next_map(void *context, size_t hdrlen,
{
struct hornet_parse_context *ctx = (struct hornet_parse_context *)context;
- if (++ctx->security->signed_hash_count >= MAX_USED_MAPS)
- return -EINVAL;
+ ctx->security->signed_hash_count++;
return 0;
}
@@ -63,6 +62,8 @@ int hornet_map_hash(void *context, size_t hdrlen,
if (vlen != SHA256_DIGEST_SIZE && vlen != 0)
return -EINVAL;
+ if (ctx->security->signed_hash_count >= MAX_USED_MAPS)
+ return -EINVAL;
memcpy(&ctx->security->signed_hashes[ctx->security->signed_hash_count * SHA256_DIGEST_SIZE],
value, vlen);
--
2.53.0
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox