diff options
| author | Paul Moore <paul@paul-moore.com> | 2026-05-28 21:40:42 -0400 |
|---|---|---|
| committer | Paul Moore <paul@paul-moore.com> | 2026-05-28 21:40:42 -0400 |
| commit | c67f6e6d2c141f202211fa9fea844f577eb233a8 (patch) | |
| tree | 48ce703efa10be0d2edf27faba2ffc2425225545 /security | |
| parent | e7ae89a0c97ce2b68b0983cd01eda67cf373517d (diff) | |
| parent | 6658fb2e7812b91a30d83abf0bede51660d19a07 (diff) | |
| download | linux-next-history-c67f6e6d2c141f202211fa9fea844f577eb233a8.tar.gz | |
Automated merge of 'dev' into 'next'
* dev:
crypto: pkcs7: export verify_pkcs7_message_sig() as EXPORT_SYMBOL_GPL
ipe: restore the kdoc comments for evaluate_property()
hornet: depend on CONFIG_SECURITY and CONFIG_BPF_SYSCALL
ipe: Add BPF program load policy enforcement via Hornet integration
selftests/hornet: Add a selftest for the Hornet LSM
hornet: Add a light skeleton data extractor scripts
hornet: Introduce gen_sig
lsm: introduce the Hornet LSM
lsm: add additional enum values for bpf integrity checks
lsm: framework for BPF integrity verification
crypto: pkcs7: add tests for pkcs7_get_authattr
crypto: pkcs7: add ability to extract signed attributes by OID
crypto: pkcs7: add flag for validated trust on a signed info block
security,fs,nfs,net: update security_inode_listsecurity() interface
Diffstat (limited to 'security')
| -rw-r--r-- | security/Kconfig | 3 | ||||
| -rw-r--r-- | security/Makefile | 1 | ||||
| -rw-r--r-- | security/hornet/Kconfig | 14 | ||||
| -rw-r--r-- | security/hornet/Makefile | 7 | ||||
| -rw-r--r-- | security/hornet/hornet.asn1 | 12 | ||||
| -rw-r--r-- | security/hornet/hornet_lsm.c | 352 | ||||
| -rw-r--r-- | security/ipe/Kconfig | 15 | ||||
| -rw-r--r-- | security/ipe/audit.c | 15 | ||||
| -rw-r--r-- | security/ipe/eval.c | 93 | ||||
| -rw-r--r-- | security/ipe/eval.h | 11 | ||||
| -rw-r--r-- | security/ipe/hooks.c | 63 | ||||
| -rw-r--r-- | security/ipe/hooks.h | 15 | ||||
| -rw-r--r-- | security/ipe/ipe.c | 14 | ||||
| -rw-r--r-- | security/ipe/ipe.h | 3 | ||||
| -rw-r--r-- | security/ipe/policy.h | 14 | ||||
| -rw-r--r-- | security/ipe/policy_parser.c | 27 | ||||
| -rw-r--r-- | security/security.c | 91 | ||||
| -rw-r--r-- | security/selinux/hooks.c | 10 | ||||
| -rw-r--r-- | security/smack/smack_lsm.c | 13 |
19 files changed, 745 insertions, 28 deletions
diff --git a/security/Kconfig b/security/Kconfig index f7bf6cdc6229e..ab7aa622455a8 100644 --- a/security/Kconfig +++ b/security/Kconfig @@ -228,6 +228,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" @@ -272,7 +273,7 @@ config LSM default "landlock,lockdown,yama,loadpin,safesetid,apparmor,selinux,smack,tomoyo,ipe,bpf" if DEFAULT_SECURITY_APPARMOR default "landlock,lockdown,yama,loadpin,safesetid,tomoyo,ipe,bpf" if DEFAULT_SECURITY_TOMOYO default "landlock,lockdown,yama,loadpin,safesetid,ipe,bpf" if DEFAULT_SECURITY_DAC - default "landlock,lockdown,yama,loadpin,safesetid,selinux,smack,tomoyo,apparmor,ipe,bpf" + default "landlock,lockdown,yama,loadpin,safesetid,selinux,smack,tomoyo,apparmor,ipe,hornet,bpf" help A comma-separated list of LSMs, in initialization order. Any LSMs left off this list, except for those with order diff --git a/security/Makefile b/security/Makefile index 4601230ba442a..b68cb56e419bc 100644 --- a/security/Makefile +++ b/security/Makefile @@ -26,6 +26,7 @@ obj-$(CONFIG_CGROUPS) += device_cgroup.o obj-$(CONFIG_BPF_LSM) += bpf/ obj-$(CONFIG_SECURITY_LANDLOCK) += landlock/ obj-$(CONFIG_SECURITY_IPE) += ipe/ +obj-$(CONFIG_SECURITY_HORNET) += hornet/ # Object integrity file lists obj-$(CONFIG_INTEGRITY) += integrity/ diff --git a/security/hornet/Kconfig b/security/hornet/Kconfig new file mode 100644 index 0000000000000..537ad015958cd --- /dev/null +++ b/security/hornet/Kconfig @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: GPL-2.0-only +config SECURITY_HORNET + bool "Hornet support" + depends on SECURITY && BPF_SYSCALL + select CRYPTO_LIB_SHA256 + select PKCS7_MESSAGE_PARSER + select SYSTEM_DATA_VERIFICATION + default n + help + This selects Hornet. + Further information can be found in + Documentation/admin-guide/LSM/Hornet.rst. + + If you are unsure how to answer this question, answer N. diff --git a/security/hornet/Makefile b/security/hornet/Makefile new file mode 100644 index 0000000000000..26b6f954f762e --- /dev/null +++ b/security/hornet/Makefile @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: GPL-2.0-only +obj-$(CONFIG_SECURITY_HORNET) := hornet.o + +hornet-y := hornet.asn1.o \ + hornet_lsm.o \ + +$(obj)/hornet.asn1.o: $(obj)/hornet.asn1.c $(obj)/hornet.asn1.h diff --git a/security/hornet/hornet.asn1 b/security/hornet/hornet.asn1 new file mode 100644 index 0000000000000..e60abf451ae23 --- /dev/null +++ b/security/hornet/hornet.asn1 @@ -0,0 +1,12 @@ +-- SPDX-License-Identifier: BSD-3-Clause +-- +-- Copyright (C) 2026 Microsoft +-- +-- https://www.rfc-editor.org/rfc/rfc5652#section-3 + +HornetData ::= SET OF Map + +Map ::= SEQUENCE { + index INTEGER ({ hornet_map_index }), + sha OCTET STRING ({ hornet_map_hash }) +} ({ hornet_next_map }) diff --git a/security/hornet/hornet_lsm.c b/security/hornet/hornet_lsm.c new file mode 100644 index 0000000000000..a4d11fa5b0889 --- /dev/null +++ b/security/hornet/hornet_lsm.c @@ -0,0 +1,352 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Hornet Linux Security Module + * + * Author: Blaise Boscaccy <bboscaccy@linux.microsoft.com> + * + * Copyright (C) 2026 Microsoft Corporation + */ + +#include <linux/lsm_hooks.h> +#include <uapi/linux/lsm.h> +#include <linux/bpf.h> +#include <linux/verification.h> +#include <crypto/public_key.h> +#include <linux/module_signature.h> +#include <crypto/pkcs7.h> +#include <linux/sort.h> +#include <linux/asn1_decoder.h> +#include <linux/oid_registry.h> +#include "hornet.asn1.h" + +#define MAX_USED_MAPS 64 + +struct hornet_maps { + bpfptr_t fd_array; +}; + +/* The only hashing algorithm available is SHA256 due to it be hardcoded + * in the bpf subsystem. + */ + +struct hornet_parse_context { + int indexes[MAX_USED_MAPS]; + bool skips[MAX_USED_MAPS]; + unsigned char hashes[SHA256_DIGEST_SIZE * MAX_USED_MAPS]; + int hash_count; +}; + +struct hornet_prog_security_struct { + int signed_hash_count; + unsigned char signed_hashes[SHA256_DIGEST_SIZE * MAX_USED_MAPS]; +}; + +struct lsm_blob_sizes hornet_blob_sizes __ro_after_init = { + .lbs_bpf_prog = sizeof(struct hornet_prog_security_struct), +}; + +static inline struct hornet_prog_security_struct * +hornet_bpf_prog_security(struct bpf_prog *prog) +{ + return prog->aux->security + hornet_blob_sizes.lbs_bpf_prog; +} + +static int hornet_verify_hashes(struct hornet_maps *maps, + struct hornet_parse_context *ctx, + struct bpf_prog *prog) +{ + int map_fd; + u32 i; + struct bpf_map *map; + int err = 0; + unsigned char hash[SHA256_DIGEST_SIZE]; + struct hornet_prog_security_struct *security = hornet_bpf_prog_security(prog); + + for (i = 0; i < ctx->hash_count; i++) { + if (ctx->skips[i]) + continue; + + err = copy_from_bpfptr_offset(&map_fd, maps->fd_array, + ctx->indexes[i] * sizeof(map_fd), + sizeof(map_fd)); + if (err != 0) + return LSM_INT_VERDICT_FAULT; + + CLASS(fd, f)(map_fd); + if (fd_empty(f)) + return LSM_INT_VERDICT_FAULT; + if (unlikely(fd_file(f)->f_op != &bpf_map_fops)) + return LSM_INT_VERDICT_FAULT; + + map = fd_file(f)->private_data; + if (!READ_ONCE(map->frozen)) + return LSM_INT_VERDICT_FAULT; + + if (!map->ops->map_get_hash) + return LSM_INT_VERDICT_FAULT; + + if (map->ops->map_get_hash(map, SHA256_DIGEST_SIZE, hash)) + return LSM_INT_VERDICT_FAULT; + + err = memcmp(hash, &ctx->hashes[i * SHA256_DIGEST_SIZE], + SHA256_DIGEST_SIZE); + if (err) + return LSM_INT_VERDICT_UNEXPECTED; + + memcpy(&security->signed_hashes[security->signed_hash_count * SHA256_DIGEST_SIZE], + &ctx->hashes[i * SHA256_DIGEST_SIZE], SHA256_DIGEST_SIZE); + security->signed_hash_count++; + } + return LSM_INT_VERDICT_OK; +} + +int hornet_next_map(void *context, size_t hdrlen, + unsigned char tag, + const void *value, size_t vlen) +{ + struct hornet_parse_context *ctx = (struct hornet_parse_context *)context; + + if (++ctx->hash_count >= MAX_USED_MAPS) + return -EINVAL; + return 0; +} + +int hornet_map_index(void *context, size_t hdrlen, + unsigned char tag, + const void *value, size_t vlen) +{ + struct hornet_parse_context *ctx = (struct hornet_parse_context *)context; + + if (vlen != 1) + return -EINVAL; + + ctx->indexes[ctx->hash_count] = *(u8 *)value; + return 0; +} + +int hornet_map_hash(void *context, size_t hdrlen, + unsigned char tag, + const void *value, size_t vlen) + +{ + struct hornet_parse_context *ctx = (struct hornet_parse_context *)context; + + if (vlen != SHA256_DIGEST_SIZE && vlen != 0) + return -EINVAL; + + if (vlen) { + ctx->skips[ctx->hash_count] = false; + memcpy(&ctx->hashes[ctx->hash_count * SHA256_DIGEST_SIZE], value, vlen); + } else + ctx->skips[ctx->hash_count] = true; + + return 0; +} + +static int hornet_check_program(struct bpf_prog *prog, union bpf_attr *attr, + struct bpf_token *token, bool is_kernel, + enum lsm_integrity_verdict *verdict) +{ + struct hornet_maps maps = {0}; + bpfptr_t usig = make_bpfptr(attr->signature, is_kernel); + struct pkcs7_message *msg; + struct hornet_parse_context *ctx; + void *sig; + int err; + const void *authattrs; + size_t authattrs_len; + struct key *key; + key_ref_t user_key = ERR_PTR(-ENOKEY); + + if (!attr->signature) { + *verdict = LSM_INT_VERDICT_UNSIGNED; + return 0; + } + + if (!attr->signature_size) { + *verdict = LSM_INT_VERDICT_BADSIG; + return 0; + } + + ctx = kzalloc(sizeof(struct hornet_parse_context), GFP_KERNEL); + if (!ctx) + return -ENOMEM; + + maps.fd_array = make_bpfptr(attr->fd_array, is_kernel); + sig = kzalloc(attr->signature_size, GFP_KERNEL); + if (!sig) { + err = -ENOMEM; + goto out; + } + err = copy_from_bpfptr(sig, usig, attr->signature_size); + if (err != 0) { + err = -EFAULT; + goto cleanup_sig; + } + + msg = pkcs7_parse_message(sig, attr->signature_size); + if (IS_ERR(msg)) { + *verdict = LSM_INT_VERDICT_BADSIG; + err = 0; + goto cleanup_sig; + } + + if (system_keyring_id_check(attr->keyring_id) == 0) + key = (struct key *)(unsigned long)attr->keyring_id; + else { + user_key = lookup_user_key(attr->keyring_id, 0, KEY_DEFER_PERM_CHECK); + if (IS_ERR(user_key)) { + *verdict = LSM_INT_VERDICT_UNKNOWNKEY; + goto cleanup_msg; + } + key = key_ref_to_ptr(user_key); + } + + if (verify_pkcs7_message_sig(prog->insnsi, prog->len * sizeof(struct bpf_insn), msg, + key, + VERIFYING_BPF_SIGNATURE, + NULL, NULL)) { + *verdict = LSM_INT_VERDICT_BADSIG; + err = 0; + goto cleanup_msg; + } + + if (pkcs7_get_authattr(msg, OID_hornet_data, + &authattrs, &authattrs_len) == -ENODATA) { + *verdict = LSM_INT_VERDICT_PARTIALSIG; + err = 0; + goto cleanup_msg; + } + + err = asn1_ber_decoder(&hornet_decoder, ctx, authattrs, authattrs_len); + if (err < 0 || authattrs == NULL) { + *verdict = LSM_INT_VERDICT_BADSIG; + err = 0; + goto cleanup_msg; + } + + *verdict = hornet_verify_hashes(&maps, ctx, prog); + err = 0; + +cleanup_msg: + pkcs7_free_message(msg); + if (!IS_ERR(user_key)) + key_put(key); +cleanup_sig: + kfree(sig); +out: + kfree(ctx); + return err; +} + +static const struct lsm_id hornet_lsmid = { + .name = "hornet", + .id = LSM_ID_HORNET, +}; + +static int hornet_bpf_prog_load_integrity(struct bpf_prog *prog, union bpf_attr *attr, + struct bpf_token *token, bool is_kernel) +{ + enum lsm_integrity_verdict verdict; + int result = hornet_check_program(prog, attr, token, is_kernel, &verdict); + + if (result < 0) + return result; + + return security_bpf_prog_load_post_integrity(prog, attr, token, is_kernel, + &hornet_lsmid, verdict); +} + +static int hornet_check_prog_maps(u32 ufd) +{ + CLASS(fd, f)(ufd); + struct bpf_prog *prog; + struct hornet_prog_security_struct *security; + unsigned char hash[SHA256_DIGEST_SIZE]; + struct bpf_map *map; + int i, j; + bool found; + int covered_count = 0; + + if (fd_empty(f)) + return -EBADF; + if (fd_file(f)->f_op != &bpf_prog_fops) + return -EINVAL; + + prog = fd_file(f)->private_data; + security = hornet_bpf_prog_security(prog); + + if (!security->signed_hash_count) + return 0; + + mutex_lock(&prog->aux->used_maps_mutex); + + /* Verify every used_map has a matching signed hash */ + for (j = 0; j < prog->aux->used_map_cnt; j++) { + map = prog->aux->used_maps[j]; + + if (!READ_ONCE(map->frozen) || !map->ops->map_get_hash) + continue; + + if (map->ops->map_get_hash(map, SHA256_DIGEST_SIZE, hash)) + continue; + + found = false; + for (i = 0; i < security->signed_hash_count; i++) { + if (memcmp(hash, + &security->signed_hashes[i * SHA256_DIGEST_SIZE], + SHA256_DIGEST_SIZE) == 0) { + found = true; + break; + } + } + if (!found) { + mutex_unlock(&prog->aux->used_maps_mutex); + return -EPERM; + } + covered_count++; + } + + mutex_unlock(&prog->aux->used_maps_mutex); + + /* Ensure all signed hashes were accounted for */ + if (covered_count != security->signed_hash_count) + return -EPERM; + + return 0; +} + +static int hornet_bpf(int cmd, union bpf_attr *attr, unsigned int size, bool kernel) +{ + /* in horent_bpf(), anything that had originated from kernel space we assume + * has already been checked, in some form or another, so we don't bother + * checking the intergity of any maps. In hornet_bpf_prog_load_integrity(), + * hornet doesn't make any opinion on that and delegates that to the downstream + * policy enforcement. + */ + + if (cmd != BPF_PROG_RUN) + return 0; + if (kernel) + return 0; + + return hornet_check_prog_maps(attr->test.prog_fd); +} + +static struct security_hook_list hornet_hooks[] __ro_after_init = { + LSM_HOOK_INIT(bpf_prog_load_integrity, hornet_bpf_prog_load_integrity), + LSM_HOOK_INIT(bpf, hornet_bpf), +}; + +static int __init hornet_init(void) +{ + pr_info("Hornet: eBPF signature verification enabled\n"); + security_add_hooks(hornet_hooks, ARRAY_SIZE(hornet_hooks), &hornet_lsmid); + return 0; +} + +DEFINE_LSM(hornet) = { + .id = &hornet_lsmid, + .blobs = &hornet_blob_sizes, + .init = hornet_init, +}; diff --git a/security/ipe/Kconfig b/security/ipe/Kconfig index a110a6cd848b7..95775139612df 100644 --- a/security/ipe/Kconfig +++ b/security/ipe/Kconfig @@ -95,6 +95,21 @@ config IPE_PROP_FS_VERITY_BUILTIN_SIG if unsure, answer Y. +config IPE_PROP_BPF_SIGNATURE + bool "Enable support for Hornet BPF program signature verification" + depends on SECURITY_HORNET + help + This option enables the 'bpf_signature', 'bpf_kernel' and + 'bpf_keyring' properties within IPE policies. The + 'bpf_signature' property allows IPE to make policy decisions + based on the integrity verdict provided by the Hornet LSM + when a BPF program is loaded. Verdicts include OK, + UNSIGNED, PARTIALSIG, BADSIG, and others. The 'bpf_keyring' + property allows policies to match against the keyring + specified in bpf_attr (BUILTIN, SECONDARY, PLATFORM). + + If unsure, answer Y. + endmenu config SECURITY_IPE_KUNIT_TEST diff --git a/security/ipe/audit.c b/security/ipe/audit.c index 93fb59fbddd60..77bbf04d950bd 100644 --- a/security/ipe/audit.c +++ b/security/ipe/audit.c @@ -41,6 +41,7 @@ static const char *const audit_op_names[__IPE_OP_MAX + 1] = { "KEXEC_INITRAMFS", "POLICY", "X509_CERT", + "BPF_PROG_LOAD", "UNKNOWN", }; @@ -51,6 +52,7 @@ static const char *const audit_hook_names[__IPE_HOOK_MAX] = { "MPROTECT", "KERNEL_READ", "KERNEL_LOAD", + "BPF_PROG_LOAD", }; static const char *const audit_prop_names[__IPE_PROP_MAX] = { @@ -62,6 +64,19 @@ static const char *const audit_prop_names[__IPE_PROP_MAX] = { "fsverity_digest=", "fsverity_signature=FALSE", "fsverity_signature=TRUE", + "bpf_signature=NONE", + "bpf_signature=OK", + "bpf_signature=UNSIGNED", + "bpf_signature=PARTIALSIG", + "bpf_signature=UNKNOWNKEY", + "bpf_signature=UNEXPECTED", + "bpf_signature=FAULT", + "bpf_signature=BADSIG", + "bpf_keyring=BUILTIN", + "bpf_keyring=SECONDARY", + "bpf_keyring=PLATFORM", + "bpf_kernel=FALSE", + "bpf_kernel=TRUE", }; /** diff --git a/security/ipe/eval.c b/security/ipe/eval.c index 21439c5be3364..23ae1edf896b0 100644 --- a/security/ipe/eval.c +++ b/security/ipe/eval.c @@ -11,6 +11,7 @@ #include <linux/rcupdate.h> #include <linux/moduleparam.h> #include <linux/fsverity.h> +#include <linux/verification.h> #include "ipe.h" #include "eval.h" @@ -265,6 +266,72 @@ static bool evaluate_fsv_sig_true(const struct ipe_eval_ctx *const ctx) } #endif /* CONFIG_IPE_PROP_FS_VERITY_BUILTIN_SIG */ +#ifdef CONFIG_IPE_PROP_BPF_SIGNATURE +/** + * evaluate_bpf_sig() - Evaluate @ctx against a bpf_signature property. + * @ctx: Supplies a pointer to the context being evaluated. + * @expected: The expected lsm_integrity_verdict to match against. + * + * Return: + * * %true - The current @ctx matches the expected verdict + * * %false - The current @ctx doesn't match the expected verdict + */ +static bool evaluate_bpf_sig(const struct ipe_eval_ctx *const ctx, + enum lsm_integrity_verdict expected) +{ + return ctx->bpf_verdict == expected; +} +#else +static bool evaluate_bpf_sig(const struct ipe_eval_ctx *const ctx, + enum lsm_integrity_verdict expected) +{ + return false; +} +#endif /* CONFIG_IPE_PROP_BPF_SIGNATURE */ + +#ifdef CONFIG_IPE_PROP_BPF_SIGNATURE +/** + * evaluate_bpf_keyring() - Evaluate @ctx against a bpf_keyring property. + * @ctx: Supplies a pointer to the context being evaluated. + * @expected: The expected keyring_id to match against. + * + * Return: + * * %true - The current @ctx matches the expected keyring + * * %false - The current @ctx doesn't match the expected keyring + */ +static bool evaluate_bpf_keyring(const struct ipe_eval_ctx *const ctx, + s32 expected) +{ + return ctx->bpf_keyring_id == expected; +} +#else +static bool evaluate_bpf_keyring(const struct ipe_eval_ctx *const ctx, + s32 expected) +{ + return false; +} +#endif /* CONFIG_IPE_PROP_BPF_SIGNATURE */ + +#ifdef CONFIG_IPE_PROP_BPF_SIGNATURE +/** + * evaluate_bpf_kernel() - Evaluate @ctx against the bpf_kernel property. + * @ctx: Supplies a pointer to the context being evaluated. + * + * Return: + * * %true - The current @ctx bpf_kernel property is true + * * %false - The current @ctx bpf_kernel property is false + */ +static bool evaluate_bpf_kernel(const struct ipe_eval_ctx *const ctx) +{ + return ctx->bpf_kernel; +} +#else +static bool evaluate_bpf_kernel(const struct ipe_eval_ctx *const ctx) +{ + return false; +} +#endif /* CONFIG_IPE_PROP_BPF_SIGNATURE */ + /** * evaluate_property() - Analyze @ctx against a rule property. * @ctx: Supplies a pointer to the context to be evaluated. @@ -297,6 +364,32 @@ static bool evaluate_property(const struct ipe_eval_ctx *const ctx, return evaluate_fsv_sig_false(ctx); case IPE_PROP_FSV_SIG_TRUE: return evaluate_fsv_sig_true(ctx); + case IPE_PROP_BPF_SIG_NONE: + return evaluate_bpf_sig(ctx, LSM_INT_VERDICT_NONE); + case IPE_PROP_BPF_SIG_OK: + return evaluate_bpf_sig(ctx, LSM_INT_VERDICT_OK); + case IPE_PROP_BPF_SIG_UNSIGNED: + return evaluate_bpf_sig(ctx, LSM_INT_VERDICT_UNSIGNED); + case IPE_PROP_BPF_SIG_PARTIALSIG: + return evaluate_bpf_sig(ctx, LSM_INT_VERDICT_PARTIALSIG); + case IPE_PROP_BPF_SIG_UNKNOWNKEY: + return evaluate_bpf_sig(ctx, LSM_INT_VERDICT_UNKNOWNKEY); + case IPE_PROP_BPF_SIG_UNEXPECTED: + return evaluate_bpf_sig(ctx, LSM_INT_VERDICT_UNEXPECTED); + case IPE_PROP_BPF_SIG_FAULT: + return evaluate_bpf_sig(ctx, LSM_INT_VERDICT_FAULT); + case IPE_PROP_BPF_SIG_BADSIG: + return evaluate_bpf_sig(ctx, LSM_INT_VERDICT_BADSIG); + case IPE_PROP_BPF_KEYRING_BUILTIN: + return evaluate_bpf_keyring(ctx, 0); + case IPE_PROP_BPF_KEYRING_SECONDARY: + return evaluate_bpf_keyring(ctx, (s32)(unsigned long)VERIFY_USE_SECONDARY_KEYRING); + case IPE_PROP_BPF_KEYRING_PLATFORM: + return evaluate_bpf_keyring(ctx, (s32)(unsigned long)VERIFY_USE_PLATFORM_KEYRING); + case IPE_PROP_BPF_KERNEL_FALSE: + return !evaluate_bpf_kernel(ctx); + case IPE_PROP_BPF_KERNEL_TRUE: + return evaluate_bpf_kernel(ctx); default: return false; } diff --git a/security/ipe/eval.h b/security/ipe/eval.h index fef65a36468cb..b061cb5ade27e 100644 --- a/security/ipe/eval.h +++ b/security/ipe/eval.h @@ -37,6 +37,12 @@ struct ipe_inode { }; #endif /* CONFIG_IPE_PROP_FS_VERITY_BUILTIN_SIG */ +#ifdef CONFIG_IPE_PROP_BPF_SIGNATURE +struct ipe_bpf_prog { + enum lsm_integrity_verdict verdict; +}; +#endif /* CONFIG_IPE_PROP_BPF_SIGNATURE */ + struct ipe_eval_ctx { enum ipe_op_type op; enum ipe_hook_type hook; @@ -52,6 +58,11 @@ struct ipe_eval_ctx { #ifdef CONFIG_IPE_PROP_FS_VERITY_BUILTIN_SIG const struct ipe_inode *ipe_inode; #endif /* CONFIG_IPE_PROP_FS_VERITY_BUILTIN_SIG */ +#ifdef CONFIG_IPE_PROP_BPF_SIGNATURE + enum lsm_integrity_verdict bpf_verdict; + s32 bpf_keyring_id; + bool bpf_kernel; +#endif /* CONFIG_IPE_PROP_BPF_SIGNATURE */ }; enum ipe_match { diff --git a/security/ipe/hooks.c b/security/ipe/hooks.c index 0ae54a880405a..9271e129a2cf2 100644 --- a/security/ipe/hooks.c +++ b/security/ipe/hooks.c @@ -340,3 +340,66 @@ int ipe_inode_setintegrity(const struct inode *inode, return -EINVAL; } #endif /* CONFIG_IPE_PROP_FS_VERITY_BUILTIN_SIG */ + +#ifdef CONFIG_IPE_PROP_BPF_SIGNATURE +/** + * ipe_bpf_prog_load_post_integrity() - Store integrity verdict in per-prog blob. + * @prog: Supplies the BPF program being loaded. + * @attr: Supplies the bpf syscall attributes. + * @token: Supplies the BPF token, if any. + * @kernel: Whether the call originated from the kernel. + * @lsmid: Supplies the LSM ID of the integrity provider. + * @verdict: Supplies the integrity verdict from the provider (e.g. Hornet). + * + * This hook stores the integrity verdict in IPE's per-prog security blob + * so that ipe_bpf_prog_load() can later read it for policy evaluation. + * + * Return: + * * %0 - Always succeeds (policy is evaluated in bpf_prog_load) + */ +int ipe_bpf_prog_load_post_integrity(struct bpf_prog *prog, + union bpf_attr *attr, + struct bpf_token *token, + bool kernel, + const struct lsm_id *lsmid, + enum lsm_integrity_verdict verdict) +{ + struct ipe_bpf_prog *blob = ipe_bpf_prog(prog); + + blob->verdict = verdict; + + return 0; +} + +/** + * ipe_bpf_prog_load() - IPE policy evaluation for BPF program load. + * @prog: Supplies the BPF program being loaded. + * @attr: Supplies the bpf syscall attributes. + * @token: Supplies the BPF token, if any. + * @kernel: Whether the call originated from the kernel. + * + * Reads the integrity verdict previously stored by post_integrity (if any) + * and evaluates IPE policy. If no integrity provider ran, the verdict + * defaults to LSM_INT_VERDICT_NONE. + * + * Return: + * * %0 - Success + * * %-EACCES - Did not pass IPE policy + */ +int ipe_bpf_prog_load(struct bpf_prog *prog, + union bpf_attr *attr, + struct bpf_token *token, + bool kernel) +{ + struct ipe_bpf_prog *blob = ipe_bpf_prog(prog); + struct ipe_eval_ctx ctx = IPE_EVAL_CTX_INIT; + + ctx.op = IPE_OP_BPF_PROG_LOAD; + ctx.hook = IPE_HOOK_BPF_PROG_LOAD; + ctx.bpf_verdict = blob->verdict; + ctx.bpf_keyring_id = attr->keyring_id; + ctx.bpf_kernel = kernel; + + return ipe_evaluate_event(&ctx); +} +#endif /* CONFIG_IPE_PROP_BPF_SIGNATURE */ diff --git a/security/ipe/hooks.h b/security/ipe/hooks.h index 07db373327402..8a6d1a459e00c 100644 --- a/security/ipe/hooks.h +++ b/security/ipe/hooks.h @@ -10,6 +10,7 @@ #include <linux/security.h> #include <linux/blk_types.h> #include <linux/fsverity.h> +#include <linux/bpf.h> enum ipe_hook_type { IPE_HOOK_BPRM_CHECK = 0, @@ -18,6 +19,7 @@ enum ipe_hook_type { IPE_HOOK_MPROTECT, IPE_HOOK_KERNEL_READ, IPE_HOOK_KERNEL_LOAD, + IPE_HOOK_BPF_PROG_LOAD, __IPE_HOOK_MAX }; @@ -52,4 +54,17 @@ int ipe_inode_setintegrity(const struct inode *inode, enum lsm_integrity_type ty const void *value, size_t size); #endif /* CONFIG_IPE_PROP_FS_VERITY_BUILTIN_SIG */ +#ifdef CONFIG_IPE_PROP_BPF_SIGNATURE +int ipe_bpf_prog_load_post_integrity(struct bpf_prog *prog, + union bpf_attr *attr, + struct bpf_token *token, + bool kernel, + const struct lsm_id *lsmid, + enum lsm_integrity_verdict verdict); +int ipe_bpf_prog_load(struct bpf_prog *prog, + union bpf_attr *attr, + struct bpf_token *token, + bool kernel); +#endif /* CONFIG_IPE_PROP_BPF_SIGNATURE */ + #endif /* _IPE_HOOKS_H */ diff --git a/security/ipe/ipe.c b/security/ipe/ipe.c index 495bb765de1b8..5af13903287fe 100644 --- a/security/ipe/ipe.c +++ b/security/ipe/ipe.c @@ -19,6 +19,9 @@ static struct lsm_blob_sizes ipe_blobs __ro_after_init = { #ifdef CONFIG_IPE_PROP_FS_VERITY_BUILTIN_SIG .lbs_inode = sizeof(struct ipe_inode), #endif /* CONFIG_IPE_PROP_FS_VERITY_BUILTIN_SIG */ +#ifdef CONFIG_IPE_PROP_BPF_SIGNATURE + .lbs_bpf_prog = sizeof(struct ipe_bpf_prog), +#endif /* CONFIG_IPE_PROP_BPF_SIGNATURE */ }; static const struct lsm_id ipe_lsmid = { @@ -45,6 +48,13 @@ struct ipe_inode *ipe_inode(const struct inode *inode) } #endif /* CONFIG_IPE_PROP_FS_VERITY_BUILTIN_SIG */ +#ifdef CONFIG_IPE_PROP_BPF_SIGNATURE +struct ipe_bpf_prog *ipe_bpf_prog(const struct bpf_prog *prog) +{ + return prog->aux->security + ipe_blobs.lbs_bpf_prog; +} +#endif /* CONFIG_IPE_PROP_BPF_SIGNATURE */ + static struct security_hook_list ipe_hooks[] __ro_after_init = { LSM_HOOK_INIT(bprm_check_security, ipe_bprm_check_security), LSM_HOOK_INIT(bprm_creds_for_exec, ipe_bprm_creds_for_exec), @@ -60,6 +70,10 @@ static struct security_hook_list ipe_hooks[] __ro_after_init = { #ifdef CONFIG_IPE_PROP_FS_VERITY_BUILTIN_SIG LSM_HOOK_INIT(inode_setintegrity, ipe_inode_setintegrity), #endif /* CONFIG_IPE_PROP_FS_VERITY_BUILTIN_SIG */ +#ifdef CONFIG_IPE_PROP_BPF_SIGNATURE + LSM_HOOK_INIT(bpf_prog_load_post_integrity, ipe_bpf_prog_load_post_integrity), + LSM_HOOK_INIT(bpf_prog_load, ipe_bpf_prog_load), +#endif /* CONFIG_IPE_PROP_BPF_SIGNATURE */ }; /** diff --git a/security/ipe/ipe.h b/security/ipe/ipe.h index 25cfdb8f0c20a..47de32b5bc938 100644 --- a/security/ipe/ipe.h +++ b/security/ipe/ipe.h @@ -22,6 +22,9 @@ struct ipe_bdev *ipe_bdev(struct block_device *b); #ifdef CONFIG_IPE_PROP_FS_VERITY_BUILTIN_SIG struct ipe_inode *ipe_inode(const struct inode *inode); #endif /* CONFIG_IPE_PROP_FS_VERITY_BUILTIN_SIG */ +#ifdef CONFIG_IPE_PROP_BPF_SIGNATURE +struct ipe_bpf_prog *ipe_bpf_prog(const struct bpf_prog *prog); +#endif /* CONFIG_IPE_PROP_BPF_SIGNATURE */ int ipe_init_securityfs(void); diff --git a/security/ipe/policy.h b/security/ipe/policy.h index 5bfbdbddeef86..748bea92beb19 100644 --- a/security/ipe/policy.h +++ b/security/ipe/policy.h @@ -17,6 +17,7 @@ enum ipe_op_type { IPE_OP_KEXEC_INITRAMFS, IPE_OP_POLICY, IPE_OP_X509, + IPE_OP_BPF_PROG_LOAD, __IPE_OP_MAX, }; @@ -39,6 +40,19 @@ enum ipe_prop_type { IPE_PROP_FSV_DIGEST, IPE_PROP_FSV_SIG_FALSE, IPE_PROP_FSV_SIG_TRUE, + IPE_PROP_BPF_SIG_NONE, + IPE_PROP_BPF_SIG_OK, + IPE_PROP_BPF_SIG_UNSIGNED, + IPE_PROP_BPF_SIG_PARTIALSIG, + IPE_PROP_BPF_SIG_UNKNOWNKEY, + IPE_PROP_BPF_SIG_UNEXPECTED, + IPE_PROP_BPF_SIG_FAULT, + IPE_PROP_BPF_SIG_BADSIG, + IPE_PROP_BPF_KEYRING_BUILTIN, + IPE_PROP_BPF_KEYRING_SECONDARY, + IPE_PROP_BPF_KEYRING_PLATFORM, + IPE_PROP_BPF_KERNEL_FALSE, + IPE_PROP_BPF_KERNEL_TRUE, __IPE_PROP_MAX }; diff --git a/security/ipe/policy_parser.c b/security/ipe/policy_parser.c index 6fa5bebf84714..71f63de56616b 100644 --- a/security/ipe/policy_parser.c +++ b/security/ipe/policy_parser.c @@ -237,6 +237,7 @@ static const match_table_t operation_tokens = { {IPE_OP_KEXEC_INITRAMFS, "op=KEXEC_INITRAMFS"}, {IPE_OP_POLICY, "op=POLICY"}, {IPE_OP_X509, "op=X509_CERT"}, + {IPE_OP_BPF_PROG_LOAD, "op=BPF_PROG_LOAD"}, {IPE_OP_INVALID, NULL} }; @@ -281,6 +282,19 @@ static const match_table_t property_tokens = { {IPE_PROP_FSV_DIGEST, "fsverity_digest=%s"}, {IPE_PROP_FSV_SIG_FALSE, "fsverity_signature=FALSE"}, {IPE_PROP_FSV_SIG_TRUE, "fsverity_signature=TRUE"}, + {IPE_PROP_BPF_SIG_NONE, "bpf_signature=NONE"}, + {IPE_PROP_BPF_SIG_OK, "bpf_signature=OK"}, + {IPE_PROP_BPF_SIG_UNSIGNED, "bpf_signature=UNSIGNED"}, + {IPE_PROP_BPF_SIG_PARTIALSIG, "bpf_signature=PARTIALSIG"}, + {IPE_PROP_BPF_SIG_UNKNOWNKEY, "bpf_signature=UNKNOWNKEY"}, + {IPE_PROP_BPF_SIG_UNEXPECTED, "bpf_signature=UNEXPECTED"}, + {IPE_PROP_BPF_SIG_FAULT, "bpf_signature=FAULT"}, + {IPE_PROP_BPF_SIG_BADSIG, "bpf_signature=BADSIG"}, + {IPE_PROP_BPF_KEYRING_BUILTIN, "bpf_keyring=BUILTIN"}, + {IPE_PROP_BPF_KEYRING_SECONDARY, "bpf_keyring=SECONDARY"}, + {IPE_PROP_BPF_KEYRING_PLATFORM, "bpf_keyring=PLATFORM"}, + {IPE_PROP_BPF_KERNEL_FALSE, "bpf_kernel=FALSE"}, + {IPE_PROP_BPF_KERNEL_TRUE, "bpf_kernel=TRUE"}, {IPE_PROP_INVALID, NULL} }; @@ -331,6 +345,19 @@ static int parse_property(char *t, struct ipe_rule *r) case IPE_PROP_DMV_SIG_TRUE: case IPE_PROP_FSV_SIG_FALSE: case IPE_PROP_FSV_SIG_TRUE: + case IPE_PROP_BPF_SIG_NONE: + case IPE_PROP_BPF_SIG_OK: + case IPE_PROP_BPF_SIG_UNSIGNED: + case IPE_PROP_BPF_SIG_PARTIALSIG: + case IPE_PROP_BPF_SIG_UNKNOWNKEY: + case IPE_PROP_BPF_SIG_UNEXPECTED: + case IPE_PROP_BPF_SIG_FAULT: + case IPE_PROP_BPF_SIG_BADSIG: + case IPE_PROP_BPF_KEYRING_BUILTIN: + case IPE_PROP_BPF_KEYRING_SECONDARY: + case IPE_PROP_BPF_KEYRING_PLATFORM: + case IPE_PROP_BPF_KERNEL_FALSE: + case IPE_PROP_BPF_KERNEL_TRUE: p->type = token; break; default: diff --git a/security/security.c b/security/security.c index 4e999f0236516..55ab49c84003b 100644 --- a/security/security.c +++ b/security/security.c @@ -2258,22 +2258,22 @@ int security_inode_setsecurity(struct inode *inode, const char *name, /** * security_inode_listsecurity() - List the xattr security label names * @inode: inode - * @buffer: buffer - * @buffer_size: size of buffer + * @buffer: pointer to buffer + * @remaining_size: pointer to remaining size of buffer * * Copy the extended attribute names for the security labels associated with - * @inode into @buffer. The maximum size of @buffer is specified by - * @buffer_size. @buffer may be NULL to request the size of the buffer - * required. + * @inode into *(@buffer). The remaining size of @buffer is specified by + * *(@remaining_size). *(@buffer) may be NULL to request the size of the + * buffer required. Updates *(@buffer) and *(@remaining_size). * - * Return: Returns number of bytes used/required on success. + * Return: Returns 0 on success, or -errno on failure. */ int security_inode_listsecurity(struct inode *inode, - char *buffer, size_t buffer_size) + char **buffer, ssize_t *remaining_size) { if (unlikely(IS_PRIVATE(inode))) return 0; - return call_int_hook(inode_listsecurity, inode, buffer, buffer_size); + return call_int_hook(inode_listsecurity, inode, buffer, remaining_size); } EXPORT_SYMBOL(security_inode_listsecurity); @@ -5356,6 +5356,50 @@ int security_bpf_map_create(struct bpf_map *map, union bpf_attr *attr, } /** + * 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 * @attr: BPF syscall attributes used to create BPF program @@ -5363,8 +5407,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. */ @@ -5377,9 +5437,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; } diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index 0f704380a8c81..1a713d96206f5 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -3680,16 +3680,12 @@ static int selinux_inode_setsecurity(struct inode *inode, const char *name, return 0; } -static int selinux_inode_listsecurity(struct inode *inode, char *buffer, size_t buffer_size) +static int selinux_inode_listsecurity(struct inode *inode, char **buffer, + ssize_t *remaining_size) { - const int len = sizeof(XATTR_NAME_SELINUX); - if (!selinux_initialized()) return 0; - - if (buffer && len <= buffer_size) - memcpy(buffer, XATTR_NAME_SELINUX, len); - return len; + return xattr_list_one(buffer, remaining_size, XATTR_NAME_SELINUX); } static void selinux_inode_getlsmprop(struct inode *inode, struct lsm_prop *prop) diff --git a/security/smack/smack_lsm.c b/security/smack/smack_lsm.c index 3f9ae05039a28..ff115068c5c06 100644 --- a/security/smack/smack_lsm.c +++ b/security/smack/smack_lsm.c @@ -1665,17 +1665,12 @@ static int smack_inode_getsecurity(struct mnt_idmap *idmap, * smack_inode_listsecurity - list the Smack attributes * @inode: the object * @buffer: where they go - * @buffer_size: size of buffer + * @remaining_size: size of buffer */ -static int smack_inode_listsecurity(struct inode *inode, char *buffer, - size_t buffer_size) +static int smack_inode_listsecurity(struct inode *inode, char **buffer, + ssize_t *remaining_size) { - int len = sizeof(XATTR_NAME_SMACK); - - if (buffer != NULL && len <= buffer_size) - memcpy(buffer, XATTR_NAME_SMACK, len); - - return len; + return xattr_list_one(buffer, remaining_size, XATTR_NAME_SMACK); } /** |
