From: James Bottomley Add new validate_pkcs7_trust() function which can operate on the system keyrings and is simply some of the innards of verify_pkcs7_message_sig(). Signed-off-by: James Bottomley --- certs/system_keyring.c | 76 +++++++++++++++++++++--------------- include/linux/verification.h | 2 + 2 files changed, 47 insertions(+), 31 deletions(-) diff --git a/certs/system_keyring.c b/certs/system_keyring.c index e0761436ec7f..dcbefc2d3f6d 100644 --- a/certs/system_keyring.c +++ b/certs/system_keyring.c @@ -298,42 +298,19 @@ late_initcall(load_system_certificate_list); #ifdef CONFIG_SYSTEM_DATA_VERIFICATION /** - * verify_pkcs7_message_sig - Verify a PKCS#7-based signature on system data. - * @data: The data to be verified (NULL if expecting internal data). - * @len: Size of @data. + * validate_pkcs7_trust - add trust markers based on keyring * @pkcs7: The PKCS#7 message that is the signature. * @trusted_keys: Trusted keys to use (NULL for builtin trusted keys only, * (void *)1UL for all trusted keys). - * @usage: The use to which the key is being put. - * @view_content: Callback to gain access to content. - * @ctx: Context for callback. */ -int verify_pkcs7_message_sig(const void *data, size_t len, - struct pkcs7_message *pkcs7, - struct key *trusted_keys, - enum key_being_used_for usage, - int (*view_content)(void *ctx, - const void *data, size_t len, - size_t asn1hdrlen), - void *ctx) +int validate_pkcs7_trust(struct pkcs7_message *pkcs7, struct key *trusted_keys) { int ret; - /* The data should be detached - so we need to supply it. */ - if (data && pkcs7_supply_detached_data(pkcs7, data, len) < 0) { - pr_err("PKCS#7 signature with non-detached data\n"); - ret = -EBADMSG; - goto error; - } - - ret = pkcs7_verify(pkcs7, usage); - if (ret < 0) - goto error; - ret = is_key_on_revocation_list(pkcs7); if (ret != -ENOKEY) { pr_devel("PKCS#7 key is on revocation list\n"); - goto error; + return ret; } if (!trusted_keys) { @@ -351,18 +328,55 @@ int verify_pkcs7_message_sig(const void *data, size_t len, trusted_keys = NULL; #endif if (!trusted_keys) { - ret = -ENOKEY; pr_devel("PKCS#7 platform keyring is not available\n"); - goto error; + return -ENOKEY; } } ret = pkcs7_validate_trust(pkcs7, trusted_keys); - if (ret < 0) { - if (ret == -ENOKEY) - pr_devel("PKCS#7 signature not signed with a trusted key\n"); + if (ret == -ENOKEY) + pr_devel("PKCS#7 signature not signed with a trusted key\n"); + + return ret; +} +EXPORT_SYMBOL_GPL(validate_pkcs7_trust); + +/** + * verify_pkcs7_message_sig - Verify a PKCS#7-based signature on system data. + * @data: The data to be verified (NULL if expecting internal data). + * @len: Size of @data. + * @pkcs7: The PKCS#7 message that is the signature. + * @trusted_keys: Trusted keys to use (NULL for builtin trusted keys only, + * (void *)1UL for all trusted keys). + * @usage: The use to which the key is being put. + * @view_content: Callback to gain access to content. + * @ctx: Context for callback. + */ +int verify_pkcs7_message_sig(const void *data, size_t len, + struct pkcs7_message *pkcs7, + struct key *trusted_keys, + enum key_being_used_for usage, + int (*view_content)(void *ctx, + const void *data, size_t len, + size_t asn1hdrlen), + void *ctx) +{ + int ret; + + /* The data should be detached - so we need to supply it. */ + if (data && pkcs7_supply_detached_data(pkcs7, data, len) < 0) { + pr_err("PKCS#7 signature with non-detached data\n"); + ret = -EBADMSG; goto error; } + ret = pkcs7_verify(pkcs7, usage); + if (ret < 0) + goto error; + + ret = validate_pkcs7_trust(pkcs7, trusted_keys); + if (ret < 0) + goto error; + if (view_content) { size_t asn1hdrlen; diff --git a/include/linux/verification.h b/include/linux/verification.h index dec7f2beabfd..57f1460d36f1 100644 --- a/include/linux/verification.h +++ b/include/linux/verification.h @@ -44,6 +44,8 @@ enum key_being_used_for { struct key; struct pkcs7_message; +extern int validate_pkcs7_trust(struct pkcs7_message *pkcs7, + struct key *trusted_keys); extern int verify_pkcs7_signature(const void *data, size_t len, const void *raw_pkcs7, size_t pkcs7_len, struct key *trusted_keys, -- 2.52.0 From: James Bottomley 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(). Signed-off-by: James Bottomley --- crypto/asymmetric_keys/pkcs7_parser.h | 1 + crypto/asymmetric_keys/pkcs7_trust.c | 1 + 2 files changed, 2 insertions(+) diff --git a/crypto/asymmetric_keys/pkcs7_parser.h b/crypto/asymmetric_keys/pkcs7_parser.h index 6ef9f335bb17..203062a33def 100644 --- a/crypto/asymmetric_keys/pkcs7_parser.h +++ b/crypto/asymmetric_keys/pkcs7_parser.h @@ -20,6 +20,7 @@ struct pkcs7_signed_info { unsigned index; bool unsupported_crypto; /* T if not usable due to missing crypto */ bool blacklisted; + bool verified; /* T if this signer has validated trust */ /* Message digest - the digest of the Content Data (or NULL) */ const void *msgdigest; diff --git a/crypto/asymmetric_keys/pkcs7_trust.c b/crypto/asymmetric_keys/pkcs7_trust.c index 9a87c34ed173..78ebfb6373b6 100644 --- a/crypto/asymmetric_keys/pkcs7_trust.c +++ b/crypto/asymmetric_keys/pkcs7_trust.c @@ -127,6 +127,7 @@ static int pkcs7_validate_trust_one(struct pkcs7_message *pkcs7, for (p = sinfo->signer; p != x509; p = p->signer) p->verified = true; } + sinfo->verified = true; kleave(" = 0"); return 0; } -- 2.52.0 From: James Bottomley Trying to run pkcs7_validate_trust() on something that parsed correctly but is not verified doesn't work because the signature digest hasn't been calculated. Fix this by adding a digest calclation in to pkcs7_validate_one(). This is almost a nop if the digest exists. Additionally, the trust validation doesn't know the data payload, so adjust the digest calculator to skip checking the data digest if pkcs7->data is NULL. A check is added in pkcs7_verify() for pkcs7->data being null (returning -EBADMSG) to guard against someone forgetting to supply data and getting an invalid success return. Signed-off-by: James Bottomley --- crypto/asymmetric_keys/pkcs7_parser.h | 3 +++ crypto/asymmetric_keys/pkcs7_trust.c | 8 ++++++++ crypto/asymmetric_keys/pkcs7_verify.c | 13 +++++++++---- 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/crypto/asymmetric_keys/pkcs7_parser.h b/crypto/asymmetric_keys/pkcs7_parser.h index 203062a33def..cbe823aeac06 100644 --- a/crypto/asymmetric_keys/pkcs7_parser.h +++ b/crypto/asymmetric_keys/pkcs7_parser.h @@ -66,3 +66,6 @@ struct pkcs7_message { size_t data_hdrlen; /* Length of Data ASN.1 header */ const void *data; /* Content Data (or 0) */ }; + +int pkcs7_digest(struct pkcs7_message *pkcs7, + struct pkcs7_signed_info *sinfo); diff --git a/crypto/asymmetric_keys/pkcs7_trust.c b/crypto/asymmetric_keys/pkcs7_trust.c index 78ebfb6373b6..7cb0a6bc7b32 100644 --- a/crypto/asymmetric_keys/pkcs7_trust.c +++ b/crypto/asymmetric_keys/pkcs7_trust.c @@ -30,6 +30,14 @@ static int pkcs7_validate_trust_one(struct pkcs7_message *pkcs7, kenter(",%u,", sinfo->index); + /* + * if we're being called immediately after parse, the + * signature won't have a calculated digest yet, so calculate + * one. This function returns immediately if a digest has + * already been calculated + */ + pkcs7_digest(pkcs7, sinfo); + if (sinfo->unsupported_crypto) { kleave(" = -ENOPKG [cached]"); return -ENOPKG; diff --git a/crypto/asymmetric_keys/pkcs7_verify.c b/crypto/asymmetric_keys/pkcs7_verify.c index 474e2c1ae21b..3080f0ec52e0 100644 --- a/crypto/asymmetric_keys/pkcs7_verify.c +++ b/crypto/asymmetric_keys/pkcs7_verify.c @@ -19,8 +19,8 @@ /* * Digest the relevant parts of the PKCS#7 data */ -static int pkcs7_digest(struct pkcs7_message *pkcs7, - struct pkcs7_signed_info *sinfo) +int pkcs7_digest(struct pkcs7_message *pkcs7, + struct pkcs7_signed_info *sinfo) { struct public_key_signature *sig = sinfo->sig; struct crypto_shash *tfm; @@ -93,8 +93,8 @@ static int pkcs7_digest(struct pkcs7_message *pkcs7, goto error; } - if (memcmp(sig->m, sinfo->msgdigest, - sinfo->msgdigest_len) != 0) { + if (pkcs7->data && memcmp(sig->m, sinfo->msgdigest, + sinfo->msgdigest_len) != 0) { pr_warn("Sig %u: Message digest doesn't match\n", sinfo->index); ret = -EKEYREJECTED; @@ -463,6 +463,11 @@ int pkcs7_verify(struct pkcs7_message *pkcs7, return -EINVAL; } + if (!pkcs7->data) { + pr_warn("Data not supplied to verify operation\n"); + return -EBADMSG; + } + for (sinfo = pkcs7->signed_infos; sinfo; sinfo = sinfo->next) { ret = pkcs7_verify_one(pkcs7, sinfo); if (sinfo->blacklisted) { -- 2.52.0 From: James Bottomley Signers may add any information they like in signed attributes and sometimes this information turns out to be relevant to specific signing cases, so add an api pkcs7_get_authattr() to extract the value of an authenticated attribute by specific OID. The current implementation is designed for the single signer use case and simply terminates the search when it finds the relevant OID. Signed-off-by: James Bottomley --- crypto/asymmetric_keys/Makefile | 4 +- crypto/asymmetric_keys/pkcs7_aa.asn1 | 18 ++++++ crypto/asymmetric_keys/pkcs7_parser.c | 81 +++++++++++++++++++++++++++ include/crypto/pkcs7.h | 4 ++ 4 files changed, 106 insertions(+), 1 deletion(-) create mode 100644 crypto/asymmetric_keys/pkcs7_aa.asn1 diff --git a/crypto/asymmetric_keys/Makefile b/crypto/asymmetric_keys/Makefile index bc65d3b98dcb..f99b7169ae7c 100644 --- a/crypto/asymmetric_keys/Makefile +++ b/crypto/asymmetric_keys/Makefile @@ -53,12 +53,14 @@ clean-files += pkcs8.asn1.c pkcs8.asn1.h obj-$(CONFIG_PKCS7_MESSAGE_PARSER) += pkcs7_message.o pkcs7_message-y := \ pkcs7.asn1.o \ + pkcs7_aa.asn1.o \ pkcs7_parser.o \ pkcs7_trust.o \ pkcs7_verify.o -$(obj)/pkcs7_parser.o: $(obj)/pkcs7.asn1.h +$(obj)/pkcs7_parser.o: $(obj)/pkcs7.asn1.h $(obj)/pkcs7_aa.asn1.h $(obj)/pkcs7.asn1.o: $(obj)/pkcs7.asn1.c $(obj)/pkcs7.asn1.h +$(obj)/pkcs7_aa.asn1.o: $(obj)/pkcs7_aa.asn1.c $(obj)/pkcs7_aa.asn1.h # # PKCS#7 parser testing key diff --git a/crypto/asymmetric_keys/pkcs7_aa.asn1 b/crypto/asymmetric_keys/pkcs7_aa.asn1 new file mode 100644 index 000000000000..7a8857bdf56e --- /dev/null +++ b/crypto/asymmetric_keys/pkcs7_aa.asn1 @@ -0,0 +1,18 @@ +-- SPDX-License-Identifier: BSD-3-Clause +-- +-- Copyright (C) 2009 IETF Trust and the persons identified as authors +-- of the code +-- +-- https://www.rfc-editor.org/rfc/rfc5652#section-3 + +AA ::= CHOICE { + aaSet [0] IMPLICIT AASet, + aaSequence [2] EXPLICIT SEQUENCE OF AuthenticatedAttribute +} + +AASet ::= SET OF AuthenticatedAttribute + +AuthenticatedAttribute ::= SEQUENCE { + type OBJECT IDENTIFIER ({ pkcs7_aa_note_OID }), + values SET OF ANY ({ pkcs7_aa_note_attr }) +} diff --git a/crypto/asymmetric_keys/pkcs7_parser.c b/crypto/asymmetric_keys/pkcs7_parser.c index 6e3ffdac83ac..d467866f7d93 100644 --- a/crypto/asymmetric_keys/pkcs7_parser.c +++ b/crypto/asymmetric_keys/pkcs7_parser.c @@ -15,6 +15,7 @@ #include #include "pkcs7_parser.h" #include "pkcs7.asn1.h" +#include "pkcs7_aa.asn1.h" MODULE_DESCRIPTION("PKCS#7 parser"); MODULE_AUTHOR("Red Hat, Inc."); @@ -211,6 +212,86 @@ int pkcs7_get_content_data(const struct pkcs7_message *pkcs7, } EXPORT_SYMBOL_GPL(pkcs7_get_content_data); +struct pkcs7_aa_context { + bool found; + enum OID oid_to_find; + const void *data; + size_t len; +}; + +int pkcs7_aa_note_OID(void *context, size_t hdrlen, + unsigned char tag, + const void *value, size_t vlen) +{ + struct pkcs7_aa_context *ctx = context; + enum OID oid = look_up_OID(value, vlen); + + ctx->found = (oid == ctx->oid_to_find); + + return 0; +} + +int pkcs7_aa_note_attr(void *context, size_t hdrlen, + unsigned char tag, + const void *value, size_t vlen) +{ + struct pkcs7_aa_context *ctx = context; + + if (ctx->found) { + ctx->data = value; + ctx->len = vlen; + } + + return 0; +} + +/** + * pkcs7_get_authattr - get authenticated attribute by OID + * @pkcs7: The preparsed PKCS#7 message + * @oid: the enum value of the OID to find + * @_data: Place to return a pointer to the attribute value + * @_len: length of the attribute value + * + * Searches the authenticated attributes until one is found with a + * matching OID. Note that because the attributes are per signer + * there could be multiple signers with different values, but this + * routine will simply return the first one in parse order. + * + * Returns -ENODATA if the attribute can't be found + */ +int pkcs7_get_authattr(const struct pkcs7_message *pkcs7, + enum OID oid, + const void **_data, size_t *_len) +{ + struct pkcs7_signed_info *sinfo = pkcs7->signed_infos; + struct pkcs7_aa_context ctx; + + ctx.data = NULL; + ctx.oid_to_find = oid; + + for (; sinfo; sinfo = sinfo->next) { + int ret; + + /* only extract OIDs from validated signers */ + if (!sinfo->verified) + continue; + + ret = asn1_ber_decoder(&pkcs7_aa_decoder, &ctx, + sinfo->authattrs, sinfo->authattrs_len); + if (ret < 0 || ctx.data != NULL) + break; + } + + if (!ctx.data) + return -ENODATA; + + *_data = ctx.data; + *_len = ctx.len; + + return 0; +} +EXPORT_SYMBOL_GPL(pkcs7_get_authattr); + /* * Note an OID when we find one for later processing when we know how * to interpret it. diff --git a/include/crypto/pkcs7.h b/include/crypto/pkcs7.h index 38ec7f5f9041..bd83202cd805 100644 --- a/include/crypto/pkcs7.h +++ b/include/crypto/pkcs7.h @@ -25,6 +25,10 @@ extern void pkcs7_free_message(struct pkcs7_message *pkcs7); extern int pkcs7_get_content_data(const struct pkcs7_message *pkcs7, const void **_data, size_t *_datalen, size_t *_headerlen); +extern int pkcs7_get_authattr(const struct pkcs7_message *pkcs7, + enum OID oid, + const void **_data, size_t *_len); + /* * pkcs7_trust.c -- 2.52.0 From: James Bottomley Add example code to the test module pkcs7_key_type.c that verifies a message and then pulls out a known authenticated attribute. Signed-off-by: James Bottomley Acked-by: David Howells --- crypto/asymmetric_keys/pkcs7_key_type.c | 42 ++++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/crypto/asymmetric_keys/pkcs7_key_type.c b/crypto/asymmetric_keys/pkcs7_key_type.c index b930d3bbf1af..5a1ecb5501b2 100644 --- a/crypto/asymmetric_keys/pkcs7_key_type.c +++ b/crypto/asymmetric_keys/pkcs7_key_type.c @@ -12,6 +12,7 @@ #include #include #include +#include MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("PKCS#7 testing key type"); @@ -51,16 +52,55 @@ 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 = validate_pkcs7_trust(pkcs7, VERIFY_USE_SECONDARY_KEYRING); + if (ret) { + pr_err("validate_pkcs7_trust 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=%ld\n", len); + + out: + pkcs7_free_message(pkcs7); + return 0; } /* -- 2.52.0 From: Paul Moore Add a new LSM hook and two new LSM hook callbacks to support LSMs that perform integrity verification, e.g. digital signature verification, of BPF programs. While the BPF subsystem does implement a signature verification scheme, it does not satisfy a number of existing requirements, adding support for BPF program integrity verification to the LSM framework allows administrators to select additional integrity verification mechanisms to meet these needs while also providing a mechanism for future expansion. Additional on why this is necessary can be found at the lore archive link below: https://lore.kernel.org/linux-security-module/CAHC9VhTQ_DR=ANzoDBjcCtrimV7XcCZVUsANPt=TjcvM4d-vjg@mail.gmail.com/ The LSM-based BPF integrity verification mechanism works within the existing security_bpf_prog_load() hook called by the BPF subsystem. It adds an additional dedicated integrity callback and a new LSM hook/callback to be called from within LSMs implementing integrity verification. The first new callback, bpf_prog_load_integrity(), located within the security_bpf_prog_load() hook, is necessary to ensure that the integrity verification callbacks are executed before any of the existing LSMs are executed via the bpf_prog_load() callback. Reusing the existing bpf_prog_load() callback for integrity verification could result in LSMs not having access to the integrity verification results when asked to authorize the BPF program load in the bpf_prog_load() callback. The new LSM hook, security_bpf_prog_load_post_integrity(), is intended to be called from within LSMs performing BPF program integrity verification. It is used to report the verdict of the integrity verification to other LSMs enforcing access control policy on BPF program loads. LSMs enforcing such access controls should register a bpf_prog_load_post_integrity() callback to receive integrity verdicts. More information on these new callbacks and hook can be found in the code comments in this patch. Signed-off-by: Paul Moore --- include/linux/lsm_hook_defs.h | 5 +++ include/linux/security.h | 25 ++++++++++++ security/security.c | 75 +++++++++++++++++++++++++++++++++-- 3 files changed, 102 insertions(+), 3 deletions(-) diff --git a/include/linux/lsm_hook_defs.h b/include/linux/lsm_hook_defs.h index 8c42b4bde09c..4971d3c36d5b 100644 --- a/include/linux/lsm_hook_defs.h +++ b/include/linux/lsm_hook_defs.h @@ -434,6 +434,11 @@ LSM_HOOK(int, 0, bpf_prog, struct bpf_prog *prog) LSM_HOOK(int, 0, bpf_map_create, struct bpf_map *map, union bpf_attr *attr, struct bpf_token *token, bool kernel) LSM_HOOK(void, LSM_RET_VOID, bpf_map_free, struct bpf_map *map) +LSM_HOOK(int, 0, bpf_prog_load_post_integrity, struct bpf_prog *prog, + union bpf_attr *attr, struct bpf_token *token, bool kernel, + const struct lsm_id *lsmid, enum lsm_integrity_verdict verdict) +LSM_HOOK(int, 0, bpf_prog_load_integrity, struct bpf_prog *prog, + union bpf_attr *attr, struct bpf_token *token, bool kernel) LSM_HOOK(int, 0, bpf_prog_load, struct bpf_prog *prog, union bpf_attr *attr, struct bpf_token *token, bool kernel) LSM_HOOK(void, LSM_RET_VOID, bpf_prog_free, struct bpf_prog *prog) diff --git a/include/linux/security.h b/include/linux/security.h index 83a646d72f6f..298a43b7744a 100644 --- a/include/linux/security.h +++ b/include/linux/security.h @@ -67,6 +67,7 @@ enum fs_value_type; struct watch; struct watch_notification; struct lsm_ctx; +struct lsm_id; /* Default (no) options for the capable function */ #define CAP_OPT_NONE 0x0 @@ -100,6 +101,14 @@ enum lsm_integrity_type { LSM_INT_FSVERITY_BUILTINSIG_VALID, }; +enum lsm_integrity_verdict { + LSM_INT_VERDICT_NONE = 0, + LSM_INT_VERDICT_OK, + LSM_INT_VERDICT_UNSIGNED, + LSM_INT_VERDICT_PARTIALSIG, + LSM_INT_VERDICT_BADSIG, +}; + /* * These are reasons that can be passed to the security_locked_down() * LSM hook. Lockdown reasons that protect kernel integrity (ie, the @@ -2269,6 +2278,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); @@ -2303,6 +2318,16 @@ static inline int security_bpf_map_create(struct bpf_map *map, union bpf_attr *a static inline void security_bpf_map_free(struct bpf_map *map) { } +static inline int security_bpf_prog_load_post_integrity(struct bpf_prog *prog, + union bpf_attr *attr, + struct bpf_token *token, + bool kernel, + const struct lsm_id *lsmid, + enum lsm_integrity_verdict verdict) +{ + return 0; +} + static inline int security_bpf_prog_load(struct bpf_prog *prog, union bpf_attr *attr, struct bpf_token *token, bool kernel) { diff --git a/security/security.c b/security/security.c index 67af9228c4e9..2d8279bd4aae 100644 --- a/security/security.c +++ b/security/security.c @@ -5232,6 +5232,50 @@ int security_bpf_map_create(struct bpf_map *map, union bpf_attr *attr, return rc; } +/** + * security_bpf_prog_load_post_integrity() - Check if the BPF prog is allowed + * @prog: BPF program object + * @attr: BPF syscall attributes used to create BPF program + * @token: BPF token used to grant user access to BPF subsystem + * @kernel: whether or not call originated from kernel + * @lsmid: LSM ID of the LSM providing @verdict + * @verdict: result of the integrity verification + * + * See the comment block for the security_bpf_prog_load() LSM hook. + * + * This LSM hook is intended to be called from within the + * bpf_prog_load_integrity() callback that is part of the + * security_bpf_prog_load() hook; kernel subsystems outside the scope of the + * LSM framework should not call this hook directly. + * + * If the LSM calling into this hook receives a non-zero error code, it should + * return the same error code back to its caller. If this hook returns a zero, + * it does not necessarily mean that all of the enabled LSMs have authorized + * the BPF program load, as there may be other LSMs implementing BPF integrity + * checks which have yet to execute. However, if a zero is returned, the LSM + * calling into this hook should continue and return zero back to its caller. + * + * LSMs which implement the bpf_prog_load_post_integrity() callback and + * determine that a particular BPF program load is not authorized may choose to + * either return an error code for immediate rejection, or store their decision + * in their own LSM state attached to @prog, later returning an error code in + * the bpf_prog_load() callback. An immediate error code return is in keeping + * with the "fail fast" practice, but waiting until the bpf_prog_load() + * callback allows the LSM to consider multiple different integrity verdicts. + * + * Return: Returns 0 on success, error on failure. + */ +int security_bpf_prog_load_post_integrity(struct bpf_prog *prog, + union bpf_attr *attr, + struct bpf_token *token, + bool kernel, + const struct lsm_id *lsmid, + enum lsm_integrity_verdict verdict) +{ + return call_int_hook(bpf_prog_load_post_integrity, prog, attr, token, + kernel, lsmid, verdict); +} + /** * security_bpf_prog_load() - Check if loading of BPF program is allowed * @prog: BPF program object @@ -5240,8 +5284,24 @@ int security_bpf_map_create(struct bpf_map *map, union bpf_attr *attr, * @kernel: whether or not call originated from kernel * * Perform an access control check when the kernel loads a BPF program and - * allocates associated BPF program object. This hook is also responsible for - * allocating any required LSM state for the BPF program. + * allocates the associated BPF program object. This hook is also responsible + * for allocating any required LSM state for the BPF program. + * + * This hook calls two LSM callbacks: bpf_prog_load_integrity() and + * bpf_prog_load(). The bpf_prog_load_integrity() callback is for those LSMs + * that wish to implement integrity verifications of BPF programs, e.g. + * signature verification, while the bpf_prog_load() callback is for general + * authorization of the BPF program load. Performing both verification and + * authorization in a single callback, with arbitrary LSM ordering, would be + * a challenge. + * + * LSMs which implement the bpf_prog_load_integrity() callback should call into + * the security_bpf_prog_load_post_integrity() hook with their integrity + * verdict. LSMs which implement BPF program integrity policy can register a + * callback for the security_bpf_prog_load_post_integrity() hook and + * either update their own internal state based on the verdict, or immediately + * reject the BPF program load with an error code. See the comment block for + * security_bpf_prog_load_post_integrity() for more information. * * Return: Returns 0 on success, error on failure. */ @@ -5254,9 +5314,18 @@ int security_bpf_prog_load(struct bpf_prog *prog, union bpf_attr *attr, if (unlikely(rc)) return rc; + rc = call_int_hook(bpf_prog_load_integrity, prog, attr, token, kernel); + if (unlikely(rc)) + goto err; + rc = call_int_hook(bpf_prog_load, prog, attr, token, kernel); if (unlikely(rc)) - security_bpf_prog_free(prog); + goto err; + + return rc; + +err: + security_bpf_prog_free(prog); return rc; } -- 2.52.0 This adds the Hornet Linux Security Module which provides enhanced signature verification and data validation for eBPF programs. This allows users to continue to maintain an invariant that all code running inside of the kernel has actually been signed and verified, by the kernel. This effort builds upon the currently excepted upstream solution. It further hardens it by providing deterministic, in-kernel checking of map hashes to solidify auditing along with preventing TOCTOU attacks against lskel map hashes. Target map hashes are passed in via PKCS#7 signed attributes. Hornet determines the extent which the eBFP program is signed and defers to other LSMs for policy decisions. Signed-off-by: Blaise Boscaccy Nacked-by: Alexei Starovoitov --- Documentation/admin-guide/LSM/Hornet.rst | 310 ++++++++++++++++++++++ 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 | 11 + security/hornet/Makefile | 7 + security/hornet/hornet.asn1 | 13 + security/hornet/hornet_lsm.c | 323 +++++++++++++++++++++++ 11 files changed, 681 insertions(+), 1 deletion(-) create mode 100644 Documentation/admin-guide/LSM/Hornet.rst create mode 100644 security/hornet/Kconfig create mode 100644 security/hornet/Makefile create mode 100644 security/hornet/hornet.asn1 create mode 100644 security/hornet/hornet_lsm.c diff --git a/Documentation/admin-guide/LSM/Hornet.rst b/Documentation/admin-guide/LSM/Hornet.rst new file mode 100644 index 000000000000..0dd4c03b8a7e --- /dev/null +++ b/Documentation/admin-guide/LSM/Hornet.rst @@ -0,0 +1,310 @@ +.. 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 the kernel's secondary keyring using the +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 signing certificate is not + trusted in the secondary keyring, or the signature did not contain + hornet map hash data. + +``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 + ` (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. + +- Hornet relies on the kernel's secondary keyring + (``VERIFY_USE_SECONDARY_KEYRING``) for certificate trust. Keys must + be provisioned into this keyring before programs can be verified. + +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_signature()`` with the kernel's secondary + 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 \ + --cert \ + --key \ + [--pass ] \ + --out \ + [--add : ...] + +``--data`` + Path to the binary file containing eBPF program instructions to sign. + +``--cert`` + Path to the signing certificate (PEM or DER format). + +``--key`` + Path to the private key (PEM or DER format). + +``--pass`` + Optional passphrase for the private key. + +``--out`` + Path to write the output PKCS#7 signature. + +``--add`` + Attach a map hash as a signed attribute. The argument is a path to a + binary map file followed by a colon and the map's index in the + ``fd_array``. This option may be specified multiple times. + +extract-skel.sh +--------------- + +Extracts a named field from an autogenerated eBPF lskel header file. +Used internally by other helper scripts. + +extract-insn.sh +--------------- + +Extracts the eBPF program instructions (``opts_insn``) from an lskel +header into a binary file suitable for signing with ``gen_sig``. + +extract-map.sh +-------------- + +Extracts the map data (``opts_data``) from an lskel header into a +binary file suitable for hashing with ``gen_sig``. + +write-sig.sh +------------ + +Replaces the signature data in an lskel header with a new signature +from a binary file. This is used to embed a freshly generated signature +back into the header after signing. + +Signing Workflow +================ + +A typical workflow for building and signing an eBPF light skeleton is: + +1. **Compile the eBPF program**:: + + clang -O2 -target bpf -c program.bpf.c -o program.bpf.o + +2. **Generate the light skeleton header** using ``bpftool``:: + + bpftool gen skeleton -S program.bpf.o > loader.h + +3. **Extract instructions and map data** from the generated header:: + + scripts/hornet/extract-insn.sh loader.h > insn.bin + scripts/hornet/extract-map.sh loader.h > map.bin + +4. **Generate the signature** with ``gen_sig``:: + + scripts/hornet/gen_sig \ + --key signing_key.pem \ + --cert signing_key.x509 \ + --data insn.bin \ + --add map.bin:0 \ + --out sig.bin + +5. **Embed the signature** back into the header:: + + scripts/hornet/write-sig.sh loader.h sig.bin > signed_loader.h + +6. **Build the loader program** using the signed header:: + + cc -o loader loader.c -lbpf + +The resulting loader program will pass the embedded signature to the +kernel when loading the eBPF program, enabling Hornet to verify it. + +Testing +======= + +Self-tests are provided in ``tools/testing/selftests/hornet/``. The test +suite builds a minimal eBPF program (``trivial.bpf.c``), signs it using +the workflow described above, and verifies that the signed program loads +successfully. diff --git a/Documentation/admin-guide/LSM/index.rst b/Documentation/admin-guide/LSM/index.rst index b44ef68f6e4d..57f6e9fbe5fd 100644 --- a/Documentation/admin-guide/LSM/index.rst +++ b/Documentation/admin-guide/LSM/index.rst @@ -49,3 +49,4 @@ subdirectories. SafeSetID ipe landlock + Hornet diff --git a/MAINTAINERS b/MAINTAINERS index 55af015174a5..6e91234a9ba4 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -11682,6 +11682,15 @@ S: Maintained F: Documentation/devicetree/bindings/iio/pressure/honeywell,mprls0025pa.yaml F: drivers/iio/pressure/mprls0025pa* +HORNET SECURITY MODULE +M: Blaise Boscaccy +L: linux-security-module@vger.kernel.org +S: Supported +T: git https://github.com/blaiseboscaccy/hornet.git +F: Documentation/admin-guide/LSM/Hornet.rst +F: scripts/hornet/ +F: security/hornet/ + HP BIOSCFG DRIVER M: Jorge Lopez L: platform-driver-x86@vger.kernel.org diff --git a/include/linux/oid_registry.h b/include/linux/oid_registry.h index ebce402854de..bf852715aaea 100644 --- a/include/linux/oid_registry.h +++ b/include/linux/oid_registry.h @@ -150,6 +150,9 @@ enum OID { OID_id_ml_dsa_65, /* 2.16.840.1.101.3.4.3.18 */ OID_id_ml_dsa_87, /* 2.16.840.1.101.3.4.3.19 */ + /* Hornet LSM */ + OID_hornet_data, /* 2.25.316487325684022475439036912669789383960 */ + OID__NR }; diff --git a/include/uapi/linux/lsm.h b/include/uapi/linux/lsm.h index 938593dfd5da..2ff9bcdd551e 100644 --- a/include/uapi/linux/lsm.h +++ b/include/uapi/linux/lsm.h @@ -65,6 +65,7 @@ struct lsm_ctx { #define LSM_ID_IMA 111 #define LSM_ID_EVM 112 #define LSM_ID_IPE 113 +#define LSM_ID_HORNET 114 /* * LSM_ATTR_XXX definitions identify different LSM attributes diff --git a/security/Kconfig b/security/Kconfig index 6a4393fce9a1..283c4a103209 100644 --- a/security/Kconfig +++ b/security/Kconfig @@ -230,6 +230,7 @@ source "security/safesetid/Kconfig" source "security/lockdown/Kconfig" source "security/landlock/Kconfig" source "security/ipe/Kconfig" +source "security/hornet/Kconfig" source "security/integrity/Kconfig" @@ -274,7 +275,7 @@ config LSM default "landlock,lockdown,yama,loadpin,safesetid,apparmor,selinux,smack,tomoyo,ipe,bpf" if DEFAULT_SECURITY_APPARMOR default "landlock,lockdown,yama,loadpin,safesetid,tomoyo,ipe,bpf" if DEFAULT_SECURITY_TOMOYO default "landlock,lockdown,yama,loadpin,safesetid,ipe,bpf" if DEFAULT_SECURITY_DAC - default "landlock,lockdown,yama,loadpin,safesetid,selinux,smack,tomoyo,apparmor,ipe,bpf" + default "landlock,lockdown,yama,loadpin,safesetid,selinux,smack,tomoyo,apparmor,ipe,hornet,bpf" help A comma-separated list of LSMs, in initialization order. Any LSMs left off this list, except for those with order diff --git a/security/Makefile b/security/Makefile index 4601230ba442..b68cb56e419b 100644 --- a/security/Makefile +++ b/security/Makefile @@ -26,6 +26,7 @@ obj-$(CONFIG_CGROUPS) += device_cgroup.o obj-$(CONFIG_BPF_LSM) += bpf/ obj-$(CONFIG_SECURITY_LANDLOCK) += landlock/ obj-$(CONFIG_SECURITY_IPE) += ipe/ +obj-$(CONFIG_SECURITY_HORNET) += hornet/ # Object integrity file lists obj-$(CONFIG_INTEGRITY) += integrity/ diff --git a/security/hornet/Kconfig b/security/hornet/Kconfig new file mode 100644 index 000000000000..19406aa237ac --- /dev/null +++ b/security/hornet/Kconfig @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: GPL-2.0-only +config SECURITY_HORNET + bool "Hornet support" + depends on SECURITY + default n + help + This selects Hornet. + Further information can be found in + Documentation/admin-guide/LSM/Hornet.rst. + + If you are unsure how to answer this question, answer N. diff --git a/security/hornet/Makefile b/security/hornet/Makefile new file mode 100644 index 000000000000..26b6f954f762 --- /dev/null +++ b/security/hornet/Makefile @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: GPL-2.0-only +obj-$(CONFIG_SECURITY_HORNET) := hornet.o + +hornet-y := hornet.asn1.o \ + hornet_lsm.o \ + +$(obj)/hornet.asn1.o: $(obj)/hornet.asn1.c $(obj)/hornet.asn1.h diff --git a/security/hornet/hornet.asn1 b/security/hornet/hornet.asn1 new file mode 100644 index 000000000000..c8d47b16b65d --- /dev/null +++ b/security/hornet/hornet.asn1 @@ -0,0 +1,13 @@ +-- 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 + +HornetData ::= SET OF Map + +Map ::= SEQUENCE { + index INTEGER ({ hornet_map_index }), + sha OCTET STRING ({ hornet_map_hash }) +} ({ hornet_next_map }) diff --git a/security/hornet/hornet_lsm.c b/security/hornet/hornet_lsm.c new file mode 100644 index 000000000000..6c821d6441fb --- /dev/null +++ b/security/hornet/hornet_lsm.c @@ -0,0 +1,323 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Hornet Linux Security Module + * + * Author: Blaise Boscaccy + * + * Copyright (C) 2026 Microsoft Corporation + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "hornet.asn1.h" + +#define MAX_USED_MAPS 64 + +struct hornet_maps { + bpfptr_t fd_array; +}; + +struct hornet_parse_context { + int indexes[MAX_USED_MAPS]; + bool skips[MAX_USED_MAPS]; + unsigned char hashes[SHA256_DIGEST_SIZE * MAX_USED_MAPS]; + int hash_count; +}; + +struct hornet_prog_security_struct { + bool checked[MAX_USED_MAPS]; + unsigned char hashes[SHA256_DIGEST_SIZE * MAX_USED_MAPS]; +}; + +struct hornet_map_security_struct { + bool checked; + int index; +}; + +struct lsm_blob_sizes hornet_blob_sizes __ro_after_init = { + .lbs_bpf_map = sizeof(struct hornet_map_security_struct), + .lbs_bpf_prog = sizeof(struct hornet_prog_security_struct), +}; + +static inline struct hornet_prog_security_struct * +hornet_bpf_prog_security(struct bpf_prog *prog) +{ + return prog->aux->security + hornet_blob_sizes.lbs_bpf_prog; +} + +static inline struct hornet_map_security_struct * +hornet_bpf_map_security(struct bpf_map *map) +{ + return map->security + hornet_blob_sizes.lbs_bpf_map; +} + +static int hornet_verify_hashes(struct hornet_maps *maps, + struct hornet_parse_context *ctx, + struct bpf_prog *prog) +{ + int map_fd; + u32 i; + struct bpf_map *map; + int err = 0; + unsigned char hash[SHA256_DIGEST_SIZE]; + struct hornet_prog_security_struct *security = hornet_bpf_prog_security(prog); + struct hornet_map_security_struct *map_security; + + for (i = 0; i < ctx->hash_count; i++) { + if (ctx->skips[i]) { + security->checked[i] = false; + continue; + } + + err = copy_from_bpfptr_offset(&map_fd, maps->fd_array, + ctx->indexes[i] * sizeof(map_fd), + sizeof(map_fd)); + if (err < 0) + return LSM_INT_VERDICT_BADSIG; + + CLASS(fd, f)(map_fd); + if (fd_empty(f)) + return LSM_INT_VERDICT_BADSIG; + if (unlikely(fd_file(f)->f_op != &bpf_map_fops)) + return LSM_INT_VERDICT_BADSIG; + + map = fd_file(f)->private_data; + if (!map->frozen) + return LSM_INT_VERDICT_BADSIG; + + map->ops->map_get_hash(map, SHA256_DIGEST_SIZE, hash); + + err = memcmp(hash, &ctx->hashes[i * SHA256_DIGEST_SIZE], + SHA256_DIGEST_SIZE); + if (err) + return LSM_INT_VERDICT_BADSIG; + + security->checked[i] = true; + memcpy(&security->hashes[i * SHA256_DIGEST_SIZE], hash, SHA256_DIGEST_SIZE); + map_security = hornet_bpf_map_security(map); + map_security->checked = true; + map_security->index = i; + } + return LSM_INT_VERDICT_OK; +} + +int hornet_next_map(void *context, size_t hdrlen, + unsigned char tag, + const void *value, size_t vlen) +{ + struct hornet_parse_context *ctx = (struct hornet_parse_context *)context; + + ctx->hash_count++; + return 0; +} + +int hornet_map_index(void *context, size_t hdrlen, + unsigned char tag, + const void *value, size_t vlen) +{ + struct hornet_parse_context *ctx = (struct hornet_parse_context *)context; + + if (vlen > 1) + return -EINVAL; + + ctx->indexes[ctx->hash_count] = *(u8 *)value; + return 0; +} + +int hornet_map_hash(void *context, size_t hdrlen, + unsigned char tag, + const void *value, size_t vlen) + +{ + struct hornet_parse_context *ctx = (struct hornet_parse_context *)context; + + if (vlen != SHA256_DIGEST_SIZE && vlen != 0) + return -EINVAL; + + if (vlen) { + ctx->skips[ctx->hash_count] = false; + memcpy(&ctx->hashes[ctx->hash_count * SHA256_DIGEST_SIZE], value, vlen); + } else + ctx->skips[ctx->hash_count] = true; + + return 0; +} + +static int hornet_check_program(struct bpf_prog *prog, union bpf_attr *attr, + struct bpf_token *token, bool is_kernel) +{ + struct hornet_maps maps = {0}; + bpfptr_t usig = make_bpfptr(attr->signature, is_kernel); + struct pkcs7_message *msg; + struct hornet_parse_context *ctx; + void *sig; + int err; + const void *authattrs; + size_t authattrs_len; + + if (!attr->signature) + return LSM_INT_VERDICT_UNSIGNED; + + ctx = kzalloc(sizeof(struct hornet_parse_context), GFP_KERNEL); + if (!ctx) + return -ENOMEM; + + maps.fd_array = make_bpfptr(attr->fd_array, is_kernel); + sig = kzalloc(attr->signature_size, GFP_KERNEL); + if (!sig) { + err = -ENOMEM; + goto out; + } + err = copy_from_bpfptr(sig, usig, attr->signature_size); + if (err != 0) + goto cleanup_sig; + + err = verify_pkcs7_signature(prog->insnsi, prog->len * sizeof(struct bpf_insn), + sig, attr->signature_size, VERIFY_USE_SECONDARY_KEYRING, + VERIFYING_BPF_SIGNATURE, NULL, NULL); + if (err < 0) { + err = LSM_INT_VERDICT_BADSIG; + goto cleanup_sig; + } + + msg = pkcs7_parse_message(sig, attr->signature_size); + if (IS_ERR(msg)) { + err = LSM_INT_VERDICT_BADSIG; + goto cleanup_sig; + } + + if (validate_pkcs7_trust(msg, VERIFY_USE_SECONDARY_KEYRING)) { + err = LSM_INT_VERDICT_PARTIALSIG; + goto cleanup_msg; + } + if (pkcs7_get_authattr(msg, OID_hornet_data, + &authattrs, &authattrs_len) == -ENODATA) { + err = LSM_INT_VERDICT_PARTIALSIG; + goto cleanup_msg; + } + + err = asn1_ber_decoder(&hornet_decoder, ctx, authattrs, authattrs_len); + if (err < 0 || authattrs == NULL) { + err = LSM_INT_VERDICT_PARTIALSIG; + goto cleanup_msg; + } + err = hornet_verify_hashes(&maps, ctx, prog); + +cleanup_msg: + pkcs7_free_message(msg); +cleanup_sig: + kfree(sig); +out: + kfree(ctx); + return err; +} + +static const struct lsm_id hornet_lsmid = { + .name = "hornet", + .id = LSM_ID_HORNET, +}; + +static int hornet_bpf_prog_load_integrity(struct bpf_prog *prog, union bpf_attr *attr, + struct bpf_token *token, bool is_kernel) +{ + int result = hornet_check_program(prog, attr, token, is_kernel); + + if (result < 0) + return result; + + return security_bpf_prog_load_post_integrity(prog, attr, token, is_kernel, + &hornet_lsmid, result); +} + +static int hornet_verify_map(struct bpf_prog *prog, int index) +{ + unsigned char hash[SHA256_DIGEST_SIZE]; + int i; + struct bpf_map *map; + struct hornet_prog_security_struct *security = hornet_bpf_prog_security(prog); + struct hornet_map_security_struct *map_security; + + if (!security->checked[index]) + return 0; + + for (i = 0; i < prog->aux->used_map_cnt; i++) { + map = prog->aux->used_maps[i]; + map_security = hornet_bpf_map_security(map); + if (map_security->index != index) + continue; + + if (!map->frozen) + return -EINVAL; + + map->ops->map_get_hash(map, SHA256_DIGEST_SIZE, hash); + if (memcmp(hash, &security->hashes[index * SHA256_DIGEST_SIZE], + SHA256_DIGEST_SIZE) != 0) + return -EINVAL; + else + return 0; + } + return -EINVAL; +} + +static int hornet_check_prog_maps(u32 ufd) +{ + CLASS(fd, f)(ufd); + struct bpf_prog *prog; + int i, result = 0; + + if (fd_empty(f)) + return -EBADF; + if (fd_file(f)->f_op != &bpf_prog_fops) + return -EINVAL; + + prog = fd_file(f)->private_data; + + mutex_lock(&prog->aux->used_maps_mutex); + if (!prog->aux->used_map_cnt) + goto out; + + for (i = 0; i < prog->aux->used_map_cnt; i++) { + result = hornet_verify_map(prog, i); + if (result) + goto out; + } +out: + mutex_unlock(&prog->aux->used_maps_mutex); + return result; +} + +static int hornet_bpf(int cmd, union bpf_attr *attr, unsigned int size, bool kernel) +{ + if (cmd != BPF_PROG_RUN) + return 0; + if (kernel) + return 0; + + return hornet_check_prog_maps(attr->test.prog_fd); +} + +static struct security_hook_list hornet_hooks[] __ro_after_init = { + LSM_HOOK_INIT(bpf_prog_load_integrity, hornet_bpf_prog_load_integrity), + LSM_HOOK_INIT(bpf, hornet_bpf), +}; + +static int __init hornet_init(void) +{ + pr_info("Hornet: eBPF signature verification enabled\n"); + security_add_hooks(hornet_hooks, ARRAY_SIZE(hornet_hooks), &hornet_lsmid); + return 0; +} + +DEFINE_LSM(hornet) = { + .id = &hornet_lsmid, + .blobs = &hornet_blob_sizes, + .init = hornet_init, +}; -- 2.52.0 This introduces the gen_sig tool. It creates a pkcs#7 signature of a data payload. Additionally it appends a signed attribute containing a set of hashes. Typical usage is to provide a payload containing the light skeleton ebpf syscall program binary and it's associated maps, which can be extracted from the auto-generated skeleton header. Signed-off-by: Blaise Boscaccy --- scripts/Makefile | 1 + scripts/hornet/Makefile | 5 + scripts/hornet/gen_sig.c | 392 ++++++++++++++++++++++++++++++++++++ scripts/hornet/write-sig.sh | 27 +++ 4 files changed, 425 insertions(+) create mode 100644 scripts/hornet/Makefile create mode 100644 scripts/hornet/gen_sig.c create mode 100755 scripts/hornet/write-sig.sh diff --git a/scripts/Makefile b/scripts/Makefile index 0941e5ce7b57..dea8ab91bbe4 100644 --- a/scripts/Makefile +++ b/scripts/Makefile @@ -63,6 +63,7 @@ subdir-$(CONFIG_GENKSYMS) += genksyms subdir-$(CONFIG_GENDWARFKSYMS) += gendwarfksyms subdir-$(CONFIG_SECURITY_SELINUX) += selinux subdir-$(CONFIG_SECURITY_IPE) += ipe +subdir-$(CONFIG_SECURITY_HORNET) += hornet # Let clean descend into subdirs subdir- += basic dtc gdb kconfig mod diff --git a/scripts/hornet/Makefile b/scripts/hornet/Makefile new file mode 100644 index 000000000000..3ee41e5e9a9f --- /dev/null +++ b/scripts/hornet/Makefile @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: GPL-2.0 +hostprogs-always-y := gen_sig + +HOSTCFLAGS_gen_sig.o = $(shell $(HOSTPKG_CONFIG) --cflags libcrypto 2> /dev/null) +HOSTLDLIBS_gen_sig = $(shell $(HOSTPKG_CONFIG) --libs libcrypto 2> /dev/null || echo -lcrypto) diff --git a/scripts/hornet/gen_sig.c b/scripts/hornet/gen_sig.c new file mode 100644 index 000000000000..f966516ebc99 --- /dev/null +++ b/scripts/hornet/gen_sig.c @@ -0,0 +1,392 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause + * + * Generate a signature for an eBPF program along with appending + * map hashes as signed attributes + * + * Copyright © 2025 Microsoft Corporation. + * + * Authors: Blaise Boscaccy + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 + * of the licence, or (at your option) any later version. + */ + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if OPENSSL_VERSION_MAJOR >= 3 +# define USE_PKCS11_PROVIDER +# include +# include +#else +# if !defined(OPENSSL_NO_ENGINE) && !defined(OPENSSL_NO_DEPRECATED_3_0) +# define USE_PKCS11_ENGINE +# include +# endif +#endif +#include "../ssl-common.h" + +#define SHA256_LEN 32 +#define BUF_SIZE (1 << 15) // 32 KiB +#define MAX_HASHES 64 + +struct hash_spec { + char *file; + int index; +}; + +typedef struct { + ASN1_INTEGER *index; + ASN1_OCTET_STRING *hash; + +} HORNET_MAP; + +DECLARE_ASN1_FUNCTIONS(HORNET_MAP) +ASN1_SEQUENCE(HORNET_MAP) = { + ASN1_SIMPLE(HORNET_MAP, index, ASN1_INTEGER), + ASN1_SIMPLE(HORNET_MAP, hash, ASN1_OCTET_STRING) +} ASN1_SEQUENCE_END(HORNET_MAP); + +IMPLEMENT_ASN1_FUNCTIONS(HORNET_MAP) + +DEFINE_STACK_OF(HORNET_MAP) + +typedef struct { + STACK_OF(HORNET_MAP) * maps; +} MAP_SET; + +DECLARE_ASN1_FUNCTIONS(MAP_SET) +ASN1_SEQUENCE(MAP_SET) = { + ASN1_SET_OF(MAP_SET, maps, HORNET_MAP) +} ASN1_SEQUENCE_END(MAP_SET); + +IMPLEMENT_ASN1_FUNCTIONS(MAP_SET) + +#define DIE(...) do { fprintf(stderr, __VA_ARGS__); fputc('\n', stderr); \ + exit(EXIT_FAILURE); } while (0) + +static BIO *bio_open_wr(const char *path) +{ + BIO *b = BIO_new_file(path, "wb"); + + if (!b) { + perror(path); + ERR_print_errors_fp(stderr); + exit(EXIT_FAILURE); + } + return b; +} + +static void usage(const char *prog) +{ + fprintf(stderr, + "Usage:\n" + " %s --data content.bin --cert signer.crt --key signer.key [-pass pass]\n" + " --out newsig.p7b \n" + " --add FILE:index [--add FILE:index ...]\n", + prog); +} + +static const char *key_pass; + +static int pem_pw_cb(char *buf, int len, int w, void *v) +{ + int pwlen; + + if (!key_pass) + return -1; + + pwlen = strlen(key_pass); + if (pwlen >= len) + return -1; + + strcpy(buf, key_pass); + + key_pass = NULL; + + return pwlen; +} + +static EVP_PKEY *read_private_key(const char *private_key_name) +{ + EVP_PKEY *private_key; + BIO *b; + + b = BIO_new_file(private_key_name, "rb"); + ERR(!b, "%s", private_key_name); + private_key = PEM_read_bio_PrivateKey(b, NULL, pem_pw_cb, + NULL); + ERR(!private_key, "%s", private_key_name); + BIO_free(b); + + return private_key; +} + +static X509 *read_x509(const char *x509_name) +{ + unsigned char buf[2]; + X509 *x509; + BIO *b; + int n; + + b = BIO_new_file(x509_name, "rb"); + ERR(!b, "%s", x509_name); + + /* Look at the first two bytes of the file to determine the encoding */ + n = BIO_read(b, buf, 2); + if (n != 2) { + if (BIO_should_retry(b)) { + fprintf(stderr, "%s: Read wanted retry\n", x509_name); + exit(1); + } + if (n >= 0) { + fprintf(stderr, "%s: Short read\n", x509_name); + exit(1); + } + ERR(1, "%s", x509_name); + } + + ERR(BIO_reset(b) != 0, "%s", x509_name); + + if (buf[0] == 0x30 && buf[1] >= 0x81 && buf[1] <= 0x84) + /* Assume raw DER encoded X.509 */ + x509 = d2i_X509_bio(b, NULL); + else + /* Assume PEM encoded X.509 */ + x509 = PEM_read_bio_X509(b, NULL, NULL, NULL); + + BIO_free(b); + ERR(!x509, "%s", x509_name); + + return x509; +} + +static int sha256(const char *path, unsigned char out[SHA256_LEN], unsigned int *out_len) +{ + FILE *f; + int rc; + EVP_MD_CTX *ctx; + unsigned char buf[BUF_SIZE]; + size_t n; + unsigned int mdlen = 0; + + if (!path || !out) + return -1; + + f = fopen(path, "rb"); + if (!f) { + perror("fopen"); + return -2; + } + + ERR_load_crypto_strings(); + + rc = -3; + ctx = EVP_MD_CTX_new(); + if (!ctx) { + rc = -4; + goto done; + } + +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + if (EVP_DigestInit_ex2(ctx, EVP_sha256(), NULL) != 1) { + rc = -5; + goto done; + } +#else + if (EVP_DigestInit_ex(ctx, EVP_sha256(), NULL) != 1) { + rc = -5; + goto done; + } +#endif + while ((n = fread(buf, 1, sizeof(buf), f)) > 0) { + if (EVP_DigestUpdate(ctx, buf, n) != 1) { + rc = -6; + goto done; + } + } + if (ferror(f)) { + rc = -7; + goto done; + } + + if (EVP_DigestFinal_ex(ctx, out, &mdlen) != 1) { + rc = -8; + goto done; + } + if (mdlen != SHA256_LEN) { + rc = -9; + goto done; + } + + if (out_len) + *out_len = mdlen; + rc = 0; + +done: + EVP_MD_CTX_free(ctx); + fclose(f); + ERR_free_strings(); + return rc; +} + +static void add_hash(MAP_SET *set, unsigned char *buffer, int buffer_len, int index) +{ + HORNET_MAP *map = NULL; + + map = HORNET_MAP_new(); + ASN1_INTEGER_set(map->index, index); + ASN1_OCTET_STRING_set(map->hash, buffer, buffer_len); + sk_HORNET_MAP_push(set->maps, map); +} + +int main(int argc, char **argv) +{ + const char *cert_path = NULL; + const char *key_path = NULL; + const char *data_path = NULL; + const char *out_path = NULL; + + X509 *signer; + EVP_PKEY *pkey; + BIO *data_in; + CMS_ContentInfo *cms_out; + struct hash_spec hashes[MAX_HASHES]; + int hash_count = 0; + int flags; + CMS_SignerInfo *si; + MAP_SET *set; + unsigned char hash_buffer[SHA256_LEN]; + unsigned int hash_len; + ASN1_OBJECT *oid; + unsigned char *der = NULL; + int der_len; + int err; + BIO *b_out; + int i; + char opt; + + const char *short_opts = "C:K:P:O:A:Sh"; + + static const struct option long_opts[] = { + {"cert", required_argument, 0, 'C'}, + {"key", required_argument, 0, 'K'}, + {"pass", required_argument, 0, 'P'}, + {"out", required_argument, 0, 'O'}, + {"data", required_argument, 0, 'D'}, + {"add", required_argument, 0, 'A'}, + {"help", no_argument, 0, 'h'}, + {0, 0, 0, 0} + }; + + while ((opt = getopt_long_only(argc, argv, short_opts, long_opts, NULL)) != -1) { + switch (opt) { + case 'C': + cert_path = optarg; + break; + case 'K': + key_path = optarg; + break; + case 'P': + key_pass = optarg; + break; + case 'O': + out_path = optarg; + break; + case 'D': + data_path = optarg; + break; + case 'A': + if (strchr(optarg, ':')) { + hashes[hash_count].file = strsep(&optarg, ":"); + hashes[hash_count].index = atoi(optarg); + hash_count++; + } else { + usage(argv[0]); + return EXIT_FAILURE; + } + } + } + + if (!cert_path || !key_path || !out_path || !data_path) { + usage(argv[0]); + return EXIT_FAILURE; + } + + OpenSSL_add_all_algorithms(); + ERR_load_crypto_strings(); + + signer = read_x509(cert_path); + ERR(!signer, "Load cert failed"); + + pkey = read_private_key(key_path); + ERR(!pkey, "Load key failed"); + + data_in = BIO_new_file(data_path, "rb"); + ERR(!data_in, "Load data failed"); + + cms_out = CMS_sign(NULL, NULL, NULL, NULL, + CMS_NOCERTS | CMS_PARTIAL | CMS_BINARY | CMS_DETACHED); + ERR(!cms_out, "create cms failed"); + + flags = CMS_NOCERTS | CMS_PARTIAL | CMS_BINARY | CMS_NOSMIMECAP | CMS_DETACHED; + + si = CMS_add1_signer(cms_out, signer, pkey, EVP_sha256(), flags); + ERR(!si, "add signer failed"); + + set = MAP_SET_new(); + set->maps = sk_HORNET_MAP_new_null(); + + for (i = 0; i < hash_count; i++) { + sha256(hashes[i].file, hash_buffer, &hash_len); + add_hash(set, hash_buffer, hash_len, hashes[i].index); + } + + oid = OBJ_txt2obj("2.25.316487325684022475439036912669789383960", 1); + if (!oid) { + ERR_print_errors_fp(stderr); + DIE("create oid failed"); + } + + der_len = ASN1_item_i2d((ASN1_VALUE *)set, &der, ASN1_ITEM_rptr(MAP_SET)); + CMS_signed_add1_attr_by_OBJ(si, oid, V_ASN1_SEQUENCE, der, der_len); + + err = CMS_final(cms_out, data_in, NULL, CMS_NOCERTS | CMS_BINARY); + ERR(!err, "cms final failed"); + + OPENSSL_free(der); + MAP_SET_free(set); + + b_out = bio_open_wr(out_path); + ERR(!b_out, "opening output path failed"); + + i2d_CMS_bio_stream(b_out, cms_out, NULL, 0); + + BIO_free(data_in); + BIO_free(b_out); + EVP_cleanup(); + ERR_free_strings(); + return 0; +} diff --git a/scripts/hornet/write-sig.sh b/scripts/hornet/write-sig.sh new file mode 100755 index 000000000000..7eaabe3bab9a --- /dev/null +++ b/scripts/hornet/write-sig.sh @@ -0,0 +1,27 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# Copyright (c) 2025 Microsoft Corporation +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of version 2 of the GNU General Public +# License as published by the Free Software Foundation. + +function usage() { + echo "Sample for rewriting an autogenerated eBPF lskel headers" + echo "with a new signature" + echo "" + echo "USAGE: header_file sig" + exit +} + +ARGC=$# + +EXPECTED_ARGS=2 + +if [ $ARGC -ne $EXPECTED_ARGS ] ; then + usage +else + SIG=$(xxd -p $2 | tr -d '\n' | sed 's/\(..\)/\\\\x\1/g') + sed '/const char opts_sig/,/;/c\\tstatic const char opts_sig[] __attribute__((__aligned__(8))) = "\\\n'"$(printf '%s\n' "$SIG")"'\";' $1 +fi -- 2.52.0 These script eases light skeleton development against Hornet by generating a data payloads which can be used for signing a light skeleton binary using gen_sig. Signed-off-by: Blaise Boscaccy --- 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 diff --git a/scripts/hornet/extract-insn.sh b/scripts/hornet/extract-insn.sh new file mode 100755 index 000000000000..52338f057ff6 --- /dev/null +++ b/scripts/hornet/extract-insn.sh @@ -0,0 +1,27 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# Copyright (c) 2025 Microsoft Corporation +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of version 2 of the GNU General Public +# License as published by the Free Software Foundation. + +function usage() { + echo "Sample script for extracting instructions" + echo "autogenerated eBPF lskel headers" + echo "" + echo "USAGE: header_file" + exit +} + +ARGC=$# + +EXPECTED_ARGS=1 + +if [ $ARGC -ne $EXPECTED_ARGS ] ; then + usage +else + printf $(gcc -E $1 | grep "opts_insn" | \ + awk -F"=" '{print $2}' | sed 's/;\+$//' | sed 's/\"//g') +fi diff --git a/scripts/hornet/extract-map.sh b/scripts/hornet/extract-map.sh new file mode 100755 index 000000000000..c309f505c623 --- /dev/null +++ b/scripts/hornet/extract-map.sh @@ -0,0 +1,27 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# Copyright (c) 2025 Microsoft Corporation +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of version 2 of the GNU General Public +# License as published by the Free Software Foundation. + +function usage() { + echo "Sample script for extracting instructions" + echo "autogenerated eBPF lskel headers" + echo "" + echo "USAGE: header_file" + exit +} + +ARGC=$# + +EXPECTED_ARGS=1 + +if [ $ARGC -ne $EXPECTED_ARGS ] ; then + usage +else + printf $(gcc -E $1 | grep "opts_data" | \ + awk -F"=" '{print $2}' | sed 's/;\+$//' | sed 's/\"//g') +fi diff --git a/scripts/hornet/extract-skel.sh b/scripts/hornet/extract-skel.sh new file mode 100755 index 000000000000..6550a86b8991 --- /dev/null +++ b/scripts/hornet/extract-skel.sh @@ -0,0 +1,27 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# Copyright (c) 2025 Microsoft Corporation +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of version 2 of the GNU General Public +# License as published by the Free Software Foundation. + +function usage() { + echo "Sample script for extracting instructions and map data out of" + echo "autogenerated eBPF lskel headers" + echo "" + echo "USAGE: header_file field" + exit +} + +ARGC=$# + +EXPECTED_ARGS=2 + +if [ $ARGC -ne $EXPECTED_ARGS ] ; then + usage +else + printf $(gcc -E $1 | grep "static const char opts_$2" | \ + awk -F"=" '{print $2}' | sed 's/;\+$//' | sed 's/\"//g') +fi -- 2.52.0 This selftest contains a testcase that utilizes light skeleton eBPF loaders and exercises hornet's map validation. Signed-off-by: Blaise Boscaccy --- tools/testing/selftests/Makefile | 1 + tools/testing/selftests/hornet/Makefile | 63 ++++++++++++++++++++ tools/testing/selftests/hornet/loader.c | 21 +++++++ tools/testing/selftests/hornet/trivial.bpf.c | 33 ++++++++++ 4 files changed, 118 insertions(+) create mode 100644 tools/testing/selftests/hornet/Makefile create mode 100644 tools/testing/selftests/hornet/loader.c create mode 100644 tools/testing/selftests/hornet/trivial.bpf.c diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile index 450f13ba4cca..4e2d1cd88c82 100644 --- a/tools/testing/selftests/Makefile +++ b/tools/testing/selftests/Makefile @@ -44,6 +44,7 @@ TARGETS += ftrace TARGETS += futex TARGETS += gpio TARGETS += hid +TARGETS += hornet TARGETS += intel_pstate TARGETS += iommu TARGETS += ipc diff --git a/tools/testing/selftests/hornet/Makefile b/tools/testing/selftests/hornet/Makefile new file mode 100644 index 000000000000..432bce59f54e --- /dev/null +++ b/tools/testing/selftests/hornet/Makefile @@ -0,0 +1,63 @@ +# SPDX-License-Identifier: GPL-2.0 +include ../../../build/Build.include +include ../../../scripts/Makefile.arch +include ../../../scripts/Makefile.include + +CLANG ?= clang +CFLAGS := -g -O2 -Wall +BPFTOOL ?= $(TOOLSDIR)/bpf/bpftool/bpftool +SCRIPTSDIR := $(abspath ../../../../scripts/hornet) +TOOLSDIR := $(abspath ../../..) +LIBDIR := $(TOOLSDIR)/lib +BPFDIR := $(LIBDIR)/bpf +TOOLSINCDIR := $(TOOLSDIR)/include +APIDIR := $(TOOLSINCDIR)/uapi +CERTDIR := $(abspath ../../../../certs) +PKG_CONFIG ?= $(CROSS_COMPILE)pkg-config + +TEST_GEN_PROGS := loader +TEST_GEN_FILES := vmlinux.h loader.h trivial.bpf.o map.bin sig.bin insn.bin signed_loader.h +$(TEST_GEN_PROGS): LDLIBS += -lbpf +$(TEST_GEN_PROGS): $(TEST_GEN_FILES) + +include ../lib.mk + +BPF_CFLAGS := -target bpf \ + -D__TARGET_ARCH_$(ARCH) \ + -I/usr/include/$(shell uname -m)-linux-gnu \ + $(KHDR_INCLUDES) + +vmlinux.h: + $(BPFTOOL) btf dump file /sys/kernel/btf/vmlinux format c > vmlinux.h + +trivial.bpf.o: trivial.bpf.c vmlinux.h + $(CLANG) $(CFLAGS) $(BPF_CFLAGS) -c $< -o $@ + +loader.h: trivial.bpf.o + $(BPFTOOL) gen skeleton -S -k $(CERTDIR)/signing_key.pem -i $(CERTDIR)/signing_key.x509 \ + -L $< name trivial > $@ + +insn.bin: loader.h + $(SCRIPTSDIR)/extract-insn.sh $< > $@ + +map.bin: loader.h + $(SCRIPTSDIR)/extract-map.sh $< > $@ + +$(OUTPUT)/gen_sig: ../../../../scripts/hornet/gen_sig.c + $(call msg,GEN_SIG,,$@) + $(Q)$(CC) $(shell $(PKG_CONFIG) --cflags libcrypto 2> /dev/null) \ + $< -o $@ \ + $(shell $(PKG_CONFIG) --libs libcrypto 2> /dev/null || echo -lcrypto) + +sig.bin: insn.bin map.bin $(OUTPUT)/gen_sig + $(OUTPUT)/gen_sig --key $(CERTDIR)/signing_key.pem --cert $(CERTDIR)/signing_key.x509 \ + --data insn.bin --add map.bin:0 --out sig.bin + +signed_loader.h: sig.bin + $(SCRIPTSDIR)/write-sig.sh loader.h sig.bin > $@ + +loader: loader.c signed_loader.h + $(CC) $(CFLAGS) -I$(LIBDIR) -I$(APIDIR) $< -o $@ -lbpf + + +EXTRA_CLEAN = $(OUTPUT)/gen_sig diff --git a/tools/testing/selftests/hornet/loader.c b/tools/testing/selftests/hornet/loader.c new file mode 100644 index 000000000000..f27580c7262b --- /dev/null +++ b/tools/testing/selftests/hornet/loader.c @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause + +#include +#include +#include +#include +#include +#include +#include "signed_loader.h" + +int main(int argc, char **argv) +{ + struct trivial *skel; + + skel = trivial__open_and_load(); + if (!skel) + return -1; + + trivial__destroy(skel); + return 0; +} diff --git a/tools/testing/selftests/hornet/trivial.bpf.c b/tools/testing/selftests/hornet/trivial.bpf.c new file mode 100644 index 000000000000..d38c5b53ff93 --- /dev/null +++ b/tools/testing/selftests/hornet/trivial.bpf.c @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause + +#include "vmlinux.h" + +#include +#include +#include + +char LICENSE[] SEC("license") = "Dual BSD/GPL"; + +int monitored_pid = 0; + +SEC("tracepoint/syscalls/sys_enter_unlinkat") +int handle_enter_unlink(struct trace_event_raw_sys_enter *ctx) +{ + char filename[128] = { 0 }; + struct task_struct *task; + unsigned long start_time = 0; + int pid = bpf_get_current_pid_tgid() >> 32; + char *pathname_ptr = (char *) BPF_CORE_READ(ctx, args[1]); + + bpf_probe_read_str(filename, sizeof(filename), pathname_ptr); + task = (struct task_struct *)bpf_get_current_task(); + start_time = BPF_CORE_READ(task, start_time); + + bpf_printk("BPF triggered unlinkat by PID: %d, start_time %ld. pathname = %s", + pid, start_time, filename); + + if (monitored_pid == pid) + bpf_printk("target pid found"); + + return 0; +} -- 2.52.0