* [PATCH v7 00/10] Reintroduce Hornet LSM
From: Blaise Boscaccy @ 2026-05-07 19:13 UTC (permalink / raw)
To: Blaise Boscaccy, Jonathan Corbet, Paul Moore, James Morris,
Serge E. Hallyn, Mickaël Salaün, Günther Noack,
Dr. David Alan Gilbert, Andrew Morton, James.Bottomley, dhowells,
Fan Wu, Ryan Foster, Randy Dunlap, linux-security-module,
linux-doc, linux-kernel, bpf, Song Liu
This patch series introduces the next iteration of the Hornet LSM.
Hornet’s goal is to provide a secure and extensible in-kernel
signature verification mechanism for eBPF programs.
Hornet addresses concerns from users who require strict audit trails and
verification guarantees for eBPF programs, especially in
security-sensitive environments. Many production systems need assurance
that only authorized, unmodified eBPF programs are loaded into the
kernel. Hornet provides this assurance through cryptographic signature
verification.
The currently accepted loader-plus-map signature verification scheme,
mandated by Alexei and KP, is simple to implement and generally
acceptable if users and administrators are satisfied with it. However,
verifying both the loader and the maps offers additional benefits
beyond verifying the loader alone:
1. Security and Audit Integrity
A key advantage is that the LSM hook for authorizing BPF program loads
can operate after signature verification. This ensures:
* Access control decisions are based on verified signature status.
* Accurate system state measurement and logging.
* Log entries claiming a verified signature are truthful, avoiding
misleading records where only the loader was verified while the actual
BPF program verification occurs later without logging.
2. TOCTOU Attack Prevention
The current map hash implementation may be vulnerable to a TOCTOU
attack because it allows unfrozen maps to cache a previously
calculated hash. The accepted “trusted loader” scheme cannot detect
this and may permit loading altered maps.
3. Supply Chain Integrity
Verify that eBPF programs and their associated map data have not been
modified since they were built and signed, in the kernel proper, may
aid in protecting against supply chain attacks.
This approach addresses concerns from users who require strict audit
trails and verification guarantees, especially in security-sensitive
environments. Map hashes for extended verification are passed via the
existing PKCS#7 UAPI and verified by the crypto subsystem. Hornet then
calculates the program’s verification state. Hornet itself does not
enforce a policy on whether unsigned or partially signed programs
should be rejected. It delegates that decision to downstream LSMs
hook, making it a composable building block in a larger security
architecture.
Changes in V7:
- minor IPE fixes
- tooling fixes in gen_sig
- checkpatch fixes
Link to V6: https://lore.kernel.org/linux-security-module/20260429191431.2345448-1-bboscaccy@linux.microsoft.com/
Changes in V6:
- Doc cleanup
- Error handling improvements
- Support for shareable maps
Link to V5: https://lore.kernel.org/linux-security-module/20260420212653.438685-1-bboscaccy@linux.microsoft.com/
Changes in V5:
- Added IPE documentation
Link to V4: https://lore.kernel.org/linux-security-module/20260416173500.176716-1-bboscaccy@linux.microsoft.com/
Changes in V4:
- IPE integration
- Arbitrary keyring support
Link to V3: https://lore.kernel.org/linux-security-module/20260326060655.2550595-1-bboscaccy@linux.microsoft.com/
Changes in V3:
- Updated for signed attribute patch series changes
- Added some new result enum values
- Minor documentation clarification
- Misc style fixes
- Added missing signed-off-by tags
Link to V2: https://lore.kernel.org/linux-security-module/20260227233930.2418522-1-bboscaccy@linux.microsoft.com/
Changes in V2:
- Addressed possible TocTou races in hash verification
- Improved documentation and tooling
- Added Alexie's nack
Link to RFC: https://lore.kernel.org/linux-security-module/20251211021257.1208712-1-bboscaccy@linux.microsoft.com/
Blaise Boscaccy (6):
lsm: security: Add additional enum values for bpf integrity checks
security: Hornet LSM
hornet: Introduce gen_sig
hornet: Add a light skeleton data extractor scripts
selftests/hornet: Add a selftest for the Hornet LSM
ipe: Add BPF program load policy enforcement via Hornet integration
James Bottomley (3):
crypto: pkcs7: add flag for validated trust on a signed info block
crypto: pkcs7: add ability to extract signed attributes by OID
crypto: pkcs7: add tests for pkcs7_get_authattr
Paul Moore (1):
lsm: framework for BPF integrity verification
Documentation/admin-guide/LSM/Hornet.rst | 323 +++++++++++++++
Documentation/admin-guide/LSM/index.rst | 1 +
Documentation/admin-guide/LSM/ipe.rst | 162 +++++++-
Documentation/security/ipe.rst | 68 ++++
MAINTAINERS | 9 +
certs/system_keyring.c | 1 +
crypto/asymmetric_keys/Makefile | 4 +-
crypto/asymmetric_keys/pkcs7_aa.asn1 | 18 +
crypto/asymmetric_keys/pkcs7_key_type.c | 44 +-
crypto/asymmetric_keys/pkcs7_parser.c | 81 ++++
crypto/asymmetric_keys/pkcs7_parser.h | 1 +
crypto/asymmetric_keys/pkcs7_trust.c | 1 +
include/crypto/pkcs7.h | 4 +
include/linux/lsm_hook_defs.h | 5 +
include/linux/oid_registry.h | 3 +
include/linux/security.h | 28 ++
include/uapi/linux/lsm.h | 1 +
scripts/Makefile | 1 +
scripts/hornet/Makefile | 5 +
scripts/hornet/extract-insn.sh | 27 ++
scripts/hornet/extract-map.sh | 27 ++
scripts/hornet/extract-skel.sh | 27 ++
scripts/hornet/gen_sig.c | 401 +++++++++++++++++++
scripts/hornet/write-sig.sh | 27 ++
security/Kconfig | 3 +-
security/Makefile | 1 +
security/hornet/Kconfig | 13 +
security/hornet/Makefile | 7 +
security/hornet/hornet.asn1 | 12 +
security/hornet/hornet_lsm.c | 352 ++++++++++++++++
security/ipe/Kconfig | 15 +
security/ipe/audit.c | 15 +
security/ipe/eval.c | 93 ++++-
security/ipe/eval.h | 11 +
security/ipe/hooks.c | 63 +++
security/ipe/hooks.h | 15 +
security/ipe/ipe.c | 14 +
security/ipe/ipe.h | 3 +
security/ipe/policy.h | 14 +
security/ipe/policy_parser.c | 27 ++
security/security.c | 75 +++-
tools/testing/selftests/Makefile | 1 +
tools/testing/selftests/hornet/Makefile | 63 +++
tools/testing/selftests/hornet/loader.c | 21 +
tools/testing/selftests/hornet/trivial.bpf.c | 33 ++
45 files changed, 2112 insertions(+), 8 deletions(-)
create mode 100644 Documentation/admin-guide/LSM/Hornet.rst
create mode 100644 crypto/asymmetric_keys/pkcs7_aa.asn1
create mode 100644 scripts/hornet/Makefile
create mode 100755 scripts/hornet/extract-insn.sh
create mode 100755 scripts/hornet/extract-map.sh
create mode 100755 scripts/hornet/extract-skel.sh
create mode 100644 scripts/hornet/gen_sig.c
create mode 100755 scripts/hornet/write-sig.sh
create mode 100644 security/hornet/Kconfig
create mode 100644 security/hornet/Makefile
create mode 100644 security/hornet/hornet.asn1
create mode 100644 security/hornet/hornet_lsm.c
create mode 100644 tools/testing/selftests/hornet/Makefile
create mode 100644 tools/testing/selftests/hornet/loader.c
create mode 100644 tools/testing/selftests/hornet/trivial.bpf.c
--
2.53.0
^ permalink raw reply
* [PATCH v7 01/10] crypto: pkcs7: add flag for validated trust on a signed info block
From: Blaise Boscaccy @ 2026-05-07 19:13 UTC (permalink / raw)
To: Blaise Boscaccy, Jonathan Corbet, Paul Moore, James Morris,
Serge E. Hallyn, Mickaël Salaün, Günther Noack,
Dr. David Alan Gilbert, Andrew Morton, James.Bottomley, dhowells,
Fan Wu, Ryan Foster, Randy Dunlap, linux-security-module,
linux-doc, linux-kernel, bpf, Song Liu
In-Reply-To: <20260507191416.2984054-1-bboscaccy@linux.microsoft.com>
From: James Bottomley <James.Bottomley@HansenPartnership.com>
Allow consumers of struct pkcs7_message to tell if any of the sinfo
fields has passed a trust validation. Note that this does not happen
in parsing, pkcs7_validate_trust() must be explicitly called or called
via validate_pkcs7_trust(). Since the way to get this trusted pkcs7
object is via verify_pkcs7_message_sig, export that so modules can use
it.
Signed-off-by: James Bottomley <James.Bottomley@HansenPartnership.com>
Signed-off-by: Blaise Boscaccy <bboscaccy@linux.microsoft.com>
---
certs/system_keyring.c | 1 +
crypto/asymmetric_keys/pkcs7_parser.h | 1 +
crypto/asymmetric_keys/pkcs7_trust.c | 1 +
3 files changed, 3 insertions(+)
diff --git a/certs/system_keyring.c b/certs/system_keyring.c
index e0761436ec7f4..9bda49295bd02 100644
--- a/certs/system_keyring.c
+++ b/certs/system_keyring.c
@@ -380,6 +380,7 @@ int verify_pkcs7_message_sig(const void *data, size_t len,
pr_devel("<==%s() = %d\n", __func__, ret);
return ret;
}
+EXPORT_SYMBOL(verify_pkcs7_message_sig);
/**
* verify_pkcs7_signature - Verify a PKCS#7-based signature on system data.
diff --git a/crypto/asymmetric_keys/pkcs7_parser.h b/crypto/asymmetric_keys/pkcs7_parser.h
index 6ef9f335bb17f..203062a33def6 100644
--- a/crypto/asymmetric_keys/pkcs7_parser.h
+++ b/crypto/asymmetric_keys/pkcs7_parser.h
@@ -20,6 +20,7 @@ struct pkcs7_signed_info {
unsigned index;
bool unsupported_crypto; /* T if not usable due to missing crypto */
bool blacklisted;
+ bool verified; /* T if this signer has validated trust */
/* Message digest - the digest of the Content Data (or NULL) */
const void *msgdigest;
diff --git a/crypto/asymmetric_keys/pkcs7_trust.c b/crypto/asymmetric_keys/pkcs7_trust.c
index 9a87c34ed1733..78ebfb6373b61 100644
--- a/crypto/asymmetric_keys/pkcs7_trust.c
+++ b/crypto/asymmetric_keys/pkcs7_trust.c
@@ -127,6 +127,7 @@ static int pkcs7_validate_trust_one(struct pkcs7_message *pkcs7,
for (p = sinfo->signer; p != x509; p = p->signer)
p->verified = true;
}
+ sinfo->verified = true;
kleave(" = 0");
return 0;
}
--
2.53.0
^ permalink raw reply related
* [PATCH v7 03/10] crypto: pkcs7: add tests for pkcs7_get_authattr
From: Blaise Boscaccy @ 2026-05-07 19:13 UTC (permalink / raw)
To: Blaise Boscaccy, Jonathan Corbet, Paul Moore, James Morris,
Serge E. Hallyn, Mickaël Salaün, Günther Noack,
Dr. David Alan Gilbert, Andrew Morton, James.Bottomley, dhowells,
Fan Wu, Ryan Foster, Randy Dunlap, linux-security-module,
linux-doc, linux-kernel, bpf, Song Liu
In-Reply-To: <20260507191416.2984054-1-bboscaccy@linux.microsoft.com>
From: James Bottomley <James.Bottomley@HansenPartnership.com>
Add example code to the test module pkcs7_key_type.c that verifies a
message and then pulls out a known authenticated attribute.
Signed-off-by: James Bottomley <James.Bottomley@HansenPartnership.com>
Signed-off-by: Blaise Boscaccy <bboscaccy@linux.microsoft.com>
Acked-by: David Howells <dhowells@redhat.com>
---
crypto/asymmetric_keys/pkcs7_key_type.c | 44 ++++++++++++++++++++++++-
1 file changed, 43 insertions(+), 1 deletion(-)
diff --git a/crypto/asymmetric_keys/pkcs7_key_type.c b/crypto/asymmetric_keys/pkcs7_key_type.c
index b930d3bbf1af5..e0b1ce0202f6d 100644
--- a/crypto/asymmetric_keys/pkcs7_key_type.c
+++ b/crypto/asymmetric_keys/pkcs7_key_type.c
@@ -12,6 +12,7 @@
#include <linux/verification.h>
#include <linux/key-type.h>
#include <keys/user-type.h>
+#include <crypto/pkcs7.h>
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("PKCS#7 testing key type");
@@ -51,16 +52,57 @@ static int pkcs7_view_content(void *ctx, const void *data, size_t len,
static int pkcs7_preparse(struct key_preparsed_payload *prep)
{
enum key_being_used_for usage = pkcs7_usage;
+ int ret;
+ struct pkcs7_message *pkcs7;
+ const void *data;
+ size_t len;
if (usage >= NR__KEY_BEING_USED_FOR) {
pr_err("Invalid usage type %d\n", usage);
return -EINVAL;
}
- return verify_pkcs7_signature(NULL, 0,
+ ret = verify_pkcs7_signature(NULL, 0,
prep->data, prep->datalen,
VERIFY_USE_SECONDARY_KEYRING, usage,
pkcs7_view_content, prep);
+ if (ret)
+ return ret;
+
+ pkcs7 = pkcs7_parse_message(prep->data, prep->datalen);
+ if (IS_ERR(pkcs7)) {
+ pr_err("pkcs7 parse error\n");
+ return PTR_ERR(pkcs7);
+ }
+
+ /*
+ * the parsed message has no trusted signer, so nothing should
+ * be returned here
+ */
+ ret = pkcs7_get_authattr(pkcs7, OID_messageDigest, &data, &len);
+ if (ret == 0) {
+ pr_err("OID returned when no trust in signer\n");
+ goto out;
+ }
+ /* add trust and check again */
+ ret = verify_pkcs7_message_sig(NULL, 0, pkcs7,
+ VERIFY_USE_SECONDARY_KEYRING, usage,
+ NULL, NULL);
+ if (ret) {
+ pr_err("verify_pkcs7_message_sig failed!!\n");
+ goto out;
+ }
+ /* now we should find the OID */
+ ret = pkcs7_get_authattr(pkcs7, OID_messageDigest, &data, &len);
+ if (ret) {
+ pr_err("Failed to get message digest\n");
+ goto out;
+ }
+ pr_info("Correctly Got message hash, size=%zu\n", len);
+
+ out:
+ pkcs7_free_message(pkcs7);
+ return 0;
}
/*
--
2.53.0
^ permalink raw reply related
* [PATCH v7 02/10] crypto: pkcs7: add ability to extract signed attributes by OID
From: Blaise Boscaccy @ 2026-05-07 19:13 UTC (permalink / raw)
To: Blaise Boscaccy, Jonathan Corbet, Paul Moore, James Morris,
Serge E. Hallyn, Mickaël Salaün, Günther Noack,
Dr. David Alan Gilbert, Andrew Morton, James.Bottomley, dhowells,
Fan Wu, Ryan Foster, Randy Dunlap, linux-security-module,
linux-doc, linux-kernel, bpf, Song Liu
In-Reply-To: <20260507191416.2984054-1-bboscaccy@linux.microsoft.com>
From: James Bottomley <James.Bottomley@HansenPartnership.com>
Signers may add any information they like in signed attributes and
sometimes this information turns out to be relevant to specific
signing cases, so add an api pkcs7_get_authattr() to extract the value
of an authenticated attribute by specific OID. The current
implementation is designed for the single signer use case and simply
terminates the search when it finds the relevant OID.
Signed-off-by: James Bottomley <James.Bottomley@HansenPartnership.com>
Signed-off-by: Blaise Boscaccy <bboscaccy@linux.microsoft.com>
---
crypto/asymmetric_keys/Makefile | 4 +-
crypto/asymmetric_keys/pkcs7_aa.asn1 | 18 ++++++
crypto/asymmetric_keys/pkcs7_parser.c | 81 +++++++++++++++++++++++++++
include/crypto/pkcs7.h | 4 ++
4 files changed, 106 insertions(+), 1 deletion(-)
create mode 100644 crypto/asymmetric_keys/pkcs7_aa.asn1
diff --git a/crypto/asymmetric_keys/Makefile b/crypto/asymmetric_keys/Makefile
index bc65d3b98dcbf..f99b7169ae7cd 100644
--- a/crypto/asymmetric_keys/Makefile
+++ b/crypto/asymmetric_keys/Makefile
@@ -53,12 +53,14 @@ clean-files += pkcs8.asn1.c pkcs8.asn1.h
obj-$(CONFIG_PKCS7_MESSAGE_PARSER) += pkcs7_message.o
pkcs7_message-y := \
pkcs7.asn1.o \
+ pkcs7_aa.asn1.o \
pkcs7_parser.o \
pkcs7_trust.o \
pkcs7_verify.o
-$(obj)/pkcs7_parser.o: $(obj)/pkcs7.asn1.h
+$(obj)/pkcs7_parser.o: $(obj)/pkcs7.asn1.h $(obj)/pkcs7_aa.asn1.h
$(obj)/pkcs7.asn1.o: $(obj)/pkcs7.asn1.c $(obj)/pkcs7.asn1.h
+$(obj)/pkcs7_aa.asn1.o: $(obj)/pkcs7_aa.asn1.c $(obj)/pkcs7_aa.asn1.h
#
# PKCS#7 parser testing key
diff --git a/crypto/asymmetric_keys/pkcs7_aa.asn1 b/crypto/asymmetric_keys/pkcs7_aa.asn1
new file mode 100644
index 0000000000000..7a8857bdf56e1
--- /dev/null
+++ b/crypto/asymmetric_keys/pkcs7_aa.asn1
@@ -0,0 +1,18 @@
+-- SPDX-License-Identifier: BSD-3-Clause
+--
+-- Copyright (C) 2009 IETF Trust and the persons identified as authors
+-- of the code
+--
+-- https://www.rfc-editor.org/rfc/rfc5652#section-3
+
+AA ::= CHOICE {
+ aaSet [0] IMPLICIT AASet,
+ aaSequence [2] EXPLICIT SEQUENCE OF AuthenticatedAttribute
+}
+
+AASet ::= SET OF AuthenticatedAttribute
+
+AuthenticatedAttribute ::= SEQUENCE {
+ type OBJECT IDENTIFIER ({ pkcs7_aa_note_OID }),
+ values SET OF ANY ({ pkcs7_aa_note_attr })
+}
diff --git a/crypto/asymmetric_keys/pkcs7_parser.c b/crypto/asymmetric_keys/pkcs7_parser.c
index 6e3ffdac83ace..d467866f7d930 100644
--- a/crypto/asymmetric_keys/pkcs7_parser.c
+++ b/crypto/asymmetric_keys/pkcs7_parser.c
@@ -15,6 +15,7 @@
#include <crypto/public_key.h>
#include "pkcs7_parser.h"
#include "pkcs7.asn1.h"
+#include "pkcs7_aa.asn1.h"
MODULE_DESCRIPTION("PKCS#7 parser");
MODULE_AUTHOR("Red Hat, Inc.");
@@ -211,6 +212,86 @@ int pkcs7_get_content_data(const struct pkcs7_message *pkcs7,
}
EXPORT_SYMBOL_GPL(pkcs7_get_content_data);
+struct pkcs7_aa_context {
+ bool found;
+ enum OID oid_to_find;
+ const void *data;
+ size_t len;
+};
+
+int pkcs7_aa_note_OID(void *context, size_t hdrlen,
+ unsigned char tag,
+ const void *value, size_t vlen)
+{
+ struct pkcs7_aa_context *ctx = context;
+ enum OID oid = look_up_OID(value, vlen);
+
+ ctx->found = (oid == ctx->oid_to_find);
+
+ return 0;
+}
+
+int pkcs7_aa_note_attr(void *context, size_t hdrlen,
+ unsigned char tag,
+ const void *value, size_t vlen)
+{
+ struct pkcs7_aa_context *ctx = context;
+
+ if (ctx->found) {
+ ctx->data = value;
+ ctx->len = vlen;
+ }
+
+ return 0;
+}
+
+/**
+ * pkcs7_get_authattr - get authenticated attribute by OID
+ * @pkcs7: The preparsed PKCS#7 message
+ * @oid: the enum value of the OID to find
+ * @_data: Place to return a pointer to the attribute value
+ * @_len: length of the attribute value
+ *
+ * Searches the authenticated attributes until one is found with a
+ * matching OID. Note that because the attributes are per signer
+ * there could be multiple signers with different values, but this
+ * routine will simply return the first one in parse order.
+ *
+ * Returns -ENODATA if the attribute can't be found
+ */
+int pkcs7_get_authattr(const struct pkcs7_message *pkcs7,
+ enum OID oid,
+ const void **_data, size_t *_len)
+{
+ struct pkcs7_signed_info *sinfo = pkcs7->signed_infos;
+ struct pkcs7_aa_context ctx;
+
+ ctx.data = NULL;
+ ctx.oid_to_find = oid;
+
+ for (; sinfo; sinfo = sinfo->next) {
+ int ret;
+
+ /* only extract OIDs from validated signers */
+ if (!sinfo->verified)
+ continue;
+
+ ret = asn1_ber_decoder(&pkcs7_aa_decoder, &ctx,
+ sinfo->authattrs, sinfo->authattrs_len);
+ if (ret < 0 || ctx.data != NULL)
+ break;
+ }
+
+ if (!ctx.data)
+ return -ENODATA;
+
+ *_data = ctx.data;
+ *_len = ctx.len;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(pkcs7_get_authattr);
+
/*
* Note an OID when we find one for later processing when we know how
* to interpret it.
diff --git a/include/crypto/pkcs7.h b/include/crypto/pkcs7.h
index 38ec7f5f90411..bd83202cd805c 100644
--- a/include/crypto/pkcs7.h
+++ b/include/crypto/pkcs7.h
@@ -25,6 +25,10 @@ extern void pkcs7_free_message(struct pkcs7_message *pkcs7);
extern int pkcs7_get_content_data(const struct pkcs7_message *pkcs7,
const void **_data, size_t *_datalen,
size_t *_headerlen);
+extern int pkcs7_get_authattr(const struct pkcs7_message *pkcs7,
+ enum OID oid,
+ const void **_data, size_t *_len);
+
/*
* pkcs7_trust.c
--
2.53.0
^ permalink raw reply related
* [PATCH v7 04/10] lsm: framework for BPF integrity verification
From: Blaise Boscaccy @ 2026-05-07 19:13 UTC (permalink / raw)
To: Blaise Boscaccy, Jonathan Corbet, Paul Moore, James Morris,
Serge E. Hallyn, Mickaël Salaün, Günther Noack,
Dr. David Alan Gilbert, Andrew Morton, James.Bottomley, dhowells,
Fan Wu, Ryan Foster, Randy Dunlap, linux-security-module,
linux-doc, linux-kernel, bpf, Song Liu
In-Reply-To: <20260507191416.2984054-1-bboscaccy@linux.microsoft.com>
From: Paul Moore <paul@paul-moore.com>
Add a new LSM hook and two new LSM hook callbacks to support LSMs that
perform integrity verification, e.g. digital signature verification,
of BPF programs.
While the BPF subsystem does implement a signature verification scheme,
it does not satisfy a number of existing requirements, adding support
for BPF program integrity verification to the LSM framework allows
administrators to select additional integrity verification mechanisms
to meet these needs while also providing a mechanism for future
expansion. Additional on why this is necessary can be found at the
lore archive link below:
https://lore.kernel.org/linux-security-module/CAHC9VhTQ_DR=ANzoDBjcCtrimV7XcCZVUsANPt=TjcvM4d-vjg@mail.gmail.com/
The LSM-based BPF integrity verification mechanism works within the
existing security_bpf_prog_load() hook called by the BPF subsystem.
It adds an additional dedicated integrity callback and a new LSM
hook/callback to be called from within LSMs implementing integrity
verification.
The first new callback, bpf_prog_load_integrity(), located within the
security_bpf_prog_load() hook, is necessary to ensure that the integrity
verification callbacks are executed before any of the existing LSMs
are executed via the bpf_prog_load() callback. Reusing the existing
bpf_prog_load() callback for integrity verification could result in LSMs
not having access to the integrity verification results when asked to
authorize the BPF program load in the bpf_prog_load() callback.
The new LSM hook, security_bpf_prog_load_post_integrity(), is intended
to be called from within LSMs performing BPF program integrity
verification. It is used to report the verdict of the integrity
verification to other LSMs enforcing access control policy on BPF
program loads. LSMs enforcing such access controls should register a
bpf_prog_load_post_integrity() callback to receive integrity verdicts.
More information on these new callbacks and hook can be found in the
code comments in this patch.
Signed-off-by: Paul Moore <paul@paul-moore.com>
Signed-off-by: Blaise Boscaccy <bboscaccy@linux.microsoft.com>
---
include/linux/lsm_hook_defs.h | 5 +++
include/linux/security.h | 25 ++++++++++++
security/security.c | 75 +++++++++++++++++++++++++++++++++--
3 files changed, 102 insertions(+), 3 deletions(-)
diff --git a/include/linux/lsm_hook_defs.h b/include/linux/lsm_hook_defs.h
index 8c42b4bde09c0..4971d3c36d5b4 100644
--- a/include/linux/lsm_hook_defs.h
+++ b/include/linux/lsm_hook_defs.h
@@ -434,6 +434,11 @@ LSM_HOOK(int, 0, bpf_prog, struct bpf_prog *prog)
LSM_HOOK(int, 0, bpf_map_create, struct bpf_map *map, union bpf_attr *attr,
struct bpf_token *token, bool kernel)
LSM_HOOK(void, LSM_RET_VOID, bpf_map_free, struct bpf_map *map)
+LSM_HOOK(int, 0, bpf_prog_load_post_integrity, struct bpf_prog *prog,
+ union bpf_attr *attr, struct bpf_token *token, bool kernel,
+ const struct lsm_id *lsmid, enum lsm_integrity_verdict verdict)
+LSM_HOOK(int, 0, bpf_prog_load_integrity, struct bpf_prog *prog,
+ union bpf_attr *attr, struct bpf_token *token, bool kernel)
LSM_HOOK(int, 0, bpf_prog_load, struct bpf_prog *prog, union bpf_attr *attr,
struct bpf_token *token, bool kernel)
LSM_HOOK(void, LSM_RET_VOID, bpf_prog_free, struct bpf_prog *prog)
diff --git a/include/linux/security.h b/include/linux/security.h
index ee88dd2d2d1f7..b3fd04baa78d0 100644
--- a/include/linux/security.h
+++ b/include/linux/security.h
@@ -67,6 +67,7 @@ enum fs_value_type;
struct watch;
struct watch_notification;
struct lsm_ctx;
+struct lsm_id;
/* Default (no) options for the capable function */
#define CAP_OPT_NONE 0x0
@@ -100,6 +101,14 @@ enum lsm_integrity_type {
LSM_INT_FSVERITY_BUILTINSIG_VALID,
};
+enum lsm_integrity_verdict {
+ LSM_INT_VERDICT_NONE = 0,
+ LSM_INT_VERDICT_OK,
+ LSM_INT_VERDICT_UNSIGNED,
+ LSM_INT_VERDICT_PARTIALSIG,
+ LSM_INT_VERDICT_BADSIG,
+};
+
/*
* These are reasons that can be passed to the security_locked_down()
* LSM hook. Lockdown reasons that protect kernel integrity (ie, the
@@ -2270,6 +2279,12 @@ extern int security_bpf_prog(struct bpf_prog *prog);
extern int security_bpf_map_create(struct bpf_map *map, union bpf_attr *attr,
struct bpf_token *token, bool kernel);
extern void security_bpf_map_free(struct bpf_map *map);
+extern int security_bpf_prog_load_post_integrity(struct bpf_prog *prog,
+ union bpf_attr *attr,
+ struct bpf_token *token,
+ bool kernel,
+ const struct lsm_id *lsmid,
+ enum lsm_integrity_verdict verdict);
extern int security_bpf_prog_load(struct bpf_prog *prog, union bpf_attr *attr,
struct bpf_token *token, bool kernel);
extern void security_bpf_prog_free(struct bpf_prog *prog);
@@ -2304,6 +2319,16 @@ static inline int security_bpf_map_create(struct bpf_map *map, union bpf_attr *a
static inline void security_bpf_map_free(struct bpf_map *map)
{ }
+static inline int security_bpf_prog_load_post_integrity(struct bpf_prog *prog,
+ union bpf_attr *attr,
+ struct bpf_token *token,
+ bool kernel,
+ const struct lsm_id *lsmid,
+ enum lsm_integrity_verdict verdict)
+{
+ return 0;
+}
+
static inline int security_bpf_prog_load(struct bpf_prog *prog, union bpf_attr *attr,
struct bpf_token *token, bool kernel)
{
diff --git a/security/security.c b/security/security.c
index a26c1474e2e49..bb78f7e45a98f 100644
--- a/security/security.c
+++ b/security/security.c
@@ -5233,6 +5233,50 @@ int security_bpf_map_create(struct bpf_map *map, union bpf_attr *attr,
return rc;
}
+/**
+ * security_bpf_prog_load_post_integrity() - Check if the BPF prog is allowed
+ * @prog: BPF program object
+ * @attr: BPF syscall attributes used to create BPF program
+ * @token: BPF token used to grant user access to BPF subsystem
+ * @kernel: whether or not call originated from kernel
+ * @lsmid: LSM ID of the LSM providing @verdict
+ * @verdict: result of the integrity verification
+ *
+ * See the comment block for the security_bpf_prog_load() LSM hook.
+ *
+ * This LSM hook is intended to be called from within the
+ * bpf_prog_load_integrity() callback that is part of the
+ * security_bpf_prog_load() hook; kernel subsystems outside the scope of the
+ * LSM framework should not call this hook directly.
+ *
+ * If the LSM calling into this hook receives a non-zero error code, it should
+ * return the same error code back to its caller. If this hook returns a zero,
+ * it does not necessarily mean that all of the enabled LSMs have authorized
+ * the BPF program load, as there may be other LSMs implementing BPF integrity
+ * checks which have yet to execute. However, if a zero is returned, the LSM
+ * calling into this hook should continue and return zero back to its caller.
+ *
+ * LSMs which implement the bpf_prog_load_post_integrity() callback and
+ * determine that a particular BPF program load is not authorized may choose to
+ * either return an error code for immediate rejection, or store their decision
+ * in their own LSM state attached to @prog, later returning an error code in
+ * the bpf_prog_load() callback. An immediate error code return is in keeping
+ * with the "fail fast" practice, but waiting until the bpf_prog_load()
+ * callback allows the LSM to consider multiple different integrity verdicts.
+ *
+ * Return: Returns 0 on success, error on failure.
+ */
+int security_bpf_prog_load_post_integrity(struct bpf_prog *prog,
+ union bpf_attr *attr,
+ struct bpf_token *token,
+ bool kernel,
+ const struct lsm_id *lsmid,
+ enum lsm_integrity_verdict verdict)
+{
+ return call_int_hook(bpf_prog_load_post_integrity, prog, attr, token,
+ kernel, lsmid, verdict);
+}
+
/**
* security_bpf_prog_load() - Check if loading of BPF program is allowed
* @prog: BPF program object
@@ -5241,8 +5285,24 @@ int security_bpf_map_create(struct bpf_map *map, union bpf_attr *attr,
* @kernel: whether or not call originated from kernel
*
* Perform an access control check when the kernel loads a BPF program and
- * allocates associated BPF program object. This hook is also responsible for
- * allocating any required LSM state for the BPF program.
+ * allocates the associated BPF program object. This hook is also responsible
+ * for allocating any required LSM state for the BPF program.
+ *
+ * This hook calls two LSM callbacks: bpf_prog_load_integrity() and
+ * bpf_prog_load(). The bpf_prog_load_integrity() callback is for those LSMs
+ * that wish to implement integrity verifications of BPF programs, e.g.
+ * signature verification, while the bpf_prog_load() callback is for general
+ * authorization of the BPF program load. Performing both verification and
+ * authorization in a single callback, with arbitrary LSM ordering, would be
+ * a challenge.
+ *
+ * LSMs which implement the bpf_prog_load_integrity() callback should call into
+ * the security_bpf_prog_load_post_integrity() hook with their integrity
+ * verdict. LSMs which implement BPF program integrity policy can register a
+ * callback for the security_bpf_prog_load_post_integrity() hook and
+ * either update their own internal state based on the verdict, or immediately
+ * reject the BPF program load with an error code. See the comment block for
+ * security_bpf_prog_load_post_integrity() for more information.
*
* Return: Returns 0 on success, error on failure.
*/
@@ -5255,9 +5315,18 @@ int security_bpf_prog_load(struct bpf_prog *prog, union bpf_attr *attr,
if (unlikely(rc))
return rc;
+ rc = call_int_hook(bpf_prog_load_integrity, prog, attr, token, kernel);
+ if (unlikely(rc))
+ goto err;
+
rc = call_int_hook(bpf_prog_load, prog, attr, token, kernel);
if (unlikely(rc))
- security_bpf_prog_free(prog);
+ goto err;
+
+ return rc;
+
+err:
+ security_bpf_prog_free(prog);
return rc;
}
--
2.53.0
^ permalink raw reply related
* [PATCH v7 05/10] lsm: security: Add additional enum values for bpf integrity checks
From: Blaise Boscaccy @ 2026-05-07 19:13 UTC (permalink / raw)
To: Blaise Boscaccy, Jonathan Corbet, Paul Moore, James Morris,
Serge E. Hallyn, Mickaël Salaün, Günther Noack,
Dr. David Alan Gilbert, Andrew Morton, James.Bottomley, dhowells,
Fan Wu, Ryan Foster, Randy Dunlap, linux-security-module,
linux-doc, linux-kernel, bpf, Song Liu
In-Reply-To: <20260507191416.2984054-1-bboscaccy@linux.microsoft.com>
First add a generic LSM_INT_VERDICT_FAULT value to indicate a system
failure during checking. Second, add a LSM_INT_VERDICT_UNKNOWNKEY to
signal that the payload was signed with a key other than one that
exists in the secondary keyring. And finally add an
LSM_INT_VERDICT_UNEXPECTED enum value to indicate that a unexpected
hash value was encountered at some stage of verification.
Signed-off-by: Blaise Boscaccy <bboscaccy@linux.microsoft.com>
---
include/linux/security.h | 3 +++
1 file changed, 3 insertions(+)
diff --git a/include/linux/security.h b/include/linux/security.h
index b3fd04baa78d0..4b4b8808f67de 100644
--- a/include/linux/security.h
+++ b/include/linux/security.h
@@ -106,6 +106,9 @@ enum lsm_integrity_verdict {
LSM_INT_VERDICT_OK,
LSM_INT_VERDICT_UNSIGNED,
LSM_INT_VERDICT_PARTIALSIG,
+ LSM_INT_VERDICT_UNKNOWNKEY,
+ LSM_INT_VERDICT_UNEXPECTED,
+ LSM_INT_VERDICT_FAULT,
LSM_INT_VERDICT_BADSIG,
};
--
2.53.0
^ permalink raw reply related
* [PATCH v7 06/10] security: Hornet LSM
From: Blaise Boscaccy @ 2026-05-07 19:14 UTC (permalink / raw)
To: Blaise Boscaccy, Jonathan Corbet, Paul Moore, James Morris,
Serge E. Hallyn, Mickaël Salaün, Günther Noack,
Dr. David Alan Gilbert, Andrew Morton, James.Bottomley, dhowells,
Fan Wu, Ryan Foster, Randy Dunlap, linux-security-module,
linux-doc, linux-kernel, bpf, Song Liu
In-Reply-To: <20260507191416.2984054-1-bboscaccy@linux.microsoft.com>
This adds the Hornet Linux Security Module which provides enhanced
signature verification and data validation for eBPF programs. This
allows users to continue to maintain an invariant that all code
running inside of the kernel has actually been signed and verified, by
the kernel.
This effort builds upon the currently excepted upstream solution. It
further hardens it by providing deterministic, in-kernel checking of
map hashes to solidify auditing along with preventing TOCTOU attacks
against lskel map hashes.
Target map hashes are passed in via PKCS#7 signed attributes. Hornet
determines the extent which the eBFP program is signed and defers to
other LSMs for policy decisions.
Signed-off-by: Blaise Boscaccy <bboscaccy@linux.microsoft.com>
Nacked-by: Alexei Starovoitov <alexei.starovoitov@gmail.com>
---
Documentation/admin-guide/LSM/Hornet.rst | 323 +++++++++++++++++++++
Documentation/admin-guide/LSM/index.rst | 1 +
MAINTAINERS | 9 +
include/linux/oid_registry.h | 3 +
include/uapi/linux/lsm.h | 1 +
security/Kconfig | 3 +-
security/Makefile | 1 +
security/hornet/Kconfig | 13 +
security/hornet/Makefile | 7 +
security/hornet/hornet.asn1 | 12 +
security/hornet/hornet_lsm.c | 352 +++++++++++++++++++++++
11 files changed, 724 insertions(+), 1 deletion(-)
create mode 100644 Documentation/admin-guide/LSM/Hornet.rst
create mode 100644 security/hornet/Kconfig
create mode 100644 security/hornet/Makefile
create mode 100644 security/hornet/hornet.asn1
create mode 100644 security/hornet/hornet_lsm.c
diff --git a/Documentation/admin-guide/LSM/Hornet.rst b/Documentation/admin-guide/LSM/Hornet.rst
new file mode 100644
index 0000000000000..0ade4c17374c6
--- /dev/null
+++ b/Documentation/admin-guide/LSM/Hornet.rst
@@ -0,0 +1,323 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+======
+Hornet
+======
+
+Hornet is a Linux Security Module that provides extensible signature
+verification for eBPF programs. This is selectable at build-time with
+``CONFIG_SECURITY_HORNET``.
+
+Overview
+========
+
+Hornet addresses concerns from users who require strict audit trails and
+verification guarantees for eBPF programs, especially in
+security-sensitive environments. Many production systems need assurance
+that only authorized, unmodified eBPF programs are loaded into the
+kernel. Hornet provides this assurance through cryptographic signature
+verification.
+
+When an eBPF program is loaded via the ``bpf()`` syscall, Hornet
+verifies a PKCS#7 signature attached to the program instructions. The
+signature is checked against whichever keyring was specified by the user
+existing kernel cryptographic infrastructure. In addition to signing the
+program bytecode, Hornet supports signing SHA-256 hashes of associated
+BPF maps, enabling integrity verification of map contents at load time
+and at runtime.
+
+After verification, Hornet classifies the program into one of the
+following integrity states and passes the result to a downstream LSM hook
+(``bpf_prog_load_post_integrity``), allowing other security modules to
+make policy decisions based on the verification outcome:
+
+``LSM_INT_VERDICT_OK``
+ The program signature and all map hashes verified successfully.
+
+``LSM_INT_VERDICT_UNSIGNED``
+ No signature was provided with the program.
+
+``LSM_INT_VERDICT_PARTIALSIG``
+ The program signature verified, but the signature did not contain
+ hornet map hash data.
+
+``LSM_INT_VERDICT_UNKNOWNKEY``
+ The keyring requested by the user is invalid.
+
+``LSM_INT_VERDICT_FAULT``
+ A system error occurred during verification.
+
+``LSM_INT_VERDICT_UNEXPECTED``
+ An unexpected map hash value was encountered.
+
+``LSM_INT_VERDICT_BADSIG``
+ The signature or a map hash failed verification.
+
+Hornet itself does not enforce a policy on whether unsigned or partially
+signed programs should be rejected. It delegates that decision to
+downstream LSMs via the ``bpf_prog_load_post_integrity`` hook, making it
+a composable building block in a larger security architecture.
+
+Use Cases
+=========
+
+- **Locked-down production environments**: Ensure only eBPF programs
+ signed by a trusted authority can be loaded, preventing unauthorized
+ or tampered programs from running in the kernel.
+
+- **Audit and compliance**: Provide cryptographic evidence that loaded
+ eBPF programs match their expected build artifacts, supporting
+ compliance requirements in regulated industries.
+
+- **Supply chain integrity**: Verify that eBPF programs and their
+ associated map data have not been modified since they were built and
+ signed, protecting against supply chain attacks.
+
+Threat Model
+============
+
+Hornet protects against the following threats:
+
+- **Unauthorized eBPF program loading**: Programs that have not been
+ signed by a trusted key will be reported as unsigned or badly signed.
+
+- **Tampering with program instructions**: Any modification to the eBPF
+ bytecode after signing will cause signature verification to fail.
+
+- **Tampering with map data**: When map hashes are included in the
+ signature, Hornet verifies that frozen BPF maps match their expected
+ SHA-256 hashes at load time. Maps are also re-verified before program
+ execution via ``BPF_PROG_RUN``.
+
+Hornet does **not** protect against:
+
+- Compromise of the signing key itself.
+- Attacks that occur after a program has been loaded and verified.
+- Programs loaded by the kernel itself (kernel-internal loads bypass
+ the ``BPF_PROG_RUN`` map check).
+
+Known Limitations
+=================
+
+- Hornet requires programs to use :doc:`light skeletons
+ </bpf/libbpf/libbpf_naming_convention>` (lskels) for the signing
+ workflow, as the tooling operates on lskel-generated headers.
+
+- A maximum of 64 maps per program can be tracked for hash
+ verification.
+
+- Map hash verification requires the maps to be frozen before loading.
+ Maps that are not frozen at load time will cause verification to fail
+ when their hashes are included in the signature.
+
+- The only hashing algorithm available is SHA256 due to it be hardcoded
+ in the bpf subsystem.
+
+- Hornet guarantees that the signed program runs only with signed map
+ data. It does not guarantee positional binding of maps to specific
+ fd_array slots.
+
+- BPF_MAP_TYPE_PROG_ARRAY maps must be frozen for Hornet to verify
+ them. Unfrozen prog array maps are not covered by verification.
+
+Configuration
+=============
+
+Build Configuration
+-------------------
+
+Enable Hornet by setting the following kernel configuration option::
+
+ CONFIG_SECURITY_HORNET=y
+
+This option is found under :menuselection:`Security options --> Hornet
+support` and depends on ``CONFIG_SECURITY``.
+
+When enabled, Hornet is included in the default LSM initialization order
+and will appear in ``/sys/kernel/security/lsm``.
+
+Architecture
+============
+
+Signature Verification Flow
+---------------------------
+
+The following describes what happens when a userspace program calls
+``bpf(BPF_PROG_LOAD, ...)`` with a signature attached:
+
+1. The ``bpf_prog_load_integrity`` LSM hook is invoked.
+
+2. Hornet reads the signature from the userspace buffer specified by
+ ``attr->signature`` (with length ``attr->signature_size``).
+
+3. The PKCS#7 signature is verified against the program instructions
+ using ``verify_pkcs7_message_sig()`` with the user specified keyring.
+
+4. The PKCS#7 message is parsed and its trust chain is validated via
+ ``validate_pkcs7_trust()``.
+
+5. Hornet extracts the authenticated attribute identified by
+ ``OID_hornet_data`` (OID ``2.25.316487325684022475439036912669789383960``)
+ from the PKCS#7 message. This attribute contains an ASN.1-encoded set
+ of map index/hash pairs.
+
+6. For each map hash entry, Hornet retrieves the corresponding BPF map
+ via its file descriptor, confirms it is frozen, computes its SHA-256
+ hash, and compares it against the signed hash.
+
+7. The resulting integrity verdict is passed to the
+ ``bpf_prog_load_post_integrity`` hook so that downstream LSMs can
+ enforce policy.
+
+Runtime Map Verification
+------------------------
+
+When ``bpf(BPF_PROG_RUN, ...)`` is called from userspace, Hornet
+re-verifies the hashes of all maps associated with the program. This
+ensures that map contents have not been modified between program load
+and execution. If any map hash no longer matches, the ``BPF_PROG_RUN``
+command is denied.
+
+Userspace Interface
+-------------------
+
+Signatures are passed to the kernel through fields in ``union bpf_attr``
+when using the ``BPF_PROG_LOAD`` command:
+
+``signature``
+ A pointer to a userspace buffer containing the PKCS#7 signature.
+
+``signature_size``
+ The size of the signature buffer in bytes.
+
+ASN.1 Schema
+------------
+
+Map hashes are encoded as a signed attribute in the PKCS#7 message using
+the following ASN.1 schema::
+
+ HornetData ::= SET OF Map
+
+ Map ::= SEQUENCE {
+ index INTEGER,
+ sha OCTET STRING
+ }
+
+Each ``Map`` entry contains the index of the map in the program's
+``fd_array`` and its expected SHA-256 hash. A zero-length ``sha`` field
+indicates that the map at that index should be skipped during
+verification.
+
+Tooling
+=======
+
+Helper scripts and a signature generation tool are provided in
+``scripts/hornet/`` to support the development of signed eBPF light
+skeletons.
+
+gen_sig
+-------
+
+``gen_sig`` is a C program (using OpenSSL) that creates a PKCS#7
+signature over eBPF program instructions and optionally includes
+SHA-256 hashes of BPF maps as signed attributes.
+
+Usage::
+
+ gen_sig --data <instructions.bin> \
+ --cert <signer.crt> \
+ --key <signer.key> \
+ [--pass <passphrase>] \
+ --out <signature.p7b> \
+ [--add <mapfile.bin>:<index> ...]
+
+``--data``
+ Path to the binary file containing eBPF program instructions to sign.
+
+``--cert``
+ Path to the signing certificate (PEM or DER format).
+
+``--key``
+ Path to the private key (PEM or DER format).
+
+``--pass``
+ Optional passphrase for the private key.
+
+``--out``
+ Path to write the output PKCS#7 signature.
+
+``--add``
+ Attach a map hash as a signed attribute. The argument is a path to a
+ binary map file followed by a colon and the map's index in the
+ ``fd_array``. This option may be specified multiple times.
+
+extract-skel.sh
+---------------
+
+Extracts a named field from an autogenerated eBPF lskel header file.
+Used internally by other helper scripts.
+
+extract-insn.sh
+---------------
+
+Extracts the eBPF program instructions (``opts_insn``) from an lskel
+header into a binary file suitable for signing with ``gen_sig``.
+
+extract-map.sh
+--------------
+
+Extracts the map data (``opts_data``) from an lskel header into a
+binary file suitable for hashing with ``gen_sig``.
+
+write-sig.sh
+------------
+
+Replaces the signature data in an lskel header with a new signature
+from a binary file. This is used to embed a freshly generated signature
+back into the header after signing.
+
+Signing Workflow
+================
+
+A typical workflow for building and signing an eBPF light skeleton is:
+
+1. **Compile the eBPF program**::
+
+ clang -O2 -target bpf -c program.bpf.c -o program.bpf.o
+
+2. **Generate the light skeleton header** using ``bpftool``::
+
+ bpftool gen skeleton -S program.bpf.o > loader.h
+
+3. **Extract instructions and map data** from the generated header::
+
+ scripts/hornet/extract-insn.sh loader.h > insn.bin
+ scripts/hornet/extract-map.sh loader.h > map.bin
+
+4. **Generate the signature** with ``gen_sig``::
+
+ scripts/hornet/gen_sig \
+ --key signing_key.pem \
+ --cert signing_key.x509 \
+ --data insn.bin \
+ --add map.bin:0 \
+ --out sig.bin
+
+5. **Embed the signature** back into the header::
+
+ scripts/hornet/write-sig.sh loader.h sig.bin > signed_loader.h
+
+6. **Build the loader program** using the signed header::
+
+ cc -o loader loader.c -lbpf
+
+The resulting loader program will pass the embedded signature to the
+kernel when loading the eBPF program, enabling Hornet to verify it.
+
+Testing
+=======
+
+Self-tests are provided in ``tools/testing/selftests/hornet/``. The test
+suite builds a minimal eBPF program (``trivial.bpf.c``), signs it using
+the workflow described above, and verifies that the signed program loads
+successfully.
diff --git a/Documentation/admin-guide/LSM/index.rst b/Documentation/admin-guide/LSM/index.rst
index b44ef68f6e4da..57f6e9fbe5fd1 100644
--- a/Documentation/admin-guide/LSM/index.rst
+++ b/Documentation/admin-guide/LSM/index.rst
@@ -49,3 +49,4 @@ subdirectories.
SafeSetID
ipe
landlock
+ Hornet
diff --git a/MAINTAINERS b/MAINTAINERS
index d1cc0e12fe1f0..0942f5453c04d 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -11692,6 +11692,15 @@ S: Maintained
F: Documentation/devicetree/bindings/iio/pressure/honeywell,mprls0025pa.yaml
F: drivers/iio/pressure/mprls0025pa*
+HORNET SECURITY MODULE
+M: Blaise Boscaccy <bboscaccy@linux.microsoft.com>
+L: linux-security-module@vger.kernel.org
+S: Supported
+T: git https://github.com/blaiseboscaccy/hornet.git
+F: Documentation/admin-guide/LSM/Hornet.rst
+F: scripts/hornet/
+F: security/hornet/
+
HP BIOSCFG DRIVER
M: Jorge Lopez <jorge.lopez2@hp.com>
L: platform-driver-x86@vger.kernel.org
diff --git a/include/linux/oid_registry.h b/include/linux/oid_registry.h
index ebce402854de4..bf852715aaea4 100644
--- a/include/linux/oid_registry.h
+++ b/include/linux/oid_registry.h
@@ -150,6 +150,9 @@ enum OID {
OID_id_ml_dsa_65, /* 2.16.840.1.101.3.4.3.18 */
OID_id_ml_dsa_87, /* 2.16.840.1.101.3.4.3.19 */
+ /* Hornet LSM */
+ OID_hornet_data, /* 2.25.316487325684022475439036912669789383960 */
+
OID__NR
};
diff --git a/include/uapi/linux/lsm.h b/include/uapi/linux/lsm.h
index 938593dfd5daf..2ff9bcdd551e2 100644
--- a/include/uapi/linux/lsm.h
+++ b/include/uapi/linux/lsm.h
@@ -65,6 +65,7 @@ struct lsm_ctx {
#define LSM_ID_IMA 111
#define LSM_ID_EVM 112
#define LSM_ID_IPE 113
+#define LSM_ID_HORNET 114
/*
* LSM_ATTR_XXX definitions identify different LSM attributes
diff --git a/security/Kconfig b/security/Kconfig
index 6a4393fce9a17..283c4a1032094 100644
--- a/security/Kconfig
+++ b/security/Kconfig
@@ -230,6 +230,7 @@ source "security/safesetid/Kconfig"
source "security/lockdown/Kconfig"
source "security/landlock/Kconfig"
source "security/ipe/Kconfig"
+source "security/hornet/Kconfig"
source "security/integrity/Kconfig"
@@ -274,7 +275,7 @@ config LSM
default "landlock,lockdown,yama,loadpin,safesetid,apparmor,selinux,smack,tomoyo,ipe,bpf" if DEFAULT_SECURITY_APPARMOR
default "landlock,lockdown,yama,loadpin,safesetid,tomoyo,ipe,bpf" if DEFAULT_SECURITY_TOMOYO
default "landlock,lockdown,yama,loadpin,safesetid,ipe,bpf" if DEFAULT_SECURITY_DAC
- default "landlock,lockdown,yama,loadpin,safesetid,selinux,smack,tomoyo,apparmor,ipe,bpf"
+ default "landlock,lockdown,yama,loadpin,safesetid,selinux,smack,tomoyo,apparmor,ipe,hornet,bpf"
help
A comma-separated list of LSMs, in initialization order.
Any LSMs left off this list, except for those with order
diff --git a/security/Makefile b/security/Makefile
index 4601230ba442a..b68cb56e419bc 100644
--- a/security/Makefile
+++ b/security/Makefile
@@ -26,6 +26,7 @@ obj-$(CONFIG_CGROUPS) += device_cgroup.o
obj-$(CONFIG_BPF_LSM) += bpf/
obj-$(CONFIG_SECURITY_LANDLOCK) += landlock/
obj-$(CONFIG_SECURITY_IPE) += ipe/
+obj-$(CONFIG_SECURITY_HORNET) += hornet/
# Object integrity file lists
obj-$(CONFIG_INTEGRITY) += integrity/
diff --git a/security/hornet/Kconfig b/security/hornet/Kconfig
new file mode 100644
index 0000000000000..5be71d97daee2
--- /dev/null
+++ b/security/hornet/Kconfig
@@ -0,0 +1,13 @@
+# SPDX-License-Identifier: GPL-2.0-only
+config SECURITY_HORNET
+ bool "Hornet support"
+ select CRYPTO_LIB_SHA256
+ select PKCS7_MESSAGE_PARSER
+ select SYSTEM_DATA_VERIFICATION
+ default n
+ help
+ This selects Hornet.
+ Further information can be found in
+ Documentation/admin-guide/LSM/Hornet.rst.
+
+ If you are unsure how to answer this question, answer N.
diff --git a/security/hornet/Makefile b/security/hornet/Makefile
new file mode 100644
index 0000000000000..26b6f954f762e
--- /dev/null
+++ b/security/hornet/Makefile
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: GPL-2.0-only
+obj-$(CONFIG_SECURITY_HORNET) := hornet.o
+
+hornet-y := hornet.asn1.o \
+ hornet_lsm.o \
+
+$(obj)/hornet.asn1.o: $(obj)/hornet.asn1.c $(obj)/hornet.asn1.h
diff --git a/security/hornet/hornet.asn1 b/security/hornet/hornet.asn1
new file mode 100644
index 0000000000000..e60abf451ae23
--- /dev/null
+++ b/security/hornet/hornet.asn1
@@ -0,0 +1,12 @@
+-- SPDX-License-Identifier: BSD-3-Clause
+--
+-- Copyright (C) 2026 Microsoft
+--
+-- https://www.rfc-editor.org/rfc/rfc5652#section-3
+
+HornetData ::= SET OF Map
+
+Map ::= SEQUENCE {
+ index INTEGER ({ hornet_map_index }),
+ sha OCTET STRING ({ hornet_map_hash })
+} ({ hornet_next_map })
diff --git a/security/hornet/hornet_lsm.c b/security/hornet/hornet_lsm.c
new file mode 100644
index 0000000000000..a4d11fa5b0889
--- /dev/null
+++ b/security/hornet/hornet_lsm.c
@@ -0,0 +1,352 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Hornet Linux Security Module
+ *
+ * Author: Blaise Boscaccy <bboscaccy@linux.microsoft.com>
+ *
+ * Copyright (C) 2026 Microsoft Corporation
+ */
+
+#include <linux/lsm_hooks.h>
+#include <uapi/linux/lsm.h>
+#include <linux/bpf.h>
+#include <linux/verification.h>
+#include <crypto/public_key.h>
+#include <linux/module_signature.h>
+#include <crypto/pkcs7.h>
+#include <linux/sort.h>
+#include <linux/asn1_decoder.h>
+#include <linux/oid_registry.h>
+#include "hornet.asn1.h"
+
+#define MAX_USED_MAPS 64
+
+struct hornet_maps {
+ bpfptr_t fd_array;
+};
+
+/* The only hashing algorithm available is SHA256 due to it be hardcoded
+ * in the bpf subsystem.
+ */
+
+struct hornet_parse_context {
+ int indexes[MAX_USED_MAPS];
+ bool skips[MAX_USED_MAPS];
+ unsigned char hashes[SHA256_DIGEST_SIZE * MAX_USED_MAPS];
+ int hash_count;
+};
+
+struct hornet_prog_security_struct {
+ int signed_hash_count;
+ unsigned char signed_hashes[SHA256_DIGEST_SIZE * MAX_USED_MAPS];
+};
+
+struct lsm_blob_sizes hornet_blob_sizes __ro_after_init = {
+ .lbs_bpf_prog = sizeof(struct hornet_prog_security_struct),
+};
+
+static inline struct hornet_prog_security_struct *
+hornet_bpf_prog_security(struct bpf_prog *prog)
+{
+ return prog->aux->security + hornet_blob_sizes.lbs_bpf_prog;
+}
+
+static int hornet_verify_hashes(struct hornet_maps *maps,
+ struct hornet_parse_context *ctx,
+ struct bpf_prog *prog)
+{
+ int map_fd;
+ u32 i;
+ struct bpf_map *map;
+ int err = 0;
+ unsigned char hash[SHA256_DIGEST_SIZE];
+ struct hornet_prog_security_struct *security = hornet_bpf_prog_security(prog);
+
+ for (i = 0; i < ctx->hash_count; i++) {
+ if (ctx->skips[i])
+ continue;
+
+ err = copy_from_bpfptr_offset(&map_fd, maps->fd_array,
+ ctx->indexes[i] * sizeof(map_fd),
+ sizeof(map_fd));
+ if (err != 0)
+ return LSM_INT_VERDICT_FAULT;
+
+ CLASS(fd, f)(map_fd);
+ if (fd_empty(f))
+ return LSM_INT_VERDICT_FAULT;
+ if (unlikely(fd_file(f)->f_op != &bpf_map_fops))
+ return LSM_INT_VERDICT_FAULT;
+
+ map = fd_file(f)->private_data;
+ if (!READ_ONCE(map->frozen))
+ return LSM_INT_VERDICT_FAULT;
+
+ if (!map->ops->map_get_hash)
+ return LSM_INT_VERDICT_FAULT;
+
+ if (map->ops->map_get_hash(map, SHA256_DIGEST_SIZE, hash))
+ return LSM_INT_VERDICT_FAULT;
+
+ err = memcmp(hash, &ctx->hashes[i * SHA256_DIGEST_SIZE],
+ SHA256_DIGEST_SIZE);
+ if (err)
+ return LSM_INT_VERDICT_UNEXPECTED;
+
+ memcpy(&security->signed_hashes[security->signed_hash_count * SHA256_DIGEST_SIZE],
+ &ctx->hashes[i * SHA256_DIGEST_SIZE], SHA256_DIGEST_SIZE);
+ security->signed_hash_count++;
+ }
+ return LSM_INT_VERDICT_OK;
+}
+
+int hornet_next_map(void *context, size_t hdrlen,
+ unsigned char tag,
+ const void *value, size_t vlen)
+{
+ struct hornet_parse_context *ctx = (struct hornet_parse_context *)context;
+
+ if (++ctx->hash_count >= MAX_USED_MAPS)
+ return -EINVAL;
+ return 0;
+}
+
+int hornet_map_index(void *context, size_t hdrlen,
+ unsigned char tag,
+ const void *value, size_t vlen)
+{
+ struct hornet_parse_context *ctx = (struct hornet_parse_context *)context;
+
+ if (vlen != 1)
+ return -EINVAL;
+
+ ctx->indexes[ctx->hash_count] = *(u8 *)value;
+ return 0;
+}
+
+int hornet_map_hash(void *context, size_t hdrlen,
+ unsigned char tag,
+ const void *value, size_t vlen)
+
+{
+ struct hornet_parse_context *ctx = (struct hornet_parse_context *)context;
+
+ if (vlen != SHA256_DIGEST_SIZE && vlen != 0)
+ return -EINVAL;
+
+ if (vlen) {
+ ctx->skips[ctx->hash_count] = false;
+ memcpy(&ctx->hashes[ctx->hash_count * SHA256_DIGEST_SIZE], value, vlen);
+ } else
+ ctx->skips[ctx->hash_count] = true;
+
+ return 0;
+}
+
+static int hornet_check_program(struct bpf_prog *prog, union bpf_attr *attr,
+ struct bpf_token *token, bool is_kernel,
+ enum lsm_integrity_verdict *verdict)
+{
+ struct hornet_maps maps = {0};
+ bpfptr_t usig = make_bpfptr(attr->signature, is_kernel);
+ struct pkcs7_message *msg;
+ struct hornet_parse_context *ctx;
+ void *sig;
+ int err;
+ const void *authattrs;
+ size_t authattrs_len;
+ struct key *key;
+ key_ref_t user_key = ERR_PTR(-ENOKEY);
+
+ if (!attr->signature) {
+ *verdict = LSM_INT_VERDICT_UNSIGNED;
+ return 0;
+ }
+
+ if (!attr->signature_size) {
+ *verdict = LSM_INT_VERDICT_BADSIG;
+ return 0;
+ }
+
+ ctx = kzalloc(sizeof(struct hornet_parse_context), GFP_KERNEL);
+ if (!ctx)
+ return -ENOMEM;
+
+ maps.fd_array = make_bpfptr(attr->fd_array, is_kernel);
+ sig = kzalloc(attr->signature_size, GFP_KERNEL);
+ if (!sig) {
+ err = -ENOMEM;
+ goto out;
+ }
+ err = copy_from_bpfptr(sig, usig, attr->signature_size);
+ if (err != 0) {
+ err = -EFAULT;
+ goto cleanup_sig;
+ }
+
+ msg = pkcs7_parse_message(sig, attr->signature_size);
+ if (IS_ERR(msg)) {
+ *verdict = LSM_INT_VERDICT_BADSIG;
+ err = 0;
+ goto cleanup_sig;
+ }
+
+ if (system_keyring_id_check(attr->keyring_id) == 0)
+ key = (struct key *)(unsigned long)attr->keyring_id;
+ else {
+ user_key = lookup_user_key(attr->keyring_id, 0, KEY_DEFER_PERM_CHECK);
+ if (IS_ERR(user_key)) {
+ *verdict = LSM_INT_VERDICT_UNKNOWNKEY;
+ goto cleanup_msg;
+ }
+ key = key_ref_to_ptr(user_key);
+ }
+
+ if (verify_pkcs7_message_sig(prog->insnsi, prog->len * sizeof(struct bpf_insn), msg,
+ key,
+ VERIFYING_BPF_SIGNATURE,
+ NULL, NULL)) {
+ *verdict = LSM_INT_VERDICT_BADSIG;
+ err = 0;
+ goto cleanup_msg;
+ }
+
+ if (pkcs7_get_authattr(msg, OID_hornet_data,
+ &authattrs, &authattrs_len) == -ENODATA) {
+ *verdict = LSM_INT_VERDICT_PARTIALSIG;
+ err = 0;
+ goto cleanup_msg;
+ }
+
+ err = asn1_ber_decoder(&hornet_decoder, ctx, authattrs, authattrs_len);
+ if (err < 0 || authattrs == NULL) {
+ *verdict = LSM_INT_VERDICT_BADSIG;
+ err = 0;
+ goto cleanup_msg;
+ }
+
+ *verdict = hornet_verify_hashes(&maps, ctx, prog);
+ err = 0;
+
+cleanup_msg:
+ pkcs7_free_message(msg);
+ if (!IS_ERR(user_key))
+ key_put(key);
+cleanup_sig:
+ kfree(sig);
+out:
+ kfree(ctx);
+ return err;
+}
+
+static const struct lsm_id hornet_lsmid = {
+ .name = "hornet",
+ .id = LSM_ID_HORNET,
+};
+
+static int hornet_bpf_prog_load_integrity(struct bpf_prog *prog, union bpf_attr *attr,
+ struct bpf_token *token, bool is_kernel)
+{
+ enum lsm_integrity_verdict verdict;
+ int result = hornet_check_program(prog, attr, token, is_kernel, &verdict);
+
+ if (result < 0)
+ return result;
+
+ return security_bpf_prog_load_post_integrity(prog, attr, token, is_kernel,
+ &hornet_lsmid, verdict);
+}
+
+static int hornet_check_prog_maps(u32 ufd)
+{
+ CLASS(fd, f)(ufd);
+ struct bpf_prog *prog;
+ struct hornet_prog_security_struct *security;
+ unsigned char hash[SHA256_DIGEST_SIZE];
+ struct bpf_map *map;
+ int i, j;
+ bool found;
+ int covered_count = 0;
+
+ if (fd_empty(f))
+ return -EBADF;
+ if (fd_file(f)->f_op != &bpf_prog_fops)
+ return -EINVAL;
+
+ prog = fd_file(f)->private_data;
+ security = hornet_bpf_prog_security(prog);
+
+ if (!security->signed_hash_count)
+ return 0;
+
+ mutex_lock(&prog->aux->used_maps_mutex);
+
+ /* Verify every used_map has a matching signed hash */
+ for (j = 0; j < prog->aux->used_map_cnt; j++) {
+ map = prog->aux->used_maps[j];
+
+ if (!READ_ONCE(map->frozen) || !map->ops->map_get_hash)
+ continue;
+
+ if (map->ops->map_get_hash(map, SHA256_DIGEST_SIZE, hash))
+ continue;
+
+ found = false;
+ for (i = 0; i < security->signed_hash_count; i++) {
+ if (memcmp(hash,
+ &security->signed_hashes[i * SHA256_DIGEST_SIZE],
+ SHA256_DIGEST_SIZE) == 0) {
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ mutex_unlock(&prog->aux->used_maps_mutex);
+ return -EPERM;
+ }
+ covered_count++;
+ }
+
+ mutex_unlock(&prog->aux->used_maps_mutex);
+
+ /* Ensure all signed hashes were accounted for */
+ if (covered_count != security->signed_hash_count)
+ return -EPERM;
+
+ return 0;
+}
+
+static int hornet_bpf(int cmd, union bpf_attr *attr, unsigned int size, bool kernel)
+{
+ /* in horent_bpf(), anything that had originated from kernel space we assume
+ * has already been checked, in some form or another, so we don't bother
+ * checking the intergity of any maps. In hornet_bpf_prog_load_integrity(),
+ * hornet doesn't make any opinion on that and delegates that to the downstream
+ * policy enforcement.
+ */
+
+ if (cmd != BPF_PROG_RUN)
+ return 0;
+ if (kernel)
+ return 0;
+
+ return hornet_check_prog_maps(attr->test.prog_fd);
+}
+
+static struct security_hook_list hornet_hooks[] __ro_after_init = {
+ LSM_HOOK_INIT(bpf_prog_load_integrity, hornet_bpf_prog_load_integrity),
+ LSM_HOOK_INIT(bpf, hornet_bpf),
+};
+
+static int __init hornet_init(void)
+{
+ pr_info("Hornet: eBPF signature verification enabled\n");
+ security_add_hooks(hornet_hooks, ARRAY_SIZE(hornet_hooks), &hornet_lsmid);
+ return 0;
+}
+
+DEFINE_LSM(hornet) = {
+ .id = &hornet_lsmid,
+ .blobs = &hornet_blob_sizes,
+ .init = hornet_init,
+};
--
2.53.0
^ permalink raw reply related
* [PATCH v7 07/10] hornet: Introduce gen_sig
From: Blaise Boscaccy @ 2026-05-07 19:14 UTC (permalink / raw)
To: Blaise Boscaccy, Jonathan Corbet, Paul Moore, James Morris,
Serge E. Hallyn, Mickaël Salaün, Günther Noack,
Dr. David Alan Gilbert, Andrew Morton, James.Bottomley, dhowells,
Fan Wu, Ryan Foster, Randy Dunlap, linux-security-module,
linux-doc, linux-kernel, bpf, Song Liu
In-Reply-To: <20260507191416.2984054-1-bboscaccy@linux.microsoft.com>
This introduces the gen_sig tool. It creates a pkcs#7 signature of a
data payload. Additionally it appends a signed attribute containing a
set of hashes.
Typical usage is to provide a payload containing the light skeleton
ebpf syscall program binary and it's associated maps, which can be
extracted from the auto-generated skeleton header.
Signed-off-by: Blaise Boscaccy <bboscaccy@linux.microsoft.com>
---
scripts/Makefile | 1 +
scripts/hornet/Makefile | 5 +
scripts/hornet/gen_sig.c | 401 ++++++++++++++++++++++++++++++++++++
scripts/hornet/write-sig.sh | 27 +++
4 files changed, 434 insertions(+)
create mode 100644 scripts/hornet/Makefile
create mode 100644 scripts/hornet/gen_sig.c
create mode 100755 scripts/hornet/write-sig.sh
diff --git a/scripts/Makefile b/scripts/Makefile
index 0941e5ce7b575..dea8ab91bbe4e 100644
--- a/scripts/Makefile
+++ b/scripts/Makefile
@@ -63,6 +63,7 @@ subdir-$(CONFIG_GENKSYMS) += genksyms
subdir-$(CONFIG_GENDWARFKSYMS) += gendwarfksyms
subdir-$(CONFIG_SECURITY_SELINUX) += selinux
subdir-$(CONFIG_SECURITY_IPE) += ipe
+subdir-$(CONFIG_SECURITY_HORNET) += hornet
# Let clean descend into subdirs
subdir- += basic dtc gdb kconfig mod
diff --git a/scripts/hornet/Makefile b/scripts/hornet/Makefile
new file mode 100644
index 0000000000000..3ee41e5e9a9ff
--- /dev/null
+++ b/scripts/hornet/Makefile
@@ -0,0 +1,5 @@
+# SPDX-License-Identifier: GPL-2.0
+hostprogs-always-y := gen_sig
+
+HOSTCFLAGS_gen_sig.o = $(shell $(HOSTPKG_CONFIG) --cflags libcrypto 2> /dev/null)
+HOSTLDLIBS_gen_sig = $(shell $(HOSTPKG_CONFIG) --libs libcrypto 2> /dev/null || echo -lcrypto)
diff --git a/scripts/hornet/gen_sig.c b/scripts/hornet/gen_sig.c
new file mode 100644
index 0000000000000..4d59b70f5fff4
--- /dev/null
+++ b/scripts/hornet/gen_sig.c
@@ -0,0 +1,401 @@
+/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
+ *
+ * Generate a signature for an eBPF program along with appending
+ * map hashes as signed attributes
+ *
+ * Copyright © 2025 Microsoft Corporation.
+ *
+ * Authors: Blaise Boscaccy <bboscaccy@linux.microsoft.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1
+ * of the licence, or (at your option) any later version.
+ */
+
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <err.h>
+#include <getopt.h>
+
+#include <openssl/cms.h>
+#include <openssl/err.h>
+#include <openssl/evp.h>
+#include <openssl/pkcs7.h>
+#include <openssl/x509.h>
+#include <openssl/pem.h>
+#include <openssl/objects.h>
+#include <openssl/asn1.h>
+#include <openssl/asn1t.h>
+#include <openssl/opensslv.h>
+#include <openssl/bio.h>
+#include <openssl/stack.h>
+
+#if OPENSSL_VERSION_MAJOR >= 3
+# define USE_PKCS11_PROVIDER
+# include <openssl/provider.h>
+# include <openssl/store.h>
+#else
+# if !defined(OPENSSL_NO_ENGINE) && !defined(OPENSSL_NO_DEPRECATED_3_0)
+# define USE_PKCS11_ENGINE
+# include <openssl/engine.h>
+# endif
+#endif
+#include "../ssl-common.h"
+
+#define SHA256_LEN 32
+#define BUF_SIZE (1 << 15) // 32 KiB
+#define MAX_HASHES 64
+
+struct hash_spec {
+ char *file;
+ int index;
+};
+
+typedef struct {
+ ASN1_INTEGER *index;
+ ASN1_OCTET_STRING *hash;
+
+} HORNET_MAP;
+
+DECLARE_ASN1_FUNCTIONS(HORNET_MAP)
+ASN1_SEQUENCE(HORNET_MAP) = {
+ ASN1_SIMPLE(HORNET_MAP, index, ASN1_INTEGER),
+ ASN1_SIMPLE(HORNET_MAP, hash, ASN1_OCTET_STRING)
+} ASN1_SEQUENCE_END(HORNET_MAP);
+
+IMPLEMENT_ASN1_FUNCTIONS(HORNET_MAP)
+
+DEFINE_STACK_OF(HORNET_MAP)
+
+typedef struct {
+ STACK_OF(HORNET_MAP) * maps;
+} MAP_SET;
+
+DECLARE_ASN1_FUNCTIONS(MAP_SET)
+ASN1_SEQUENCE(MAP_SET) = {
+ ASN1_SET_OF(MAP_SET, maps, HORNET_MAP)
+} ASN1_SEQUENCE_END(MAP_SET);
+
+IMPLEMENT_ASN1_FUNCTIONS(MAP_SET)
+
+#define DIE(...) do { fprintf(stderr, __VA_ARGS__); fputc('\n', stderr); \
+ exit(EXIT_FAILURE); } while (0)
+
+static BIO *bio_open_wr(const char *path)
+{
+ BIO *b = BIO_new_file(path, "wb");
+
+ if (!b) {
+ perror(path);
+ ERR_print_errors_fp(stderr);
+ exit(EXIT_FAILURE);
+ }
+ return b;
+}
+
+static void usage(const char *prog)
+{
+ fprintf(stderr,
+ "Usage:\n"
+ " %s --data content.bin --cert signer.crt --key signer.key [-pass pass]\n"
+ " --out newsig.p7b \n"
+ " --add FILE:index [--add FILE:index ...]\n",
+ prog);
+}
+
+static const char *key_pass;
+
+static int pem_pw_cb(char *buf, int len, int w, void *v)
+{
+ int pwlen;
+
+ if (!key_pass)
+ return -1;
+
+ pwlen = strlen(key_pass);
+ if (pwlen >= len)
+ return -1;
+
+ strcpy(buf, key_pass);
+
+ key_pass = NULL;
+
+ return pwlen;
+}
+
+static EVP_PKEY *read_private_key(const char *private_key_name)
+{
+ EVP_PKEY *private_key;
+ BIO *b;
+
+ b = BIO_new_file(private_key_name, "rb");
+ ERR(!b, "%s", private_key_name);
+ private_key = PEM_read_bio_PrivateKey(b, NULL, pem_pw_cb,
+ NULL);
+ ERR(!private_key, "%s", private_key_name);
+ BIO_free(b);
+
+ return private_key;
+}
+
+static X509 *read_x509(const char *x509_name)
+{
+ unsigned char buf[2];
+ X509 *x509;
+ BIO *b;
+ int n;
+
+ b = BIO_new_file(x509_name, "rb");
+ ERR(!b, "%s", x509_name);
+
+ /* Look at the first two bytes of the file to determine the encoding */
+ n = BIO_read(b, buf, 2);
+ if (n != 2) {
+ if (BIO_should_retry(b)) {
+ fprintf(stderr, "%s: Read wanted retry\n", x509_name);
+ exit(1);
+ }
+ if (n >= 0) {
+ fprintf(stderr, "%s: Short read\n", x509_name);
+ exit(1);
+ }
+ ERR(1, "%s", x509_name);
+ }
+
+ ERR(BIO_reset(b) != 0, "%s", x509_name);
+
+ if (buf[0] == 0x30 && buf[1] >= 0x81 && buf[1] <= 0x84)
+ /* Assume raw DER encoded X.509 */
+ x509 = d2i_X509_bio(b, NULL);
+ else
+ /* Assume PEM encoded X.509 */
+ x509 = PEM_read_bio_X509(b, NULL, NULL, NULL);
+
+ BIO_free(b);
+ ERR(!x509, "%s", x509_name);
+
+ return x509;
+}
+
+static int sha256(const char *path, unsigned char out[SHA256_LEN], unsigned int *out_len)
+{
+ FILE *f;
+ int rc;
+ EVP_MD_CTX *ctx;
+ unsigned char buf[BUF_SIZE];
+ size_t n;
+ unsigned int mdlen = 0;
+
+ if (!path || !out)
+ return -1;
+
+ f = fopen(path, "rb");
+ if (!f) {
+ perror("fopen");
+ return -2;
+ }
+
+ ERR_load_crypto_strings();
+
+ rc = -3;
+ ctx = EVP_MD_CTX_new();
+ if (!ctx) {
+ rc = -4;
+ goto done;
+ }
+
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L
+ if (EVP_DigestInit_ex2(ctx, EVP_sha256(), NULL) != 1) {
+ rc = -5;
+ goto done;
+ }
+#else
+ if (EVP_DigestInit_ex(ctx, EVP_sha256(), NULL) != 1) {
+ rc = -5;
+ goto done;
+ }
+#endif
+ while ((n = fread(buf, 1, sizeof(buf), f)) > 0) {
+ if (EVP_DigestUpdate(ctx, buf, n) != 1) {
+ rc = -6;
+ goto done;
+ }
+ }
+ if (ferror(f)) {
+ rc = -7;
+ goto done;
+ }
+
+ if (EVP_DigestFinal_ex(ctx, out, &mdlen) != 1) {
+ rc = -8;
+ goto done;
+ }
+ if (mdlen != SHA256_LEN) {
+ rc = -9;
+ goto done;
+ }
+
+ if (out_len)
+ *out_len = mdlen;
+ rc = 0;
+
+done:
+ EVP_MD_CTX_free(ctx);
+ fclose(f);
+ ERR_free_strings();
+ return rc;
+}
+
+static void add_hash(MAP_SET *set, unsigned char *buffer, int buffer_len, int index)
+{
+ HORNET_MAP *map = NULL;
+
+ map = HORNET_MAP_new();
+ ASN1_INTEGER_set(map->index, index);
+ ASN1_OCTET_STRING_set(map->hash, buffer, buffer_len);
+ sk_HORNET_MAP_push(set->maps, map);
+}
+
+int main(int argc, char **argv)
+{
+ const char *cert_path = NULL;
+ const char *key_path = NULL;
+ const char *data_path = NULL;
+ const char *out_path = NULL;
+
+ X509 *signer;
+ EVP_PKEY *pkey;
+ BIO *data_in;
+ CMS_ContentInfo *cms_out;
+ struct hash_spec hashes[MAX_HASHES];
+ int hash_count = 0;
+ int flags;
+ CMS_SignerInfo *si;
+ MAP_SET *set;
+ unsigned char hash_buffer[SHA256_LEN];
+ unsigned int hash_len;
+ ASN1_OBJECT *oid;
+ unsigned char *der = NULL;
+ int der_len;
+ int err;
+ BIO *b_out;
+ int i;
+ int opt;
+
+ const char *short_opts = "C:K:P:O:A:Sh";
+
+ static const struct option long_opts[] = {
+ {"cert", required_argument, 0, 'C'},
+ {"key", required_argument, 0, 'K'},
+ {"pass", required_argument, 0, 'P'},
+ {"out", required_argument, 0, 'O'},
+ {"data", required_argument, 0, 'D'},
+ {"add", required_argument, 0, 'A'},
+ {"help", no_argument, 0, 'h'},
+ {0, 0, 0, 0}
+ };
+
+ while ((opt = getopt_long_only(argc, argv, short_opts, long_opts, NULL)) != -1) {
+ switch (opt) {
+ case 'C':
+ cert_path = optarg;
+ break;
+ case 'K':
+ key_path = optarg;
+ break;
+ case 'P':
+ key_pass = optarg;
+ break;
+ case 'O':
+ out_path = optarg;
+ break;
+ case 'D':
+ data_path = optarg;
+ break;
+ case 'A':
+ if (strchr(optarg, ':')) {
+ hashes[hash_count].file = strsep(&optarg, ":");
+ hashes[hash_count].index = atoi(optarg);
+ if (++hash_count >= MAX_HASHES) {
+ usage(argv[0]);
+ return EXIT_FAILURE;
+ }
+ } else {
+ usage(argv[0]);
+ return EXIT_FAILURE;
+ }
+ break;
+ default:
+ usage(argv[0]);
+ return EXIT_FAILURE;
+ }
+ }
+
+ if (!cert_path || !key_path || !out_path || !data_path) {
+ usage(argv[0]);
+ return EXIT_FAILURE;
+ }
+
+ OpenSSL_add_all_algorithms();
+ ERR_load_crypto_strings();
+
+ signer = read_x509(cert_path);
+ ERR(!signer, "Load cert failed");
+
+ pkey = read_private_key(key_path);
+ ERR(!pkey, "Load key failed");
+
+ data_in = BIO_new_file(data_path, "rb");
+ ERR(!data_in, "Load data failed");
+
+ cms_out = CMS_sign(NULL, NULL, NULL, NULL,
+ CMS_NOCERTS | CMS_PARTIAL | CMS_BINARY | CMS_DETACHED);
+ ERR(!cms_out, "create cms failed");
+
+ flags = CMS_NOCERTS | CMS_PARTIAL | CMS_BINARY | CMS_NOSMIMECAP | CMS_DETACHED;
+
+ si = CMS_add1_signer(cms_out, signer, pkey, EVP_sha256(), flags);
+ ERR(!si, "add signer failed");
+
+ set = MAP_SET_new();
+ set->maps = sk_HORNET_MAP_new_null();
+
+ 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, hashes[i].index);
+ }
+
+ oid = OBJ_txt2obj("2.25.316487325684022475439036912669789383960", 1);
+ if (!oid) {
+ ERR_print_errors_fp(stderr);
+ DIE("create oid failed");
+ }
+
+ der_len = ASN1_item_i2d((ASN1_VALUE *)set, &der, ASN1_ITEM_rptr(MAP_SET));
+ CMS_signed_add1_attr_by_OBJ(si, oid, V_ASN1_SEQUENCE, der, der_len);
+
+ err = CMS_final(cms_out, data_in, NULL, CMS_NOCERTS | CMS_BINARY);
+ ERR(!err, "cms final failed");
+
+ OPENSSL_free(der);
+ MAP_SET_free(set);
+
+ b_out = bio_open_wr(out_path);
+ ERR(!b_out, "opening output path failed");
+
+ i2d_CMS_bio_stream(b_out, cms_out, NULL, 0);
+
+ BIO_free(data_in);
+ BIO_free(b_out);
+ EVP_cleanup();
+ ERR_free_strings();
+ return 0;
+}
diff --git a/scripts/hornet/write-sig.sh b/scripts/hornet/write-sig.sh
new file mode 100755
index 0000000000000..7eaabe3bab9aa
--- /dev/null
+++ b/scripts/hornet/write-sig.sh
@@ -0,0 +1,27 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+#
+# Copyright (c) 2025 Microsoft Corporation
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of version 2 of the GNU General Public
+# 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
+}
+
+ARGC=$#
+
+EXPECTED_ARGS=2
+
+if [ $ARGC -ne $EXPECTED_ARGS ] ; then
+ usage
+else
+ SIG=$(xxd -p $2 | tr -d '\n' | sed 's/\(..\)/\\\\x\1/g')
+ sed '/const char opts_sig/,/;/c\\tstatic const char opts_sig[] __attribute__((__aligned__(8))) = "\\\n'"$(printf '%s\n' "$SIG")"'\";' $1
+fi
--
2.53.0
^ permalink raw reply related
* [PATCH v7 09/10] selftests/hornet: Add a selftest for the Hornet LSM
From: Blaise Boscaccy @ 2026-05-07 19:14 UTC (permalink / raw)
To: Blaise Boscaccy, Jonathan Corbet, Paul Moore, James Morris,
Serge E. Hallyn, Mickaël Salaün, Günther Noack,
Dr. David Alan Gilbert, Andrew Morton, James.Bottomley, dhowells,
Fan Wu, Ryan Foster, Randy Dunlap, linux-security-module,
linux-doc, linux-kernel, bpf, Song Liu
In-Reply-To: <20260507191416.2984054-1-bboscaccy@linux.microsoft.com>
This selftest contains a testcase that utilizes light skeleton eBPF
loaders and exercises hornet's map validation.
Signed-off-by: Blaise Boscaccy <bboscaccy@linux.microsoft.com>
---
tools/testing/selftests/Makefile | 1 +
tools/testing/selftests/hornet/Makefile | 63 ++++++++++++++++++++
tools/testing/selftests/hornet/loader.c | 21 +++++++
tools/testing/selftests/hornet/trivial.bpf.c | 33 ++++++++++
4 files changed, 118 insertions(+)
create mode 100644 tools/testing/selftests/hornet/Makefile
create mode 100644 tools/testing/selftests/hornet/loader.c
create mode 100644 tools/testing/selftests/hornet/trivial.bpf.c
diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile
index 450f13ba4cca9..4e2d1cd88c825 100644
--- a/tools/testing/selftests/Makefile
+++ b/tools/testing/selftests/Makefile
@@ -44,6 +44,7 @@ TARGETS += ftrace
TARGETS += futex
TARGETS += gpio
TARGETS += hid
+TARGETS += hornet
TARGETS += intel_pstate
TARGETS += iommu
TARGETS += ipc
diff --git a/tools/testing/selftests/hornet/Makefile b/tools/testing/selftests/hornet/Makefile
new file mode 100644
index 0000000000000..432bce59f54e7
--- /dev/null
+++ b/tools/testing/selftests/hornet/Makefile
@@ -0,0 +1,63 @@
+# SPDX-License-Identifier: GPL-2.0
+include ../../../build/Build.include
+include ../../../scripts/Makefile.arch
+include ../../../scripts/Makefile.include
+
+CLANG ?= clang
+CFLAGS := -g -O2 -Wall
+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
+
+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 \
+ $(KHDR_INCLUDES)
+
+vmlinux.h:
+ $(BPFTOOL) btf dump file /sys/kernel/btf/vmlinux format c > vmlinux.h
+
+trivial.bpf.o: trivial.bpf.c vmlinux.h
+ $(CLANG) $(CFLAGS) $(BPF_CFLAGS) -c $< -o $@
+
+loader.h: trivial.bpf.o
+ $(BPFTOOL) gen skeleton -S -k $(CERTDIR)/signing_key.pem -i $(CERTDIR)/signing_key.x509 \
+ -L $< name trivial > $@
+
+insn.bin: loader.h
+ $(SCRIPTSDIR)/extract-insn.sh $< > $@
+
+map.bin: 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) \
+ $< -o $@ \
+ $(shell $(PKG_CONFIG) --libs libcrypto 2> /dev/null || echo -lcrypto)
+
+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:0 --out sig.bin
+
+signed_loader.h: sig.bin
+ $(SCRIPTSDIR)/write-sig.sh loader.h sig.bin > $@
+
+loader: loader.c signed_loader.h
+ $(CC) $(CFLAGS) -I$(LIBDIR) -I$(APIDIR) $< -o $@ -lbpf
+
+
+EXTRA_CLEAN = $(OUTPUT)/gen_sig
diff --git a/tools/testing/selftests/hornet/loader.c b/tools/testing/selftests/hornet/loader.c
new file mode 100644
index 0000000000000..f27580c7262b3
--- /dev/null
+++ b/tools/testing/selftests/hornet/loader.c
@@ -0,0 +1,21 @@
+// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
+
+#include <stdio.h>
+#include <unistd.h>
+#include <stddef.h>
+#include <sys/resource.h>
+#include <bpf/libbpf.h>
+#include <errno.h>
+#include "signed_loader.h"
+
+int main(int argc, char **argv)
+{
+ struct trivial *skel;
+
+ skel = trivial__open_and_load();
+ if (!skel)
+ return -1;
+
+ trivial__destroy(skel);
+ return 0;
+}
diff --git a/tools/testing/selftests/hornet/trivial.bpf.c b/tools/testing/selftests/hornet/trivial.bpf.c
new file mode 100644
index 0000000000000..d38c5b53ff932
--- /dev/null
+++ b/tools/testing/selftests/hornet/trivial.bpf.c
@@ -0,0 +1,33 @@
+// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
+
+#include "vmlinux.h"
+
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+#include <bpf/bpf_core_read.h>
+
+char LICENSE[] SEC("license") = "Dual BSD/GPL";
+
+int monitored_pid = 0;
+
+SEC("tracepoint/syscalls/sys_enter_unlinkat")
+int handle_enter_unlink(struct trace_event_raw_sys_enter *ctx)
+{
+ char filename[128] = { 0 };
+ struct task_struct *task;
+ unsigned long start_time = 0;
+ int pid = bpf_get_current_pid_tgid() >> 32;
+ char *pathname_ptr = (char *) BPF_CORE_READ(ctx, args[1]);
+
+ bpf_probe_read_str(filename, sizeof(filename), pathname_ptr);
+ task = (struct task_struct *)bpf_get_current_task();
+ start_time = BPF_CORE_READ(task, start_time);
+
+ bpf_printk("BPF triggered unlinkat by PID: %d, start_time %ld. pathname = %s",
+ pid, start_time, filename);
+
+ if (monitored_pid == pid)
+ bpf_printk("target pid found");
+
+ return 0;
+}
--
2.53.0
^ permalink raw reply related
* [PATCH v7 08/10] hornet: Add a light skeleton data extractor scripts
From: Blaise Boscaccy @ 2026-05-07 19:14 UTC (permalink / raw)
To: Blaise Boscaccy, Jonathan Corbet, Paul Moore, James Morris,
Serge E. Hallyn, Mickaël Salaün, Günther Noack,
Dr. David Alan Gilbert, Andrew Morton, James.Bottomley, dhowells,
Fan Wu, Ryan Foster, Randy Dunlap, linux-security-module,
linux-doc, linux-kernel, bpf, Song Liu
In-Reply-To: <20260507191416.2984054-1-bboscaccy@linux.microsoft.com>
These script eases light skeleton development against Hornet by
generating a data payloads which can be used for signing a light
skeleton binary using gen_sig.
Signed-off-by: Blaise Boscaccy <bboscaccy@linux.microsoft.com>
---
| 27 +++++++++++++++++++++++++++
| 27 +++++++++++++++++++++++++++
| 27 +++++++++++++++++++++++++++
3 files changed, 81 insertions(+)
create mode 100755 scripts/hornet/extract-insn.sh
create mode 100755 scripts/hornet/extract-map.sh
create mode 100755 scripts/hornet/extract-skel.sh
--git a/scripts/hornet/extract-insn.sh b/scripts/hornet/extract-insn.sh
new file mode 100755
index 0000000000000..52338f057ff6b
--- /dev/null
+++ b/scripts/hornet/extract-insn.sh
@@ -0,0 +1,27 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+#
+# Copyright (c) 2025 Microsoft Corporation
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of version 2 of the GNU General Public
+# 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
+}
+
+ARGC=$#
+
+EXPECTED_ARGS=1
+
+if [ $ARGC -ne $EXPECTED_ARGS ] ; then
+ usage
+else
+ printf $(gcc -E $1 | grep "opts_insn" | \
+ awk -F"=" '{print $2}' | sed 's/;\+$//' | sed 's/\"//g')
+fi
--git a/scripts/hornet/extract-map.sh b/scripts/hornet/extract-map.sh
new file mode 100755
index 0000000000000..c309f505c6238
--- /dev/null
+++ b/scripts/hornet/extract-map.sh
@@ -0,0 +1,27 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+#
+# Copyright (c) 2025 Microsoft Corporation
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of version 2 of the GNU General Public
+# 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
+}
+
+ARGC=$#
+
+EXPECTED_ARGS=1
+
+if [ $ARGC -ne $EXPECTED_ARGS ] ; then
+ usage
+else
+ printf $(gcc -E $1 | grep "opts_data" | \
+ awk -F"=" '{print $2}' | sed 's/;\+$//' | sed 's/\"//g')
+fi
--git a/scripts/hornet/extract-skel.sh b/scripts/hornet/extract-skel.sh
new file mode 100755
index 0000000000000..6550a86b89917
--- /dev/null
+++ b/scripts/hornet/extract-skel.sh
@@ -0,0 +1,27 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+#
+# Copyright (c) 2025 Microsoft Corporation
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of version 2 of the GNU General Public
+# 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
+}
+
+ARGC=$#
+
+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/;\+$//' | sed 's/\"//g')
+fi
--
2.53.0
^ permalink raw reply related
* [PATCH v7 10/10] ipe: Add BPF program load policy enforcement via Hornet integration
From: Blaise Boscaccy @ 2026-05-07 19:14 UTC (permalink / raw)
To: Blaise Boscaccy, Jonathan Corbet, Paul Moore, James Morris,
Serge E. Hallyn, Mickaël Salaün, Günther Noack,
Dr. David Alan Gilbert, Andrew Morton, James.Bottomley, dhowells,
Fan Wu, Ryan Foster, Randy Dunlap, linux-security-module,
linux-doc, linux-kernel, bpf, Song Liu
In-Reply-To: <20260507191416.2984054-1-bboscaccy@linux.microsoft.com>
Add support for the bpf_prog_load_post_integrity LSM hook, enabling IPE
to make policy decisions about BPF program loading based on integrity
verdicts provided by the Hornet LSM.
New policy operation:
op=BPF_PROG_LOAD - Matches BPF program load events
New policy properties:
bpf_signature=NONE - No Verdict
bpf_signature=OK - Program signature and map hashes verified
bpf_signature=UNSIGNED - No signature provided
bpf_signature=PARTIALSIG - Signature OK but no map hash data
bpf_signature=UNKNOWNKEY - The keyring requested by the user is invalid
bpf_signature=UNEXPECTED - An unexpected hash value was encountered
bpf_signature=FAULT - System error during verification
bpf_signature=BADSIG - Signature or map hash verification failed
bpf_keyring=BUILTIN - Program was signed using a builtin keyring
bpf_keyring=SECONDARY - Program was signed using the secondary keyring
bpf_keyring=PLATFORM - Program was signed using the platform keyring
bpf_kernel=TRUE - Program originated from kernelspace
bpf_kernel=FALSE - Program originated from userspace
These properties map directly to the lsm_integrity_verdict enum values
provided by the Hornet LSM through security_bpf_prog_load_post_integrity.
The feature is gated on CONFIG_IPE_PROP_BPF_SIGNATURE which depends on
CONFIG_SECURITY_HORNET.
Signed-off-by: Blaise Boscaccy <bboscaccy@linux.microsoft.com>
---
Documentation/admin-guide/LSM/ipe.rst | 162 +++++++++++++++++++++++++-
Documentation/security/ipe.rst | 68 +++++++++++
security/ipe/Kconfig | 15 +++
security/ipe/audit.c | 15 +++
security/ipe/eval.c | 93 ++++++++++++++-
security/ipe/eval.h | 11 ++
security/ipe/hooks.c | 63 ++++++++++
security/ipe/hooks.h | 15 +++
security/ipe/ipe.c | 14 +++
security/ipe/ipe.h | 3 +
security/ipe/policy.h | 14 +++
security/ipe/policy_parser.c | 27 +++++
12 files changed, 498 insertions(+), 2 deletions(-)
diff --git a/Documentation/admin-guide/LSM/ipe.rst b/Documentation/admin-guide/LSM/ipe.rst
index a756d81585317..d68ba9d98859e 100644
--- a/Documentation/admin-guide/LSM/ipe.rst
+++ b/Documentation/admin-guide/LSM/ipe.rst
@@ -559,7 +559,8 @@ policy. Two properties are built-into the policy parser: 'op' and 'action'.
The other properties are used to restrict immutable security properties
about the files being evaluated. Currently those properties are:
'``boot_verified``', '``dmverity_signature``', '``dmverity_roothash``',
-'``fsverity_signature``', '``fsverity_digest``'. A description of all
+'``fsverity_signature``', '``fsverity_digest``', '``bpf_signature``',
+'``bpf_keyring``', '``bpf_kernel``'. A description of all
properties supported by IPE are listed below:
op
@@ -603,6 +604,14 @@ as the first token. IPE supports the following operations:
Controls loading IMA certificates through the Kconfigs,
``CONFIG_IMA_X509_PATH`` and ``CONFIG_EVM_X509_PATH``.
+ ``BPF_PROG_LOAD``:
+
+ Pertains to BPF programs being loaded via the ``bpf()`` syscall.
+ This operation is used in conjunction with the ``bpf_signature``,
+ ``bpf_keyring``, and ``bpf_kernel`` properties to control BPF
+ program loading based on integrity verification provided by the
+ Hornet LSM.
+
action
~~~~~~
@@ -713,6 +722,105 @@ fsverity_signature
fsverity_signature=(TRUE|FALSE)
+bpf_signature
+~~~~~~~~~~~~~
+
+ This property can be utilized for authorization of BPF program loads based
+ on the integrity verdict provided by the Hornet LSM. When a BPF program is
+ loaded, Hornet performs cryptographic verification of the program's PKCS#7
+ signature (if present) and passes an integrity verdict to IPE via the
+ ``security_bpf_prog_load_post_integrity`` hook. IPE can then allow or deny
+ the load based on the verdict.
+
+ This property depends on ``SECURITY_HORNET`` and is controlled by the
+ ``IPE_PROP_BPF_SIGNATURE`` config option.
+ The format of this property is::
+
+ bpf_signature=(NONE|OK|UNSIGNED|PARTIALSIG|UNKNOWNKEY|UNEXPECTED|FAULT|BADSIG)
+
+ The possible values correspond to the integrity verdicts from Hornet:
+
+ ``NONE``
+
+ No integrity verdict was set (default/uninitialized).
+
+ ``OK``
+
+ The BPF program's signature and all map hashes were successfully
+ verified.
+
+ ``UNSIGNED``
+
+ No signature was provided with the BPF program.
+
+ ``PARTIALSIG``
+
+ The program signature was verified, but no authenticated map hash
+ data was present.
+
+ ``UNKNOWNKEY``
+
+ The keyring requested by the user is invalid.
+
+ ``UNEXPECTED``
+
+ An unexpected map hash value was encountered during verification.
+
+ ``FAULT``
+
+ A system error occurred during signature verification.
+
+ ``BADSIG``
+
+ The signature or hash verification failed.
+
+bpf_keyring
+~~~~~~~~~~~~
+
+ This property can be utilized for authorization of BPF program loads based
+ on the keyring specified in the ``bpf_attr`` during the ``BPF_PROG_LOAD``
+ syscall. This allows policies to restrict which keyring must be used for
+ signature verification of BPF programs.
+
+ This property shares the ``IPE_PROP_BPF_SIGNATURE`` config option with
+ ``bpf_signature``.
+ The format of this property is::
+
+ bpf_keyring=(BUILTIN|SECONDARY|PLATFORM)
+
+ The possible values correspond to the system keyrings:
+
+ ``BUILTIN``
+
+ The builtin trusted keyring (``.builtin_trusted_keys``), which
+ contains keys embedded at kernel compile time.
+
+ ``SECONDARY``
+
+ The secondary trusted keyring (``.secondary_trusted_keys``), which
+ includes both builtin trusted keys and keys added at runtime.
+
+ ``PLATFORM``
+
+ The platform keyring (``.platform``), which contains keys provided
+ by the platform firmware (e.g. UEFI db keys).
+
+bpf_kernel
+~~~~~~~~~~
+
+ This property can be utilized for authorization of BPF program loads based
+ on whether the load originated from kernel space or user space. The BPF
+ light skeleton infrastructure performs a secondary kernel-originated program
+ load that will not carry a signature. This property allows policies to
+ permit such kernel-originated loads while still requiring signatures for
+ user-space loads.
+
+ This property shares the ``IPE_PROP_BPF_SIGNATURE`` config option with
+ ``bpf_signature``.
+ The format of this property is::
+
+ bpf_kernel=(TRUE|FALSE)
+
Policy Examples
---------------
@@ -788,6 +896,58 @@ Allow execution of a specific fs-verity file
op=EXECUTE fsverity_digest=sha256:fd88f2b8824e197f850bf4c5109bea5cf0ee38104f710843bb72da796ba5af9e action=ALLOW
+Allow only signed BPF programs
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+::
+
+ policy_name=Allow_Signed_BPF policy_version=0.0.0
+ DEFAULT action=ALLOW
+
+ DEFAULT op=BPF_PROG_LOAD action=DENY
+ op=BPF_PROG_LOAD bpf_kernel=TRUE action=ALLOW
+ op=BPF_PROG_LOAD bpf_signature=OK action=ALLOW
+
+This policy allows all other operations but restricts BPF program loading
+to only programs that either originate from kernel space (e.g. light skeleton
+reloads) or have a valid signature verified by the Hornet LSM. Unsigned or
+improperly signed BPF programs from user space will be denied.
+
+Allow signed BPF programs from a specific keyring
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+::
+
+ policy_name=Allow_BPF_Builtin_Keyring policy_version=0.0.0
+ DEFAULT action=ALLOW
+
+ DEFAULT op=BPF_PROG_LOAD action=DENY
+ op=BPF_PROG_LOAD bpf_kernel=TRUE action=ALLOW
+ op=BPF_PROG_LOAD bpf_signature=OK bpf_keyring=BUILTIN action=ALLOW
+
+This policy further restricts BPF program loading to only accept programs
+whose signatures were verified using the builtin trusted keyring. Programs
+signed against the secondary or platform keyrings will be denied, providing
+tighter control over which signing keys are acceptable.
+
+Allow signed BPF programs with relaxed partial signatures
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+::
+
+ policy_name=Allow_BPF_Partial policy_version=0.0.0
+ DEFAULT action=ALLOW
+
+ DEFAULT op=BPF_PROG_LOAD action=DENY
+ op=BPF_PROG_LOAD bpf_kernel=TRUE action=ALLOW
+ op=BPF_PROG_LOAD bpf_signature=OK action=ALLOW
+ op=BPF_PROG_LOAD bpf_signature=PARTIALSIG action=ALLOW
+
+This policy allows BPF programs that have been fully verified (``OK``) as
+well as programs with a valid program signature but without authenticated
+map hash data (``PARTIALSIG``). This can be useful during development or
+for programs that do not use maps.
+
Additional Information
----------------------
diff --git a/Documentation/security/ipe.rst b/Documentation/security/ipe.rst
index 4a7d953abcdc3..2363734e934f0 100644
--- a/Documentation/security/ipe.rst
+++ b/Documentation/security/ipe.rst
@@ -412,6 +412,73 @@ a standard securityfs policy tree::
The policy is stored in the ``->i_private`` data of the MyPolicy inode.
+BPF/Hornet Integration
+~~~~~~~~~~~~~~~~~~~~~~
+
+IPE integrates with the Hornet LSM to enforce integrity policies on BPF
+program loading. Hornet performs cryptographic verification of BPF program
+signatures (PKCS#7 with authenticated attributes containing map hashes)
+and produces an ``enum lsm_integrity_verdict``. IPE acts as the policy
+enforcer: it stores Hornet's verdict in a per-program LSM blob and later
+evaluates it against the active IPE policy when the BPF program load is
+finalized.
+
+Enforcement is split across two LSM hooks so that signature verification
+and policy evaluation are cleanly separated. This also lets IPE evaluate
+policy uniformly even when no integrity provider ran.
+
+The hook flow is:
+
+ 1. User space (or the kernel, via the BPF light skeleton) invokes
+ ``BPF_PROG_LOAD`` through the ``bpf()`` syscall.
+ 2. Hornet's ``bpf_prog_load_integrity`` hook runs first and calls
+ ``hornet_check_program()`` to verify the program's PKCS#7 signature
+ against ``attr->keyring_id`` and to check the signed map hashes
+ (decoded from the ``OID_hornet_data`` authenticated attribute)
+ against the live hashes of the frozen maps referenced from
+ ``attr->fd_array``. The function produces one of
+ ``LSM_INT_VERDICT_OK``, ``LSM_INT_VERDICT_UNSIGNED``,
+ ``LSM_INT_VERDICT_BADSIG``, ``LSM_INT_VERDICT_PARTIALSIG``,
+ ``LSM_INT_VERDICT_UNKNOWNKEY``, ``LSM_INT_VERDICT_UNEXPECTED``, or
+ ``LSM_INT_VERDICT_FAULT``.
+ 3. Hornet calls ``security_bpf_prog_load_post_integrity()`` with the
+ resulting verdict and its ``lsm_id``. IPE's
+ ``ipe_bpf_prog_load_post_integrity`` handler does **not** enforce
+ policy here; it only stashes the verdict in IPE's per-program blob
+ and returns ``0``. This keeps the integrity step decoupled from
+ policy evaluation and allows multiple providers to coexist without
+ short-circuiting each other.
+ 4. The core BPF load path then invokes the standard ``bpf_prog_load``
+ LSM hook. IPE's ``ipe_bpf_prog_load`` reads the verdict back out of
+ the per-program blob, populates an ``ipe_eval_ctx``, and calls
+ ``ipe_evaluate_event()`` against the active policy's
+ ``BPF_PROG_LOAD`` rules. A deny verdict returns ``-EACCES`` and
+ aborts the load.
+
+If no integrity provider populated the blob (e.g. Hornet is not enabled,
+or the load came from a path Hornet does not cover), the verdict defaults
+to ``LSM_INT_VERDICT_NONE`` and IPE evaluates accordingly. Policy can
+therefore express "deny anything Hornet did not vouch for".
+
+Three properties are available for BPF policy rules:
+
+ - ``bpf_signature``: Matches against the integrity verdict (OK, UNSIGNED,
+ BADSIG, etc.)
+ - ``bpf_keyring``: Matches against the keyring specified in ``bpf_attr``
+ (BUILTIN, SECONDARY, PLATFORM)
+ - ``bpf_kernel``: Matches whether the load originated from kernel space
+ (TRUE/FALSE). This is important because the BPF light skeleton
+ infrastructure performs a secondary kernel-originated program load that
+ does not carry a signature.
+
+All three properties are gated on ``CONFIG_IPE_PROP_BPF_SIGNATURE`` which
+depends on ``CONFIG_SECURITY_HORNET``.
+
+The evaluation context (``struct ipe_eval_ctx``) carries three BPF-specific
+fields: ``bpf_verdict`` (the integrity verdict enum), ``bpf_keyring_id``
+(the ``s32`` keyring ID from ``bpf_attr``), and ``bpf_kernel`` (bool
+indicating kernel origin).
+
Tests
-----
@@ -439,6 +506,7 @@ IPE has KUnit Tests for the policy parser. Recommended kunitconfig::
CONFIG_IPE_PROP_DM_VERITY_SIGNATURE=y
CONFIG_IPE_PROP_FS_VERITY=y
CONFIG_IPE_PROP_FS_VERITY_BUILTIN_SIG=y
+ CONFIG_IPE_PROP_BPF_SIGNATURE=y
CONFIG_SECURITY_IPE_KUNIT_TEST=y
In addition, IPE has a python based integration
diff --git a/security/ipe/Kconfig b/security/ipe/Kconfig
index a110a6cd848b7..95775139612df 100644
--- a/security/ipe/Kconfig
+++ b/security/ipe/Kconfig
@@ -95,6 +95,21 @@ config IPE_PROP_FS_VERITY_BUILTIN_SIG
if unsure, answer Y.
+config IPE_PROP_BPF_SIGNATURE
+ bool "Enable support for Hornet BPF program signature verification"
+ depends on SECURITY_HORNET
+ help
+ This option enables the 'bpf_signature', 'bpf_kernel' and
+ 'bpf_keyring' properties within IPE policies. The
+ 'bpf_signature' property allows IPE to make policy decisions
+ based on the integrity verdict provided by the Hornet LSM
+ when a BPF program is loaded. Verdicts include OK,
+ UNSIGNED, PARTIALSIG, BADSIG, and others. The 'bpf_keyring'
+ property allows policies to match against the keyring
+ specified in bpf_attr (BUILTIN, SECONDARY, PLATFORM).
+
+ If unsure, answer Y.
+
endmenu
config SECURITY_IPE_KUNIT_TEST
diff --git a/security/ipe/audit.c b/security/ipe/audit.c
index 3f0deeb549127..251c6ec2f8423 100644
--- a/security/ipe/audit.c
+++ b/security/ipe/audit.c
@@ -41,6 +41,7 @@ static const char *const audit_op_names[__IPE_OP_MAX + 1] = {
"KEXEC_INITRAMFS",
"POLICY",
"X509_CERT",
+ "BPF_PROG_LOAD",
"UNKNOWN",
};
@@ -51,6 +52,7 @@ static const char *const audit_hook_names[__IPE_HOOK_MAX] = {
"MPROTECT",
"KERNEL_READ",
"KERNEL_LOAD",
+ "BPF_PROG_LOAD",
};
static const char *const audit_prop_names[__IPE_PROP_MAX] = {
@@ -62,6 +64,19 @@ static const char *const audit_prop_names[__IPE_PROP_MAX] = {
"fsverity_digest=",
"fsverity_signature=FALSE",
"fsverity_signature=TRUE",
+ "bpf_signature=NONE",
+ "bpf_signature=OK",
+ "bpf_signature=UNSIGNED",
+ "bpf_signature=PARTIALSIG",
+ "bpf_signature=UNKNOWNKEY",
+ "bpf_signature=UNEXPECTED",
+ "bpf_signature=FAULT",
+ "bpf_signature=BADSIG",
+ "bpf_keyring=BUILTIN",
+ "bpf_keyring=SECONDARY",
+ "bpf_keyring=PLATFORM",
+ "bpf_kernel=FALSE",
+ "bpf_kernel=TRUE",
};
/**
diff --git a/security/ipe/eval.c b/security/ipe/eval.c
index 21439c5be3364..705c4ecfda696 100644
--- a/security/ipe/eval.c
+++ b/security/ipe/eval.c
@@ -11,6 +11,7 @@
#include <linux/rcupdate.h>
#include <linux/moduleparam.h>
#include <linux/fsverity.h>
+#include <linux/verification.h>
#include "ipe.h"
#include "eval.h"
@@ -265,8 +266,72 @@ static bool evaluate_fsv_sig_true(const struct ipe_eval_ctx *const ctx)
}
#endif /* CONFIG_IPE_PROP_FS_VERITY_BUILTIN_SIG */
+#ifdef CONFIG_IPE_PROP_BPF_SIGNATURE
+/**
+ * evaluate_bpf_sig() - Evaluate @ctx against a bpf_signature property.
+ * @ctx: Supplies a pointer to the context being evaluated.
+ * @expected: The expected lsm_integrity_verdict to match against.
+ *
+ * Return:
+ * * %true - The current @ctx matches the expected verdict
+ * * %false - The current @ctx doesn't match the expected verdict
+ */
+static bool evaluate_bpf_sig(const struct ipe_eval_ctx *const ctx,
+ enum lsm_integrity_verdict expected)
+{
+ return ctx->bpf_verdict == expected;
+}
+#else
+static bool evaluate_bpf_sig(const struct ipe_eval_ctx *const ctx,
+ enum lsm_integrity_verdict expected)
+{
+ return false;
+}
+#endif /* CONFIG_IPE_PROP_BPF_SIGNATURE */
+
+#ifdef CONFIG_IPE_PROP_BPF_SIGNATURE
+/**
+ * evaluate_bpf_keyring() - Evaluate @ctx against a bpf_keyring property.
+ * @ctx: Supplies a pointer to the context being evaluated.
+ * @expected: The expected keyring_id to match against.
+ *
+ * Return:
+ * * %true - The current @ctx matches the expected keyring
+ * * %false - The current @ctx doesn't match the expected keyring
+ */
+static bool evaluate_bpf_keyring(const struct ipe_eval_ctx *const ctx,
+ s32 expected)
+{
+ return ctx->bpf_keyring_id == expected;
+}
+#else
+static bool evaluate_bpf_keyring(const struct ipe_eval_ctx *const ctx,
+ s32 expected)
+{
+ return false;
+}
+#endif /* CONFIG_IPE_PROP_BPF_SIGNATURE */
+
+#ifdef CONFIG_IPE_PROP_BPF_SIGNATURE
+/**
+ * evaluate_bpf_kernel() - Evaluate @ctx against the bpf_kernel property.
+ * @ctx: Supplies a pointer to the context being evaluated.
+ *
+ * Return:
+ * * %true - The current @ctx bpf_kernel property is true
+ * * %false - The current @ctx bpf_kernel property is false
+ */
+static bool evaluate_bpf_kernel(const struct ipe_eval_ctx *const ctx)
+{
+ return ctx->bpf_kernel;
+}
+#else
+static bool evaluate_bpf_kernel(const struct ipe_eval_ctx *const ctx)
+{
+ return false;
+}
+#endif /* CONFIG_IPE_PROP_BPF_SIGNATURE */
/**
- * evaluate_property() - Analyze @ctx against a rule property.
* @ctx: Supplies a pointer to the context to be evaluated.
* @p: Supplies a pointer to the property to be evaluated.
*
@@ -297,6 +362,32 @@ static bool evaluate_property(const struct ipe_eval_ctx *const ctx,
return evaluate_fsv_sig_false(ctx);
case IPE_PROP_FSV_SIG_TRUE:
return evaluate_fsv_sig_true(ctx);
+ case IPE_PROP_BPF_SIG_NONE:
+ return evaluate_bpf_sig(ctx, LSM_INT_VERDICT_NONE);
+ case IPE_PROP_BPF_SIG_OK:
+ return evaluate_bpf_sig(ctx, LSM_INT_VERDICT_OK);
+ case IPE_PROP_BPF_SIG_UNSIGNED:
+ return evaluate_bpf_sig(ctx, LSM_INT_VERDICT_UNSIGNED);
+ case IPE_PROP_BPF_SIG_PARTIALSIG:
+ return evaluate_bpf_sig(ctx, LSM_INT_VERDICT_PARTIALSIG);
+ case IPE_PROP_BPF_SIG_UNKNOWNKEY:
+ return evaluate_bpf_sig(ctx, LSM_INT_VERDICT_UNKNOWNKEY);
+ case IPE_PROP_BPF_SIG_UNEXPECTED:
+ return evaluate_bpf_sig(ctx, LSM_INT_VERDICT_UNEXPECTED);
+ case IPE_PROP_BPF_SIG_FAULT:
+ return evaluate_bpf_sig(ctx, LSM_INT_VERDICT_FAULT);
+ case IPE_PROP_BPF_SIG_BADSIG:
+ return evaluate_bpf_sig(ctx, LSM_INT_VERDICT_BADSIG);
+ case IPE_PROP_BPF_KEYRING_BUILTIN:
+ return evaluate_bpf_keyring(ctx, 0);
+ case IPE_PROP_BPF_KEYRING_SECONDARY:
+ return evaluate_bpf_keyring(ctx, (s32)(unsigned long)VERIFY_USE_SECONDARY_KEYRING);
+ case IPE_PROP_BPF_KEYRING_PLATFORM:
+ return evaluate_bpf_keyring(ctx, (s32)(unsigned long)VERIFY_USE_PLATFORM_KEYRING);
+ case IPE_PROP_BPF_KERNEL_FALSE:
+ return !evaluate_bpf_kernel(ctx);
+ case IPE_PROP_BPF_KERNEL_TRUE:
+ return evaluate_bpf_kernel(ctx);
default:
return false;
}
diff --git a/security/ipe/eval.h b/security/ipe/eval.h
index fef65a36468cb..b061cb5ade27e 100644
--- a/security/ipe/eval.h
+++ b/security/ipe/eval.h
@@ -37,6 +37,12 @@ struct ipe_inode {
};
#endif /* CONFIG_IPE_PROP_FS_VERITY_BUILTIN_SIG */
+#ifdef CONFIG_IPE_PROP_BPF_SIGNATURE
+struct ipe_bpf_prog {
+ enum lsm_integrity_verdict verdict;
+};
+#endif /* CONFIG_IPE_PROP_BPF_SIGNATURE */
+
struct ipe_eval_ctx {
enum ipe_op_type op;
enum ipe_hook_type hook;
@@ -52,6 +58,11 @@ struct ipe_eval_ctx {
#ifdef CONFIG_IPE_PROP_FS_VERITY_BUILTIN_SIG
const struct ipe_inode *ipe_inode;
#endif /* CONFIG_IPE_PROP_FS_VERITY_BUILTIN_SIG */
+#ifdef CONFIG_IPE_PROP_BPF_SIGNATURE
+ enum lsm_integrity_verdict bpf_verdict;
+ s32 bpf_keyring_id;
+ bool bpf_kernel;
+#endif /* CONFIG_IPE_PROP_BPF_SIGNATURE */
};
enum ipe_match {
diff --git a/security/ipe/hooks.c b/security/ipe/hooks.c
index 0ae54a880405a..9271e129a2cf2 100644
--- a/security/ipe/hooks.c
+++ b/security/ipe/hooks.c
@@ -340,3 +340,66 @@ int ipe_inode_setintegrity(const struct inode *inode,
return -EINVAL;
}
#endif /* CONFIG_IPE_PROP_FS_VERITY_BUILTIN_SIG */
+
+#ifdef CONFIG_IPE_PROP_BPF_SIGNATURE
+/**
+ * ipe_bpf_prog_load_post_integrity() - Store integrity verdict in per-prog blob.
+ * @prog: Supplies the BPF program being loaded.
+ * @attr: Supplies the bpf syscall attributes.
+ * @token: Supplies the BPF token, if any.
+ * @kernel: Whether the call originated from the kernel.
+ * @lsmid: Supplies the LSM ID of the integrity provider.
+ * @verdict: Supplies the integrity verdict from the provider (e.g. Hornet).
+ *
+ * This hook stores the integrity verdict in IPE's per-prog security blob
+ * so that ipe_bpf_prog_load() can later read it for policy evaluation.
+ *
+ * Return:
+ * * %0 - Always succeeds (policy is evaluated in bpf_prog_load)
+ */
+int ipe_bpf_prog_load_post_integrity(struct bpf_prog *prog,
+ union bpf_attr *attr,
+ struct bpf_token *token,
+ bool kernel,
+ const struct lsm_id *lsmid,
+ enum lsm_integrity_verdict verdict)
+{
+ struct ipe_bpf_prog *blob = ipe_bpf_prog(prog);
+
+ blob->verdict = verdict;
+
+ return 0;
+}
+
+/**
+ * ipe_bpf_prog_load() - IPE policy evaluation for BPF program load.
+ * @prog: Supplies the BPF program being loaded.
+ * @attr: Supplies the bpf syscall attributes.
+ * @token: Supplies the BPF token, if any.
+ * @kernel: Whether the call originated from the kernel.
+ *
+ * Reads the integrity verdict previously stored by post_integrity (if any)
+ * and evaluates IPE policy. If no integrity provider ran, the verdict
+ * defaults to LSM_INT_VERDICT_NONE.
+ *
+ * Return:
+ * * %0 - Success
+ * * %-EACCES - Did not pass IPE policy
+ */
+int ipe_bpf_prog_load(struct bpf_prog *prog,
+ union bpf_attr *attr,
+ struct bpf_token *token,
+ bool kernel)
+{
+ struct ipe_bpf_prog *blob = ipe_bpf_prog(prog);
+ struct ipe_eval_ctx ctx = IPE_EVAL_CTX_INIT;
+
+ ctx.op = IPE_OP_BPF_PROG_LOAD;
+ ctx.hook = IPE_HOOK_BPF_PROG_LOAD;
+ ctx.bpf_verdict = blob->verdict;
+ ctx.bpf_keyring_id = attr->keyring_id;
+ ctx.bpf_kernel = kernel;
+
+ return ipe_evaluate_event(&ctx);
+}
+#endif /* CONFIG_IPE_PROP_BPF_SIGNATURE */
diff --git a/security/ipe/hooks.h b/security/ipe/hooks.h
index 07db373327402..8a6d1a459e00c 100644
--- a/security/ipe/hooks.h
+++ b/security/ipe/hooks.h
@@ -10,6 +10,7 @@
#include <linux/security.h>
#include <linux/blk_types.h>
#include <linux/fsverity.h>
+#include <linux/bpf.h>
enum ipe_hook_type {
IPE_HOOK_BPRM_CHECK = 0,
@@ -18,6 +19,7 @@ enum ipe_hook_type {
IPE_HOOK_MPROTECT,
IPE_HOOK_KERNEL_READ,
IPE_HOOK_KERNEL_LOAD,
+ IPE_HOOK_BPF_PROG_LOAD,
__IPE_HOOK_MAX
};
@@ -52,4 +54,17 @@ int ipe_inode_setintegrity(const struct inode *inode, enum lsm_integrity_type ty
const void *value, size_t size);
#endif /* CONFIG_IPE_PROP_FS_VERITY_BUILTIN_SIG */
+#ifdef CONFIG_IPE_PROP_BPF_SIGNATURE
+int ipe_bpf_prog_load_post_integrity(struct bpf_prog *prog,
+ union bpf_attr *attr,
+ struct bpf_token *token,
+ bool kernel,
+ const struct lsm_id *lsmid,
+ enum lsm_integrity_verdict verdict);
+int ipe_bpf_prog_load(struct bpf_prog *prog,
+ union bpf_attr *attr,
+ struct bpf_token *token,
+ bool kernel);
+#endif /* CONFIG_IPE_PROP_BPF_SIGNATURE */
+
#endif /* _IPE_HOOKS_H */
diff --git a/security/ipe/ipe.c b/security/ipe/ipe.c
index 495bb765de1b8..5af13903287fe 100644
--- a/security/ipe/ipe.c
+++ b/security/ipe/ipe.c
@@ -19,6 +19,9 @@ static struct lsm_blob_sizes ipe_blobs __ro_after_init = {
#ifdef CONFIG_IPE_PROP_FS_VERITY_BUILTIN_SIG
.lbs_inode = sizeof(struct ipe_inode),
#endif /* CONFIG_IPE_PROP_FS_VERITY_BUILTIN_SIG */
+#ifdef CONFIG_IPE_PROP_BPF_SIGNATURE
+ .lbs_bpf_prog = sizeof(struct ipe_bpf_prog),
+#endif /* CONFIG_IPE_PROP_BPF_SIGNATURE */
};
static const struct lsm_id ipe_lsmid = {
@@ -45,6 +48,13 @@ struct ipe_inode *ipe_inode(const struct inode *inode)
}
#endif /* CONFIG_IPE_PROP_FS_VERITY_BUILTIN_SIG */
+#ifdef CONFIG_IPE_PROP_BPF_SIGNATURE
+struct ipe_bpf_prog *ipe_bpf_prog(const struct bpf_prog *prog)
+{
+ return prog->aux->security + ipe_blobs.lbs_bpf_prog;
+}
+#endif /* CONFIG_IPE_PROP_BPF_SIGNATURE */
+
static struct security_hook_list ipe_hooks[] __ro_after_init = {
LSM_HOOK_INIT(bprm_check_security, ipe_bprm_check_security),
LSM_HOOK_INIT(bprm_creds_for_exec, ipe_bprm_creds_for_exec),
@@ -60,6 +70,10 @@ static struct security_hook_list ipe_hooks[] __ro_after_init = {
#ifdef CONFIG_IPE_PROP_FS_VERITY_BUILTIN_SIG
LSM_HOOK_INIT(inode_setintegrity, ipe_inode_setintegrity),
#endif /* CONFIG_IPE_PROP_FS_VERITY_BUILTIN_SIG */
+#ifdef CONFIG_IPE_PROP_BPF_SIGNATURE
+ LSM_HOOK_INIT(bpf_prog_load_post_integrity, ipe_bpf_prog_load_post_integrity),
+ LSM_HOOK_INIT(bpf_prog_load, ipe_bpf_prog_load),
+#endif /* CONFIG_IPE_PROP_BPF_SIGNATURE */
};
/**
diff --git a/security/ipe/ipe.h b/security/ipe/ipe.h
index 25cfdb8f0c20a..47de32b5bc938 100644
--- a/security/ipe/ipe.h
+++ b/security/ipe/ipe.h
@@ -22,6 +22,9 @@ struct ipe_bdev *ipe_bdev(struct block_device *b);
#ifdef CONFIG_IPE_PROP_FS_VERITY_BUILTIN_SIG
struct ipe_inode *ipe_inode(const struct inode *inode);
#endif /* CONFIG_IPE_PROP_FS_VERITY_BUILTIN_SIG */
+#ifdef CONFIG_IPE_PROP_BPF_SIGNATURE
+struct ipe_bpf_prog *ipe_bpf_prog(const struct bpf_prog *prog);
+#endif /* CONFIG_IPE_PROP_BPF_SIGNATURE */
int ipe_init_securityfs(void);
diff --git a/security/ipe/policy.h b/security/ipe/policy.h
index 5bfbdbddeef86..748bea92beb19 100644
--- a/security/ipe/policy.h
+++ b/security/ipe/policy.h
@@ -17,6 +17,7 @@ enum ipe_op_type {
IPE_OP_KEXEC_INITRAMFS,
IPE_OP_POLICY,
IPE_OP_X509,
+ IPE_OP_BPF_PROG_LOAD,
__IPE_OP_MAX,
};
@@ -39,6 +40,19 @@ enum ipe_prop_type {
IPE_PROP_FSV_DIGEST,
IPE_PROP_FSV_SIG_FALSE,
IPE_PROP_FSV_SIG_TRUE,
+ IPE_PROP_BPF_SIG_NONE,
+ IPE_PROP_BPF_SIG_OK,
+ IPE_PROP_BPF_SIG_UNSIGNED,
+ IPE_PROP_BPF_SIG_PARTIALSIG,
+ IPE_PROP_BPF_SIG_UNKNOWNKEY,
+ IPE_PROP_BPF_SIG_UNEXPECTED,
+ IPE_PROP_BPF_SIG_FAULT,
+ IPE_PROP_BPF_SIG_BADSIG,
+ IPE_PROP_BPF_KEYRING_BUILTIN,
+ IPE_PROP_BPF_KEYRING_SECONDARY,
+ IPE_PROP_BPF_KEYRING_PLATFORM,
+ IPE_PROP_BPF_KERNEL_FALSE,
+ IPE_PROP_BPF_KERNEL_TRUE,
__IPE_PROP_MAX
};
diff --git a/security/ipe/policy_parser.c b/security/ipe/policy_parser.c
index 6fa5bebf84714..71f63de56616b 100644
--- a/security/ipe/policy_parser.c
+++ b/security/ipe/policy_parser.c
@@ -237,6 +237,7 @@ static const match_table_t operation_tokens = {
{IPE_OP_KEXEC_INITRAMFS, "op=KEXEC_INITRAMFS"},
{IPE_OP_POLICY, "op=POLICY"},
{IPE_OP_X509, "op=X509_CERT"},
+ {IPE_OP_BPF_PROG_LOAD, "op=BPF_PROG_LOAD"},
{IPE_OP_INVALID, NULL}
};
@@ -281,6 +282,19 @@ static const match_table_t property_tokens = {
{IPE_PROP_FSV_DIGEST, "fsverity_digest=%s"},
{IPE_PROP_FSV_SIG_FALSE, "fsverity_signature=FALSE"},
{IPE_PROP_FSV_SIG_TRUE, "fsverity_signature=TRUE"},
+ {IPE_PROP_BPF_SIG_NONE, "bpf_signature=NONE"},
+ {IPE_PROP_BPF_SIG_OK, "bpf_signature=OK"},
+ {IPE_PROP_BPF_SIG_UNSIGNED, "bpf_signature=UNSIGNED"},
+ {IPE_PROP_BPF_SIG_PARTIALSIG, "bpf_signature=PARTIALSIG"},
+ {IPE_PROP_BPF_SIG_UNKNOWNKEY, "bpf_signature=UNKNOWNKEY"},
+ {IPE_PROP_BPF_SIG_UNEXPECTED, "bpf_signature=UNEXPECTED"},
+ {IPE_PROP_BPF_SIG_FAULT, "bpf_signature=FAULT"},
+ {IPE_PROP_BPF_SIG_BADSIG, "bpf_signature=BADSIG"},
+ {IPE_PROP_BPF_KEYRING_BUILTIN, "bpf_keyring=BUILTIN"},
+ {IPE_PROP_BPF_KEYRING_SECONDARY, "bpf_keyring=SECONDARY"},
+ {IPE_PROP_BPF_KEYRING_PLATFORM, "bpf_keyring=PLATFORM"},
+ {IPE_PROP_BPF_KERNEL_FALSE, "bpf_kernel=FALSE"},
+ {IPE_PROP_BPF_KERNEL_TRUE, "bpf_kernel=TRUE"},
{IPE_PROP_INVALID, NULL}
};
@@ -331,6 +345,19 @@ static int parse_property(char *t, struct ipe_rule *r)
case IPE_PROP_DMV_SIG_TRUE:
case IPE_PROP_FSV_SIG_FALSE:
case IPE_PROP_FSV_SIG_TRUE:
+ case IPE_PROP_BPF_SIG_NONE:
+ case IPE_PROP_BPF_SIG_OK:
+ case IPE_PROP_BPF_SIG_UNSIGNED:
+ case IPE_PROP_BPF_SIG_PARTIALSIG:
+ case IPE_PROP_BPF_SIG_UNKNOWNKEY:
+ case IPE_PROP_BPF_SIG_UNEXPECTED:
+ case IPE_PROP_BPF_SIG_FAULT:
+ case IPE_PROP_BPF_SIG_BADSIG:
+ case IPE_PROP_BPF_KEYRING_BUILTIN:
+ case IPE_PROP_BPF_KEYRING_SECONDARY:
+ case IPE_PROP_BPF_KEYRING_PLATFORM:
+ case IPE_PROP_BPF_KERNEL_FALSE:
+ case IPE_PROP_BPF_KERNEL_TRUE:
p->type = token;
break;
default:
--
2.53.0
^ permalink raw reply related
* Re: [v6 00/10] Reintroduce Hornet LSM
From: Paul Moore @ 2026-05-07 19:19 UTC (permalink / raw)
To: Blaise Boscaccy
Cc: Jonathan Corbet, James Morris, Serge E. Hallyn,
Mickaël Salaün, Günther Noack,
Dr. David Alan Gilbert, Andrew Morton, James.Bottomley, dhowells,
Fan Wu, Ryan Foster, Randy Dunlap, linux-security-module,
linux-doc, linux-kernel, bpf, Song Liu
In-Reply-To: <20260429191431.2345448-1-bboscaccy@linux.microsoft.com>
On Wed, Apr 29, 2026 at 3:14 PM Blaise Boscaccy
<bboscaccy@linux.microsoft.com> wrote:
>
> This patch series introduces the next iteration of the Hornet LSM.
> Hornet’s goal is to provide a secure and extensible in-kernel
> signature verification mechanism for eBPF programs.
I see that Fan identified a few issues that need resolution, but I
just wanted to make sure you've read the expectations for a new LSM.
To be clear, I think you've ticked all the boxes, and there is a
MAINTAINERS entry with your name attached, but I just wanted to make
sure you're okay with maintaining Hornet. I like Hornet, I think it's
a nice and fairly clever solution, but the last thing I need is a new
LSM to maintain :)
https://github.com/LinuxSecurityModule/kernel#new-lsms
--
paul-moore.com
^ permalink raw reply
* Re: [PATCH] Documentation: fix typo and formattting in security/credentials.rst
From: Mayank Gite @ 2026-05-07 19:35 UTC (permalink / raw)
To: Jonathan Corbet
Cc: Randy Dunlap, Paul Moore, Serge Hallyn, Shuah Khan,
linux-security-module, linux-doc, linux-kernel
In-Reply-To: <87pl378417.fsf@trenco.lwn.net>
On Thu, May 07, 2026 at 12:31:48PM -0600, Jonathan Corbet wrote:
> John Doe <drapl0n.kernel@gmail.com> writes:
>
> > Thank you for the feedback and acknowledgment. I thought that since my
> > earlier patch was invalid/corrupted, I should submit a new one rather than
> > version 2.
> >
> > I understand the procedure now and will ensure that future revisions follow
> > the guidelines.
>
> Along those lines, seeing multiple names associated with the same email
> account is a bit disconcerting. We are not generally interested in
> taking patches submitted under pseudonyms...
While configuring mutt, I forgot to change the default placeholder value
for the "realname" variable, resulting in an inconsistent sender name.
I have corrected the configuration and verified that my environment is
now set up appropriately.
Sorry for the confusion.
Mayank
^ permalink raw reply
* Re: [PATCH] ima: debugging late_initcall_sync measurements
From: Yeoreum Yun @ 2026-05-07 20:03 UTC (permalink / raw)
To: Mimi Zohar
Cc: David Safford, Jonathan McDowell, linux-security-module,
linux-kernel, linux-integrity, linux-arm-kernel, kvmarm, paul,
jmorris, serge, roberto.sassu, dmitry.kasatkin, eric.snowberg,
jarkko, jgg, sudeep.holla, maz, oupton, joey.gouly,
suzuki.poulose, yuzenghui, catalin.marinas, will, noodles,
sebastianene
In-Reply-To: <afyIbW5cljh5ZvTL@e129823.arm.com>
Hi Mimi,
>
> > On Thu, 2026-05-07 at 06:50 +0100, Yeoreum Yun wrote:
> > > Hi Mimi,
> > >
> > > > On Wed, 2026-05-06 at 14:57 +0100, Yeoreum Yun wrote:
> > > > > > > > On both Z and PowerVM, there are ~30 measurements between boot_aggregate and
> > > > > > > > boot_aggregate_late. For example, on PowerVM:
> > > > > > > >
> > > > > > > > # grep -n boot_aggregate
> > > > > > > > /sys/kernel/security/integrity/ima/ascii_runtime_measurements
> > > > > > > >
> > > > > > > > 1:10 f60a05d7354fb34aabc02965216abd3428ea52bb ima-sig
> > > > > > > > sha256:9887dd089ee19a6517bca10580b02c1bb9aa6cd86c157b6ead8a1c0403f348d5
> > > > > > > > boot_aggregate
> > > > > > > > 31:10 e2592b0d61da6300d3db447b143897a9792231ea ima-sig
> > > > > > > > sha256:9887dd089ee19a6517bca10580b02c1bb9aa6cd86c157b6ead8a1c0403f348d5
> > > > > > > > boot_aggregate_late
> > > > > > > >
> > > > > > > > It would be interesting to the results from a Raspberry Pi 5 as well,
> > > > > > > > with/without a TPM.
> > > > > > >
> > > > > > > Honestly, I find this result hard to accept.
> > > > > > >
> > > > > > > This effectively means that there is code invoking IMA measurement during late_initcall().
> > > > > > > It also implies that if, in the future, a late_initcall is added that performs
> > > > > > > an IMA measurement before IMA initialization has occurred accoding to order by linker,
> > > > > > > that measurement could be missed.
> > > > > >
> > > > > > Exactly. The results are simply from booting with the builtin "tcb" and
> > > > > > "critical_data" policies.
> > > > > >
> > > > > > $ sudo grubby --args="ima_policy=\"tcb|critical_data\"" --update-kernel
> > > > > > /boot/vmlinuz-${SUFFIX}
> > > > >
> > > > > Thanks. but I still wonder what meaasurements there are between
> > > > > boot_aggregate and boot_aggregate_late.
> > > > > Might be there would be key measurements if it takes more than
> > > > > 5 mins before generating boot_aggregate_late but this seems rare.
> > > > >
> > > > > If you don't mind, would you share the contents of the log between
> > > > > boot_aggregate and boot_aggregate_late?
> > > > > since I only get a kernel_version in my environment.
> > > >
> > > > 1 10 f60a05d7354fb34aabc02965216abd3428ea52bb ima-sig
> > > > sha256:9887dd089ee19a6517bca10580b02c1bb9aa6cd86c157b6ead8a1c0403f348d5
> > > > boot_aggregate
> > > > 2 10 49ab61dd97ea2f759edcb6c6a3387ac67f0aa576 ima-buf
> > > > sha256:0c907aab3261194f16b0c2a422a82f145bc9b9ecb8fdb633fa43e3e5379f0af2
> > > > kernel_version 372e312e302d7263312b
> > > > 3 10 92c40bfd65512d5224cddb9fb64fef0d72e1c182 ima-sig
> > > > sha256:412bae0d0e85a99971d6eda198dd2fed3c2959715e8a17a4caddc7bc605bdeeb
> > > > /usr/bin/kmod
> > > > 4 10 a18f997e1e82d0ef416f93683966d7dda875d71c ima-sig
> > > > sha256:0050fcc672e03cfdc3a50c771ca9f5219478e5538980a26fd4484620712d8163
> > > > /usr/lib64/ld64.so.2
> > > > 5 10 88f343618caeeed92ed8281d627f4565b0499d66 ima-sig
> > > > sha256:a0e83c084d8c227f1150a8cd94eece61f62bc1da30f98d1cf57ca7db241a9c45
> > > > /etc/ld.so.cache
> > > > 6 10 e047868f01908eb95aa180693291decab82bb6be ima-sig
> > > > sha256:42ebf9cc684419de4d8a1d624102716d88fbcf957f47e50a9a08e38b338023ac
> > > > /usr/lib64/libzstd.so.1.5.5
> > > > 7 10 da069bc6a44d454510a76c69d3a54c3b238ae27e ima-sig
> > > > sha256:9b7c788e75c16c8827062016cf15826e43661c4b5b56813ea07ff2635bea2710
> > > > /usr/lib64/liblzma.so.5.6.2
> > > > 8 10 7ade414e736e7b449cda5ec5e0277b99548e89c6 ima-sig
> > > > sha256:d899452e8e6369e436ba1a565833d6dcf0d09c35e40ffc0979cf4de2bdb8f421
> > > > /usr/lib64/libz.so.1.3.1.zlib-ng
> > > > 9 10 9a9da8326f36237a47d6ed21bdffd0e1ff855e2a ima-sig
> > > > sha256:a848f396db7ad135f851b5e9aeb32f4a3ef1439c7913b9b95ab1cda69251f6ad
> > > > /usr/lib64/libcrypto.so.3.5.1
> > > > 10 10 3201d27cd4028f02fc9088ec33e2d0ceb72d2c5b ima-sig
> > > > sha256:e52dcd1850555c08d60fefe56694c1179b4eaa5796db0907606552ece8e1bab1
> > > > /usr/lib64/libgcc_s-14-20250617.so.1
> > > > 11 10 3b4c6f13e52ca060b290709f737b1ff66564226f ima-sig
> > > > sha256:f2a900a5b980b289dc028dd3caab16b1b0ad037f2e875546bb3197d23ff241f0
> > > > /usr/lib64/glibc-hwcaps/power10/libc.so.6
> > > > 12 10 b23b616cbd3c9dc4c5743d121c1c5a702b461a9c ima-sig
> > > > sha256:5a682022beeea9ee7f36a70f0465942bf32e9675d3f45355088e148787e02175
> > > > /usr/lib/modprobe.d/dist-alsa.conf
> > > > 13 10 aec07fad18697f295d7e06796fc8dfd3b472f9c3 ima-sig
> > > > sha256:067d949bab3bb085d0936031881ff73b2ab39f34b9a90cbd01396d1987ff6658
> > > > /usr/lib/modprobe.d/dist-blacklist.conf
> > > > 14 10 c402c56b66e65914148efd6e3cf0b1d616daabe6 ima-sig
> > > > sha256:120a02e9b88ba74949224eca7385825e39880f5687f739ade07d94ee22ffe325
> > > > /etc/modprobe.d/firewalld-sysctls.conf
> > > > 15 10 e358ca12bd58e1ce4845e299e1aea8b81edf86f9 ima-sig
> > > > sha256:fa27abcd357a16ee1254ba38d1225b7f0724036c07ce3d0e83b29eb72d97c419
> > > > /etc/modprobe.d/l2tp_eth-blacklist.conf
> > > > 16 10 4b036d41435d7df3a72b38880f5fe231904b7b66 ima-sig
> > > > sha256:ecf5f948bfbfb726879a910b3174d139c8af6b1745c88dcc1e4a1cf532c02299
> > > > /etc/modprobe.d/l2tp_ip-blacklist.conf
> > > > 17 10 9c53a7a48c1b5218417c4f25c4a34c09a9f39830 ima-sig
> > > > sha256:f76c4ac232d5e96c57961a9f10194703b4df6d119530046f0b23eee70bfcb089
> > > > /etc/modprobe.d/l2tp_ip6-blacklist.conf
> > > > 18 10 6c41d7b7d251c400b7e0ba76f7b386a746e8f4ec ima-sig
> > > > sha256:5cbc958f893a599ef19437014696dd7b112cf9af6a4348830177f8a8f78aa1b3
> > > > /etc/modprobe.d/l2tp_netlink-blacklist.conf
> > > > 19 10 f37ef48faef5bc51e29d47531726af0bd0654655 ima-sig
> > > > sha256:7a3d63acb49e4a69b482f26624761b5778fbd6b77be8a3f36926b379b5f965ed
> > > > /etc/modprobe.d/l2tp_ppp-blacklist.conf
> > > > 20 10 82ef59779acdfd6e9b35521bfa09e6ba86fd6174 ima-sig
> > > > sha256:6a8f2009d87deba7a2de46e3d0c46b114fe388d188b00b9a382fc2156aabb676
> > > > /usr/lib/modules/7.1.0-rc1+/modules.softdep
> > > > 21 10 6ae994e33a6313ab4535da90f5cb6c3beaec7b86 ima-sig
> > > > sha256:268695dbf23bd0170ec9a95b10e8d596205fd7436617d10101907171bf004b7c
> > > > /etc/modprobe.d/sctp-blacklist.conf
> > > > 22 10 b2c238ae66b03f56191d9955a5ad0f3110bb7e2b ima-sig
> > > > sha256:64a8ebb0a1fd712a9aeb7aa0f0ad0b72d3277034c8bfa3b66ab063e201d6527e
> > > > /etc/modprobe.d/sctp_diag-blacklist.conf
> > > > 23 10 c0443f2d3c078959ae86276df23abe172234a55d ima-sig
> > > > sha256:e5a3958cbd3684b63f3cada6604469cc56f727b106d5524daf5aefa6935a48ce
> > > > /usr/lib/modprobe.d/systemd.conf
> > > > 24 10 5c46e012bc7fffc3256b166282a7eaa4bea5fa33 ima-sig
> > > > sha256:6560abcdd2cdb41e1d0fe73052298d612920d5bccb4a3a7c82bc73895128e760
> > > > /etc/modprobe.d/tipc_diag-blacklist.conf
> > > > 25 10 d5fb1836364732fbc4f87aa7d2c984cf30bdbfd3 ima-sig
> > > > sha256:358703c09ac2d2c653e11bbc7c65d378c8496e87ca47307f86c36b0b29640598
> > > > /etc/modprobe.d/tuned.conf
> > > > 26 10 a85107163729f696f316d46c0bf3f65f713ba972 ima-sig
> > > > sha256:7410bb4cec56892e8b0010c5c8b72be532784ccf0240aa0677c5be085a530f65
> > > > /usr/lib/modules/7.1.0-rc1+/modules.dep.bin
> > > > 27 10 80eb261ffb2cc3528d90c33b1c624f657a045867 ima-sig
> > > > sha256:856e0f083226f8b4fb7d1d71447fb841dae18ea9a50ea6d8505a206167288e1d
> > > > /usr/lib/modules/7.1.0-rc1+/modules.alias.bin
> > > > 28 10 6af2d661da470d7a1c9909ddbc074d3d265eb1d7 ima-sig
> > > > sha256:4853ca200598c52970c380fda99484068e7db4961a4f94faac6abcfbbd52d150
> > > > /usr/lib/modules/7.1.0-rc1+/modules.symbols.bin
> > > > 29 10 6f9cd405bd57d925baae6ae66c273c61c90b3bc8 ima-sig
> > > > sha256:193d1e1004848f7d391877507b69a7953e1f94ddbe70eb0e2cf6dc45fce7cd6a
> > > > /usr/lib/modules/7.1.0-rc1+/modules.builtin.alias.bin
> > > > 30 10 4e20b980bf3a825a866be0c46033ed654df4aeba ima-sig
> > > > sha256:3a0e3c56d51ba98258ff13f93f82c837de22f4b707d24678f82893babf4d77ea
> > > > /usr/lib/modules/7.1.0-rc1+/modules.builtin.bin
> > > > 31 10 e2592b0d61da6300d3db447b143897a9792231ea ima-sig
> > > > sha256:9887dd089ee19a6517bca10580b02c1bb9aa6cd86c157b6ead8a1c0403f348d5
> > > > boot_aggregate_late
> > > > 32 10 81830cd3d799e006698258dc1b11fe29a56eeef5 ima-sig
> > > > sha256:d1651dc50bb5b92c1badcab9aa4dbbca40cb704cdc707d1c536b41d7b1aa465e
> > > > /usr/lib/systemd/systemd
> > >
> > > Hmm... Theses measurements seems to happen while populating
> > > initramfs which triggers file_post_open as async
> > > (and I see the RAMFS seems not part of DONT_MEASURE in IMA).
> > >
> > > If you don't mind, Could you check whether this measurements still
> > > happen with initramfs_async=0?
> > >
> > > If this measurements aren't generated with above option,
> > > there is a question whether let IMA measure initramfs which can be
> > > measured or not depending on boot option or timing (some file measurements
> > > could be lost all when do_populate_rootfs() executes concurrently while
> > > running late_initcall).
> >
> > There's no difference when adding the "initramfs_async=0" boot command line
> > argument. The measurement list between boot_aggregate and boot_aggregate_late is
> > exactly the same.
> >
>
> That's quite interesting. This means there're some file operation
> between late_initcall and late_initcall_sync
> even before run_init_process() except initramfs population.
>
> Since initramfs population flush all fputs() it generates and
> boot_aggregate_late is generated before init process is loaded,
> I honestly have no idea who triggers above file operations.
>
> And it seems a little bit weird that it opens the /usr/bin/kmod
> and its configuration from kernel not by init_process or other user
> process...
>
If you don't mind, Could i get some stacktrace for one of file measurement log
between boot_aggregate and boot_aggreate_late?
Though I try to produce like you, I've gotten failure to reproduce.
Sorry to bother you. but it would be helpful to understand where they
came from.
--
Sincerely,
Yeoreum Yun
^ permalink raw reply
* Re: [PATCH v7 00/10] Reintroduce Hornet LSM
From: Paul Moore @ 2026-05-07 20:57 UTC (permalink / raw)
To: Blaise Boscaccy, linux-crypto
Cc: Jonathan Corbet, James Morris, Serge E. Hallyn,
Mickaël Salaün, Günther Noack,
Dr. David Alan Gilbert, Andrew Morton, James.Bottomley, dhowells,
Fan Wu, Ryan Foster, Randy Dunlap, linux-security-module,
linux-doc, linux-kernel, bpf, Song Liu
In-Reply-To: <20260507191416.2984054-1-bboscaccy@linux.microsoft.com>
On Thu, May 7, 2026 at 3:14 PM Blaise Boscaccy
<bboscaccy@linux.microsoft.com> wrote:
>
> This patch series introduces the next iteration of the Hornet LSM.
> Hornet’s goal is to provide a secure and extensible in-kernel
> signature verification mechanism for eBPF programs.
>
> Hornet addresses concerns from users who require strict audit trails and
> verification guarantees for eBPF programs, especially in
> security-sensitive environments. Many production systems need assurance
> that only authorized, unmodified eBPF programs are loaded into the
> kernel. Hornet provides this assurance through cryptographic signature
> verification.
>
> The currently accepted loader-plus-map signature verification scheme,
> mandated by Alexei and KP, is simple to implement and generally
> acceptable if users and administrators are satisfied with it. However,
> verifying both the loader and the maps offers additional benefits
> beyond verifying the loader alone:
>
> 1. Security and Audit Integrity
>
> A key advantage is that the LSM hook for authorizing BPF program loads
> can operate after signature verification. This ensures:
>
> * Access control decisions are based on verified signature status.
> * Accurate system state measurement and logging.
> * Log entries claiming a verified signature are truthful, avoiding
> misleading records where only the loader was verified while the actual
> BPF program verification occurs later without logging.
>
> 2. TOCTOU Attack Prevention
>
> The current map hash implementation may be vulnerable to a TOCTOU
> attack because it allows unfrozen maps to cache a previously
> calculated hash. The accepted “trusted loader” scheme cannot detect
> this and may permit loading altered maps.
>
> 3. Supply Chain Integrity
>
> Verify that eBPF programs and their associated map data have not been
> modified since they were built and signed, in the kernel proper, may
> aid in protecting against supply chain attacks.
>
> This approach addresses concerns from users who require strict audit
> trails and verification guarantees, especially in security-sensitive
> environments. Map hashes for extended verification are passed via the
> existing PKCS#7 UAPI and verified by the crypto subsystem. Hornet then
> calculates the program’s verification state. Hornet itself does not
> enforce a policy on whether unsigned or partially signed programs
> should be rejected. It delegates that decision to downstream LSMs
> hook, making it a composable building block in a larger security
> architecture.
[NOTE: trimmed changelog for brevity]
> Blaise Boscaccy (6):
> lsm: security: Add additional enum values for bpf integrity checks
> security: Hornet LSM
> hornet: Introduce gen_sig
> hornet: Add a light skeleton data extractor scripts
> selftests/hornet: Add a selftest for the Hornet LSM
> ipe: Add BPF program load policy enforcement via Hornet integration
>
> James Bottomley (3):
> crypto: pkcs7: add flag for validated trust on a signed info block
> crypto: pkcs7: add ability to extract signed attributes by OID
> crypto: pkcs7: add tests for pkcs7_get_authattr
>
> Paul Moore (1):
> lsm: framework for BPF integrity verification
>
> Documentation/admin-guide/LSM/Hornet.rst | 323 +++++++++++++++
> Documentation/admin-guide/LSM/index.rst | 1 +
> Documentation/admin-guide/LSM/ipe.rst | 162 +++++++-
> Documentation/security/ipe.rst | 68 ++++
> MAINTAINERS | 9 +
> certs/system_keyring.c | 1 +
> crypto/asymmetric_keys/Makefile | 4 +-
> crypto/asymmetric_keys/pkcs7_aa.asn1 | 18 +
> crypto/asymmetric_keys/pkcs7_key_type.c | 44 +-
> crypto/asymmetric_keys/pkcs7_parser.c | 81 ++++
> crypto/asymmetric_keys/pkcs7_parser.h | 1 +
> crypto/asymmetric_keys/pkcs7_trust.c | 1 +
> include/crypto/pkcs7.h | 4 +
> include/linux/lsm_hook_defs.h | 5 +
> include/linux/oid_registry.h | 3 +
> include/linux/security.h | 28 ++
> include/uapi/linux/lsm.h | 1 +
> scripts/Makefile | 1 +
> scripts/hornet/Makefile | 5 +
> scripts/hornet/extract-insn.sh | 27 ++
> scripts/hornet/extract-map.sh | 27 ++
> scripts/hornet/extract-skel.sh | 27 ++
> scripts/hornet/gen_sig.c | 401 +++++++++++++++++++
> scripts/hornet/write-sig.sh | 27 ++
> security/Kconfig | 3 +-
> security/Makefile | 1 +
> security/hornet/Kconfig | 13 +
> security/hornet/Makefile | 7 +
> security/hornet/hornet.asn1 | 12 +
> security/hornet/hornet_lsm.c | 352 ++++++++++++++++
> security/ipe/Kconfig | 15 +
> security/ipe/audit.c | 15 +
> security/ipe/eval.c | 93 ++++-
> security/ipe/eval.h | 11 +
> security/ipe/hooks.c | 63 +++
> security/ipe/hooks.h | 15 +
> security/ipe/ipe.c | 14 +
> security/ipe/ipe.h | 3 +
> security/ipe/policy.h | 14 +
> security/ipe/policy_parser.c | 27 ++
> security/security.c | 75 +++-
> tools/testing/selftests/Makefile | 1 +
> tools/testing/selftests/hornet/Makefile | 63 +++
> tools/testing/selftests/hornet/loader.c | 21 +
> tools/testing/selftests/hornet/trivial.bpf.c | 33 ++
> 45 files changed, 2112 insertions(+), 8 deletions(-)
> create mode 100644 Documentation/admin-guide/LSM/Hornet.rst
> create mode 100644 crypto/asymmetric_keys/pkcs7_aa.asn1
> create mode 100644 scripts/hornet/Makefile
> create mode 100755 scripts/hornet/extract-insn.sh
> create mode 100755 scripts/hornet/extract-map.sh
> create mode 100755 scripts/hornet/extract-skel.sh
> create mode 100644 scripts/hornet/gen_sig.c
> create mode 100755 scripts/hornet/write-sig.sh
> create mode 100644 security/hornet/Kconfig
> create mode 100644 security/hornet/Makefile
> create mode 100644 security/hornet/hornet.asn1
> create mode 100644 security/hornet/hornet_lsm.c
> create mode 100644 tools/testing/selftests/hornet/Makefile
> create mode 100644 tools/testing/selftests/hornet/loader.c
> create mode 100644 tools/testing/selftests/hornet/trivial.bpf.c
[NOTE: added the linux-crypto list to the To/CC lines]
Hi crypto folks,
You'll notice there are three patches from James Bottomley in this
patchset that touch crypto code and I'd appreciate it if you could
take a look and either ACK the patches or let James and Blaise know
what you would like changed. James did send these patches to you for
review some time ago, so they aren't necessarily new, but I wanted to
make sure you saw them again.
Unfortunately, it doesn't look like the crypto list was CC'd on this
patchset, so here is a lore link to the patchset as a whole:
https://lore.kernel.org/linux-security-module/20260507191416.2984054-1-bboscaccy@linux.microsoft.com
... and here are lore links to the three crypto patches:
https://lore.kernel.org/linux-security-module/20260507191416.2984054-2-bboscaccy@linux.microsoft.com
https://lore.kernel.org/linux-security-module/20260507191416.2984054-3-bboscaccy@linux.microsoft.com
https://lore.kernel.org/linux-security-module/20260507191416.2984054-4-bboscaccy@linux.microsoft.com
Thanks.
--
paul-moore.com
^ permalink raw reply
* Re: [PATCH] ima: debugging late_initcall_sync measurements
From: Mimi Zohar @ 2026-05-07 21:36 UTC (permalink / raw)
To: Yeoreum Yun
Cc: David Safford, Jonathan McDowell, linux-security-module,
linux-kernel, linux-integrity, linux-arm-kernel, kvmarm, paul,
jmorris, serge, roberto.sassu, dmitry.kasatkin, eric.snowberg,
jarkko, jgg, sudeep.holla, maz, oupton, joey.gouly,
suzuki.poulose, yuzenghui, catalin.marinas, will, noodles,
sebastianene
In-Reply-To: <afzwJn+5r15jFxrB@e129823.arm.com>
On Thu, 2026-05-07 at 21:03 +0100, Yeoreum Yun wrote:
> Hi Mimi,
>
> >
> > > On Thu, 2026-05-07 at 06:50 +0100, Yeoreum Yun wrote:
> > > > Hi Mimi,
> > > >
> > > > > On Wed, 2026-05-06 at 14:57 +0100, Yeoreum Yun wrote:
> > > > > > > > > On both Z and PowerVM, there are ~30 measurements between boot_aggregate and
> > > > > > > > > boot_aggregate_late. For example, on PowerVM:
> > > > > > > > >
> > > > > > > > > # grep -n boot_aggregate
> > > > > > > > > /sys/kernel/security/integrity/ima/ascii_runtime_measurements
> > > > > > > > >
> > > > > > > > > 1:10 f60a05d7354fb34aabc02965216abd3428ea52bb ima-sig
> > > > > > > > > sha256:9887dd089ee19a6517bca10580b02c1bb9aa6cd86c157b6ead8a1c0403f348d5
> > > > > > > > > boot_aggregate
> > > > > > > > > 31:10 e2592b0d61da6300d3db447b143897a9792231ea ima-sig
> > > > > > > > > sha256:9887dd089ee19a6517bca10580b02c1bb9aa6cd86c157b6ead8a1c0403f348d5
> > > > > > > > > boot_aggregate_late
> > > > > > > > >
> > > > > > > > > It would be interesting to the results from a Raspberry Pi 5 as well,
> > > > > > > > > with/without a TPM.
> > > > > > > >
> > > > > > > > Honestly, I find this result hard to accept.
> > > > > > > >
> > > > > > > > This effectively means that there is code invoking IMA measurement during late_initcall().
> > > > > > > > It also implies that if, in the future, a late_initcall is added that performs
> > > > > > > > an IMA measurement before IMA initialization has occurred accoding to order by linker,
> > > > > > > > that measurement could be missed.
> > > > > > >
> > > > > > > Exactly. The results are simply from booting with the builtin "tcb" and
> > > > > > > "critical_data" policies.
> > > > > > >
> > > > > > > $ sudo grubby --args="ima_policy=\"tcb|critical_data\"" --update-kernel
> > > > > > > /boot/vmlinuz-${SUFFIX}
> > > > > >
> > > > > > Thanks. but I still wonder what meaasurements there are between
> > > > > > boot_aggregate and boot_aggregate_late.
> > > > > > Might be there would be key measurements if it takes more than
> > > > > > 5 mins before generating boot_aggregate_late but this seems rare.
> > > > > >
> > > > > > If you don't mind, would you share the contents of the log between
> > > > > > boot_aggregate and boot_aggregate_late?
> > > > > > since I only get a kernel_version in my environment.
> > > > >
> > > > > 1 10 f60a05d7354fb34aabc02965216abd3428ea52bb ima-sig
> > > > > sha256:9887dd089ee19a6517bca10580b02c1bb9aa6cd86c157b6ead8a1c0403f348d5
> > > > > boot_aggregate
> > > > > 2 10 49ab61dd97ea2f759edcb6c6a3387ac67f0aa576 ima-buf
> > > > > sha256:0c907aab3261194f16b0c2a422a82f145bc9b9ecb8fdb633fa43e3e5379f0af2
> > > > > kernel_version 372e312e302d7263312b
> > > > > 3 10 92c40bfd65512d5224cddb9fb64fef0d72e1c182 ima-sig
> > > > > sha256:412bae0d0e85a99971d6eda198dd2fed3c2959715e8a17a4caddc7bc605bdeeb
> > > > > /usr/bin/kmod
> > > > > 4 10 a18f997e1e82d0ef416f93683966d7dda875d71c ima-sig
> > > > > sha256:0050fcc672e03cfdc3a50c771ca9f5219478e5538980a26fd4484620712d8163
> > > > > /usr/lib64/ld64.so.2
> > > > > 5 10 88f343618caeeed92ed8281d627f4565b0499d66 ima-sig
> > > > > sha256:a0e83c084d8c227f1150a8cd94eece61f62bc1da30f98d1cf57ca7db241a9c45
> > > > > /etc/ld.so.cache
> > > > > 6 10 e047868f01908eb95aa180693291decab82bb6be ima-sig
> > > > > sha256:42ebf9cc684419de4d8a1d624102716d88fbcf957f47e50a9a08e38b338023ac
> > > > > /usr/lib64/libzstd.so.1.5.5
> > > > > 7 10 da069bc6a44d454510a76c69d3a54c3b238ae27e ima-sig
> > > > > sha256:9b7c788e75c16c8827062016cf15826e43661c4b5b56813ea07ff2635bea2710
> > > > > /usr/lib64/liblzma.so.5.6.2
> > > > > 8 10 7ade414e736e7b449cda5ec5e0277b99548e89c6 ima-sig
> > > > > sha256:d899452e8e6369e436ba1a565833d6dcf0d09c35e40ffc0979cf4de2bdb8f421
> > > > > /usr/lib64/libz.so.1.3.1.zlib-ng
> > > > > 9 10 9a9da8326f36237a47d6ed21bdffd0e1ff855e2a ima-sig
> > > > > sha256:a848f396db7ad135f851b5e9aeb32f4a3ef1439c7913b9b95ab1cda69251f6ad
> > > > > /usr/lib64/libcrypto.so.3.5.1
> > > > > 10 10 3201d27cd4028f02fc9088ec33e2d0ceb72d2c5b ima-sig
> > > > > sha256:e52dcd1850555c08d60fefe56694c1179b4eaa5796db0907606552ece8e1bab1
> > > > > /usr/lib64/libgcc_s-14-20250617.so.1
> > > > > 11 10 3b4c6f13e52ca060b290709f737b1ff66564226f ima-sig
> > > > > sha256:f2a900a5b980b289dc028dd3caab16b1b0ad037f2e875546bb3197d23ff241f0
> > > > > /usr/lib64/glibc-hwcaps/power10/libc.so.6
> > > > > 12 10 b23b616cbd3c9dc4c5743d121c1c5a702b461a9c ima-sig
> > > > > sha256:5a682022beeea9ee7f36a70f0465942bf32e9675d3f45355088e148787e02175
> > > > > /usr/lib/modprobe.d/dist-alsa.conf
> > > > > 13 10 aec07fad18697f295d7e06796fc8dfd3b472f9c3 ima-sig
> > > > > sha256:067d949bab3bb085d0936031881ff73b2ab39f34b9a90cbd01396d1987ff6658
> > > > > /usr/lib/modprobe.d/dist-blacklist.conf
> > > > > 14 10 c402c56b66e65914148efd6e3cf0b1d616daabe6 ima-sig
> > > > > sha256:120a02e9b88ba74949224eca7385825e39880f5687f739ade07d94ee22ffe325
> > > > > /etc/modprobe.d/firewalld-sysctls.conf
> > > > > 15 10 e358ca12bd58e1ce4845e299e1aea8b81edf86f9 ima-sig
> > > > > sha256:fa27abcd357a16ee1254ba38d1225b7f0724036c07ce3d0e83b29eb72d97c419
> > > > > /etc/modprobe.d/l2tp_eth-blacklist.conf
> > > > > 16 10 4b036d41435d7df3a72b38880f5fe231904b7b66 ima-sig
> > > > > sha256:ecf5f948bfbfb726879a910b3174d139c8af6b1745c88dcc1e4a1cf532c02299
> > > > > /etc/modprobe.d/l2tp_ip-blacklist.conf
> > > > > 17 10 9c53a7a48c1b5218417c4f25c4a34c09a9f39830 ima-sig
> > > > > sha256:f76c4ac232d5e96c57961a9f10194703b4df6d119530046f0b23eee70bfcb089
> > > > > /etc/modprobe.d/l2tp_ip6-blacklist.conf
> > > > > 18 10 6c41d7b7d251c400b7e0ba76f7b386a746e8f4ec ima-sig
> > > > > sha256:5cbc958f893a599ef19437014696dd7b112cf9af6a4348830177f8a8f78aa1b3
> > > > > /etc/modprobe.d/l2tp_netlink-blacklist.conf
> > > > > 19 10 f37ef48faef5bc51e29d47531726af0bd0654655 ima-sig
> > > > > sha256:7a3d63acb49e4a69b482f26624761b5778fbd6b77be8a3f36926b379b5f965ed
> > > > > /etc/modprobe.d/l2tp_ppp-blacklist.conf
> > > > > 20 10 82ef59779acdfd6e9b35521bfa09e6ba86fd6174 ima-sig
> > > > > sha256:6a8f2009d87deba7a2de46e3d0c46b114fe388d188b00b9a382fc2156aabb676
> > > > > /usr/lib/modules/7.1.0-rc1+/modules.softdep
> > > > > 21 10 6ae994e33a6313ab4535da90f5cb6c3beaec7b86 ima-sig
> > > > > sha256:268695dbf23bd0170ec9a95b10e8d596205fd7436617d10101907171bf004b7c
> > > > > /etc/modprobe.d/sctp-blacklist.conf
> > > > > 22 10 b2c238ae66b03f56191d9955a5ad0f3110bb7e2b ima-sig
> > > > > sha256:64a8ebb0a1fd712a9aeb7aa0f0ad0b72d3277034c8bfa3b66ab063e201d6527e
> > > > > /etc/modprobe.d/sctp_diag-blacklist.conf
> > > > > 23 10 c0443f2d3c078959ae86276df23abe172234a55d ima-sig
> > > > > sha256:e5a3958cbd3684b63f3cada6604469cc56f727b106d5524daf5aefa6935a48ce
> > > > > /usr/lib/modprobe.d/systemd.conf
> > > > > 24 10 5c46e012bc7fffc3256b166282a7eaa4bea5fa33 ima-sig
> > > > > sha256:6560abcdd2cdb41e1d0fe73052298d612920d5bccb4a3a7c82bc73895128e760
> > > > > /etc/modprobe.d/tipc_diag-blacklist.conf
> > > > > 25 10 d5fb1836364732fbc4f87aa7d2c984cf30bdbfd3 ima-sig
> > > > > sha256:358703c09ac2d2c653e11bbc7c65d378c8496e87ca47307f86c36b0b29640598
> > > > > /etc/modprobe.d/tuned.conf
> > > > > 26 10 a85107163729f696f316d46c0bf3f65f713ba972 ima-sig
> > > > > sha256:7410bb4cec56892e8b0010c5c8b72be532784ccf0240aa0677c5be085a530f65
> > > > > /usr/lib/modules/7.1.0-rc1+/modules.dep.bin
> > > > > 27 10 80eb261ffb2cc3528d90c33b1c624f657a045867 ima-sig
> > > > > sha256:856e0f083226f8b4fb7d1d71447fb841dae18ea9a50ea6d8505a206167288e1d
> > > > > /usr/lib/modules/7.1.0-rc1+/modules.alias.bin
> > > > > 28 10 6af2d661da470d7a1c9909ddbc074d3d265eb1d7 ima-sig
> > > > > sha256:4853ca200598c52970c380fda99484068e7db4961a4f94faac6abcfbbd52d150
> > > > > /usr/lib/modules/7.1.0-rc1+/modules.symbols.bin
> > > > > 29 10 6f9cd405bd57d925baae6ae66c273c61c90b3bc8 ima-sig
> > > > > sha256:193d1e1004848f7d391877507b69a7953e1f94ddbe70eb0e2cf6dc45fce7cd6a
> > > > > /usr/lib/modules/7.1.0-rc1+/modules.builtin.alias.bin
> > > > > 30 10 4e20b980bf3a825a866be0c46033ed654df4aeba ima-sig
> > > > > sha256:3a0e3c56d51ba98258ff13f93f82c837de22f4b707d24678f82893babf4d77ea
> > > > > /usr/lib/modules/7.1.0-rc1+/modules.builtin.bin
> > > > > 31 10 e2592b0d61da6300d3db447b143897a9792231ea ima-sig
> > > > > sha256:9887dd089ee19a6517bca10580b02c1bb9aa6cd86c157b6ead8a1c0403f348d5
> > > > > boot_aggregate_late
> > > > > 32 10 81830cd3d799e006698258dc1b11fe29a56eeef5 ima-sig
> > > > > sha256:d1651dc50bb5b92c1badcab9aa4dbbca40cb704cdc707d1c536b41d7b1aa465e
> > > > > /usr/lib/systemd/systemd
> > > >
> > > > Hmm... Theses measurements seems to happen while populating
> > > > initramfs which triggers file_post_open as async
> > > > (and I see the RAMFS seems not part of DONT_MEASURE in IMA).
> > > >
> > > > If you don't mind, Could you check whether this measurements still
> > > > happen with initramfs_async=0?
> > > >
> > > > If this measurements aren't generated with above option,
> > > > there is a question whether let IMA measure initramfs which can be
> > > > measured or not depending on boot option or timing (some file measurements
> > > > could be lost all when do_populate_rootfs() executes concurrently while
> > > > running late_initcall).
> > >
> > > There's no difference when adding the "initramfs_async=0" boot command line
> > > argument. The measurement list between boot_aggregate and boot_aggregate_late is
> > > exactly the same.
> > >
> >
> > That's quite interesting. This means there're some file operation
> > between late_initcall and late_initcall_sync
> > even before run_init_process() except initramfs population.
> >
> > Since initramfs population flush all fputs() it generates and
> > boot_aggregate_late is generated before init process is loaded,
> > I honestly have no idea who triggers above file operations.
> >
> > And it seems a little bit weird that it opens the /usr/bin/kmod
> > and its configuration from kernel not by init_process or other user
> > process...
> >
>
> If you don't mind, Could i get some stacktrace for one of file measurement log
> between boot_aggregate and boot_aggreate_late?
>
> Though I try to produce like you, I've gotten failure to reproduce.
> Sorry to bother you. but it would be helpful to understand where they
> came from.
The kernel selftests caused the measurements between late_initcall and
late_initcall_sync. After disabling all of the kernel selftests, there weren't
any measurements. Re-enabling the FIPS selftests on PowerVM LPAR resulted in
measurements. (I didn't try re-enabling any of the other selftests.)
CONFIG_FIPS_SIGNATURE_SELFTEST=y
CONFIG_FIPS_SIGNATURE_SELFTEST_RSA=y
CONFIG_FIPS_SIGNATURE_SELFTEST_ECDSA=y
Mimi
^ permalink raw reply
* Re: [PATCH v7 00/10] Reintroduce Hornet LSM
From: Eric Biggers @ 2026-05-07 21:58 UTC (permalink / raw)
To: Paul Moore
Cc: Blaise Boscaccy, linux-crypto, Jonathan Corbet, James Morris,
Serge E. Hallyn, Mickaël Salaün, Günther Noack,
Dr. David Alan Gilbert, Andrew Morton, James.Bottomley, dhowells,
Fan Wu, Ryan Foster, Randy Dunlap, linux-security-module,
linux-doc, linux-kernel, bpf, Song Liu
In-Reply-To: <CAHC9VhR0Abikk+2=DWtVu1cEkcwkudKFRJH51BFOh0Qt01wLJw@mail.gmail.com>
On Thu, May 07, 2026 at 04:57:35PM -0400, Paul Moore wrote:
> On Thu, May 7, 2026 at 3:14 PM Blaise Boscaccy
> <bboscaccy@linux.microsoft.com> wrote:
> >
> > This patch series introduces the next iteration of the Hornet LSM.
> > Hornet’s goal is to provide a secure and extensible in-kernel
> > signature verification mechanism for eBPF programs.
> >
> > Hornet addresses concerns from users who require strict audit trails and
> > verification guarantees for eBPF programs, especially in
> > security-sensitive environments. Many production systems need assurance
> > that only authorized, unmodified eBPF programs are loaded into the
> > kernel. Hornet provides this assurance through cryptographic signature
> > verification.
> >
> > The currently accepted loader-plus-map signature verification scheme,
> > mandated by Alexei and KP, is simple to implement and generally
> > acceptable if users and administrators are satisfied with it. However,
> > verifying both the loader and the maps offers additional benefits
> > beyond verifying the loader alone:
> >
> > 1. Security and Audit Integrity
> >
> > A key advantage is that the LSM hook for authorizing BPF program loads
> > can operate after signature verification. This ensures:
> >
> > * Access control decisions are based on verified signature status.
> > * Accurate system state measurement and logging.
> > * Log entries claiming a verified signature are truthful, avoiding
> > misleading records where only the loader was verified while the actual
> > BPF program verification occurs later without logging.
> >
> > 2. TOCTOU Attack Prevention
> >
> > The current map hash implementation may be vulnerable to a TOCTOU
> > attack because it allows unfrozen maps to cache a previously
> > calculated hash. The accepted “trusted loader” scheme cannot detect
> > this and may permit loading altered maps.
> >
> > 3. Supply Chain Integrity
> >
> > Verify that eBPF programs and their associated map data have not been
> > modified since they were built and signed, in the kernel proper, may
> > aid in protecting against supply chain attacks.
> >
> > This approach addresses concerns from users who require strict audit
> > trails and verification guarantees, especially in security-sensitive
> > environments. Map hashes for extended verification are passed via the
> > existing PKCS#7 UAPI and verified by the crypto subsystem. Hornet then
> > calculates the program’s verification state. Hornet itself does not
> > enforce a policy on whether unsigned or partially signed programs
> > should be rejected. It delegates that decision to downstream LSMs
> > hook, making it a composable building block in a larger security
> > architecture.
>
> [NOTE: trimmed changelog for brevity]
>
> > Blaise Boscaccy (6):
> > lsm: security: Add additional enum values for bpf integrity checks
> > security: Hornet LSM
> > hornet: Introduce gen_sig
> > hornet: Add a light skeleton data extractor scripts
> > selftests/hornet: Add a selftest for the Hornet LSM
> > ipe: Add BPF program load policy enforcement via Hornet integration
> >
> > James Bottomley (3):
> > crypto: pkcs7: add flag for validated trust on a signed info block
> > crypto: pkcs7: add ability to extract signed attributes by OID
> > crypto: pkcs7: add tests for pkcs7_get_authattr
> >
> > Paul Moore (1):
> > lsm: framework for BPF integrity verification
> >
> > Documentation/admin-guide/LSM/Hornet.rst | 323 +++++++++++++++
> > Documentation/admin-guide/LSM/index.rst | 1 +
> > Documentation/admin-guide/LSM/ipe.rst | 162 +++++++-
> > Documentation/security/ipe.rst | 68 ++++
> > MAINTAINERS | 9 +
> > certs/system_keyring.c | 1 +
> > crypto/asymmetric_keys/Makefile | 4 +-
> > crypto/asymmetric_keys/pkcs7_aa.asn1 | 18 +
> > crypto/asymmetric_keys/pkcs7_key_type.c | 44 +-
> > crypto/asymmetric_keys/pkcs7_parser.c | 81 ++++
> > crypto/asymmetric_keys/pkcs7_parser.h | 1 +
> > crypto/asymmetric_keys/pkcs7_trust.c | 1 +
> > include/crypto/pkcs7.h | 4 +
> > include/linux/lsm_hook_defs.h | 5 +
> > include/linux/oid_registry.h | 3 +
> > include/linux/security.h | 28 ++
> > include/uapi/linux/lsm.h | 1 +
> > scripts/Makefile | 1 +
> > scripts/hornet/Makefile | 5 +
> > scripts/hornet/extract-insn.sh | 27 ++
> > scripts/hornet/extract-map.sh | 27 ++
> > scripts/hornet/extract-skel.sh | 27 ++
> > scripts/hornet/gen_sig.c | 401 +++++++++++++++++++
> > scripts/hornet/write-sig.sh | 27 ++
> > security/Kconfig | 3 +-
> > security/Makefile | 1 +
> > security/hornet/Kconfig | 13 +
> > security/hornet/Makefile | 7 +
> > security/hornet/hornet.asn1 | 12 +
> > security/hornet/hornet_lsm.c | 352 ++++++++++++++++
> > security/ipe/Kconfig | 15 +
> > security/ipe/audit.c | 15 +
> > security/ipe/eval.c | 93 ++++-
> > security/ipe/eval.h | 11 +
> > security/ipe/hooks.c | 63 +++
> > security/ipe/hooks.h | 15 +
> > security/ipe/ipe.c | 14 +
> > security/ipe/ipe.h | 3 +
> > security/ipe/policy.h | 14 +
> > security/ipe/policy_parser.c | 27 ++
> > security/security.c | 75 +++-
> > tools/testing/selftests/Makefile | 1 +
> > tools/testing/selftests/hornet/Makefile | 63 +++
> > tools/testing/selftests/hornet/loader.c | 21 +
> > tools/testing/selftests/hornet/trivial.bpf.c | 33 ++
> > 45 files changed, 2112 insertions(+), 8 deletions(-)
> > create mode 100644 Documentation/admin-guide/LSM/Hornet.rst
> > create mode 100644 crypto/asymmetric_keys/pkcs7_aa.asn1
> > create mode 100644 scripts/hornet/Makefile
> > create mode 100755 scripts/hornet/extract-insn.sh
> > create mode 100755 scripts/hornet/extract-map.sh
> > create mode 100755 scripts/hornet/extract-skel.sh
> > create mode 100644 scripts/hornet/gen_sig.c
> > create mode 100755 scripts/hornet/write-sig.sh
> > create mode 100644 security/hornet/Kconfig
> > create mode 100644 security/hornet/Makefile
> > create mode 100644 security/hornet/hornet.asn1
> > create mode 100644 security/hornet/hornet_lsm.c
> > create mode 100644 tools/testing/selftests/hornet/Makefile
> > create mode 100644 tools/testing/selftests/hornet/loader.c
> > create mode 100644 tools/testing/selftests/hornet/trivial.bpf.c
>
> [NOTE: added the linux-crypto list to the To/CC lines]
>
> Hi crypto folks,
>
> You'll notice there are three patches from James Bottomley in this
> patchset that touch crypto code and I'd appreciate it if you could
> take a look and either ACK the patches or let James and Blaise know
> what you would like changed. James did send these patches to you for
> review some time ago, so they aren't necessarily new, but I wanted to
> make sure you saw them again.
>
> Unfortunately, it doesn't look like the crypto list was CC'd on this
> patchset, so here is a lore link to the patchset as a whole:
>
> https://lore.kernel.org/linux-security-module/20260507191416.2984054-1-bboscaccy@linux.microsoft.com
>
> ... and here are lore links to the three crypto patches:
We discussed before how the actual signature check seemed to have been
overlooked in some cases, due to the complexities of PKCS#7
(https://lore.kernel.org/r/20260305185016.GC2796@quark/). Looks like
that was fixed. It is really hard to do any meaningful review of a
PKCS#7 based system, though. And it sounds like this one is proceeding
anyway due to some requirement to be compatible with an existing PKCS#7
based system. So I'm not sure what you're looking for.
- Eric
^ permalink raw reply
* Re: [PATCH v4 0/7] landlock: Add UDP access control support
From: Matthieu Buffet @ 2026-05-07 22:11 UTC (permalink / raw)
To: Günther Noack
Cc: Mickaël Salaün, linux-security-module, Mikhail Ivanov,
konstantin.meskhidze, Tingmao Wang, netdev
In-Reply-To: <aftfVvru3npQ9kWq@google.com>
Hi Günther,
On 5/6/2026 5:33 PM, Günther Noack wrote:
> For the final revision, I think it would be good to squash the two
> commits that are about LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP. That
> reduces the chances that someone backports the first but not the
> second to one of the distribution kernels.
I did indeed split the implementation of that access right into two
commits, 100% to ease reading each part of the change semi-independently
for reviewers. It can/should indeed be squashed without losing anything.
--
Matthieu
^ permalink raw reply
* [GIT PULL] selinux/selinux-pr-20260507
From: Paul Moore @ 2026-05-07 22:16 UTC (permalink / raw)
To: Linus Torvalds; +Cc: selinux, linux-security-module, linux-kernel
Linus,
We've got five more SELinux patches to fix a few problems, quick summary
below:
- Allow for multiple opens of /sys/fs/selinux/policy
Prevents a single process from blocking others from reading the
SELinux policy loaded in the kernel. This does have the side effect
of potentially allowing userspace to trigger additional kernel memory
allocations as part of the open/read operation, but this is mitigated
by requiring the SELinux security/read_policy permission.
- Reduce the critical sections where the SELinux policy mutex is held
This includes the patch to the policy loader code where we move the
permission checks and an allocation outside the mutex as well as the
the patch to checkreqprot which drops the code/lock entirely. While
the checkreqprot code had effectively been dropped in an earlier release,
portions of the code still remained that would have triggered the mutex
to perform an IMA measurement. This pull request finally drops all of
that while preserving the user visible behavior.
- Eliminate potential sources of log spamming
There were a few areas where processes could flood the system logs and
hide other, more critical events. The previously disabled checkreqprot
and runtime disable knobs in selinuxfs were two such areas that have now
been greatly simplified and a pr_err() replaced with a pr_err_once(). The
third such place is the /sys/fs/selinux/user file, which hasn't been
used by a userspace release since 2020 and was scheduled for removal
after 2025; this pull request effectively disables this functionality,
but similar to checkreqprot, it is done in a way that should not break
old userspace.
Paul
--
The following changes since commit 7fd2df204f342fc17d1a0bfcd474b24232fb0f32:
Linux 7.1-rc2 (2026-05-03 14:21:25 -0700)
are available in the Git repository at:
https://git.kernel.org/pub/scm/linux/kernel/git/pcmoore/selinux.git
tags/selinux-pr-20260507
for you to fetch changes up to 868f31e4061eca8c3cd607d79d954d5e54f204aa:
selinux: shrink critical section in sel_write_load()
(2026-05-05 16:02:28 -0400)
----------------------------------------------------------------
selinux/stable-7.1 PR 20260507
----------------------------------------------------------------
Stephen Smalley (5):
selinux: prune /sys/fs/selinux/checkreqprot
selinux: prune /sys/fs/selinux/disable
selinux: prune /sys/fs/selinux/user
selinux: allow multiple opens of /sys/fs/selinux/policy
selinux: shrink critical section in sel_write_load()
security/selinux/include/security.h | 2
security/selinux/selinuxfs.c | 196 ++++------------------------
security/selinux/ss/services.c | 125 -----------------
3 files changed, 31 insertions(+), 292 deletions(-)
--
paul-moore.com
^ permalink raw reply
* Re: [PATCH v7 00/10] Reintroduce Hornet LSM
From: Paul Moore @ 2026-05-07 22:22 UTC (permalink / raw)
To: Eric Biggers
Cc: Blaise Boscaccy, linux-crypto, Jonathan Corbet, James Morris,
Serge E. Hallyn, Mickaël Salaün, Günther Noack,
Dr. David Alan Gilbert, Andrew Morton, James.Bottomley, dhowells,
Fan Wu, Ryan Foster, Randy Dunlap, linux-security-module,
linux-doc, linux-kernel, bpf, Song Liu
In-Reply-To: <20260507215841.GA440717@google.com>
On Thu, May 7, 2026 at 5:58 PM Eric Biggers <ebiggers@kernel.org> wrote:
> On Thu, May 07, 2026 at 04:57:35PM -0400, Paul Moore wrote:
> > On Thu, May 7, 2026 at 3:14 PM Blaise Boscaccy
> > <bboscaccy@linux.microsoft.com> wrote:
> > >
> > > This patch series introduces the next iteration of the Hornet LSM.
> > > Hornet’s goal is to provide a secure and extensible in-kernel
> > > signature verification mechanism for eBPF programs.
> > >
> > > Hornet addresses concerns from users who require strict audit trails and
> > > verification guarantees for eBPF programs, especially in
> > > security-sensitive environments. Many production systems need assurance
> > > that only authorized, unmodified eBPF programs are loaded into the
> > > kernel. Hornet provides this assurance through cryptographic signature
> > > verification.
> > >
> > > The currently accepted loader-plus-map signature verification scheme,
> > > mandated by Alexei and KP, is simple to implement and generally
> > > acceptable if users and administrators are satisfied with it. However,
> > > verifying both the loader and the maps offers additional benefits
> > > beyond verifying the loader alone:
> > >
> > > 1. Security and Audit Integrity
> > >
> > > A key advantage is that the LSM hook for authorizing BPF program loads
> > > can operate after signature verification. This ensures:
> > >
> > > * Access control decisions are based on verified signature status.
> > > * Accurate system state measurement and logging.
> > > * Log entries claiming a verified signature are truthful, avoiding
> > > misleading records where only the loader was verified while the actual
> > > BPF program verification occurs later without logging.
> > >
> > > 2. TOCTOU Attack Prevention
> > >
> > > The current map hash implementation may be vulnerable to a TOCTOU
> > > attack because it allows unfrozen maps to cache a previously
> > > calculated hash. The accepted “trusted loader” scheme cannot detect
> > > this and may permit loading altered maps.
> > >
> > > 3. Supply Chain Integrity
> > >
> > > Verify that eBPF programs and their associated map data have not been
> > > modified since they were built and signed, in the kernel proper, may
> > > aid in protecting against supply chain attacks.
> > >
> > > This approach addresses concerns from users who require strict audit
> > > trails and verification guarantees, especially in security-sensitive
> > > environments. Map hashes for extended verification are passed via the
> > > existing PKCS#7 UAPI and verified by the crypto subsystem. Hornet then
> > > calculates the program’s verification state. Hornet itself does not
> > > enforce a policy on whether unsigned or partially signed programs
> > > should be rejected. It delegates that decision to downstream LSMs
> > > hook, making it a composable building block in a larger security
> > > architecture.
> >
> > [NOTE: trimmed changelog for brevity]
> >
> > > Blaise Boscaccy (6):
> > > lsm: security: Add additional enum values for bpf integrity checks
> > > security: Hornet LSM
> > > hornet: Introduce gen_sig
> > > hornet: Add a light skeleton data extractor scripts
> > > selftests/hornet: Add a selftest for the Hornet LSM
> > > ipe: Add BPF program load policy enforcement via Hornet integration
> > >
> > > James Bottomley (3):
> > > crypto: pkcs7: add flag for validated trust on a signed info block
> > > crypto: pkcs7: add ability to extract signed attributes by OID
> > > crypto: pkcs7: add tests for pkcs7_get_authattr
> > >
> > > Paul Moore (1):
> > > lsm: framework for BPF integrity verification
> > >
> > > Documentation/admin-guide/LSM/Hornet.rst | 323 +++++++++++++++
> > > Documentation/admin-guide/LSM/index.rst | 1 +
> > > Documentation/admin-guide/LSM/ipe.rst | 162 +++++++-
> > > Documentation/security/ipe.rst | 68 ++++
> > > MAINTAINERS | 9 +
> > > certs/system_keyring.c | 1 +
> > > crypto/asymmetric_keys/Makefile | 4 +-
> > > crypto/asymmetric_keys/pkcs7_aa.asn1 | 18 +
> > > crypto/asymmetric_keys/pkcs7_key_type.c | 44 +-
> > > crypto/asymmetric_keys/pkcs7_parser.c | 81 ++++
> > > crypto/asymmetric_keys/pkcs7_parser.h | 1 +
> > > crypto/asymmetric_keys/pkcs7_trust.c | 1 +
> > > include/crypto/pkcs7.h | 4 +
> > > include/linux/lsm_hook_defs.h | 5 +
> > > include/linux/oid_registry.h | 3 +
> > > include/linux/security.h | 28 ++
> > > include/uapi/linux/lsm.h | 1 +
> > > scripts/Makefile | 1 +
> > > scripts/hornet/Makefile | 5 +
> > > scripts/hornet/extract-insn.sh | 27 ++
> > > scripts/hornet/extract-map.sh | 27 ++
> > > scripts/hornet/extract-skel.sh | 27 ++
> > > scripts/hornet/gen_sig.c | 401 +++++++++++++++++++
> > > scripts/hornet/write-sig.sh | 27 ++
> > > security/Kconfig | 3 +-
> > > security/Makefile | 1 +
> > > security/hornet/Kconfig | 13 +
> > > security/hornet/Makefile | 7 +
> > > security/hornet/hornet.asn1 | 12 +
> > > security/hornet/hornet_lsm.c | 352 ++++++++++++++++
> > > security/ipe/Kconfig | 15 +
> > > security/ipe/audit.c | 15 +
> > > security/ipe/eval.c | 93 ++++-
> > > security/ipe/eval.h | 11 +
> > > security/ipe/hooks.c | 63 +++
> > > security/ipe/hooks.h | 15 +
> > > security/ipe/ipe.c | 14 +
> > > security/ipe/ipe.h | 3 +
> > > security/ipe/policy.h | 14 +
> > > security/ipe/policy_parser.c | 27 ++
> > > security/security.c | 75 +++-
> > > tools/testing/selftests/Makefile | 1 +
> > > tools/testing/selftests/hornet/Makefile | 63 +++
> > > tools/testing/selftests/hornet/loader.c | 21 +
> > > tools/testing/selftests/hornet/trivial.bpf.c | 33 ++
> > > 45 files changed, 2112 insertions(+), 8 deletions(-)
> > > create mode 100644 Documentation/admin-guide/LSM/Hornet.rst
> > > create mode 100644 crypto/asymmetric_keys/pkcs7_aa.asn1
> > > create mode 100644 scripts/hornet/Makefile
> > > create mode 100755 scripts/hornet/extract-insn.sh
> > > create mode 100755 scripts/hornet/extract-map.sh
> > > create mode 100755 scripts/hornet/extract-skel.sh
> > > create mode 100644 scripts/hornet/gen_sig.c
> > > create mode 100755 scripts/hornet/write-sig.sh
> > > create mode 100644 security/hornet/Kconfig
> > > create mode 100644 security/hornet/Makefile
> > > create mode 100644 security/hornet/hornet.asn1
> > > create mode 100644 security/hornet/hornet_lsm.c
> > > create mode 100644 tools/testing/selftests/hornet/Makefile
> > > create mode 100644 tools/testing/selftests/hornet/loader.c
> > > create mode 100644 tools/testing/selftests/hornet/trivial.bpf.c
> >
> > [NOTE: added the linux-crypto list to the To/CC lines]
> >
> > Hi crypto folks,
> >
> > You'll notice there are three patches from James Bottomley in this
> > patchset that touch crypto code and I'd appreciate it if you could
> > take a look and either ACK the patches or let James and Blaise know
> > what you would like changed. James did send these patches to you for
> > review some time ago, so they aren't necessarily new, but I wanted to
> > make sure you saw them again.
> >
> > Unfortunately, it doesn't look like the crypto list was CC'd on this
> > patchset, so here is a lore link to the patchset as a whole:
> >
> > https://lore.kernel.org/linux-security-module/20260507191416.2984054-1-bboscaccy@linux.microsoft.com
> >
> > ... and here are lore links to the three crypto patches:
>
> We discussed before how the actual signature check seemed to have been
> overlooked in some cases, due to the complexities of PKCS#7
> (https://lore.kernel.org/r/20260305185016.GC2796@quark/). Looks like
> that was fixed. It is really hard to do any meaningful review of a
> PKCS#7 based system, though. And it sounds like this one is proceeding
> anyway due to some requirement to be compatible with an existing PKCS#7
> based system. So I'm not sure what you're looking for.
Ideally an ACK that you approve of merging those three crypto patches
via the LSM tree, or a quick comment if you happen to see anything
that needs changing.
--
paul-moore.com
^ permalink raw reply
* Re: [GIT PULL] selinux/selinux-pr-20260507
From: pr-tracker-bot @ 2026-05-08 0:30 UTC (permalink / raw)
To: Paul Moore; +Cc: Linus Torvalds, selinux, linux-security-module, linux-kernel
In-Reply-To: <71f5ecc18e71e40093ddaf1b0dd1df2b@paul-moore.com>
The pull request you sent on Thu, 07 May 2026 18:16:57 -0400:
> https://git.kernel.org/pub/scm/linux/kernel/git/pcmoore/selinux.git tags/selinux-pr-20260507
has been merged into torvalds/linux.git:
https://git.kernel.org/torvalds/c/917719c412c48687d4a176965d1fa35320ec457c
Thank you!
--
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/prtracker.html
^ permalink raw reply
* Re: [PATCH] ima: debugging late_initcall_sync measurements
From: Yeoreum Yun @ 2026-05-08 9:06 UTC (permalink / raw)
To: Mimi Zohar
Cc: David Safford, Jonathan McDowell, linux-security-module,
linux-kernel, linux-integrity, linux-arm-kernel, kvmarm, paul,
jmorris, serge, roberto.sassu, dmitry.kasatkin, eric.snowberg,
jarkko, jgg, sudeep.holla, maz, oupton, joey.gouly,
suzuki.poulose, yuzenghui, catalin.marinas, will, noodles,
sebastianene
In-Reply-To: <77ad49cca1acf707f4152ed3e2066b2f24c90c16.camel@linux.ibm.com>
Hi Mimi,
> On Thu, 2026-05-07 at 21:03 +0100, Yeoreum Yun wrote:
> > Hi Mimi,
> >
> > >
> > > > On Thu, 2026-05-07 at 06:50 +0100, Yeoreum Yun wrote:
> > > > > Hi Mimi,
> > > > >
> > > > > > On Wed, 2026-05-06 at 14:57 +0100, Yeoreum Yun wrote:
> > > > > > > > > > On both Z and PowerVM, there are ~30 measurements between boot_aggregate and
> > > > > > > > > > boot_aggregate_late. For example, on PowerVM:
> > > > > > > > > >
> > > > > > > > > > # grep -n boot_aggregate
> > > > > > > > > > /sys/kernel/security/integrity/ima/ascii_runtime_measurements
> > > > > > > > > >
> > > > > > > > > > 1:10 f60a05d7354fb34aabc02965216abd3428ea52bb ima-sig
> > > > > > > > > > sha256:9887dd089ee19a6517bca10580b02c1bb9aa6cd86c157b6ead8a1c0403f348d5
> > > > > > > > > > boot_aggregate
> > > > > > > > > > 31:10 e2592b0d61da6300d3db447b143897a9792231ea ima-sig
> > > > > > > > > > sha256:9887dd089ee19a6517bca10580b02c1bb9aa6cd86c157b6ead8a1c0403f348d5
> > > > > > > > > > boot_aggregate_late
> > > > > > > > > >
> > > > > > > > > > It would be interesting to the results from a Raspberry Pi 5 as well,
> > > > > > > > > > with/without a TPM.
> > > > > > > > >
> > > > > > > > > Honestly, I find this result hard to accept.
> > > > > > > > >
> > > > > > > > > This effectively means that there is code invoking IMA measurement during late_initcall().
> > > > > > > > > It also implies that if, in the future, a late_initcall is added that performs
> > > > > > > > > an IMA measurement before IMA initialization has occurred accoding to order by linker,
> > > > > > > > > that measurement could be missed.
> > > > > > > >
> > > > > > > > Exactly. The results are simply from booting with the builtin "tcb" and
> > > > > > > > "critical_data" policies.
> > > > > > > >
> > > > > > > > $ sudo grubby --args="ima_policy=\"tcb|critical_data\"" --update-kernel
> > > > > > > > /boot/vmlinuz-${SUFFIX}
> > > > > > >
> > > > > > > Thanks. but I still wonder what meaasurements there are between
> > > > > > > boot_aggregate and boot_aggregate_late.
> > > > > > > Might be there would be key measurements if it takes more than
> > > > > > > 5 mins before generating boot_aggregate_late but this seems rare.
> > > > > > >
> > > > > > > If you don't mind, would you share the contents of the log between
> > > > > > > boot_aggregate and boot_aggregate_late?
> > > > > > > since I only get a kernel_version in my environment.
> > > > > >
> > > > > > 1 10 f60a05d7354fb34aabc02965216abd3428ea52bb ima-sig
> > > > > > sha256:9887dd089ee19a6517bca10580b02c1bb9aa6cd86c157b6ead8a1c0403f348d5
> > > > > > boot_aggregate
> > > > > > 2 10 49ab61dd97ea2f759edcb6c6a3387ac67f0aa576 ima-buf
> > > > > > sha256:0c907aab3261194f16b0c2a422a82f145bc9b9ecb8fdb633fa43e3e5379f0af2
> > > > > > kernel_version 372e312e302d7263312b
> > > > > > 3 10 92c40bfd65512d5224cddb9fb64fef0d72e1c182 ima-sig
> > > > > > sha256:412bae0d0e85a99971d6eda198dd2fed3c2959715e8a17a4caddc7bc605bdeeb
> > > > > > /usr/bin/kmod
> > > > > > 4 10 a18f997e1e82d0ef416f93683966d7dda875d71c ima-sig
> > > > > > sha256:0050fcc672e03cfdc3a50c771ca9f5219478e5538980a26fd4484620712d8163
> > > > > > /usr/lib64/ld64.so.2
> > > > > > 5 10 88f343618caeeed92ed8281d627f4565b0499d66 ima-sig
> > > > > > sha256:a0e83c084d8c227f1150a8cd94eece61f62bc1da30f98d1cf57ca7db241a9c45
> > > > > > /etc/ld.so.cache
> > > > > > 6 10 e047868f01908eb95aa180693291decab82bb6be ima-sig
> > > > > > sha256:42ebf9cc684419de4d8a1d624102716d88fbcf957f47e50a9a08e38b338023ac
> > > > > > /usr/lib64/libzstd.so.1.5.5
> > > > > > 7 10 da069bc6a44d454510a76c69d3a54c3b238ae27e ima-sig
> > > > > > sha256:9b7c788e75c16c8827062016cf15826e43661c4b5b56813ea07ff2635bea2710
> > > > > > /usr/lib64/liblzma.so.5.6.2
> > > > > > 8 10 7ade414e736e7b449cda5ec5e0277b99548e89c6 ima-sig
> > > > > > sha256:d899452e8e6369e436ba1a565833d6dcf0d09c35e40ffc0979cf4de2bdb8f421
> > > > > > /usr/lib64/libz.so.1.3.1.zlib-ng
> > > > > > 9 10 9a9da8326f36237a47d6ed21bdffd0e1ff855e2a ima-sig
> > > > > > sha256:a848f396db7ad135f851b5e9aeb32f4a3ef1439c7913b9b95ab1cda69251f6ad
> > > > > > /usr/lib64/libcrypto.so.3.5.1
> > > > > > 10 10 3201d27cd4028f02fc9088ec33e2d0ceb72d2c5b ima-sig
> > > > > > sha256:e52dcd1850555c08d60fefe56694c1179b4eaa5796db0907606552ece8e1bab1
> > > > > > /usr/lib64/libgcc_s-14-20250617.so.1
> > > > > > 11 10 3b4c6f13e52ca060b290709f737b1ff66564226f ima-sig
> > > > > > sha256:f2a900a5b980b289dc028dd3caab16b1b0ad037f2e875546bb3197d23ff241f0
> > > > > > /usr/lib64/glibc-hwcaps/power10/libc.so.6
> > > > > > 12 10 b23b616cbd3c9dc4c5743d121c1c5a702b461a9c ima-sig
> > > > > > sha256:5a682022beeea9ee7f36a70f0465942bf32e9675d3f45355088e148787e02175
> > > > > > /usr/lib/modprobe.d/dist-alsa.conf
> > > > > > 13 10 aec07fad18697f295d7e06796fc8dfd3b472f9c3 ima-sig
> > > > > > sha256:067d949bab3bb085d0936031881ff73b2ab39f34b9a90cbd01396d1987ff6658
> > > > > > /usr/lib/modprobe.d/dist-blacklist.conf
> > > > > > 14 10 c402c56b66e65914148efd6e3cf0b1d616daabe6 ima-sig
> > > > > > sha256:120a02e9b88ba74949224eca7385825e39880f5687f739ade07d94ee22ffe325
> > > > > > /etc/modprobe.d/firewalld-sysctls.conf
> > > > > > 15 10 e358ca12bd58e1ce4845e299e1aea8b81edf86f9 ima-sig
> > > > > > sha256:fa27abcd357a16ee1254ba38d1225b7f0724036c07ce3d0e83b29eb72d97c419
> > > > > > /etc/modprobe.d/l2tp_eth-blacklist.conf
> > > > > > 16 10 4b036d41435d7df3a72b38880f5fe231904b7b66 ima-sig
> > > > > > sha256:ecf5f948bfbfb726879a910b3174d139c8af6b1745c88dcc1e4a1cf532c02299
> > > > > > /etc/modprobe.d/l2tp_ip-blacklist.conf
> > > > > > 17 10 9c53a7a48c1b5218417c4f25c4a34c09a9f39830 ima-sig
> > > > > > sha256:f76c4ac232d5e96c57961a9f10194703b4df6d119530046f0b23eee70bfcb089
> > > > > > /etc/modprobe.d/l2tp_ip6-blacklist.conf
> > > > > > 18 10 6c41d7b7d251c400b7e0ba76f7b386a746e8f4ec ima-sig
> > > > > > sha256:5cbc958f893a599ef19437014696dd7b112cf9af6a4348830177f8a8f78aa1b3
> > > > > > /etc/modprobe.d/l2tp_netlink-blacklist.conf
> > > > > > 19 10 f37ef48faef5bc51e29d47531726af0bd0654655 ima-sig
> > > > > > sha256:7a3d63acb49e4a69b482f26624761b5778fbd6b77be8a3f36926b379b5f965ed
> > > > > > /etc/modprobe.d/l2tp_ppp-blacklist.conf
> > > > > > 20 10 82ef59779acdfd6e9b35521bfa09e6ba86fd6174 ima-sig
> > > > > > sha256:6a8f2009d87deba7a2de46e3d0c46b114fe388d188b00b9a382fc2156aabb676
> > > > > > /usr/lib/modules/7.1.0-rc1+/modules.softdep
> > > > > > 21 10 6ae994e33a6313ab4535da90f5cb6c3beaec7b86 ima-sig
> > > > > > sha256:268695dbf23bd0170ec9a95b10e8d596205fd7436617d10101907171bf004b7c
> > > > > > /etc/modprobe.d/sctp-blacklist.conf
> > > > > > 22 10 b2c238ae66b03f56191d9955a5ad0f3110bb7e2b ima-sig
> > > > > > sha256:64a8ebb0a1fd712a9aeb7aa0f0ad0b72d3277034c8bfa3b66ab063e201d6527e
> > > > > > /etc/modprobe.d/sctp_diag-blacklist.conf
> > > > > > 23 10 c0443f2d3c078959ae86276df23abe172234a55d ima-sig
> > > > > > sha256:e5a3958cbd3684b63f3cada6604469cc56f727b106d5524daf5aefa6935a48ce
> > > > > > /usr/lib/modprobe.d/systemd.conf
> > > > > > 24 10 5c46e012bc7fffc3256b166282a7eaa4bea5fa33 ima-sig
> > > > > > sha256:6560abcdd2cdb41e1d0fe73052298d612920d5bccb4a3a7c82bc73895128e760
> > > > > > /etc/modprobe.d/tipc_diag-blacklist.conf
> > > > > > 25 10 d5fb1836364732fbc4f87aa7d2c984cf30bdbfd3 ima-sig
> > > > > > sha256:358703c09ac2d2c653e11bbc7c65d378c8496e87ca47307f86c36b0b29640598
> > > > > > /etc/modprobe.d/tuned.conf
> > > > > > 26 10 a85107163729f696f316d46c0bf3f65f713ba972 ima-sig
> > > > > > sha256:7410bb4cec56892e8b0010c5c8b72be532784ccf0240aa0677c5be085a530f65
> > > > > > /usr/lib/modules/7.1.0-rc1+/modules.dep.bin
> > > > > > 27 10 80eb261ffb2cc3528d90c33b1c624f657a045867 ima-sig
> > > > > > sha256:856e0f083226f8b4fb7d1d71447fb841dae18ea9a50ea6d8505a206167288e1d
> > > > > > /usr/lib/modules/7.1.0-rc1+/modules.alias.bin
> > > > > > 28 10 6af2d661da470d7a1c9909ddbc074d3d265eb1d7 ima-sig
> > > > > > sha256:4853ca200598c52970c380fda99484068e7db4961a4f94faac6abcfbbd52d150
> > > > > > /usr/lib/modules/7.1.0-rc1+/modules.symbols.bin
> > > > > > 29 10 6f9cd405bd57d925baae6ae66c273c61c90b3bc8 ima-sig
> > > > > > sha256:193d1e1004848f7d391877507b69a7953e1f94ddbe70eb0e2cf6dc45fce7cd6a
> > > > > > /usr/lib/modules/7.1.0-rc1+/modules.builtin.alias.bin
> > > > > > 30 10 4e20b980bf3a825a866be0c46033ed654df4aeba ima-sig
> > > > > > sha256:3a0e3c56d51ba98258ff13f93f82c837de22f4b707d24678f82893babf4d77ea
> > > > > > /usr/lib/modules/7.1.0-rc1+/modules.builtin.bin
> > > > > > 31 10 e2592b0d61da6300d3db447b143897a9792231ea ima-sig
> > > > > > sha256:9887dd089ee19a6517bca10580b02c1bb9aa6cd86c157b6ead8a1c0403f348d5
> > > > > > boot_aggregate_late
> > > > > > 32 10 81830cd3d799e006698258dc1b11fe29a56eeef5 ima-sig
> > > > > > sha256:d1651dc50bb5b92c1badcab9aa4dbbca40cb704cdc707d1c536b41d7b1aa465e
> > > > > > /usr/lib/systemd/systemd
> > > > >
> > > > > Hmm... Theses measurements seems to happen while populating
> > > > > initramfs which triggers file_post_open as async
> > > > > (and I see the RAMFS seems not part of DONT_MEASURE in IMA).
> > > > >
> > > > > If you don't mind, Could you check whether this measurements still
> > > > > happen with initramfs_async=0?
> > > > >
> > > > > If this measurements aren't generated with above option,
> > > > > there is a question whether let IMA measure initramfs which can be
> > > > > measured or not depending on boot option or timing (some file measurements
> > > > > could be lost all when do_populate_rootfs() executes concurrently while
> > > > > running late_initcall).
> > > >
> > > > There's no difference when adding the "initramfs_async=0" boot command line
> > > > argument. The measurement list between boot_aggregate and boot_aggregate_late is
> > > > exactly the same.
> > > >
> > >
> > > That's quite interesting. This means there're some file operation
> > > between late_initcall and late_initcall_sync
> > > even before run_init_process() except initramfs population.
> > >
> > > Since initramfs population flush all fputs() it generates and
> > > boot_aggregate_late is generated before init process is loaded,
> > > I honestly have no idea who triggers above file operations.
> > >
> > > And it seems a little bit weird that it opens the /usr/bin/kmod
> > > and its configuration from kernel not by init_process or other user
> > > process...
> > >
> >
> > If you don't mind, Could i get some stacktrace for one of file measurement log
> > between boot_aggregate and boot_aggreate_late?
> >
> > Though I try to produce like you, I've gotten failure to reproduce.
> > Sorry to bother you. but it would be helpful to understand where they
> > came from.
>
> The kernel selftests caused the measurements between late_initcall and
> late_initcall_sync. After disabling all of the kernel selftests, there weren't
> any measurements. Re-enabling the FIPS selftests on PowerVM LPAR resulted in
> measurements. (I didn't try re-enabling any of the other selftests.)
>
> CONFIG_FIPS_SIGNATURE_SELFTEST=y
> CONFIG_FIPS_SIGNATURE_SELFTEST_RSA=y
> CONFIG_FIPS_SIGNATURE_SELFTEST_ECDSA=y
Thanks for shraring this ;)
I found the reason for those mesaurements. Those come from the
request_module() and usermode-thread generates them while handling module
loading request for crypto-x962(ecdsa-nist-p256).
Since it's not a real kernel module,
I confirmed file measurements between late_initcall and
late_initcall_sync are gone for modeprobe with below change:
@@ -1246,9 +1250,14 @@ EXPORT_SYMBOL_GPL(ima_measure_critical_data);
*/
static int ima_kernel_module_request(char *kmod_name)
{
if (strncmp(kmod_name, "crypto-pkcs1(rsa,", 17) == 0)
return -EINVAL;
+ if (IS_BUILTIN(CONFIG_CRYPTO_ECDSA) &&
+ (strncmp(kmod_name, "crypto-x962(ecdsa", 17) == 0))
+ return -EINVAL;
+
return 0;
}
Though this is the only request_module() call between
late_initcall and late_initcall_sync, but I also confirmed there're
request_modules() call before ima initalisation before "late_initcall":
/*
* NOTE: kmod_name is printed on ima_kernel_module_request()
*/
// This is called from module_init(stm_core_init) -> device_initcall()
// which is in driver/hwtracing/stm/core.c (built-in)
[ 1.421986] ima: kmod_name: stm_p_basic
...
[ 1.444900] ima: kmod_name: crypto-pkcs1(rsa,sha512)
[ 1.444903] ima: kmod_name: crypto-pkcs1(rsa,sha512)-all
...
[ 1.452029] ima: kmod_name: crypto-cbc(aes)
[ 1.465321] ima: kmod_name: crypto-cbc(aes)-all
...
[ 1.467845] Key type encrypted registered
[ 1.467848] AppArmor: AppArmor sha256 policy hashing enabled
// IMA is initailised at late_initcall level.
[ 1.467850] ima: [init_ima_late:1336]
If IMA should care request_module() from kernel before IMA init,
I think there is no way to solve except queuing those events
(kernel_load_data/kernel_load_post_data and open for module binary etc.)
though it breaks "measure before use" principle since IMA couldn't
measure at that time.
But if you don't care about those things -- some events happend before
IMA init, I think your suggestion -- controlling the init time of ima_init()
via a Kconfig option is good and ignoring some usermodehelper request
including request_module() before IMA initialisation upto user by that option.
--
Sincerely,
Yeoreum Yun
^ permalink raw reply
* Re: [PATCH] ima: debugging late_initcall_sync measurements
From: Mimi Zohar @ 2026-05-08 12:55 UTC (permalink / raw)
To: Yeoreum Yun
Cc: David Safford, Jonathan McDowell, linux-security-module,
linux-kernel, linux-integrity, linux-arm-kernel, kvmarm, paul,
jmorris, serge, roberto.sassu, dmitry.kasatkin, eric.snowberg,
jarkko, jgg, sudeep.holla, maz, oupton, joey.gouly,
suzuki.poulose, yuzenghui, catalin.marinas, will, noodles,
sebastianene
In-Reply-To: <af2nhZyyNaP7LM3w@e129823.arm.com>
On Fri, 2026-05-08 at 10:06 +0100, Yeoreum Yun wrote:
> > The kernel selftests caused the measurements between late_initcall and
> > late_initcall_sync. After disabling all of the kernel selftests, there weren't
> > any measurements. Re-enabling the FIPS selftests on PowerVM LPAR resulted in
> > measurements. (I didn't try re-enabling any of the other selftests.)
> >
> > CONFIG_FIPS_SIGNATURE_SELFTEST=y
> > CONFIG_FIPS_SIGNATURE_SELFTEST_RSA=y
> > CONFIG_FIPS_SIGNATURE_SELFTEST_ECDSA=y
>
> Thanks for shraring this ;)
>
> I found the reason for those mesaurements. Those come from the
> request_module() and usermode-thread generates them while handling module
> loading request for crypto-x962(ecdsa-nist-p256).
> Since it's not a real kernel module,
> I confirmed file measurements between late_initcall and
> late_initcall_sync are gone for modeprobe with below change:
>
> @@ -1246,9 +1250,14 @@ EXPORT_SYMBOL_GPL(ima_measure_critical_data);
> */
> static int ima_kernel_module_request(char *kmod_name)
> {
> if (strncmp(kmod_name, "crypto-pkcs1(rsa,", 17) == 0)
> return -EINVAL;
>
> + if (IS_BUILTIN(CONFIG_CRYPTO_ECDSA) &&
> + (strncmp(kmod_name, "crypto-x962(ecdsa", 17) == 0))
> + return -EINVAL;
> +
> return 0;
> }
>
> Though this is the only request_module() call between
> late_initcall and late_initcall_sync, but I also confirmed there're
> request_modules() call before ima initalisation before "late_initcall":
>
> /*
> * NOTE: kmod_name is printed on ima_kernel_module_request()
> */
>
> // This is called from module_init(stm_core_init) -> device_initcall()
> // which is in driver/hwtracing/stm/core.c (built-in)
> [ 1.421986] ima: kmod_name: stm_p_basic
> ...
> [ 1.444900] ima: kmod_name: crypto-pkcs1(rsa,sha512)
> [ 1.444903] ima: kmod_name: crypto-pkcs1(rsa,sha512)-all
> ...
> [ 1.452029] ima: kmod_name: crypto-cbc(aes)
> [ 1.465321] ima: kmod_name: crypto-cbc(aes)-all
> ...
> [ 1.467845] Key type encrypted registered
> [ 1.467848] AppArmor: AppArmor sha256 policy hashing enabled
>
> // IMA is initailised at late_initcall level.
> [ 1.467850] ima: [init_ima_late:1336]
>
> If IMA should care request_module() from kernel before IMA init,
> I think there is no way to solve except queuing those events
> (kernel_load_data/kernel_load_post_data and open for module binary etc.)
> though it breaks "measure before use" principle since IMA couldn't
> measure at that time.
>
> But if you don't care about those things -- some events happend before
> IMA init, I think your suggestion -- controlling the init time of ima_init()
> via a Kconfig option is good and ignoring some usermodehelper request
> including request_module() before IMA initialisation upto user by that option.
Thank you for the complete analysis. The early measurements before the TPM is
initialized is a problem that needs to be addressed. As to whether the solution
will require queueing is yet to be determined. (Roberto has some thoughts on
addressing it.) This discussion makes it clear that simply delaying IMA
initialization by moving it from late_initcall to late_initcall_sync could miss
measurements. That said, exposing it as an opt-in Kconfig for those who accept
the risk is a sensible pragmatic compromise.
Mimi
^ permalink raw reply
* Re: [RFC PATCH v4 01/19] landlock: Support socket access-control
From: Mickaël Salaün @ 2026-05-08 13:29 UTC (permalink / raw)
To: Mikhail Ivanov
Cc: Günther Noack, gnoack, willemdebruijn.kernel, matthieu,
linux-security-module, netdev, netfilter-devel, yusongping,
artem.kuzin, konstantin.meskhidze
In-Reply-To: <d7b3a4ed-034e-a0a3-4a68-9bc5fdc6e2ff@huawei-partners.com>
On Sat, Apr 18, 2026 at 02:29:04PM +0300, Mikhail Ivanov wrote:
> On 11/22/2025 2:13 PM, Mikhail Ivanov wrote:
> > On 11/22/2025 1:49 PM, Günther Noack wrote:
> > > On Tue, Nov 18, 2025 at 09:46:21PM +0800, Mikhail Ivanov wrote:
> > > > +/**
> > > > + * struct landlock_socket_attr - Socket protocol definition
> > > > + *
> > > > + * Argument of sys_landlock_add_rule().
> > > > + */
> > > > +struct landlock_socket_attr {
> > > > + /**
> > > > + * @allowed_access: Bitmask of allowed access for a socket protocol
> > > > + * (cf. `Socket flags`_).
> > > > + */
> > > > + __u64 allowed_access;
> > > > + /**
> > > > + * @family: Protocol family used for communication
> > > > + * (cf. include/linux/socket.h).
> > > > + */
> > > > + __s32 family;
> > > > + /**
> > > > + * @type: Socket type (cf. include/linux/net.h)
> > > > + */
> > > > + __s32 type;
> > > > + /**
> > > > + * @protocol: Communication protocol specific to protocol
> > > > family set in
> > > > + * @family field.
> > >
> > > This is specific to both the @family and the @type, not just the @family.
> > >
> > > > From socket(2):
> > >
> > > Normally only a single protocol exists to support a particular
> > > socket type within a given protocol family.
> > >
> > > For instance, in your commit message above the protocol in the example
> > > is IPPROTO_TCP, which would imply the type SOCK_STREAM, but not work
> > > with SOCK_DGRAM.
> >
> > You're right.
> >
>
> I revised the socket(2) semantics and this part is about that kernel
> maps (family, type, 0) to the default protocol of given family and type.
> Eg. (AF_INET, SOCK_STREAM, 0) is mapped to (AF_INET, SOCK_STREAM,
> IPPROTO_TCP). I would like to clarify that such mapping is taking place
> in landlock_socket_attr.protocol field doc.
>
> There should be list of protocols defined per protocol family. From
> socket(2):
> The domain argument specifies a communication domain.
> ...
> The protocol number to use is specific to the “communication
> domain” in which communication is to take place.
>
> Such mapping allows to define strange socket rules if setting @type=-1.
> For example:
> struct landlock_socket_attr attr = {
> .family = AF_INET,
> .type = -1,
> .protocol = 0,
> };
Looking again at this API, I think we should not have a special handling
of the "-1" values but instead change the struct landlock_socket_attr to
start with a "wildcards" field to properly identify which socket
property should be "any" value (according to a dedicated flag):
struct landlock_socket_attr {
__u64 allowed_perm; /* see the ns/cap patch series */
__u32 wildcards; /* LANDLOCK_SOCKET_ANY_PROTOCOL */
__u32 family;
__u32 type;
__u32 protocol;
};
In fact, I though a lot about the two potential wildcards (type and
family we previously discussed), and my conclusion is that we should
only handle "any protocol" (instead of any type too). This makes the
UAPI simpler and less dangerous, especially wrt families that have very
specific and sometime privileged types (e.g. SOCK_RAW). Another
important point is that it would allow to do only one rbtree lookup
(tweaking a bit the rbtree walk) instead of four lookup like with the
current implementation. The idea is to generate an rbtree key with:
family | type | !any-protocol-boolean | protocol
This key format allows a one-descent walk lookup. We'll have to replace
the use of landlock_find_rule() with a custom walk that first look for
the any-protocol-boolean (which should probably be represented by 0 for
"any protocol" and by 1 for "specific protocol"), and if no "any
protocol" key is found, to continue the walk to match the full protocol
value.
>
> This definition corresponds to (AF_INET, SOCK_STREAM, 0->IPPROTO_TCP)
> and to (AF_INET, SOCK_DGRAM, 0->IPPROTO_UDP).
>
> I don't see this as a bad thing as far as there is proper documentation
> for landlock_socket_attr.
Thinking more about the asymmetry between UAPI and kernel state, I think
the best approach is to canonicalize the rules' values to make them
match the kernel equivalent. This behavior would be much less
surprising to users (this is mostly an UX improvement, but also a way to
deduplicate some rules). Indeed, users would be able to use the default
value (e.g. protocol 0 for INET/STREAM) *and* the canonicalized value
(e.g. protocol IPPROTO_TCP). Here is a patch to implement this approach
and (most importantly) with the related kernel tests to make sure the
canonicalizations are correct:
[PATCH] landlock: Canonicalize socket rules and add drift detection
The kernel socket stack performs family-specific rewrites between the
(family, type, protocol) triple passed to socket(2) and the resulting
socket object. Rewrites currently mirrored:
- __sock_create rewrites AF_INET + SOCK_PACKET to AF_PACKET for pre-2.2
compatibility.
- unix_create rewrites AF_UNIX + SOCK_RAW to AF_UNIX + SOCK_DGRAM as a
BSD leftover, and ignores the user protocol (sk_protocol stays 0 for
every AF_UNIX socket).
- inet_create and inet6_create resolve protocol=0 to a type-specific
default (IPPROTO_TCP for SOCK_STREAM, IPPROTO_UDP for SOCK_DGRAM,
IPPROTO_SCTP for SOCK_SEQPACKET) via the inetsw walk.
- ax25_create rewrites protocol=0 and protocol=PF_AX25 to AX25_P_TEXT
for SOCK_DGRAM and SOCK_SEQPACKET.
- pn_socket_create rewrites protocol=0 to PN_PROTO_PHONET (SOCK_DGRAM)
or PN_PROTO_PIPE (SOCK_SEQPACKET).
- vsock_create accepts protocol=0 or PF_VSOCK and stores sk_protocol=0
for both, so PF_VSOCK aliases protocol=0.
- Several families (AF_PACKET, AF_KEY, AF_APPLETALK, AF_ATMPVC,
AF_ATMSVC, AF_LLC, AF_CAN raw/bcm, AF_RXRPC, AF_IEEE802154,
AF_QIPCRTR) accept the user protocol but never write sk_protocol, so
the kernel stores 0 regardless of input.
- AF_CAN + SOCK_DGRAM is asymmetric: bcm and raw leave sk_protocol=0,
but j1939_sk_init writes sk_protocol = CAN_J1939.
Without Landlock-side canonicalization, a rule inserted with the user-
facing form (e.g., AF_INET + SOCK_STREAM + 0) silently misses sockets
the user wants to match: a socket created with protocol=0 reaches
sk_protocol=IPPROTO_TCP, so a rule keyed on 0 does not apply. Each
socket call form would require the user to write a separate rule.
Store rules in the canonical form at insertion. The new
landlock_canon_map[AF_MAX][SOCK_MAX] table with flag-driven entries
(_LANDLOCK_CANON_REWRITE_FAMILY, _LANDLOCK_CANON_REWRITE_TYPE,
_LANDLOCK_CANON_PROTOCOL_ZERO, _LANDLOCK_CANON_PROTOCOL_FAMILY_ID,
_LANDLOCK_CANON_PROTOCOL_ALWAYS, _LANDLOCK_CANON_PROTOCOL_PRESERVE)
encodes the rewrites. landlock_canonicalize_socket_key applies them
idempotently; wildcards (TYPE_ALL, PROTOCOL_ALL) are preserved so
wildcard rules remain first-class. Per-protocol overrides (the
preserve_protocol field) handle asymmetries like AF_CAN + SOCK_DGRAM
where one sub-protocol writes sk_protocol differently from the others.
Lookup remains O(1): the override is part of the same cell the array
index returns.
Keep enforcement at security_socket_create (pre-create) so Landlock
denies unauthorized triples before the kernel loads any family-specific
module or allocates a socket. This preserves the EACCES error path for
triples the kernel itself would reject (AF_UNSPEC, invalid family and
type pairs) rather than leaking EAFNOSUPPORT, ESOCKTNOSUPPORT, or
EPROTONOSUPPORT as a sandbox bypass signal. The pre-create hook
canonicalizes the caller input through the same map before the rule
lookup so that a rule keyed on the user form matches at the hook.
Add security_socket_post_create purely for runtime drift detection. At
post_create the family .create() has completed, so sk_family,
sock->type, and sk_protocol are authoritative. Per-field WARN_ONCE
fires when landlock_canonicalize_socket_key disagrees with these values,
identifying which axis (family, type, or protocol) drifted and the
user-supplied triple. This hook only runs for tasks sandboxed by a
domain that handles LANDLOCK_ACCESS_SOCKET_CREATE, so non-sandboxed
tasks pay no overhead.
Four layers guard against landlock_canon_map drift:
- static_assert(AF_MAX == N) and static_assert(SOCK_MAX == N) anchor the
dimensions at the current kernel ABI; a new AF_* or SOCK_* value
breaks the build and forces a map audit.
- A new KUnit suite (landlock_socket) iterates every (family, type,
protocol) triple over a probe range that covers all family IDs
(0..NPROTO) plus IPPROTO_SCTP, 255, and 0xFFFF. For triples the
kernel accepts, sock_create_kern is the oracle; canonicalization is
validated against the resulting sk_family, sock->type, and
sk_protocol. Families known to be unsupported by the default
configuration are marked, and -EAFNOSUPPORT on those families is
ignored so additional sub-protocol configs can extend coverage without
per-arch .kunitconfig fragmentation. A separate map-entry test covers
cells unreachable through sock_create_kern (currently AF_VSOCK +
SOCK_DGRAM, which requires CONFIG_VIRTIO_VSOCKETS). A
wildcard-preservation test pins the TYPE_ALL and PROTOCOL_ALL
invariants.
- The per-field runtime WARN_ONCE described above.
- security/landlock/.kunitconfig enumerates the CONFIG entries required
to exercise every audited family so a family disabled by kernel config
causes a build-visible coverage loss rather than a silent KUnit skip.
The four-way wildcard grid lookup in hook_socket_create gates every
pack_socket_key call on a successful return. This incidentally fixes a
latent bug in the original hook where the first pack failure was
detected with == -EACCES (the helper only ever returns 0 or -EINVAL), so
an out-of-range triple fed three subsequent check_socket_access calls
with an uninitialized key.
The tcp_protocol.variant2 selftest previously asserted that a rule for
IPPROTO_TCP did NOT match socket(AF_INET, SOCK_STREAM, 0). Under
canonicalization both forms alias by design, so the test is renamed to
alias_equivalence and its body is updated to assert the aliasing (both
call forms match irrespective of which form the rule was inserted with).
Unrelated families remain restricted.
Signed-off-by: Mickaël Salaün <mic@digikod.net>
---
security/landlock/.kunitconfig | 40 +
security/landlock/socket.c | 871 +++++++++++++++++-
.../testing/selftests/landlock/socket_test.c | 38 +-
3 files changed, 910 insertions(+), 39 deletions(-)
diff --git a/security/landlock/.kunitconfig b/security/landlock/.kunitconfig
index f9423f01ac5b..5aafd56e8ebd 100644
--- a/security/landlock/.kunitconfig
+++ b/security/landlock/.kunitconfig
@@ -1,6 +1,46 @@
+CONFIG_AF_RXRPC=y
+CONFIG_ATALK=y
+CONFIG_ATM=y
CONFIG_AUDIT=y
+CONFIG_AX25=y
+CONFIG_BT=y
+CONFIG_CAIF=y
+CONFIG_CAN=y
+CONFIG_CAN_BCM=y
+CONFIG_CRYPTO=y
+CONFIG_CRYPTO_USER_API_AEAD=y
+CONFIG_HAMRADIO=y
+CONFIG_IEEE802154=y
+CONFIG_IEEE802154_SOCKET=y
+CONFIG_INET=y
+CONFIG_INFINIBAND=y
+CONFIG_IP_SCTP=y
+CONFIG_IPV6=y
+CONFIG_ISDN=y
CONFIG_KUNIT=y
+CONFIG_LLC=y
+CONFIG_LLC2=y
+CONFIG_MCTP=y
+CONFIG_MISDN=y
+CONFIG_MPTCP=y
+CONFIG_MPTCP_IPV6=y
CONFIG_NET=y
+CONFIG_NET_KEY=y
+CONFIG_NETDEVICES=y
+CONFIG_NETROM=y
+CONFIG_NFC=y
+CONFIG_PACKET=y
+CONFIG_PHONET=y
+CONFIG_PPP=y
+CONFIG_PPPOE=y
+CONFIG_QRTR=y
+CONFIG_RDS=y
+CONFIG_ROSE=y
CONFIG_SECURITY=y
CONFIG_SECURITY_LANDLOCK=y
CONFIG_SECURITY_LANDLOCK_KUNIT_TEST=y
+CONFIG_SMC=y
+CONFIG_TIPC=y
+CONFIG_UNIX=y
+CONFIG_VSOCKETS=y
+CONFIG_X25=y
diff --git a/security/landlock/socket.c b/security/landlock/socket.c
index 6afd5a0ac6d7..ef48949fa7d3 100644
--- a/security/landlock/socket.c
+++ b/security/landlock/socket.c
@@ -5,20 +5,385 @@
* Copyright © 2025 Huawei Tech. Co., Ltd.
*/
+#include <linux/in.h>
#include <linux/net.h>
#include <linux/socket.h>
#include <linux/stddef.h>
#include <net/ipv6.h>
+#include <net/sock.h>
#include "audit.h"
+#include "cred.h"
#include "limits.h"
#include "ruleset.h"
#include "socket.h"
-#include "cred.h"
#define TYPE_ALL (-1)
#define PROTOCOL_ALL (-1)
+/*
+ * Compensation for kernel-internal socket rewrites.
+ *
+ * The kernel maps the user-visible (family, type, protocol) triple into
+ * (sk->sk_family, sock->type, sk->sk_protocol) following per-family rules.
+ * Landlock mirrors those rules at rule insertion and at hook time so that rules
+ * inserted with a user-facing form match the canonical triple seen after
+ * socket(2) completes. The known patterns are:
+ *
+ * - __sock_create rewrites AF_INET + SOCK_PACKET to AF_PACKET for
+ * pre-2.2 compatibility (net/socket.c).
+ * - unix_create rewrites AF_UNIX + SOCK_RAW to AF_UNIX + SOCK_DGRAM and
+ * ignores the user protocol (sk_protocol stays 0).
+ * - inet_create and inet6_create resolve protocol=0 to a type-specific
+ * default (IPPROTO_TCP for SOCK_STREAM, IPPROTO_UDP for SOCK_DGRAM,
+ * IPPROTO_SCTP for SOCK_SEQPACKET) via the inetsw walk.
+ * - ax25_create rewrites protocol=0 or PF_AX25 to AX25_P_TEXT for
+ * SOCK_DGRAM and SOCK_SEQPACKET.
+ * - pn_socket_create rewrites protocol=0 to PN_PROTO_PHONET for
+ * SOCK_DGRAM and PN_PROTO_PIPE for SOCK_SEQPACKET.
+ * - vsock_create accepts either 0 or PF_VSOCK and stores sk_protocol=0
+ * for both; Landlock canonicalizes the rule to protocol=0.
+ *
+ * Enforcement happens at security_socket_create (pre-create) so Landlock denies
+ * unauthorized triples before any family-specific module is loaded or socket
+ * allocated, preserving the EACCES error path for triples the kernel itself
+ * would reject.
+ *
+ * Drift between landlock_canon_map and kernel behavior is detected in four
+ * layers: static_asserts on AF_MAX and SOCK_MAX (new AF or SOCK values break
+ * the build), the exhaustive KUnit suite in this file using sock_create_kern as
+ * the oracle, a runtime WARN_ON_ONCE in the post_create hook, and an explicit
+ * CONFIG list in .kunitconfig so a missing family fails the build instead of
+ * silently skipping.
+ */
+
+#define _LANDLOCK_CANON_REWRITE_FAMILY BIT(0)
+#define _LANDLOCK_CANON_REWRITE_TYPE BIT(1)
+#define _LANDLOCK_CANON_PROTOCOL_ZERO BIT(2)
+#define _LANDLOCK_CANON_PROTOCOL_FAMILY_ID BIT(3)
+#define _LANDLOCK_CANON_PROTOCOL_ALWAYS BIT(4)
+#define _LANDLOCK_CANON_PROTOCOL_PRESERVE BIT(5)
+
+/*
+ * All fields fit in u8 today: ops uses 6 bits; SOCK_MAX-1 and AF_MAX-1 are both
+ * small; the largest canonicalization target is AX25_P_TEXT (0xF0), well within
+ * u8. -Woverflow catches a too-wide initializer at build time if a future
+ * entry exceeds the field width.
+ */
+struct landlock_canon_entry {
+ u8 ops;
+ u8 new_type;
+ u8 new_family;
+ u8 new_protocol;
+ /*
+ * When PROTOCOL_PRESERVE is set and the user protocol matches this
+ * value, the protocol field is left unchanged regardless of the other
+ * PROTOCOL_* flags. Captures sub-protocols whose .create() writes
+ * sk_protocol explicitly (e.g. CAN_J1939) when other sub-protocols of
+ * the same (family, type) do not. Lookup stays O(1) because the
+ * override is part of the same cell.
+ */
+ u8 preserve_protocol;
+};
+
+/*
+ * Shared initializer for Case 4 families (AF_UNIX, AF_PACKET, AF_VSOCK, AF_KEY,
+ * ...) whose .create() ignores the user protocol and leaves sk_protocol at 0.
+ * The canonical form is any type, protocol=0.
+ */
+#define _LANDLOCK_CANON_ALWAYS_ZERO \
+ { \
+ .ops = _LANDLOCK_CANON_PROTOCOL_ALWAYS, .new_protocol = 0, \
+ }
+
+/*
+ * A change to AF_MAX or SOCK_MAX implies a new protocol family or socket type
+ * reached upstream. The assertion fires a build error that forces an audit:
+ * does the new family or type introduce a kernel-internal rewrite that Landlock
+ * must mirror? If not, bump the expected value below.
+ */
+static_assert(AF_MAX == 46,
+ "AF_MAX changed; audit landlock_canon_map for new families.");
+static_assert(SOCK_MAX == 11,
+ "SOCK_MAX changed; audit landlock_canon_map for new types.");
+
+static const struct landlock_canon_entry
+ landlock_canon_map[AF_MAX][SOCK_MAX] = {
+ /*
+ * unix_create rewrites SOCK_RAW to SOCK_DGRAM and ignores the
+ * user protocol; normalize every AF_UNIX socket to protocol=0.
+ */
+ [AF_UNIX] = {
+ [SOCK_STREAM] = _LANDLOCK_CANON_ALWAYS_ZERO,
+ [SOCK_DGRAM] = _LANDLOCK_CANON_ALWAYS_ZERO,
+ [SOCK_SEQPACKET] = _LANDLOCK_CANON_ALWAYS_ZERO,
+ [SOCK_RAW] = {
+ .ops = _LANDLOCK_CANON_REWRITE_TYPE |
+ _LANDLOCK_CANON_PROTOCOL_ALWAYS,
+ .new_type = SOCK_DGRAM,
+ .new_protocol = 0,
+ },
+ },
+ [AF_INET] = {
+ [SOCK_STREAM] = {
+ .ops = _LANDLOCK_CANON_PROTOCOL_ZERO,
+ .new_protocol = IPPROTO_TCP,
+ },
+ [SOCK_DGRAM] = {
+ .ops = _LANDLOCK_CANON_PROTOCOL_ZERO,
+ .new_protocol = IPPROTO_UDP,
+ },
+ [SOCK_SEQPACKET] = {
+ .ops = _LANDLOCK_CANON_PROTOCOL_ZERO,
+ .new_protocol = IPPROTO_SCTP,
+ },
+ /*
+ * __sock_create rewrites AF_INET + SOCK_PACKET to
+ * AF_PACKET; packet_create then stores sk_protocol=0
+ * regardless of the user protocol (see AF_PACKET rows
+ * below), so the canonical triple is (AF_PACKET,
+ * SOCK_PACKET, 0).
+ */
+ [SOCK_PACKET] = {
+ .ops = _LANDLOCK_CANON_REWRITE_FAMILY |
+ _LANDLOCK_CANON_PROTOCOL_ALWAYS,
+ .new_family = AF_PACKET,
+ .new_protocol = 0,
+ },
+ },
+ /*
+ * packet_create stores the user-provided protocol in po->num
+ * (for filtering) but never writes sk_protocol, so sk_protocol
+ * stays at 0 for every AF_PACKET socket. Canonicalize rules to
+ * protocol=0 to match.
+ */
+ [AF_PACKET] = {
+ [SOCK_DGRAM] = _LANDLOCK_CANON_ALWAYS_ZERO,
+ [SOCK_RAW] = _LANDLOCK_CANON_ALWAYS_ZERO,
+ [SOCK_PACKET] = _LANDLOCK_CANON_ALWAYS_ZERO,
+ },
+ /*
+ * AX.25 rewrites protocol=0 AND PF_AX25 to AX25_P_TEXT (0xF0)
+ * for SOCK_DGRAM and SOCK_SEQPACKET; see ax25_create in
+ * net/ax25/af_ax25.c. SOCK_RAW preserves user input.
+ */
+ [AF_AX25] = {
+ [SOCK_DGRAM] = {
+ .ops = _LANDLOCK_CANON_PROTOCOL_ZERO |
+ _LANDLOCK_CANON_PROTOCOL_FAMILY_ID,
+ .new_protocol = 0xF0, /* AX25_P_TEXT */
+ },
+ [SOCK_SEQPACKET] = {
+ .ops = _LANDLOCK_CANON_PROTOCOL_ZERO |
+ _LANDLOCK_CANON_PROTOCOL_FAMILY_ID,
+ .new_protocol = 0xF0, /* AX25_P_TEXT */
+ },
+ },
+ [AF_INET6] = {
+ [SOCK_STREAM] = {
+ .ops = _LANDLOCK_CANON_PROTOCOL_ZERO,
+ .new_protocol = IPPROTO_TCP,
+ },
+ [SOCK_DGRAM] = {
+ .ops = _LANDLOCK_CANON_PROTOCOL_ZERO,
+ .new_protocol = IPPROTO_UDP,
+ },
+ [SOCK_SEQPACKET] = {
+ .ops = _LANDLOCK_CANON_PROTOCOL_ZERO,
+ .new_protocol = IPPROTO_SCTP,
+ },
+ },
+ /*
+ * Phonet rewrites protocol=0 to PN_PROTO_PHONET for SOCK_DGRAM
+ * and PN_PROTO_PIPE for SOCK_SEQPACKET; see pn_socket_create in
+ * net/phonet/af_phonet.c.
+ */
+ [AF_PHONET] = {
+ [SOCK_DGRAM] = {
+ .ops = _LANDLOCK_CANON_PROTOCOL_ZERO,
+ .new_protocol = 1, /* PN_PROTO_PHONET */
+ },
+ [SOCK_SEQPACKET] = {
+ .ops = _LANDLOCK_CANON_PROTOCOL_ZERO,
+ .new_protocol = 2, /* PN_PROTO_PIPE */
+ },
+ },
+ /*
+ * atalk_create accepts SOCK_DGRAM and SOCK_RAW but never writes
+ * sk_protocol.
+ */
+ [AF_APPLETALK] = {
+ [SOCK_DGRAM] = _LANDLOCK_CANON_ALWAYS_ZERO,
+ [SOCK_RAW] = _LANDLOCK_CANON_ALWAYS_ZERO,
+ },
+ /*
+ * ATM PVC and SVC go through vcc_create, which rejects
+ * SOCK_STREAM and accepts every other type in the
+ * 0..SOCK_PACKET range without writing sk_protocol.
+ */
+ [AF_ATMPVC] = {
+ [0] = _LANDLOCK_CANON_ALWAYS_ZERO, /* reserved hole */
+ [SOCK_DGRAM] = _LANDLOCK_CANON_ALWAYS_ZERO,
+ [SOCK_RAW] = _LANDLOCK_CANON_ALWAYS_ZERO,
+ [SOCK_RDM] = _LANDLOCK_CANON_ALWAYS_ZERO,
+ [SOCK_SEQPACKET] = _LANDLOCK_CANON_ALWAYS_ZERO,
+ [SOCK_DCCP] = _LANDLOCK_CANON_ALWAYS_ZERO,
+ [7] = _LANDLOCK_CANON_ALWAYS_ZERO, /* reserved hole */
+ [8] = _LANDLOCK_CANON_ALWAYS_ZERO, /* reserved hole */
+ [9] = _LANDLOCK_CANON_ALWAYS_ZERO, /* reserved hole */
+ [SOCK_PACKET] = _LANDLOCK_CANON_ALWAYS_ZERO,
+ },
+ [AF_ATMSVC] = {
+ [0] = _LANDLOCK_CANON_ALWAYS_ZERO, /* reserved hole */
+ [SOCK_DGRAM] = _LANDLOCK_CANON_ALWAYS_ZERO,
+ [SOCK_RAW] = _LANDLOCK_CANON_ALWAYS_ZERO,
+ [SOCK_RDM] = _LANDLOCK_CANON_ALWAYS_ZERO,
+ [SOCK_SEQPACKET] = _LANDLOCK_CANON_ALWAYS_ZERO,
+ [SOCK_DCCP] = _LANDLOCK_CANON_ALWAYS_ZERO,
+ [7] = _LANDLOCK_CANON_ALWAYS_ZERO, /* reserved hole */
+ [8] = _LANDLOCK_CANON_ALWAYS_ZERO, /* reserved hole */
+ [9] = _LANDLOCK_CANON_ALWAYS_ZERO, /* reserved hole */
+ [SOCK_PACKET] = _LANDLOCK_CANON_ALWAYS_ZERO,
+ },
+ /*
+ * LLC, CAN, RxRPC, IEEE 802.15.4, and QRTR all rely on
+ * sock_init_data to zero sk_protocol and never override it, so
+ * any reachable triple is canonicalized to protocol=0.
+ */
+ [AF_LLC] = {
+ [SOCK_STREAM] = _LANDLOCK_CANON_ALWAYS_ZERO,
+ [SOCK_DGRAM] = _LANDLOCK_CANON_ALWAYS_ZERO,
+ },
+ /*
+ * AF_CAN dispatches by protocol: can_raw and can_bcm leave
+ * sk_protocol=0, but j1939_sk_init writes sk_protocol =
+ * CAN_J1939. Use PROTOCOL_PRESERVE on SOCK_DGRAM so the J1939
+ * value survives the canonicalization while all other
+ * sub-protocols get normalized to 0. SOCK_SEQPACKET has no
+ * registered CAN sub-protocol so no entry is needed (the cell
+ * is unreachable at the kernel).
+ */
+ [AF_CAN] = {
+ [SOCK_DGRAM] = {
+ .ops = _LANDLOCK_CANON_PROTOCOL_ALWAYS |
+ _LANDLOCK_CANON_PROTOCOL_PRESERVE,
+ .new_protocol = 0,
+ .preserve_protocol =
+ 7 /* CAN_J1939 */,
+ },
+ [SOCK_RAW] = _LANDLOCK_CANON_ALWAYS_ZERO,
+ },
+ [AF_RXRPC] = {
+ [SOCK_DGRAM] = _LANDLOCK_CANON_ALWAYS_ZERO,
+ },
+ [AF_IEEE802154] = {
+ [SOCK_RAW] = _LANDLOCK_CANON_ALWAYS_ZERO,
+ [SOCK_DGRAM] = _LANDLOCK_CANON_ALWAYS_ZERO,
+ },
+ [AF_QIPCRTR] = {
+ [SOCK_DGRAM] = _LANDLOCK_CANON_ALWAYS_ZERO,
+ },
+ /*
+ * pfkey_create accepts only PF_KEY_V2 for SOCK_RAW but never
+ * writes sk_protocol, so the kernel stores sk_protocol=0.
+ */
+ [AF_KEY] = {
+ [SOCK_RAW] = _LANDLOCK_CANON_ALWAYS_ZERO,
+ },
+ /*
+ * vsock accepts protocol=0 or PF_VSOCK and leaves sk_protocol
+ * at 0 in either case, for every transport (loopback, VMCI,
+ * virtio, hyperv) because vsock_create never writes
+ * sk_protocol. Canonicalize every AF_VSOCK rule to protocol=0
+ * so both call forms match the same rule.
+ *
+ * The vsock loopback transport (the only one enabled by the
+ * default .kunitconfig) does not advertise DGRAM capability, so
+ * the SOCK_DGRAM cell is not reachable through sock_create_kern
+ * at KUnit time; DGRAM reachability requires
+ * CONFIG_VIRTIO_VSOCKETS=y. The canonicalization is
+ * nonetheless correct; a dedicated map-entry test case
+ * validates it independently of any kernel configuration.
+ */
+ [AF_VSOCK] = {
+ [SOCK_STREAM] = _LANDLOCK_CANON_ALWAYS_ZERO,
+ [SOCK_DGRAM] = _LANDLOCK_CANON_ALWAYS_ZERO,
+ [SOCK_SEQPACKET] = _LANDLOCK_CANON_ALWAYS_ZERO,
+ },
+ };
+
+/*
+ * landlock_canonicalize_socket_key - Apply kernel-equivalent rewrites
+ *
+ * @family: in/out protocol family.
+ * @type: in/out socket type.
+ * @protocol: in/out protocol.
+ *
+ * Transforms a (@family, @type, @protocol) triple into the form the kernel
+ * stores after the family .create() completes. Wildcards (TYPE_ALL,
+ * PROTOCOL_ALL) are preserved unchanged since they do not pin a specific
+ * triple. Out-of-range values are left for the caller (pack_socket_key) to
+ * reject.
+ */
+static void landlock_canonicalize_socket_key(s32 *family, s32 *type,
+ s32 *protocol)
+{
+ const struct landlock_canon_entry *entry;
+ s32 input_family;
+
+ /*
+ * Type is the map second index, so it must be concrete. Family is the
+ * map first index; checked below via bounds.
+ */
+ if (*type == TYPE_ALL)
+ return;
+
+ if (*family < 0 || *family >= AF_MAX || *type < 0 || *type >= SOCK_MAX)
+ return;
+
+ entry = &landlock_canon_map[*family][*type];
+ input_family = *family;
+
+ /*
+ * Family and type rewrites apply regardless of protocol value; a
+ * PROTOCOL_ALL rule still benefits from an alias family or type
+ * rewrite.
+ */
+ if (entry->ops & _LANDLOCK_CANON_REWRITE_FAMILY)
+ *family = entry->new_family;
+ if (entry->ops & _LANDLOCK_CANON_REWRITE_TYPE)
+ *type = entry->new_type;
+
+ /*
+ * Protocol rewrites are skipped for wildcard protocols so that a
+ * PROTOCOL_ALL rule stays wildcard even for families the kernel
+ * canonicalizes.
+ */
+ if (*protocol == PROTOCOL_ALL)
+ return;
+
+ /*
+ * Per-protocol preservation: when the map declares that one specific
+ * user protocol survives unchanged in sk_protocol (e.g., AF_CAN +
+ * SOCK_DGRAM + CAN_J1939: j1939_sk_init writes sk_protocol = CAN_J1939,
+ * while raw_init and bcm_init leave sk_protocol = 0), skip all protocol
+ * rewrites when the user input matches that value. Keeps the runtime
+ * drift WARN silent for the exception while PROTOCOL_ALWAYS still
+ * normalizes the rest of the cell to new_protocol.
+ */
+ if ((entry->ops & _LANDLOCK_CANON_PROTOCOL_PRESERVE) &&
+ *protocol == entry->preserve_protocol)
+ return;
+
+ if (entry->ops & _LANDLOCK_CANON_PROTOCOL_ALWAYS)
+ *protocol = entry->new_protocol;
+ else if (((entry->ops & _LANDLOCK_CANON_PROTOCOL_ZERO) &&
+ *protocol == 0) ||
+ ((entry->ops & _LANDLOCK_CANON_PROTOCOL_FAMILY_ID) &&
+ *protocol == input_family))
+ *protocol = entry->new_protocol;
+}
+
static int pack_socket_key(const s32 family, const s32 type, const s32 protocol,
uintptr_t *val)
{
@@ -78,12 +443,12 @@ int landlock_append_socket_rule(struct landlock_ruleset *const ruleset,
{
int err;
uintptr_t key;
+
/*
- * (AF_INET, SOCK_PACKET) is an alias for (AF_PACKET, SOCK_PACKET)
- * (cf. __sock_create).
+ * Apply the kernel rewrites so that this rule matches the triple seen
+ * at hook time. See landlock_canon_map.
*/
- if (family == AF_INET && type == SOCK_PACKET)
- family = AF_PACKET;
+ landlock_canonicalize_socket_key(&family, &type, &protocol);
err = pack_socket_key(family, type, protocol, &key);
if (err)
@@ -123,6 +488,12 @@ static int check_socket_access(const struct landlock_ruleset *dom,
return -EACCES;
}
+/*
+ * Enforcement happens at security_socket_create (pre-create) so Landlock denies
+ * unauthorized triples before the kernel loads any family-specific module or
+ * allocates a socket. This preserves the EACCES error path even for triples
+ * the kernel itself would reject (AF_UNSPEC, invalid family and type pairs).
+ */
static int hook_socket_create(int family, int type, int protocol, int kern)
{
layer_mask_t layer_masks[LANDLOCK_NUM_ACCESS_SOCKET] = {};
@@ -133,45 +504,52 @@ static int hook_socket_create(int family, int type, int protocol, int kern)
const struct landlock_cred_security *const subject =
landlock_get_applicable_subject(current_cred(), masks, NULL);
uintptr_t key;
+ s32 canon_family = family, canon_type = type, canon_protocol = protocol;
struct lsm_socket_audit audit_socket = {
.family = family,
.type = type,
.protocol = protocol,
};
- if (!subject)
- return 0;
- /* Checks only user space sockets. */
+ /* Kernel-internal sockets bypass (accept, sock_create_lite, ...). */
if (kern)
return 0;
+ if (!subject)
+ return 0;
+
+ /* Canonicalize the user-facing triple to match the rule storage. */
+ landlock_canonicalize_socket_key(&canon_family, &canon_type,
+ &canon_protocol);
handled_access = landlock_init_layer_masks(
subject->domain, LANDLOCK_ACCESS_SOCKET_CREATE, &layer_masks,
LANDLOCK_KEY_SOCKET);
+
/*
- * Error could happen due to parameters are outside of the allowed range,
- * so this combination couldn't be added in ruleset previously.
- * Therefore, it's not permitted.
+ * Four-way wildcard grid lookup. Each pack_socket_key call is gated on
+ * its return value so a rejected triple does not feed an uninitialized
+ * key into check_socket_access.
*/
- if (pack_socket_key(family, type, protocol, &key) == -EACCES)
- return -EACCES;
- if (check_socket_access(subject->domain, key, &layer_masks,
+ if (pack_socket_key(canon_family, canon_type, canon_protocol, &key) ==
+ 0 &&
+ check_socket_access(subject->domain, key, &layer_masks,
handled_access) == 0)
return 0;
- /* Ranges were already checked. */
- (void)pack_socket_key(family, TYPE_ALL, protocol, &key);
- if (check_socket_access(subject->domain, key, &layer_masks,
+ if (pack_socket_key(canon_family, TYPE_ALL, canon_protocol, &key) ==
+ 0 &&
+ check_socket_access(subject->domain, key, &layer_masks,
handled_access) == 0)
return 0;
- (void)pack_socket_key(family, type, PROTOCOL_ALL, &key);
- if (check_socket_access(subject->domain, key, &layer_masks,
+ if (pack_socket_key(canon_family, canon_type, PROTOCOL_ALL, &key) ==
+ 0 &&
+ check_socket_access(subject->domain, key, &layer_masks,
handled_access) == 0)
return 0;
- (void)pack_socket_key(family, TYPE_ALL, PROTOCOL_ALL, &key);
- if (check_socket_access(subject->domain, key, &layer_masks,
+ if (pack_socket_key(canon_family, TYPE_ALL, PROTOCOL_ALL, &key) == 0 &&
+ check_socket_access(subject->domain, key, &layer_masks,
handled_access) == 0)
return 0;
@@ -187,8 +565,64 @@ static int hook_socket_create(int family, int type, int protocol, int kern)
return -EACCES;
}
+/*
+ * Runtime drift detection: at post_create the family .create() has completed,
+ * so sk_family, sock->type and sk_protocol are authoritative. Compare them
+ * against what landlock_canonicalize_socket_key produces from the user input; a
+ * mismatch means a kernel canonicalization is not mirrored in
+ * landlock_canon_map. Enforcement already happened at socket_create, so this
+ * hook always returns 0.
+ *
+ * This hook only runs for tasks sandboxed by a Landlock domain that handles
+ * LANDLOCK_ACCESS_SOCKET_CREATE, so non-sandboxed tasks pay no overhead.
+ * Sandboxed tasks still cover enough of the reachable (family, type, protocol)
+ * space over the lifetime of a typical workload to surface drift in the field.
+ */
+static int hook_socket_post_create(struct socket *sock, int family, int type,
+ int protocol, int kern)
+{
+ const struct access_masks masks = {
+ .socket = LANDLOCK_ACCESS_SOCKET_CREATE,
+ };
+ s32 canon_family = family, canon_type = type, canon_protocol = protocol;
+
+ if (kern)
+ return 0;
+ if (!landlock_get_applicable_subject(current_cred(), masks, NULL))
+ return 0;
+
+ /*
+ * Kernel-resolved values are authoritative; no fallback to the user
+ * protocol argument. Families that ignore the user protocol (e.g.,
+ * AF_UNIX, AF_VSOCK) are represented in landlock_canon_map with
+ * PROTOCOL_ALWAYS so the canonicalization below yields the same value
+ * for both rule insertion and this comparison.
+ *
+ * Drift is reported per field so a reviewer knows which axis disagrees
+ * with the kernel without having to diff three values. WARN_ONCE
+ * rather than WARN_ON_ONCE keeps the stack trace but swallows
+ * subsequent identical drifts in the same boot.
+ */
+ landlock_canonicalize_socket_key(&canon_family, &canon_type,
+ &canon_protocol);
+ WARN_ONCE(
+ canon_family != sock->sk->sk_family,
+ "Landlock canon family drift: canon=%d kernel=%d (user family=%d type=%d protocol=%d)\n",
+ canon_family, sock->sk->sk_family, family, type, protocol);
+ WARN_ONCE(
+ canon_type != sock->type,
+ "Landlock canon type drift: canon=%d kernel=%d (user family=%d type=%d protocol=%d)\n",
+ canon_type, sock->type, family, type, protocol);
+ WARN_ONCE(
+ canon_protocol != sock->sk->sk_protocol,
+ "Landlock canon protocol drift: canon=%d kernel=%d (user family=%d type=%d protocol=%d)\n",
+ canon_protocol, sock->sk->sk_protocol, family, type, protocol);
+ return 0;
+}
+
static struct security_hook_list landlock_hooks[] __ro_after_init = {
LSM_HOOK_INIT(socket_create, hook_socket_create),
+ LSM_HOOK_INIT(socket_post_create, hook_socket_post_create),
};
__init void landlock_add_socket_hooks(void)
@@ -196,3 +630,398 @@ __init void landlock_add_socket_hooks(void)
security_add_hooks(landlock_hooks, ARRAY_SIZE(landlock_hooks),
&landlock_lsmid);
}
+
+#ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST
+
+#include <kunit/test.h>
+#include <net/net_namespace.h>
+
+/*
+ * Per-family parameterized coverage test. One subtest per AF_*; each subtest
+ * iterates every (type, protocol) probe through sock_create_kern and checks
+ * either that landlock_canonicalize_socket_key matches the kernel-resolved
+ * triple (for reachable families) or that the call fails with the expected
+ * errno (for unsupported families). No subtest is skipped: every family
+ * produces PASS or FAIL, so an unsupported entry that quietly becomes stale is
+ * caught immediately.
+ *
+ * Every unsupported family entry expects sock_create_kern to fail with
+ * -EAFNOSUPPORT because no handler is registered for that family in the KUnit
+ * build environment (either the family was never implemented, was removed from
+ * Linux, is a reserved pseudo family, or requires an arch or CONFIG not
+ * satisfied by .kunitconfig). The family name in the TAP subtest header,
+ * combined with this errno, identifies the case unambiguously.
+ */
+struct landlock_canon_family_case {
+ int af;
+ const char *name;
+ /*
+ * True means the family must be supported in the KUnit environment and
+ * canonicalization is exercised against the kernel. False means every
+ * probe must fail with -EAFNOSUPPORT.
+ */
+ bool supported;
+};
+
+#define _LANDLOCK_AF_SUPPORTED(f) \
+ { \
+ .af = (f), .name = #f, .supported = true, \
+ }
+
+#define _LANDLOCK_AF_UNSUPPORTED(f) \
+ { \
+ .af = (f), .name = #f, .supported = false, \
+ }
+
+static const struct landlock_canon_family_case landlock_canon_families[] = {
+ /* kernel rejects socket(AF_UNSPEC) */
+ _LANDLOCK_AF_UNSUPPORTED(AF_UNSPEC),
+ _LANDLOCK_AF_SUPPORTED(AF_UNIX),
+ _LANDLOCK_AF_SUPPORTED(AF_INET),
+ _LANDLOCK_AF_SUPPORTED(AF_AX25),
+ /* AF_IPX removed from Linux */
+ _LANDLOCK_AF_UNSUPPORTED(AF_IPX),
+ _LANDLOCK_AF_SUPPORTED(AF_APPLETALK),
+ _LANDLOCK_AF_SUPPORTED(AF_NETROM),
+ /* AF_BRIDGE cannot be used to create sockets */
+ _LANDLOCK_AF_UNSUPPORTED(AF_BRIDGE),
+ _LANDLOCK_AF_SUPPORTED(AF_ATMPVC),
+ _LANDLOCK_AF_SUPPORTED(AF_X25),
+ _LANDLOCK_AF_SUPPORTED(AF_INET6),
+ _LANDLOCK_AF_SUPPORTED(AF_ROSE),
+ /* AF_DECnet removed from Linux */
+ _LANDLOCK_AF_UNSUPPORTED(AF_DECnet),
+ /* AF_NETBEUI not implemented */
+ _LANDLOCK_AF_UNSUPPORTED(AF_NETBEUI),
+ /* AF_SECURITY is a pseudo family */
+ _LANDLOCK_AF_UNSUPPORTED(AF_SECURITY),
+ _LANDLOCK_AF_SUPPORTED(AF_KEY),
+ _LANDLOCK_AF_SUPPORTED(AF_NETLINK),
+ _LANDLOCK_AF_SUPPORTED(AF_PACKET),
+ /* AF_ASH not implemented */
+ _LANDLOCK_AF_UNSUPPORTED(AF_ASH),
+ /* AF_ECONET removed from Linux */
+ _LANDLOCK_AF_UNSUPPORTED(AF_ECONET),
+ _LANDLOCK_AF_SUPPORTED(AF_ATMSVC),
+ _LANDLOCK_AF_SUPPORTED(AF_RDS),
+ /* AF_SNA not implemented */
+ _LANDLOCK_AF_UNSUPPORTED(AF_SNA),
+ /* AF_IRDA removed from Linux */
+ _LANDLOCK_AF_UNSUPPORTED(AF_IRDA),
+ _LANDLOCK_AF_SUPPORTED(AF_PPPOX),
+ /* AF_WANPIPE not implemented */
+ _LANDLOCK_AF_UNSUPPORTED(AF_WANPIPE),
+ _LANDLOCK_AF_SUPPORTED(AF_LLC),
+ /* AF_IB reserved by infiniband */
+ _LANDLOCK_AF_UNSUPPORTED(AF_IB),
+ /* AF_MPLS cannot be used to create sockets */
+ _LANDLOCK_AF_UNSUPPORTED(AF_MPLS),
+ _LANDLOCK_AF_SUPPORTED(AF_CAN),
+ _LANDLOCK_AF_SUPPORTED(AF_TIPC),
+ _LANDLOCK_AF_SUPPORTED(AF_BLUETOOTH),
+ /* AF_IUCV only on s390 */
+ _LANDLOCK_AF_UNSUPPORTED(AF_IUCV),
+ _LANDLOCK_AF_SUPPORTED(AF_RXRPC),
+ _LANDLOCK_AF_SUPPORTED(AF_ISDN),
+ _LANDLOCK_AF_SUPPORTED(AF_PHONET),
+ _LANDLOCK_AF_SUPPORTED(AF_IEEE802154),
+ _LANDLOCK_AF_SUPPORTED(AF_CAIF),
+ _LANDLOCK_AF_SUPPORTED(AF_ALG),
+ _LANDLOCK_AF_SUPPORTED(AF_NFC),
+ _LANDLOCK_AF_SUPPORTED(AF_VSOCK),
+ /* AF_KCM requires a kcm multiplexer setup */
+ _LANDLOCK_AF_UNSUPPORTED(AF_KCM),
+ _LANDLOCK_AF_SUPPORTED(AF_QIPCRTR),
+ _LANDLOCK_AF_SUPPORTED(AF_SMC),
+ /* AF_XDP requires a netdev with XDP support */
+ _LANDLOCK_AF_UNSUPPORTED(AF_XDP),
+ _LANDLOCK_AF_SUPPORTED(AF_MCTP),
+};
+
+static_assert(ARRAY_SIZE(landlock_canon_families) == AF_MAX,
+ "landlock_canon_families size must track AF_MAX.");
+
+static void
+landlock_canon_family_to_desc(const struct landlock_canon_family_case *c,
+ char *desc)
+{
+ strscpy(desc, c->name, KUNIT_PARAM_DESC_SIZE);
+}
+
+KUNIT_ARRAY_PARAM(landlock_canon_family, landlock_canon_families,
+ landlock_canon_family_to_desc);
+
+/*
+ * Edge protocol sentinels beyond the 0..NPROTO range exercised by the main
+ * sweep. IPPROTO_SCTP is the only value in landlock_canon_map above NPROTO
+ * today; 255 = IPPROTO_RAW covers the top of the IP protocol byte; 0xFFFF = u16
+ * upper bound covers the ethertype-wide range accepted by AF_PACKET and
+ * saturates pack_socket_key's u16 field.
+ */
+static const int landlock_edge_protocols[] = {
+ IPPROTO_SCTP,
+ 255 /* IPPROTO_RAW */,
+ 0xFFFF /* u16 max */,
+};
+
+/*
+ * Return the protocol value for probe index @p: indices 0..NPROTO cover every
+ * family-ID range (and NPROTO itself, one past the last valid PF_*, exercises
+ * an out-of-range probe); subsequent indices draw from landlock_edge_protocols.
+ * Returns -1 when the iteration is exhausted.
+ */
+static int landlock_probe_protocol(size_t p)
+{
+ if (p <= NPROTO)
+ return (int)p;
+ if (p - (NPROTO + 1) < ARRAY_SIZE(landlock_edge_protocols))
+ return landlock_edge_protocols[p - (NPROTO + 1)];
+ return -1;
+}
+
+static void test_family_canonicalization(struct kunit *const test)
+{
+ const struct landlock_canon_family_case *const c = test->param_value;
+ unsigned int tested = 0, canonicalized = 0, confirmed_unsupported = 0;
+ int type;
+ size_t probe;
+
+ for (type = 0; type < SOCK_MAX; type++) {
+ for (probe = 0;; probe++) {
+ const int protocol = landlock_probe_protocol(probe);
+ struct socket *sock = NULL;
+ int ret, kernel_family, kernel_type, kernel_protocol;
+ s32 landlock_family, landlock_type, landlock_protocol;
+
+ if (protocol < 0)
+ break;
+
+ ret = sock_create_kern(&init_net, c->af, type, protocol,
+ &sock);
+
+ if (ret == 0) {
+ /*
+ * Reached the kernel: fully verify Landlock
+ * canonicalization against the resolved
+ * sk_family / sock->type / sk_protocol triple.
+ * This path runs whether or not the family is
+ * listed as unsupported: if an extra CONFIG_*
+ * turns an unsupported family entry reachable,
+ * the test picks it up naturally.
+ */
+ kernel_family = sock->sk->sk_family;
+ kernel_type = sock->type;
+ kernel_protocol = sock->sk->sk_protocol;
+ sock_release(sock);
+
+ landlock_family = c->af;
+ landlock_type = type;
+ landlock_protocol = protocol;
+ landlock_canonicalize_socket_key(
+ &landlock_family, &landlock_type,
+ &landlock_protocol);
+
+ /* Drift axis identified by the failing macro
+ * line; KUNIT_EXPECT_EQ_MSG already prints
+ * expected vs actual values. The format
+ * "(F,T,P)" carries the user input that
+ * triggered the mismatch.
+ */
+ KUNIT_EXPECT_EQ_MSG(test, landlock_family,
+ kernel_family,
+ "family (%d,%d,%d)", c->af,
+ type, protocol);
+ KUNIT_EXPECT_EQ_MSG(test, landlock_type,
+ kernel_type,
+ "type (%d,%d,%d)", c->af,
+ type, protocol);
+ KUNIT_EXPECT_EQ_MSG(test, landlock_protocol,
+ kernel_protocol,
+ "protocol (%d,%d,%d)",
+ c->af, type, protocol);
+
+ tested++;
+ if (landlock_family != c->af ||
+ landlock_type != type ||
+ landlock_protocol != protocol)
+ canonicalized++;
+ } else if (ret == -EAFNOSUPPORT && !c->supported) {
+ /*
+ * Ignore this specific combination when the
+ * kernel says the family is not registered AND
+ * the family is on the unsupported list.
+ * Rationale: the unsupported flag declares "we
+ * expect no kernel handler for this family in
+ * the KUnit build". If extra CONFIG_* entries
+ * are added later (e.g. via --kconfig_add or a
+ * per-arch config layer), an unsupported family
+ * entry may start returning ret == 0 for some
+ * probes; those fall into the branch above and
+ * are fully tested. No per-arch .kunitconfig
+ * fragmentation is required.
+ *
+ * Other negative errno values (e.g.,
+ * -EPROTONOSUPPORT, -ESOCKTNOSUPPORT,
+ * -EPROTOTYPE) indicate that this specific
+ * (type, protocol) triple is not supported by
+ * an otherwise-loaded family; silently skip the
+ * probe in that case too.
+ */
+ confirmed_unsupported++;
+ }
+ /*
+ * All other non-zero returns are silently skipped: the
+ * kernel rejected the specific triple; nothing to
+ * canonicalize.
+ */
+ }
+ }
+
+ if (!c->supported) {
+ kunit_info(test,
+ "%u probes confirmed %s unsupported (errno %d)",
+ confirmed_unsupported, c->name, -EAFNOSUPPORT);
+ return;
+ }
+ if (tested == 0) {
+ /*
+ * Resolve by either (a) adding a sub-protocol CONFIG to
+ * .kunitconfig so sock_create_kern can reach the family, or (b)
+ * flipping the family entry's .supported field to false to
+ * declare it expected-unreachable.
+ */
+ KUNIT_FAIL(test, "%s: no reachable triple", c->name);
+ return;
+ }
+ kunit_info(test, "%u reachable triples (%u canonicalized)", tested,
+ canonicalized);
+}
+
+/*
+ * Wildcards bypass canonicalization: the input triple is returned unchanged for
+ * TYPE_ALL, and protocol rewrites are skipped for PROTOCOL_ALL while family and
+ * type rewrites still apply.
+ */
+static void test_canonicalization_preserves_wildcards(struct kunit *const test)
+{
+ s32 family, type, protocol;
+
+ /* Type wildcard: no map lookup. */
+ family = AF_INET;
+ type = TYPE_ALL;
+ protocol = 0;
+ landlock_canonicalize_socket_key(&family, &type, &protocol);
+ KUNIT_EXPECT_EQ(test, family, AF_INET);
+ KUNIT_EXPECT_EQ(test, type, TYPE_ALL);
+ KUNIT_EXPECT_EQ(test, protocol, 0);
+
+ /* Protocol wildcard with REWRITE_FAMILY row: family still rewrites. */
+ family = AF_INET;
+ type = SOCK_PACKET;
+ protocol = PROTOCOL_ALL;
+ landlock_canonicalize_socket_key(&family, &type, &protocol);
+ KUNIT_EXPECT_EQ(test, family, AF_PACKET);
+ KUNIT_EXPECT_EQ(test, type, SOCK_PACKET);
+ KUNIT_EXPECT_EQ(test, protocol, PROTOCOL_ALL);
+
+ /* Protocol wildcard with PROTOCOL_ZERO row: protocol stays wildcard. */
+ family = AF_INET;
+ type = SOCK_STREAM;
+ protocol = PROTOCOL_ALL;
+ landlock_canonicalize_socket_key(&family, &type, &protocol);
+ KUNIT_EXPECT_EQ(test, family, AF_INET);
+ KUNIT_EXPECT_EQ(test, type, SOCK_STREAM);
+ KUNIT_EXPECT_EQ(test, protocol, PROTOCOL_ALL);
+
+ /*
+ * Protocol wildcard with PROTOCOL_ALWAYS row: protocol stays wildcard
+ * even though the entry would unconditionally rewrite concrete protocol
+ * values.
+ */
+ family = AF_UNIX;
+ type = SOCK_STREAM;
+ protocol = PROTOCOL_ALL;
+ landlock_canonicalize_socket_key(&family, &type, &protocol);
+ KUNIT_EXPECT_EQ(test, family, AF_UNIX);
+ KUNIT_EXPECT_EQ(test, type, SOCK_STREAM);
+ KUNIT_EXPECT_EQ(test, protocol, PROTOCOL_ALL);
+
+ /*
+ * REWRITE_TYPE with PROTOCOL_ALL still folds the type while leaving the
+ * protocol wildcard intact.
+ */
+ family = AF_UNIX;
+ type = SOCK_RAW;
+ protocol = PROTOCOL_ALL;
+ landlock_canonicalize_socket_key(&family, &type, &protocol);
+ KUNIT_EXPECT_EQ(test, family, AF_UNIX);
+ KUNIT_EXPECT_EQ(test, type, SOCK_DGRAM);
+ KUNIT_EXPECT_EQ(test, protocol, PROTOCOL_ALL);
+}
+
+/*
+ * Each map entry produces the expected rewrite for a concrete triple.
+ * Parameterized for per-entry pass/fail visibility; semantic descriptors name
+ * what each entry is supposed to do.
+ */
+struct landlock_canon_map_case {
+ const char *name;
+ s32 in_family, in_type, in_protocol;
+ s32 out_family, out_type, out_protocol;
+};
+
+/*
+ * test_family_canonicalization covers every (family, type, protocol) cell that
+ * sock_create_kern can reach. The cases listed here are the kernel-unreachable
+ * cells whose canonicalization still matters in production and therefore needs
+ * config-independent validation.
+ *
+ * AF_VSOCK SOCK_DGRAM is the only such cell today: its reachability requires
+ * CONFIG_VIRTIO_VSOCKETS=y, which the default .kunitconfig does not enable.
+ * The two entries below cover both user call forms (protocol=0 and
+ * protocol=PF_VSOCK) that vsock_create accepts and canonicalize to 0.
+ */
+static const struct landlock_canon_map_case landlock_canon_map_cases[] = {
+ { "vsock_dgram_zero_stays_zero", AF_VSOCK, SOCK_DGRAM, 0, AF_VSOCK,
+ SOCK_DGRAM, 0 },
+ { "vsock_dgram_family_id_forced_to_zero", AF_VSOCK, SOCK_DGRAM,
+ AF_VSOCK, AF_VSOCK, SOCK_DGRAM, 0 },
+};
+
+static void landlock_canon_map_to_desc(const struct landlock_canon_map_case *c,
+ char *desc)
+{
+ strscpy(desc, c->name, KUNIT_PARAM_DESC_SIZE);
+}
+
+KUNIT_ARRAY_PARAM(landlock_canon_map, landlock_canon_map_cases,
+ landlock_canon_map_to_desc);
+
+static void test_canonicalization_map_entry(struct kunit *const test)
+{
+ const struct landlock_canon_map_case *const c = test->param_value;
+ s32 family = c->in_family, type = c->in_type, protocol = c->in_protocol;
+
+ landlock_canonicalize_socket_key(&family, &type, &protocol);
+ KUNIT_EXPECT_EQ(test, family, c->out_family);
+ KUNIT_EXPECT_EQ(test, type, c->out_type);
+ KUNIT_EXPECT_EQ(test, protocol, c->out_protocol);
+}
+
+static struct kunit_case test_cases[] = {
+ KUNIT_CASE_PARAM(test_family_canonicalization,
+ landlock_canon_family_gen_params),
+ KUNIT_CASE(test_canonicalization_preserves_wildcards),
+ KUNIT_CASE_PARAM(test_canonicalization_map_entry,
+ landlock_canon_map_gen_params),
+ {}
+};
+
+static struct kunit_suite test_suite = {
+ .name = "landlock_socket",
+ .test_cases = test_cases,
+};
+
+kunit_test_suite(test_suite);
+
+#endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */
diff --git a/tools/testing/selftests/landlock/socket_test.c b/tools/testing/selftests/landlock/socket_test.c
index a091b8a883c8..5c0959f50ba2 100644
--- a/tools/testing/selftests/landlock/socket_test.c
+++ b/tools/testing/selftests/landlock/socket_test.c
@@ -7,15 +7,15 @@
#define _GNU_SOURCE
+#include <arpa/inet.h>
+#include <linux/can.h>
+#include <linux/kcm.h>
#include <linux/landlock.h>
-#include <sys/prctl.h>
#include <linux/pfkeyv2.h>
-#include <linux/kcm.h>
-#include <linux/can.h>
-#include <sys/socket.h>
-#include <stdint.h>
#include <linux/sctp.h>
-#include <arpa/inet.h>
+#include <stdint.h>
+#include <sys/prctl.h>
+#include <sys/socket.h>
#include "audit.h"
#include "common.h"
@@ -829,11 +829,12 @@ FIXTURE_VARIANT_ADD(tcp_protocol, variant2) {
};
/*
- * Landlock doesn't perform protocol mappings handled by network stack on
- * protocol family level. Test verifies that if only one definition is
- * allowed another becomes restricted.
+ * Landlock canonicalizes AF_INET, SOCK_STREAM, protocol=0 to IPPROTO_TCP at
+ * rule insertion so that a rule inserted with either the default-protocol form
+ * or the explicit IPPROTO_TCP form matches both call syntaxes. Test verifies
+ * this aliasing. Unrelated families (AF_PACKET here) remain restricted.
*/
-TEST_F(tcp_protocol, alias_restriction)
+TEST_F(tcp_protocol, alias_equivalence)
{
const struct landlock_ruleset_attr ruleset_attr = {
.handled_access_socket = LANDLOCK_ACCESS_SOCKET_CREATE,
@@ -861,14 +862,15 @@ TEST_F(tcp_protocol, alias_restriction)
enforce_ruleset(_metadata, ruleset_fd);
ASSERT_EQ(0, close(ruleset_fd));
- if (protocol == 0) {
- EXPECT_EQ(0, test_socket(AF_INET, SOCK_STREAM, 0));
- EXPECT_EQ(EACCES,
- test_socket(AF_PACKET, SOCK_STREAM, IPPROTO_TCP));
- } else if (protocol == IPPROTO_TCP) {
- EXPECT_EQ(EACCES, test_socket(AF_INET, SOCK_STREAM, 0));
- EXPECT_EQ(0, test_socket(AF_INET, SOCK_STREAM, IPPROTO_TCP));
- }
+ /*
+ * Irrespective of which call form was used to insert the rule, both
+ * call forms of socket(2) match.
+ */
+ EXPECT_EQ(0, test_socket(AF_INET, SOCK_STREAM, 0));
+ EXPECT_EQ(0, test_socket(AF_INET, SOCK_STREAM, IPPROTO_TCP));
+
+ /* Other families remain restricted. */
+ EXPECT_EQ(EACCES, test_socket(AF_PACKET, SOCK_STREAM, IPPROTO_TCP));
}
static int test_socketpair(int family, int type, int protocol)
--
2.53.0
^ permalink raw reply related
* Re: [PATCH] ima: debugging late_initcall_sync measurements
From: Yeoreum Yun @ 2026-05-08 13:41 UTC (permalink / raw)
To: Mimi Zohar
Cc: David Safford, Jonathan McDowell, linux-security-module,
linux-kernel, linux-integrity, linux-arm-kernel, kvmarm, paul,
jmorris, serge, roberto.sassu, dmitry.kasatkin, eric.snowberg,
jarkko, jgg, sudeep.holla, maz, oupton, joey.gouly,
suzuki.poulose, yuzenghui, catalin.marinas, will, noodles,
sebastianene
In-Reply-To: <2b3782398cc17ce9d355490a0c42ebce9120a9ae.camel@linux.ibm.com>
> On Fri, 2026-05-08 at 10:06 +0100, Yeoreum Yun wrote:
>
> > > The kernel selftests caused the measurements between late_initcall and
> > > late_initcall_sync. After disabling all of the kernel selftests, there weren't
> > > any measurements. Re-enabling the FIPS selftests on PowerVM LPAR resulted in
> > > measurements. (I didn't try re-enabling any of the other selftests.)
> > >
> > > CONFIG_FIPS_SIGNATURE_SELFTEST=y
> > > CONFIG_FIPS_SIGNATURE_SELFTEST_RSA=y
> > > CONFIG_FIPS_SIGNATURE_SELFTEST_ECDSA=y
> >
> > Thanks for shraring this ;)
> >
> > I found the reason for those mesaurements. Those come from the
> > request_module() and usermode-thread generates them while handling module
> > loading request for crypto-x962(ecdsa-nist-p256).
> > Since it's not a real kernel module,
> > I confirmed file measurements between late_initcall and
> > late_initcall_sync are gone for modeprobe with below change:
> >
> > @@ -1246,9 +1250,14 @@ EXPORT_SYMBOL_GPL(ima_measure_critical_data);
> > */
> > static int ima_kernel_module_request(char *kmod_name)
> > {
> > if (strncmp(kmod_name, "crypto-pkcs1(rsa,", 17) == 0)
> > return -EINVAL;
> >
> > + if (IS_BUILTIN(CONFIG_CRYPTO_ECDSA) &&
> > + (strncmp(kmod_name, "crypto-x962(ecdsa", 17) == 0))
> > + return -EINVAL;
> > +
> > return 0;
> > }
> >
> > Though this is the only request_module() call between
> > late_initcall and late_initcall_sync, but I also confirmed there're
> > request_modules() call before ima initalisation before "late_initcall":
> >
> > /*
> > * NOTE: kmod_name is printed on ima_kernel_module_request()
> > */
> >
> > // This is called from module_init(stm_core_init) -> device_initcall()
> > // which is in driver/hwtracing/stm/core.c (built-in)
> > [ 1.421986] ima: kmod_name: stm_p_basic
> > ...
> > [ 1.444900] ima: kmod_name: crypto-pkcs1(rsa,sha512)
> > [ 1.444903] ima: kmod_name: crypto-pkcs1(rsa,sha512)-all
> > ...
> > [ 1.452029] ima: kmod_name: crypto-cbc(aes)
> > [ 1.465321] ima: kmod_name: crypto-cbc(aes)-all
> > ...
> > [ 1.467845] Key type encrypted registered
> > [ 1.467848] AppArmor: AppArmor sha256 policy hashing enabled
> >
> > // IMA is initailised at late_initcall level.
> > [ 1.467850] ima: [init_ima_late:1336]
> >
> > If IMA should care request_module() from kernel before IMA init,
> > I think there is no way to solve except queuing those events
> > (kernel_load_data/kernel_load_post_data and open for module binary etc.)
> > though it breaks "measure before use" principle since IMA couldn't
> > measure at that time.
> >
> > But if you don't care about those things -- some events happend before
> > IMA init, I think your suggestion -- controlling the init time of ima_init()
> > via a Kconfig option is good and ignoring some usermodehelper request
> > including request_module() before IMA initialisation upto user by that option.
>
> Thank you for the complete analysis. The early measurements before the TPM is
> initialized is a problem that needs to be addressed. As to whether the solution
> will require queueing is yet to be determined. (Roberto has some thoughts on
> addressing it.) This discussion makes it clear that simply delaying IMA
> initialization by moving it from late_initcall to late_initcall_sync could miss
> measurements. That said, exposing it as an opt-in Kconfig for those who accept
> the risk is a sensible pragmatic compromise.
I think once we address ealry measurements before intialising TPM,
It doesn't matter when IMA is initialissed since they're considered as
ealry measurements anyway.
BTW, I'm not sure whether we should take pragmatic compromise first to
support deferred TPM initialisation or solving it together via solution
of ealry measurements (whatever it is) in now.
--
Sincerely,
Yeoreum Yun
^ permalink raw reply
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