Reintroduce Hornet LSM
Subsystems (9)
v6 cover Reintroduce Hornet LSM
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 V6:
- Doc cleanup
- Error handling improvements
- Support for shareable maps
Link to V5: https://lore.kernel.org/linux-security-module/[email protected]/
Changes in V5:
- Added IPE documentation
Link to V4: https://lore.kernel.org/linux-security-module/[email protected]/
Changes in V4:
- IPE integration
- Arbitrary keyring support
Link to V3: https://lore.kernel.org/linux-security-module/[email protected]/
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/[email protected]/
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/[email protected]/
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 | 39 ++ 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 | 397 +++++++++++++++++++ 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 | 350 ++++++++++++++++ security/ipe/Kconfig | 14 + security/ipe/audit.c | 15 + security/ipe/eval.c | 73 +++- 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, 2056 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
Signature · 1 line
2.53.0
PATCH v6 1/10 crypto: pkcs7: add flag for validated trust on a signed info block
From: James Bottomley <[email protected]>
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.
--- certs/system_keyring.c | 1 + crypto/asymmetric_keys/pkcs7_parser.h | 1 + crypto/asymmetric_keys/pkcs7_trust.c | 1 + 3 files changed, 3 insertions(+)
certs/system_keyring.c +1 -0
@@ -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.
crypto/asymmetric_keys/pkcs7_parser.h +1 -0
@@ -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;
crypto/asymmetric_keys/pkcs7_trust.c +1 -0
@@ -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 ; }
Signature · 1 line
2.53.0
[email protected]
AI reviewRe: [v6 01/10] crypto: pkcs7: add flag for validated trust on a signed info block
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider: - [High] State persistence across validations causes false blacklist rejections and API breakage if a…
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] State persistence across validations causes false blacklist rejections and API breakage if a pkcs7_message is evaluated multiple times.
- [Medium] verify_pkcs7_message_sig is exported using EXPORT_SYMBOL, creating a licensing mismatch with its prerequisite GPL functions.
- [Low] The commit message references a nonexistent function.
--
commit f9218a5ea80d770fbf301c4067d4e4b1febdf4ab
Author: James Bottomley <[email protected]>
crypto: pkcs7: add flag for validated trust on a signed info block
This commit adds a verified flag to the pkcs7_signed_info struct to track if a signer has passed trust validation. It also exports verify_pkcs7_message_sig so consumers can retrieve trusted pkcs7 objects.
Quoted 4 lines
Allow consumers of struct
pkcs7_messageto 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 viavalidate_pkcs7_trust(). Since the way to get this trusted pkcs7
This isn't a bug, but does validate_pkcs7_trust() exist in the tree? It looks like this might be a typo for verify_pkcs7_signature() or verify_pkcs7_message_sig().
certs/system_keyring.c int verify_pkcs7_message_sig(const void *data, size_t len, · line 383
Quoted 9 lines
certs/system_keyring.c +1
-0@@ -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);
Could this cause an API licensing mismatch?
To legitimately use verify_pkcs7_message_sig(), a consumer must parse the raw payload into a pkcs7_message and eventually free it using pkcs7_parse_message() and pkcs7_free_message().
Since those prerequisite functions are restricted to EXPORT_SYMBOL_GPL, non-GPL modules wouldn't be able to legally acquire or manage the required structures to use this new export. Should this be EXPORT_SYMBOL_GPL instead?
Quoted 4 lines
crypto/asymmetric_keys/pkcs7_trust.c +0
-0
[ ... ]
crypto/asymmetric_keys/pkcs7_trust.c static int pkcs7_validate_trust_one(struct pkcs7_message *pkcs7, · line 133
Quoted 8 lines
crypto/asymmetric_keys/pkcs7_trust.c +1
-0@@ -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 ; }
Will these verified flags cause issues if a parsed pkcs7_message is evaluated multiple times?
Exporting verify_pkcs7_message_sig() allows a caller to evaluate a parsed PKCS#7 message against multiple keyrings sequentially. If a message is validated successfully against the first keyring, the certificates and sinfo are marked verified = true.
When the same message is then validated against a second keyring, the internal blacklist check is_key_on_revocation_list() invokes pkcs7_validate_trust() against the blacklist keyring.
In pkcs7_validate_trust_one(), the code checks:
for (x509 = sinfo->signer; x509; x509 = x509->signer) {
if (x509->seen) {
if (x509->verified)
goto verified;Because the certificate's verified flag is still true from the first keyring check, won't the blacklist check hit this goto and falsely conclude the certificate is blacklisted?
This seems like it would cause the second validation to unconditionally return -EKEYREJECTED for a valid message.
Additionally, if detached data is provided, pkcs7_supply_detached_data() unconditionally returns -EINVAL on a second call. Does the state need to be cleared before a subsequent validation?
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=1
PATCH v6 2/10 crypto: pkcs7: add ability to extract signed attributes by OID
In reply to Blaise BoscaccyFrom: James Bottomley <[email protected]>
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.
--- 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
crypto/asymmetric_keys/Makefile +3 -1
@@ -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
crypto/asymmetric_keys/pkcs7_aa.asn1 new file +18 -0
@@ -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 }) +}
crypto/asymmetric_keys/pkcs7_parser.c +81 -0
93 lines not on this page · raw message
include/crypto/pkcs7.h +4 -0
@@ -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
Signature · 1 line
2.53.0
[email protected]
AI reviewRe: [v6 02/10] crypto: pkcs7: add ability to extract signed attributes by OID
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider: - [High] Premature loop termination skips valid signers when a prior signer lacks authenticated attributes…
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] Premature loop termination skips valid signers when a prior signer lacks authenticated attributes or contains malformed data.
- [Medium] Parser differential risk due to blindly overwriting multiple attribute values in pkcs7_aa_note_attr. --
commit 06f8e69e2d93f6dbe1e1035741251e357d0c5e30
Author: James Bottomley <[email protected]>
crypto: pkcs7: add ability to extract signed attributes by OID
This commit adds an API to extract authenticated attributes from a PKCS#7 message by OID. It introduces a new ASN.1 grammar and a set of callbacks to parse the attributes for verified signers.
Quoted 4 lines
crypto/asymmetric_keys/pkcs7_parser.c +0
-0
[ ... ]
crypto/asymmetric_keys/pkcs7_parser.c int pkcs7_get_content_data(const struct pkcs7_message *pkcs7, · line 246
Quoted 13 lines
crypto/asymmetric_keys/pkcs7_parser.c +13
-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 ; +}
Does this create a parser differential risk?
The ASN.1 definition uses a set of ANY for authenticated attributes. If a single attribute contains multiple values, this callback will be triggered for each element.
Since the callback overwrites ctx->data without checking if a value was already found, it will silently return only the last value in the set. Could an attacker craft a signature with multiple conflicting values, causing Linux to extract and enforce a policy based on a different payload than what other tools verify?
[ ... ]
Quoted 11 lines
crypto/asymmetric_keys/pkcs7_parser.c +11
-0+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) {
crypto/asymmetric_keys/pkcs7_parser.c int pkcs7_get_content_data(const struct pkcs7_message *pkcs7, · line 282
Quoted 10 lines
crypto/asymmetric_keys/pkcs7_parser.c +10
-0+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 ;
Can this loop exit prematurely when a prior signer lacks authenticated attributes or contains malformed data?
If a verified PKCS#7 signer has no authenticated attributes, sinfo->authattrs is NULL and sinfo->authattrs_len is 0. Calling asn1_ber_decoder() with a 0-length buffer triggers a data overrun check and returns a negative error code:
lib/asn1_decoder.c:asn1_ber_decoder() {
...
if (unlikely(datalen - dp < 2))
goto data_overrun_error;
...
}Because of the break statement on a negative return value, this aborts the search entirely and returns -ENODATA.
Does this prevent the parser from checking any subsequent verified signers that might actually contain the requested attribute? Should this skip empty attributes and use continue instead of break?
Quoted 11 lines
crypto/asymmetric_keys/pkcs7_parser.c +11
-0+ } + +if (!ctx.data) +return -ENODATA ; + + *_data = ctx.data; + *_len = ctx.len; + +return 0 ; +} +EXPORT_SYMBOL_GPL (pkcs7_get_authattr);
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=2
PATCH v6 3/10 crypto: pkcs7: add tests for pkcs7_get_authattr
In reply to Blaise BoscaccyFrom: James Bottomley <[email protected]>
Add example code to the test module pkcs7_key_type.c that verifies a message and then pulls out a known authenticated attribute.
--- crypto/asymmetric_keys/pkcs7_key_type.c | 44 ++++++++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-)
crypto/asymmetric_keys/pkcs7_key_type.c +43 -1
@@ -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 ; }/*
Signature · 1 line
2.53.0
[email protected]
AI reviewRe: [v6 03/10] crypto: pkcs7: add tests for pkcs7_get_authattr
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [Medium] Test failures are silently masked because `pkcs7_preparse` unconditionally returns success on…
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] Test failures are silently masked because pkcs7_preparse unconditionally returns success on failure paths. --
commit e5c9359ab8a0636cf9b8d7481caa515ada8b2819
Author: James Bottomley <[email protected]>
crypto: pkcs7: add tests for pkcs7_get_authattr
This commit adds example code to the pkcs7_key_type test module to verify a message and extract a known authenticated attribute.
Quoted 4 lines
crypto/asymmetric_keys/pkcs7_key_type.c +0
-0
[ ... ]
Quoted 4 lines
crypto/asymmetric_keys/pkcs7_key_type.c +0
-0@@ -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;
[ ... ]
crypto/asymmetric_keys/pkcs7_key_type.c static int pkcs7_view_content(void *ctx, const void *data, size_t len, · line 86
Quoted 9 lines
crypto/asymmetric_keys/pkcs7_key_type.c +9
-0+/* +* 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; + }
If this condition triggers, ret is 0. Would this result in the test reporting success even if the function were updated to return ret at the end instead of 0? Should this set a negative error code before the goto?
[ ... ]
Quoted 10 lines
crypto/asymmetric_keys/pkcs7_key_type.c +10
-0+/* 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);
crypto/asymmetric_keys/pkcs7_key_type.c static int pkcs7_view_content(void *ctx, const void *data, size_t len, · line 106
Quoted 10 lines
crypto/asymmetric_keys/pkcs7_key_type.c +9
-0+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 ; }
Since all failure paths jump to the out label which unconditionally returns 0, does this silently mask test failures from the caller?
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=3
PATCH v6 4/10 lsm: framework for BPF integrity verification
In reply to Blaise BoscaccyFrom: Paul Moore <[email protected]>
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:
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.
--- include/linux/lsm_hook_defs.h | 5 +++ include/linux/security.h | 25 ++++++++++++ security/security.c | 75 +++++++++++++++++++++++++++++++++-- 3 files changed, 102 insertions(+), 3 deletions(-)
include/linux/lsm_hook_defs.h +5 -0
@@ -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)
include/linux/security.h +25 -0
@@ -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 *astatic 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) {
security/security.c +72 -3
95 lines not on this page · raw message
Signature · 1 line
2.53.0
[email protected]
AI reviewRe: [v6 04/10] lsm: framework for BPF integrity verification
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [Medium] The newly added LSM hooks `bpf_prog_load_integrity` and `bpf_prog_load_post_integrity` are…
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] The newly added LSM hooks bpf_prog_load_integrity and bpf_prog_load_post_integrity are missing from the sleepable_lsm_hooks BTF set. --
commit 8148d31126f70fa43314056c3dc696e4fcd7470d
Author: Paul Moore <[email protected]>
lsm: framework for BPF integrity verification
This commit adds a new LSM hook and two new callbacks to support BPF program integrity verification. It allows administrators to select additional integrity verification mechanisms through the LSM framework.
include/linux/lsm_hook_defs.h LSM_HOOK(int, 0, bpf_prog, struct bpf_prog *prog) · line 441
Quoted 13 lines
include/linux/lsm_hook_defs.h +5
-0@@ -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)
Should these two new hooks be added to the sleepable_lsm_hooks BTF set in kernel/bpf/bpf_lsm.c?
Since they execute in the same context as the existing bpf_prog_load hook (which is a known sleepable context), omitting them restricts any BPF LSM program attaching to them to a non-sleepable context.
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=4
PATCH v6 5/10 lsm: security: Add additional enum values for bpf integrity checks
In reply to Blaise BoscaccyFirst 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.
--- include/linux/security.h | 3 +++ 1 file changed, 3 insertions(+)
include/linux/security.h +3 -0
@@ -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, };
Signature · 1 line
2.53.0
Blaise Boscaccy
NAKPATCH v6 6/10 security: Hornet LSM
In reply to Blaise BoscaccyThis 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.
--- 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 | 350 +++++++++++++++++++++++ 11 files changed, 722 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
Documentation/admin-guide/LSM/Hornet.rst new file +323 -0
@@ -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 occured 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.
Documentation/admin-guide/LSM/index.rst +1 -0
@@ -49,3 +49,4 @@ subdirectories. SafeSetID ipe landlock + Hornet
MAINTAINERS +9 -0
@@ -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 <[email protected]> +L: [email protected] +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 <[email protected]> L: [email protected]
include/linux/oid_registry.h +3 -0
@@ -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 };
include/uapi/linux/lsm.h +1 -0
@@ -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
security/Kconfig +2 -1
@@ -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
security/Makefile +1 -0
@@ -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/
security/hornet/Kconfig new file +13 -0
@@ -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.
security/hornet/Makefile new file +7 -0
@@ -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
security/hornet/hornet.asn1 new file +12 -0
@@ -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 })
security/hornet/hornet_lsm.c new file +350 -0
350 lines not on this page · raw message
Signature · 1 line
2.53.0
[email protected]
AI reviewRe: [v6 06/10] security: Hornet LSM
Thank you for your contribution! Sashiko AI review found 9 potential issue(s) to consider: - [Critical] A Time-of-Check to Time-of-Use (TOCTOU) race condition in the `bpf` LSM hook allows attackers…
Thank you for your contribution! Sashiko AI review found 9 potential issue(s) to consider:
- [Critical] A Time-of-Check to Time-of-Use (TOCTOU) race condition in the bpf LSM hook allows attackers to bypass runtime map verification.
- [Critical] TOCTOU race condition on attr->fd_array allows loading unsigned maps.
- [High] Unbounded memory allocation for the signature buffer based on user input allows local Denial of Service.
- [High] Synchronous map hashing over potentially massive BPF maps causes severe CPU stalls.
- [High] Lockless read of map->frozen lacks memory barriers, allowing race condition bypass.
- [High] Hardcoded fail-open logic for unsigned programs overrides downstream LSM policy.
- [Medium] Off-by-one error in ASN.1 decoder rejects valid signatures with exactly 64 maps.
- [Medium] Legitimately skipped frozen maps are unconditionally rejected at runtime.
- [Medium] Deduplication of used_maps causes state mismatch, rejecting valid programs.
--
commit 88fd67d9d38369a17372ee4205b99a1b455d1487
Author: Blaise Boscaccy <[email protected]>
security: Hornet LSM
This adds the Hornet Linux Security Module which provides enhanced signature verification and data validation for eBPF programs. It verifies PKCS#7 signatures and map hashes to solidify auditing and prevent tampering.
Quoted 5 lines
security/hornet/hornet_lsm.c new file +0
-0
[ ... ]
Quoted 4 lines
security/hornet/hornet_lsm.c new file +4
-0+static int hornet_verify_hashes (struct hornet_maps *maps, +struct hornet_parse_context *ctx, +struct bpf_prog *prog) +{
[ ... ]
security/hornet/hornet_lsm.c line 70
Quoted 7 lines
security/hornet/hornet_lsm.c new file +7
-0+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));
Can this lead to a Time-of-Check to Time-of-Use race condition? The map file descriptors are read directly from the user-provided fd_array. After Hornet successfully verifies these maps against the signature, the BPF verifier reads from the exact same user-space memory to bind the maps to the program. It appears an attacker could swap the map file descriptors in user memory with malicious, unsigned maps between the LSM check and the verifier binding.
security/hornet/hornet_lsm.c line 81
Quoted 11 lines
security/hornet/hornet_lsm.c new file +11
-0+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 (!map->frozen)
Could this check bypass the integrity verification due to missing memory barriers? map->frozen is read locklessly here without smp_load_acquire(). While bpf_map_freeze uses a mutex, memory reordering could allow Hornet to read benign map data while seeing map->frozen as true from a concurrent freeze. An attacker could potentially update the map with malicious data right before freezing it.
Quoted 1 line
security/hornet/hornet_lsm.c new file +1
-0+return LSM_INT_VERDICT_FAULT ;
[ ... ]
security/hornet/hornet_lsm.c line 108
Quoted 7 lines
security/hornet/hornet_lsm.c new file +7
-0+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 )
Does this reject valid signatures with exactly 64 maps? Since hash_count is incremented before the comparison, processing the 64th map will trigger the condition and cause the function to return -EINVAL.
Quoted 3 lines
security/hornet/hornet_lsm.c new file +3
-0+return -EINVAL ; +return 0 ; +}
[ ... ]
Quoted 4 lines
security/hornet/hornet_lsm.c new file +4
-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) +{
[ ... ]
security/hornet/hornet_lsm.c line 175
Quoted 2 lines
security/hornet/hornet_lsm.c new file +2
-0+ maps.fd_array =make_bpfptr (attr->fd_array, is_kernel); + sig =kzalloc (attr->signature_size,GFP_KERNEL );
Is there a risk of memory pressure or Denial of Service here? The allocation size relies directly on the user-controlled attr->signature_size without any upper bound check. An excessively large size could trigger a loud warning from the page allocator and spam the kernel log.
Quoted 4 lines
security/hornet/hornet_lsm.c new file +4
-0+if (!sig) { + err = -ENOMEM ; + goto out; + }
[ ... ]
Quoted 10 lines
security/hornet/hornet_lsm.c new file +10
-0+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 ;
security/hornet/hornet_lsm.c line 278
Quoted 10 lines
security/hornet/hornet_lsm.c new file +10
-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)
Does this hardcoded fail-open logic override downstream LSM policy? If a downstream LSM allows unsigned programs, Hornet returns 0 here and bypasses map checks. However, if it allows partially signed programs, Hornet still enforces that all frozen maps match, breaking LSM composition.
security/hornet/hornet_lsm.c line 290
Quoted 12 lines
security/hornet/hornet_lsm.c new file +12
-0+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 (!map->frozen || !map->ops->map_get_hash) +continue ; + +if (map->ops->map_get_hash (map,SHA256_DIGEST_SIZE , hash))
Could this cause CPU stalls or local Denial of Service?
BPF maps can be extremely large. Computing the hash over the entire map memory synchronously without yielding the CPU could cause multi-second RCU stalls.
security/hornet/hornet_lsm.c line 302
Quoted 12 lines
security/hornet/hornet_lsm.c new file +12
-0+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) {
Does this incorrectly reject legitimately skipped maps?
If a signature specifies a zero-length hash for a map, hornet_map_hash sets skips to true and excludes it from signed_hashes. However, if the map is frozen, this loop will fail to find it in signed_hashes and return -EPERM.
security/hornet/hornet_lsm.c line 312
Quoted 10 lines
security/hornet/hornet_lsm.c new file +10
-0+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)
Can this fail due to map deduplication?
If a program's signature contains multiple index entries for the same map file descriptor, signed_hash_count is incremented for each entry. The BPF core deduplicates maps in prog->aux->used_maps, so covered_count is only incremented once per unique map, causing this check to reject valid programs.
Quoted 7 lines
security/hornet/hornet_lsm.c new file +7
-0+return -EPERM ; + +return 0 ; +} + +static int hornet_bpf (int cmd,union bpf_attr *attr,unsigned int size,bool kernel) +{
[ ... ]
security/hornet/hornet_lsm.c line 331
Quoted 6 lines
security/hornet/hornet_lsm.c new file +6
-0+if (cmd !=BPF_PROG_RUN ) +return 0 ; +if (kernel) +return 0 ; + +return hornet_check_prog_maps (attr->test.prog_fd);
Is there a Time-of-Check to Time-of-Use race condition here? attr->test.prog_fd is resolved and checked inside hornet_check_prog_maps, but the BPF core will resolve the same file descriptor again via bpf_prog_get() before executing it. Because the file descriptor table is shared across threads, an attacker could use dup2() to swap the file descriptor to point to a malicious BPF program after Hornet's check but before execution.
Quoted 1 line
+}
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=6
PATCH v6 7/10 hornet: Introduce gen_sig
In reply to Blaise BoscaccyThis 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.
--- scripts/Makefile | 1 + scripts/hornet/Makefile | 5 + scripts/hornet/gen_sig.c | 397 ++++++++++++++++++++++++++++++++++++ scripts/hornet/write-sig.sh | 27 +++ 4 files changed, 430 insertions(+) create mode 100644 scripts/hornet/Makefile create mode 100644 scripts/hornet/gen_sig.c create mode 100755 scripts/hornet/write-sig.sh
scripts/Makefile +1 -0
@@ -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
scripts/hornet/Makefile new file +5 -0
@@ -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)
scripts/hornet/gen_sig.c new file +397 -0
397 lines not on this page · raw message
scripts/hornet/write-sig.sh new file +27 -0
@@ -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
Signature · 1 line
2.53.0
[email protected]
AI reviewRe: [v6 07/10] hornet: Introduce gen_sig
Thank you for your contribution! Sashiko AI review found 7 potential issue(s) to consider: - [High] Off-by-one bounds check logic in argument parsing aborts execution on valid inputs - [High]…
Thank you for your contribution! Sashiko AI review found 7 potential issue(s) to consider:
- [High] Off-by-one bounds check logic in argument parsing aborts execution on valid inputs
- [High] Private key passphrase is exposed via command-line arguments
- [Medium] write-sig.sh does not modify the target header file as intended
- [Medium] Missing error handling for OpenSSL allocations and encoding can lead to crashes
- [Medium] sha256() destroys global OpenSSL error strings, breaking subsequent error reporting
- [Medium] Incomplete command-line argument parsing ignores --help and unrecognized flags
- [Medium] Memory leaks of OpenSSL context objects and ASN.1 structures
--
commit bf20721f572ef02a45417d08219036dd03d54e92
Author: Blaise Boscaccy <[email protected]>
hornet: Introduce gen_sig
This commit introduces the gen_sig tool to create pkcs#7 signatures for data payloads with an appended set of hashes. It also provides a script to rewrite an autogenerated eBPF lskel header with a new signature.
Quoted 6 lines
scripts/hornet/gen_sig.c new file +0
-0@@ -0,0 +1,397 @@
[ ... ]
scripts/hornet/gen_sig.c line 106
Quoted 5 lines
scripts/hornet/gen_sig.c new file +5
-0+static void usage (const char *prog) +{ +fprintf (stderr, +"Usage:\n" + " %s --data content.bin --cert signer.crt --key signer.key [-pass pass]\n"
Is it safe to accept the private key passphrase via a command-line argument? Local users can view process arguments through the proc filesystem or tools like ps, which could allow them to harvest the passphrase.
[ ... ]
Quoted 2 lines
scripts/hornet/gen_sig.c new file +2
-0+static int sha256 (const char *path,unsigned char out[SHA256_LEN ],unsigned int *out_len) +{
[ ... ]
scripts/hornet/gen_sig.c line 253
Quoted 6 lines
scripts/hornet/gen_sig.c new file +6
-0+done: +EVP_MD_CTX_free (ctx); +fclose (f); +ERR_free_strings (); +return rc; +}
Could clearing the error strings here cause issues with error reporting later in the program? If a subsequent cryptographic operation fails after this function returns, the global OpenSSL error state will be empty, preventing tools from printing human-readable error messages.
[ ... ]
scripts/hornet/gen_sig.c line 260
Quoted 6 lines
scripts/hornet/gen_sig.c new file +6
-0+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);
Is it possible for HORNET_MAP_new() to return a NULL pointer? If so, dereferencing it immediately on the next line might cause a crash.
scripts/hornet/gen_sig.c line 263
Quoted 3 lines
scripts/hornet/gen_sig.c new file +3
-0+ASN1_OCTET_STRING_set (map->hash, buffer, buffer_len); +sk_HORNET_MAP_push (set->maps, map); +}
If pushing the map onto the stack fails, will the allocated map object be leaked since it won't be freed when the parent set is later destroyed?
Quoted 3 lines
scripts/hornet/gen_sig.c new file +3
-0+ +int main (int argc,char **argv) +{
[ ... ]
scripts/hornet/gen_sig.c line 291
Quoted 1 line
scripts/hornet/gen_sig.c new file +1
-0+const char *short_opts ="C:K:P:O:A:Sh" ;
This isn't a bug, but the short option D for data appears to be missing from this string.
Quoted 4 lines
scripts/hornet/gen_sig.c new file +4
-0+ +static const struct option long_opts[] = { + {"cert" , required_argument,0 ,'C' }, + {"key" , required_argument,0 ,'K' },
scripts/hornet/gen_sig.c line 305
Quoted 10 lines
scripts/hornet/gen_sig.c new file +10
-0+ {"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) {
Should there be a handler for the help flag or unrecognized options here? If someone passes the help flag, the program seems to silently ignore it and proceed instead of displaying the usage information.
[ ... ]
scripts/hornet/gen_sig.c line 328
Quoted 8 lines
scripts/hornet/gen_sig.c new file +8
-0+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 ; + }
Does this check prematurely abort the program on valid input? Since the array can hold exactly MAX_HASHES items, if a user provides exactly that many items, incrementing hash_count to MAX_HASHES will trigger this failure path rather than allowing the program to continue processing.
Quoted 6 lines
scripts/hornet/gen_sig.c new file +6
-0+ }else { +usage (argv[0 ]); +return EXIT_FAILURE ; + } + } + }
[ ... ]
scripts/hornet/gen_sig.c line 363
Quoted 5 lines
scripts/hornet/gen_sig.c new file +5
-0+ 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 ();
When MAP_SET_new() is called, doesn't it automatically allocate an empty stack for the maps field? Overwriting it with sk_HORNET_MAP_new_null() might leak that initially allocated stack.
Also, can MAP_SET_new() fail and return NULL here before set->maps is accessed?
Quoted 6 lines
scripts/hornet/gen_sig.c new file +6
-0+ +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);
scripts/hornet/gen_sig.c line 379
Quoted 10 lines
scripts/hornet/gen_sig.c new file +10
-0+ } + + 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);
Are we sure ASN1_item_i2d() succeeded? If it fails, it can return a negative value, which would be passed as the length to CMS_signed_add1_attr_by_OBJ(). Some OpenSSL APIs treat negative lengths as null-terminated strings and attempt to call strlen() on a potentially NULL pointer.
Quoted 8 lines
scripts/hornet/gen_sig.c new file +8
-0+ + 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);
scripts/hornet/gen_sig.c line 397
Quoted 10 lines
scripts/hornet/gen_sig.c new file +10
-0+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 ; +}
Does this function leak the signer, pkey, and cms_out objects? I don't see calls to X509_free(), EVP_PKEY_free(), or CMS_ContentInfo_free() before returning.
Quoted 6 lines
scripts/hornet/write-sig.sh new file +0
-0@@ -0,0 +1,27 @@
[ ... ]
scripts/hornet/write-sig.sh line 27
Quoted 6 lines
scripts/hornet/write-sig.sh new file +6
-0+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
Does this script actually modify the intended header file? It looks like the sed command is missing the in-place edit flag, so the output is merely printed to standard output while the original file remains unchanged.
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=7
PATCH v6 8/10 hornet: Add a light skeleton data extractor scripts
In reply to Blaise BoscaccyThese 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.
--- scripts/hornet/extract-insn.sh | 27 +++++++++++++++++++++++++++ scripts/hornet/extract-map.sh | 27 +++++++++++++++++++++++++++ scripts/hornet/extract-skel.sh | 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
scripts/hornet/extract-insn.sh new file +27 -0
@@ -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
scripts/hornet/extract-map.sh new file +27 -0
@@ -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
scripts/hornet/extract-skel.sh new file +27 -0
@@ -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
Signature · 1 line
2.53.0
[email protected]
AI reviewRe: [v6 08/10] hornet: Add a light skeleton data extractor scripts
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider: - [Critical] Silent data corruption of the extracted eBPF payload due to unquoted printf format string…
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
- [Critical] Silent data corruption of the extracted eBPF payload due to unquoted printf format string injection.
- [Critical] Overly broad grep patterns in extract-insn.sh and extract-map.sh cause payload duplication and severe corruption.
- [High] Execution failure due to missing C preprocessor include paths.
- [Medium] Bash scripting pitfalls: Incorrect exit code on failure, command injection, and copy-paste error in usage. --
commit 398e95d4981b28182554e7639f02b8c22e6185c8
Author: Blaise Boscaccy <[email protected]>
hornet: Add a light skeleton data extractor scripts
This commit introduces scripts to extract eBPF instruction and map data payloads from autogenerated light skeleton headers. These payloads can then be used for signing a light skeleton binary using gen_sig.
Quoted 6 lines
scripts/hornet/extract-insn.sh new file +0
-0@@ -0,0 +1,27 @@
[ ... ]
scripts/hornet/extract-insn.sh line 16
Quoted 7 lines
scripts/hornet/extract-insn.sh new file +7
-0+function usage() { + echo "Sample script for extracting instructions" + echo "autogenerated eBPF lskel headers" + echo "" + echo "USAGE: header_file" + exit +}
Could this exit successfully (code 0) upon a usage error?
Calling exit without an argument defaults to the exit status of the previous echo command, which is 0. If invoked incorrectly by a build system or Makefile, this might fail to halt the build process.
This issue is also present in extract-map.sh and extract-skel.sh.
[ ... ]
scripts/hornet/extract-insn.sh line 27
Quoted 6 lines
scripts/hornet/extract-insn.sh new file +6
-0+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
The autogenerated light skeletons explicitly include headers like <bpf/bpf.h>.
Since the script does not provide include paths (-I) to GCC or accept CFLAGS, will the C preprocessor terminate with a fatal error if these headers are not present in the standard system include paths, resulting in an empty output file?
Additionally, is it possible for the unquoted $1 to cause word-splitting or unintended command execution if a filename contains spaces or starts with a compiler flag?
The grep pattern "opts_insn" appears to be overly broad. Could this match not only the target byte array but also structural assignments generated by bpftool, such as opts.insns_sz = sizeof(opts_insn) - 1?
Because the extra code is unquoted, it would split into multiple arguments. When printf receives extra arguments without format specifiers, it repeatedly reuses the format string, which in this case would cause it to duplicate and corrupt the eBPF binary payload output.
Finally, since printf is called without a format specifier, could the extracted eBPF payload be silently corrupted?
Bash's printf dynamically evaluates escapes. A byte sequence like \x25 (the % character) followed by \x73 (s) would be interpreted as %s and replaced with an empty string, or \x64 (d) to 0, silently altering the bytes and invalidating cryptographic signatures.
Could this be prevented by using a format string like printf "%b" "$(...)"?
The gcc include, unquoted $1, and printf issues are also present in extract-map.sh and extract-skel.sh.
Quoted 6 lines
scripts/hornet/extract-map.sh new file +0
-0@@ -0,0 +1,27 @@
[ ... ]
scripts/hornet/extract-map.sh
Quoted 2 lines
scripts/hornet/extract-map.sh new file +2
-0+function usage() { + echo "Sample script for extracting instructions"
This isn't a bug, but there is a copy-paste error in the usage message here. Should this say "extracting map data" instead of "extracting instructions"?
[ ... ]
scripts/hornet/extract-map.sh line 27
Quoted 6 lines
scripts/hornet/extract-map.sh new file +6
-0+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
Similar to extract-insn.sh, does the overly broad "opts_data" grep pattern also match extra struct assignments like opts.data_sz = sizeof(opts_data) - 1 and cause payload duplication?
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=8
PATCH v6 9/10 selftests/hornet: Add a selftest for the Hornet LSM
In reply to Blaise BoscaccyThis selftest contains a testcase that utilizes light skeleton eBPF loaders and exercises hornet's map validation.
--- 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
tools/testing/selftests/Makefile +1 -0
@@ -44,6 +44,7 @@ TARGETS += ftrace TARGETS += futex TARGETS += gpio TARGETS += hid +TARGETS += hornet TARGETS += intel_pstate TARGETS += iommu TARGETS += ipc
tools/testing/selftests/hornet/Makefile new file +63 -0
63 lines not on this page · raw message
tools/testing/selftests/hornet/loader.c new file +21 -0
@@ -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 ; +}
tools/testing/selftests/hornet/trivial.bpf.c new file +33 -0
@@ -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 ; +}
Signature · 1 line
2.53.0
[email protected]
AI reviewRe: [v6 09/10] selftests/hornet: Add a selftest for the Hornet LSM
Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider: - [Medium] Makefile targets lack `$(OUTPUT)/` prefix, breaking out-of-tree builds. - [Medium] Makefile…
Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider:
- [Medium] Makefile targets lack $(OUTPUT)/ prefix, breaking out-of-tree builds.
- [Medium] Makefile hardcodes host architecture and kernel paths, breaking cross-compilation.
- [Medium] Makefile has an unconditional build dependency on kernel cryptographic signing keys.
- [Medium] BPF program uses bpf_probe_read_str() for a user-space pointer.
- [Medium] BPF program uses BPF_CORE_READ() to access tracepoint context fields. --
commit 1e13c1e9492cd70a4a039a9763aae8236928bf0c
Author: Blaise Boscaccy <[email protected]> selftests/hornet: Add a selftest for the Hornet LSM
This selftest contains a testcase that utilizes light skeleton eBPF loaders and exercises hornet's map validation.
Quoted 5 lines
tools/testing/selftests/hornet/Makefile new file +0
-0
[ ... ]
tools/testing/selftests/hornet/Makefile line 27
Quoted 3 lines
tools/testing/selftests/hornet/Makefile new file +3
-0+BPF_CFLAGS := -target bpf \ + -D__TARGET_ARCH_$(ARCH) \ + -I/usr/include/$(shell uname -m)-linux-gnu \
Does this hardcoded include path break cross-compilation? If building for arm64 on an x86 host, it looks like this will pull in x86 headers instead of the target architecture headers.
tools/testing/selftests/hornet/Makefile line 31
Quoted 4 lines
tools/testing/selftests/hornet/Makefile new file +4
-0+ $(KHDR_INCLUDES) + +vmlinux.h: + $(BPFTOOL) btf dump file /sys/kernel/btf/vmlinux format c > vmlinux.h
Does this hardcode the host kernel's BTF? This will fail in build environments where the host kernel lacks BTF, or when cross-compiling where the target kernel's built BTF ($(objtree)/vmlinux) should be used.
Also, shouldn't custom build rules like this use the $(OUTPUT)/ prefix? Since this Makefile includes ../lib.mk, items in TEST_GEN_PROGS and TEST_GEN_FILES are prefixed with $(OUTPUT)/. When building out-of-tree (make O=dir), make might fail to match the expected $(OUTPUT)/ targets for vmlinux.h, trivial.bpf.o, loader.h, and the other explicit rules.
tools/testing/selftests/hornet/Makefile line 37
Quoted 6 lines
tools/testing/selftests/hornet/Makefile new file +6
-0+ +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 \
Does this create an unconditional dependency on kernel cryptographic signing keys? If the target kernel configuration disables module signing or other features that generate these specific keys, the files won't exist and the build might fail unconditionally instead of gracefully skipping.
Quoted 1 line
tools/testing/selftests/hornet/Makefile new file +1
-0+ -L $< name trivial > $@
[ ... ]
Quoted 5 lines
tools/testing/selftests/hornet/trivial.bpf.c new file +0
-0
[ ... ]
tools/testing/selftests/hornet/trivial.bpf.c line 20
Quoted 8 lines
tools/testing/selftests/hornet/trivial.bpf.c new file +8
-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 ]);
Should tracepoint arguments be accessed directly (e.g., ctx->args[1]) instead of using BPF_CORE_READ()?
Passing the verifier-translated tracepoint context pointer (PTR_TO_CTX) to memory-reading helpers (which BPF_CORE_READ expands to) is an anti-pattern that defeats direct memory access optimizations and can lead to verifier rejection.
tools/testing/selftests/hornet/trivial.bpf.c
Quoted 2 lines
tools/testing/selftests/hornet/trivial.bpf.c new file +2
-0+ +bpf_probe_read_str (filename,sizeof (filename), pathname_ptr);
Will this fail to read the user-space pointer? On architectures with strict user/kernel memory separation (like x86 with SMAP), bpf_probe_read_str() cannot read user memory. Would bpf_probe_read_user_str() be more appropriate here?
Quoted 2 lines
tools/testing/selftests/hornet/trivial.bpf.c new file +2
-0+ task = (struct task_struct *)bpf_get_current_task (); + start_time =BPF_CORE_READ (task, start_time);
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=9
PATCH v6 10/10 ipe: Add BPF program load policy enforcement via Hornet integration
In reply to Blaise BoscaccyAdd 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 - Cert not trusted 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.
--- Documentation/admin-guide/LSM/ipe.rst | 162 +++++++++++++++++++++++++- Documentation/security/ipe.rst | 39 +++++++ security/ipe/Kconfig | 14 +++ security/ipe/audit.c | 15 +++ security/ipe/eval.c | 73 +++++++++++- 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, 448 insertions(+), 2 deletions(-)
Documentation/admin-guide/LSM/ipe.rst +161 -1
@@ -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 signing certificate is not trusted by the specified keyring. + + ``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 ----------------------
Documentation/security/ipe.rst +39 -0
@@ -412,6 +412,44 @@ 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 +provides an integrity verdict to IPE via the +``security_bpf_prog_load_post_integrity`` hook. + +The hook flow is: + + 1. User space invokes ``BPF_PROG_LOAD`` via the ``bpf()`` syscall. + 2. Hornet's ``bpf_prog_load_integrity`` hook calls ``hornet_check_program()`` + to verify the program's signature and map hashes. + 3. Hornet calls ``security_bpf_prog_load_post_integrity()`` with the + resulting ``lsm_integrity_verdict``. + 4. IPE evaluates the verdict against the active policy's ``BPF_PROG_LOAD`` + rules and returns ``-EACCES`` if denied. + +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 +477,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
security/ipe/Kconfig +14 -0
@@ -95,6 +95,20 @@ 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' 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
security/ipe/audit.c +15 -0
@@ -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", };/**
security/ipe/eval.c +72 -1
92 lines not on this page · raw message
security/ipe/eval.h +11 -0
@@ -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 {
security/ipe/hooks.c +63 -0
66 lines not on this page · raw message
security/ipe/hooks.h +15 -0
@@ -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 */
security/ipe/ipe.c +14 -0
@@ -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 */ };/**
security/ipe/ipe.h +3 -0
@@ -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 );
security/ipe/policy.h +14 -0
@@ -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 };
security/ipe/policy_parser.c +27 -0
@@ -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 :
Signature · 1 line
2.53.0
[email protected]
AI reviewRe: [v6 10/10] ipe: Add BPF program load policy enforcement via Hornet integration
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider: - [High] Silent fail-open of `BPF_PROG_LOAD` policies when `CONFIG_IPE_PROP_BPF_SIGNATURE` is disabled…
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] Silent fail-open of BPF_PROG_LOAD policies when CONFIG_IPE_PROP_BPF_SIGNATURE is disabled
- [Low] Build failure due to unconditional access to conditionally compiled field ctx->bpf_kernel --
commit 168706f9eca9b1981fccd36319334eda60264af6
Author: Blaise Boscaccy <[email protected]>
ipe: Add BPF program load policy enforcement via Hornet integration
This commit adds support for the bpf_prog_load_post_integrity LSM hook, enabling IPE to make policy decisions about BPF program loading based on integrity verdicts from the Hornet LSM. It introduces the BPF_PROG_LOAD operation and multiple properties like bpf_signature, bpf_keyring, and bpf_kernel.
Quoted 4 lines
security/ipe/eval.c +0
-0
[ ... ]
Quoted 6 lines
security/ipe/eval.c +2
-0@@ -297,6 +342,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 );
[ ... ]
security/ipe/eval.c static bool evaluate_property(const struct ipe_eval_ctx *const ctx, · line 370
Quoted 4 lines
security/ipe/eval.c +4
-0+case IPE_PROP_BPF_KERNEL_FALSE : +return !ctx->bpf_kernel; +case IPE_PROP_BPF_KERNEL_TRUE : +return ctx->bpf_kernel;
If the kernel is compiled with CONFIG_IPE_PROP_BPF_SIGNATURE disabled, will this cause a build failure?
In security/ipe/eval.h, the bpf_kernel field is conditionally compiled:
#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 */
Since evaluate_property() accesses ctx->bpf_kernel unconditionally, it seems this would result in an undefined struct member error when the config is turned off.
Quoted 8 lines
security/ipe/eval.c +0
-0default :return false; }security/ipe/ipe.c +0
-0
[ ... ]
security/ipe/ipe.c static struct security_hook_list ipe_hooks[] __ro_after_init = { · line 77
Quoted 9 lines
security/ipe/ipe.c +4
-0@@ -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 */ };
Does this create a scenario where policies fail-open silently if CONFIG_IPE_PROP_BPF_SIGNATURE is disabled?
Quoted 4 lines
security/ipe/policy_parser.c +0
-0
[ ... ]
security/ipe/policy_parser.c static const match_table_t operation_tokens = { · line 242
Quoted 7 lines
security/ipe/policy_parser.c +1
-0@@ -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} };
Because BPF_PROG_LOAD is added unconditionally to the parser, an administrator could load a policy containing "DEFAULT op=BPF_PROG_LOAD action=DENY" successfully.
However, because the LSM hooks are not registered, IPE will never evaluate BPF program loads. This results in all BPF loads being silently allowed, which contradicts the loaded policy.
Could the parser be updated to reject BPF_PROG_LOAD rules when the corresponding enforcement hooks are disabled?
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=10
Fan Wu
maintainerRe: [v6 10/10] ipe: Add BPF program load policy enforcement via Hornet integration
In reply to Blaise BoscaccyQuoted 8 lines · Blaise Boscaccy
Add support for the
bpf_prog_load_post_integrityLSM 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
Quoted 6 lines
New policy properties:
bpf_signature=NONE - No Verdict bpf_signature=OK - Program signature and map hashes verified bpf_signature=UNSIGNED - No signature providedbpf_signature=PARTIALSIG - Signature OK but no map hash data bpf_signature=UNKNOWNKEY - Cert not trusted
This one should be: The keyring requested by the user is invalid.
Quoted 93 lines
bpf_signature=UNEXPECTED - An unexpected hash value was encountered
bpf_signature=FAULT - System error during verificationbpf_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 userspaceThese properties map directly to the
lsm_integrity_verdictenum values provided by the Hornet LSM throughsecurity_bpf_prog_load_post_integrity.The feature is gated on
CONFIG_IPE_PROP_BPF_SIGNATUREwhich depends onCONFIG_SECURITY_HORNET.Signed-off-by Blaise Boscaccy <[email protected]>--- Documentation/admin-guide/LSM/ipe.rst | 162 +++++++++++++++++++++++++- Documentation/security/ipe.rst | 39 +++++++ security/ipe/Kconfig | 14 +++ security/ipe/audit.c | 15 +++ security/ipe/eval.c | 73 +++++++++++- 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, 448 insertions(+), 2 deletions(-)Documentation/admin-guide/LSM/ipe.rst +39
-1@@ -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`` +
Documentation/admin-guide/LSM/ipe.rst fsverity_signature · line 763
Quoted 10 lines
Documentation/admin-guide/LSM/ipe.rst +10
-0+ No signature was provided with the BPF program. + + ``PARTIALSIG`` + + The program signature was verified, but no authenticated map hash + data was present. + + ``UNKNOWNKEY`` + + The signing certificate is not trusted by the specified keyring.
Same above.
Quoted 139 lines
Documentation/admin-guide/LSM/ipe.rst +112
-0+ + ``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 ----------------------Documentation/security/ipe.rst +9
-0@@ -412,6 +412,44 @@ 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 +provides an integrity verdict to IPE via the +``security_bpf_prog_load_post_integrity`` hook. +
Documentation/security/ipe.rst a standard securityfs policy tree:: · line 433
Quoted 10 lines
Documentation/security/ipe.rst +10
-0+The hook flow is: + + 1. User space invokes ``BPF_PROG_LOAD`` via the ``bpf()`` syscall. + 2. Hornet's ``bpf_prog_load_integrity`` hook calls ``hornet_check_program()`` + to verify the program's signature and map hashes. + 3. Hornet calls ``security_bpf_prog_load_post_integrity()`` with the + resulting ``lsm_integrity_verdict``. + 4. IPE evaluates the verdict against the active policy's ``BPF_PROG_LOAD`` + rules and returns ``-EACCES`` if denied. +
This part needs to be updated.
Quoted 35 lines
Documentation/security/ipe.rst +20
-0+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 +477,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 integrationsecurity/ipe/Kconfig +0
-0
security/ipe/Kconfig config IPE_PROP_FS_VERITY_BUILTIN_SIG · line 102
Quoted 8 lines
security/ipe/Kconfig +5
-0@@ -95,6 +95,20 @@ 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' and 'bpf_keyring'
bpf_kernel is missing.
Quoted 138 lines
security/ipe/Kconfig +9
-0+ 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_TESTsecurity/ipe/audit.c +15
-0@@ -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", };/** security/ipe/eval.c +62
-1@@ -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,52 @@ 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 */ /** -* 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 +342,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 );
security/ipe/eval.c static bool evaluate_property(const struct ipe_eval_ctx *const ctx, · line 370
Quoted 10 lines
security/ipe/eval.c +10
-0+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 !ctx->bpf_kernel; +case IPE_PROP_BPF_KERNEL_TRUE : +return ctx->bpf_kernel;
bpf_kernel part needs to be guarded by #ifdef, like the other two.
-Fan
Quoted 3 lines
security/ipe/eval.c +0
-0default :return false; }
Paul Moore
maintainerQuoted 4 lines · Blaise Boscaccy
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
Quoted 7 lines · Paul Moore
Quoted 4 lines · Blaise Boscaccy
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.
Quoted 8 lines
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
MAINTAINERSentry 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 :)
Yes, I'm good with maintaining Hornet. Thanks Paul
-blaise
Quoted 4 lines
https://github.com/LinuxSecurityModule/kernel#new-lsms
--
paul-moore.com