diff options
| author | Mark Brown <broonie@kernel.org> | 2026-05-29 22:43:00 +0100 |
|---|---|---|
| committer | Mark Brown <broonie@kernel.org> | 2026-05-29 22:43:00 +0100 |
| commit | c6c23350fbf64731e47f4d1576ac2ba6c03b1ed5 (patch) | |
| tree | ec124a98ce774bf0bc31f9c73359122d65ed06e9 | |
| parent | e114941bfd16ae83f7075511371dad18aa62ad73 (diff) | |
| parent | c67f6e6d2c141f202211fa9fea844f577eb233a8 (diff) | |
| download | linux-next-history-c6c23350fbf64731e47f4d1576ac2ba6c03b1ed5.tar.gz | |
Merge branch 'next' of https://git.kernel.org/pub/scm/linux/kernel/git/pcmoore/lsm.git
50 files changed, 2145 insertions, 44 deletions
diff --git a/Documentation/admin-guide/LSM/Hornet.rst b/Documentation/admin-guide/LSM/Hornet.rst new file mode 100644 index 0000000000000..0ade4c17374c6 --- /dev/null +++ b/Documentation/admin-guide/LSM/Hornet.rst @@ -0,0 +1,323 @@ +.. SPDX-License-Identifier: GPL-2.0 + +====== +Hornet +====== + +Hornet is a Linux Security Module that provides extensible signature +verification for eBPF programs. This is selectable at build-time with +``CONFIG_SECURITY_HORNET``. + +Overview +======== + +Hornet addresses concerns from users who require strict audit trails and +verification guarantees for eBPF programs, especially in +security-sensitive environments. Many production systems need assurance +that only authorized, unmodified eBPF programs are loaded into the +kernel. Hornet provides this assurance through cryptographic signature +verification. + +When an eBPF program is loaded via the ``bpf()`` syscall, Hornet +verifies a PKCS#7 signature attached to the program instructions. The +signature is checked against whichever keyring was specified by the user +existing kernel cryptographic infrastructure. In addition to signing the +program bytecode, Hornet supports signing SHA-256 hashes of associated +BPF maps, enabling integrity verification of map contents at load time +and at runtime. + +After verification, Hornet classifies the program into one of the +following integrity states and passes the result to a downstream LSM hook +(``bpf_prog_load_post_integrity``), allowing other security modules to +make policy decisions based on the verification outcome: + +``LSM_INT_VERDICT_OK`` + The program signature and all map hashes verified successfully. + +``LSM_INT_VERDICT_UNSIGNED`` + No signature was provided with the program. + +``LSM_INT_VERDICT_PARTIALSIG`` + The program signature verified, but the signature did not contain + hornet map hash data. + +``LSM_INT_VERDICT_UNKNOWNKEY`` + The keyring requested by the user is invalid. + +``LSM_INT_VERDICT_FAULT`` + A system error occurred during verification. + +``LSM_INT_VERDICT_UNEXPECTED`` + An unexpected map hash value was encountered. + +``LSM_INT_VERDICT_BADSIG`` + The signature or a map hash failed verification. + +Hornet itself does not enforce a policy on whether unsigned or partially +signed programs should be rejected. It delegates that decision to +downstream LSMs via the ``bpf_prog_load_post_integrity`` hook, making it +a composable building block in a larger security architecture. + +Use Cases +========= + +- **Locked-down production environments**: Ensure only eBPF programs + signed by a trusted authority can be loaded, preventing unauthorized + or tampered programs from running in the kernel. + +- **Audit and compliance**: Provide cryptographic evidence that loaded + eBPF programs match their expected build artifacts, supporting + compliance requirements in regulated industries. + +- **Supply chain integrity**: Verify that eBPF programs and their + associated map data have not been modified since they were built and + signed, protecting against supply chain attacks. + +Threat Model +============ + +Hornet protects against the following threats: + +- **Unauthorized eBPF program loading**: Programs that have not been + signed by a trusted key will be reported as unsigned or badly signed. + +- **Tampering with program instructions**: Any modification to the eBPF + bytecode after signing will cause signature verification to fail. + +- **Tampering with map data**: When map hashes are included in the + signature, Hornet verifies that frozen BPF maps match their expected + SHA-256 hashes at load time. Maps are also re-verified before program + execution via ``BPF_PROG_RUN``. + +Hornet does **not** protect against: + +- Compromise of the signing key itself. +- Attacks that occur after a program has been loaded and verified. +- Programs loaded by the kernel itself (kernel-internal loads bypass + the ``BPF_PROG_RUN`` map check). + +Known Limitations +================= + +- Hornet requires programs to use :doc:`light skeletons + </bpf/libbpf/libbpf_naming_convention>` (lskels) for the signing + workflow, as the tooling operates on lskel-generated headers. + +- A maximum of 64 maps per program can be tracked for hash + verification. + +- Map hash verification requires the maps to be frozen before loading. + Maps that are not frozen at load time will cause verification to fail + when their hashes are included in the signature. + +- The only hashing algorithm available is SHA256 due to it be hardcoded + in the bpf subsystem. + +- Hornet guarantees that the signed program runs only with signed map + data. It does not guarantee positional binding of maps to specific + fd_array slots. + +- BPF_MAP_TYPE_PROG_ARRAY maps must be frozen for Hornet to verify + them. Unfrozen prog array maps are not covered by verification. + +Configuration +============= + +Build Configuration +------------------- + +Enable Hornet by setting the following kernel configuration option:: + + CONFIG_SECURITY_HORNET=y + +This option is found under :menuselection:`Security options --> Hornet +support` and depends on ``CONFIG_SECURITY``. + +When enabled, Hornet is included in the default LSM initialization order +and will appear in ``/sys/kernel/security/lsm``. + +Architecture +============ + +Signature Verification Flow +--------------------------- + +The following describes what happens when a userspace program calls +``bpf(BPF_PROG_LOAD, ...)`` with a signature attached: + +1. The ``bpf_prog_load_integrity`` LSM hook is invoked. + +2. Hornet reads the signature from the userspace buffer specified by + ``attr->signature`` (with length ``attr->signature_size``). + +3. The PKCS#7 signature is verified against the program instructions + using ``verify_pkcs7_message_sig()`` with the user specified keyring. + +4. The PKCS#7 message is parsed and its trust chain is validated via + ``validate_pkcs7_trust()``. + +5. Hornet extracts the authenticated attribute identified by + ``OID_hornet_data`` (OID ``2.25.316487325684022475439036912669789383960``) + from the PKCS#7 message. This attribute contains an ASN.1-encoded set + of map index/hash pairs. + +6. For each map hash entry, Hornet retrieves the corresponding BPF map + via its file descriptor, confirms it is frozen, computes its SHA-256 + hash, and compares it against the signed hash. + +7. The resulting integrity verdict is passed to the + ``bpf_prog_load_post_integrity`` hook so that downstream LSMs can + enforce policy. + +Runtime Map Verification +------------------------ + +When ``bpf(BPF_PROG_RUN, ...)`` is called from userspace, Hornet +re-verifies the hashes of all maps associated with the program. This +ensures that map contents have not been modified between program load +and execution. If any map hash no longer matches, the ``BPF_PROG_RUN`` +command is denied. + +Userspace Interface +------------------- + +Signatures are passed to the kernel through fields in ``union bpf_attr`` +when using the ``BPF_PROG_LOAD`` command: + +``signature`` + A pointer to a userspace buffer containing the PKCS#7 signature. + +``signature_size`` + The size of the signature buffer in bytes. + +ASN.1 Schema +------------ + +Map hashes are encoded as a signed attribute in the PKCS#7 message using +the following ASN.1 schema:: + + HornetData ::= SET OF Map + + Map ::= SEQUENCE { + index INTEGER, + sha OCTET STRING + } + +Each ``Map`` entry contains the index of the map in the program's +``fd_array`` and its expected SHA-256 hash. A zero-length ``sha`` field +indicates that the map at that index should be skipped during +verification. + +Tooling +======= + +Helper scripts and a signature generation tool are provided in +``scripts/hornet/`` to support the development of signed eBPF light +skeletons. + +gen_sig +------- + +``gen_sig`` is a C program (using OpenSSL) that creates a PKCS#7 +signature over eBPF program instructions and optionally includes +SHA-256 hashes of BPF maps as signed attributes. + +Usage:: + + gen_sig --data <instructions.bin> \ + --cert <signer.crt> \ + --key <signer.key> \ + [--pass <passphrase>] \ + --out <signature.p7b> \ + [--add <mapfile.bin>:<index> ...] + +``--data`` + Path to the binary file containing eBPF program instructions to sign. + +``--cert`` + Path to the signing certificate (PEM or DER format). + +``--key`` + Path to the private key (PEM or DER format). + +``--pass`` + Optional passphrase for the private key. + +``--out`` + Path to write the output PKCS#7 signature. + +``--add`` + Attach a map hash as a signed attribute. The argument is a path to a + binary map file followed by a colon and the map's index in the + ``fd_array``. This option may be specified multiple times. + +extract-skel.sh +--------------- + +Extracts a named field from an autogenerated eBPF lskel header file. +Used internally by other helper scripts. + +extract-insn.sh +--------------- + +Extracts the eBPF program instructions (``opts_insn``) from an lskel +header into a binary file suitable for signing with ``gen_sig``. + +extract-map.sh +-------------- + +Extracts the map data (``opts_data``) from an lskel header into a +binary file suitable for hashing with ``gen_sig``. + +write-sig.sh +------------ + +Replaces the signature data in an lskel header with a new signature +from a binary file. This is used to embed a freshly generated signature +back into the header after signing. + +Signing Workflow +================ + +A typical workflow for building and signing an eBPF light skeleton is: + +1. **Compile the eBPF program**:: + + clang -O2 -target bpf -c program.bpf.c -o program.bpf.o + +2. **Generate the light skeleton header** using ``bpftool``:: + + bpftool gen skeleton -S program.bpf.o > loader.h + +3. **Extract instructions and map data** from the generated header:: + + scripts/hornet/extract-insn.sh loader.h > insn.bin + scripts/hornet/extract-map.sh loader.h > map.bin + +4. **Generate the signature** with ``gen_sig``:: + + scripts/hornet/gen_sig \ + --key signing_key.pem \ + --cert signing_key.x509 \ + --data insn.bin \ + --add map.bin:0 \ + --out sig.bin + +5. **Embed the signature** back into the header:: + + scripts/hornet/write-sig.sh loader.h sig.bin > signed_loader.h + +6. **Build the loader program** using the signed header:: + + cc -o loader loader.c -lbpf + +The resulting loader program will pass the embedded signature to the +kernel when loading the eBPF program, enabling Hornet to verify it. + +Testing +======= + +Self-tests are provided in ``tools/testing/selftests/hornet/``. The test +suite builds a minimal eBPF program (``trivial.bpf.c``), signs it using +the workflow described above, and verifies that the signed program loads +successfully. diff --git a/Documentation/admin-guide/LSM/index.rst b/Documentation/admin-guide/LSM/index.rst index b44ef68f6e4da..57f6e9fbe5fd1 100644 --- a/Documentation/admin-guide/LSM/index.rst +++ b/Documentation/admin-guide/LSM/index.rst @@ -49,3 +49,4 @@ subdirectories. SafeSetID ipe landlock + Hornet diff --git a/Documentation/admin-guide/LSM/ipe.rst b/Documentation/admin-guide/LSM/ipe.rst index a756d81585317..d68ba9d98859e 100644 --- a/Documentation/admin-guide/LSM/ipe.rst +++ b/Documentation/admin-guide/LSM/ipe.rst @@ -559,7 +559,8 @@ policy. Two properties are built-into the policy parser: 'op' and 'action'. The other properties are used to restrict immutable security properties about the files being evaluated. Currently those properties are: '``boot_verified``', '``dmverity_signature``', '``dmverity_roothash``', -'``fsverity_signature``', '``fsverity_digest``'. A description of all +'``fsverity_signature``', '``fsverity_digest``', '``bpf_signature``', +'``bpf_keyring``', '``bpf_kernel``'. A description of all properties supported by IPE are listed below: op @@ -603,6 +604,14 @@ as the first token. IPE supports the following operations: Controls loading IMA certificates through the Kconfigs, ``CONFIG_IMA_X509_PATH`` and ``CONFIG_EVM_X509_PATH``. + ``BPF_PROG_LOAD``: + + Pertains to BPF programs being loaded via the ``bpf()`` syscall. + This operation is used in conjunction with the ``bpf_signature``, + ``bpf_keyring``, and ``bpf_kernel`` properties to control BPF + program loading based on integrity verification provided by the + Hornet LSM. + action ~~~~~~ @@ -713,6 +722,105 @@ fsverity_signature fsverity_signature=(TRUE|FALSE) +bpf_signature +~~~~~~~~~~~~~ + + This property can be utilized for authorization of BPF program loads based + on the integrity verdict provided by the Hornet LSM. When a BPF program is + loaded, Hornet performs cryptographic verification of the program's PKCS#7 + signature (if present) and passes an integrity verdict to IPE via the + ``security_bpf_prog_load_post_integrity`` hook. IPE can then allow or deny + the load based on the verdict. + + This property depends on ``SECURITY_HORNET`` and is controlled by the + ``IPE_PROP_BPF_SIGNATURE`` config option. + The format of this property is:: + + bpf_signature=(NONE|OK|UNSIGNED|PARTIALSIG|UNKNOWNKEY|UNEXPECTED|FAULT|BADSIG) + + The possible values correspond to the integrity verdicts from Hornet: + + ``NONE`` + + No integrity verdict was set (default/uninitialized). + + ``OK`` + + The BPF program's signature and all map hashes were successfully + verified. + + ``UNSIGNED`` + + No signature was provided with the BPF program. + + ``PARTIALSIG`` + + The program signature was verified, but no authenticated map hash + data was present. + + ``UNKNOWNKEY`` + + The keyring requested by the user is invalid. + + ``UNEXPECTED`` + + An unexpected map hash value was encountered during verification. + + ``FAULT`` + + A system error occurred during signature verification. + + ``BADSIG`` + + The signature or hash verification failed. + +bpf_keyring +~~~~~~~~~~~~ + + This property can be utilized for authorization of BPF program loads based + on the keyring specified in the ``bpf_attr`` during the ``BPF_PROG_LOAD`` + syscall. This allows policies to restrict which keyring must be used for + signature verification of BPF programs. + + This property shares the ``IPE_PROP_BPF_SIGNATURE`` config option with + ``bpf_signature``. + The format of this property is:: + + bpf_keyring=(BUILTIN|SECONDARY|PLATFORM) + + The possible values correspond to the system keyrings: + + ``BUILTIN`` + + The builtin trusted keyring (``.builtin_trusted_keys``), which + contains keys embedded at kernel compile time. + + ``SECONDARY`` + + The secondary trusted keyring (``.secondary_trusted_keys``), which + includes both builtin trusted keys and keys added at runtime. + + ``PLATFORM`` + + The platform keyring (``.platform``), which contains keys provided + by the platform firmware (e.g. UEFI db keys). + +bpf_kernel +~~~~~~~~~~ + + This property can be utilized for authorization of BPF program loads based + on whether the load originated from kernel space or user space. The BPF + light skeleton infrastructure performs a secondary kernel-originated program + load that will not carry a signature. This property allows policies to + permit such kernel-originated loads while still requiring signatures for + user-space loads. + + This property shares the ``IPE_PROP_BPF_SIGNATURE`` config option with + ``bpf_signature``. + The format of this property is:: + + bpf_kernel=(TRUE|FALSE) + Policy Examples --------------- @@ -788,6 +896,58 @@ Allow execution of a specific fs-verity file op=EXECUTE fsverity_digest=sha256:fd88f2b8824e197f850bf4c5109bea5cf0ee38104f710843bb72da796ba5af9e action=ALLOW +Allow only signed BPF programs +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +:: + + policy_name=Allow_Signed_BPF policy_version=0.0.0 + DEFAULT action=ALLOW + + DEFAULT op=BPF_PROG_LOAD action=DENY + op=BPF_PROG_LOAD bpf_kernel=TRUE action=ALLOW + op=BPF_PROG_LOAD bpf_signature=OK action=ALLOW + +This policy allows all other operations but restricts BPF program loading +to only programs that either originate from kernel space (e.g. light skeleton +reloads) or have a valid signature verified by the Hornet LSM. Unsigned or +improperly signed BPF programs from user space will be denied. + +Allow signed BPF programs from a specific keyring +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +:: + + policy_name=Allow_BPF_Builtin_Keyring policy_version=0.0.0 + DEFAULT action=ALLOW + + DEFAULT op=BPF_PROG_LOAD action=DENY + op=BPF_PROG_LOAD bpf_kernel=TRUE action=ALLOW + op=BPF_PROG_LOAD bpf_signature=OK bpf_keyring=BUILTIN action=ALLOW + +This policy further restricts BPF program loading to only accept programs +whose signatures were verified using the builtin trusted keyring. Programs +signed against the secondary or platform keyrings will be denied, providing +tighter control over which signing keys are acceptable. + +Allow signed BPF programs with relaxed partial signatures +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +:: + + policy_name=Allow_BPF_Partial policy_version=0.0.0 + DEFAULT action=ALLOW + + DEFAULT op=BPF_PROG_LOAD action=DENY + op=BPF_PROG_LOAD bpf_kernel=TRUE action=ALLOW + op=BPF_PROG_LOAD bpf_signature=OK action=ALLOW + op=BPF_PROG_LOAD bpf_signature=PARTIALSIG action=ALLOW + +This policy allows BPF programs that have been fully verified (``OK``) as +well as programs with a valid program signature but without authenticated +map hash data (``PARTIALSIG``). This can be useful during development or +for programs that do not use maps. + Additional Information ---------------------- diff --git a/Documentation/security/ipe.rst b/Documentation/security/ipe.rst index 5eb3e6265fbde..c51dcb16a377b 100644 --- a/Documentation/security/ipe.rst +++ b/Documentation/security/ipe.rst @@ -412,6 +412,73 @@ a standard securityfs policy tree:: The policy is stored in the ``->i_private`` data of the MyPolicy inode. +BPF/Hornet Integration +~~~~~~~~~~~~~~~~~~~~~~ + +IPE integrates with the Hornet LSM to enforce integrity policies on BPF +program loading. Hornet performs cryptographic verification of BPF program +signatures (PKCS#7 with authenticated attributes containing map hashes) +and produces an ``enum lsm_integrity_verdict``. IPE acts as the policy +enforcer: it stores Hornet's verdict in a per-program LSM blob and later +evaluates it against the active IPE policy when the BPF program load is +finalized. + +Enforcement is split across two LSM hooks so that signature verification +and policy evaluation are cleanly separated. This also lets IPE evaluate +policy uniformly even when no integrity provider ran. + +The hook flow is: + + 1. User space (or the kernel, via the BPF light skeleton) invokes + ``BPF_PROG_LOAD`` through the ``bpf()`` syscall. + 2. Hornet's ``bpf_prog_load_integrity`` hook runs first and calls + ``hornet_check_program()`` to verify the program's PKCS#7 signature + against ``attr->keyring_id`` and to check the signed map hashes + (decoded from the ``OID_hornet_data`` authenticated attribute) + against the live hashes of the frozen maps referenced from + ``attr->fd_array``. The function produces one of + ``LSM_INT_VERDICT_OK``, ``LSM_INT_VERDICT_UNSIGNED``, + ``LSM_INT_VERDICT_BADSIG``, ``LSM_INT_VERDICT_PARTIALSIG``, + ``LSM_INT_VERDICT_UNKNOWNKEY``, ``LSM_INT_VERDICT_UNEXPECTED``, or + ``LSM_INT_VERDICT_FAULT``. + 3. Hornet calls ``security_bpf_prog_load_post_integrity()`` with the + resulting verdict and its ``lsm_id``. IPE's + ``ipe_bpf_prog_load_post_integrity`` handler does **not** enforce + policy here; it only stashes the verdict in IPE's per-program blob + and returns ``0``. This keeps the integrity step decoupled from + policy evaluation and allows multiple providers to coexist without + short-circuiting each other. + 4. The core BPF load path then invokes the standard ``bpf_prog_load`` + LSM hook. IPE's ``ipe_bpf_prog_load`` reads the verdict back out of + the per-program blob, populates an ``ipe_eval_ctx``, and calls + ``ipe_evaluate_event()`` against the active policy's + ``BPF_PROG_LOAD`` rules. A deny verdict returns ``-EACCES`` and + aborts the load. + +If no integrity provider populated the blob (e.g. Hornet is not enabled, +or the load came from a path Hornet does not cover), the verdict defaults +to ``LSM_INT_VERDICT_NONE`` and IPE evaluates accordingly. Policy can +therefore express "deny anything Hornet did not vouch for". + +Three properties are available for BPF policy rules: + + - ``bpf_signature``: Matches against the integrity verdict (OK, UNSIGNED, + BADSIG, etc.) + - ``bpf_keyring``: Matches against the keyring specified in ``bpf_attr`` + (BUILTIN, SECONDARY, PLATFORM) + - ``bpf_kernel``: Matches whether the load originated from kernel space + (TRUE/FALSE). This is important because the BPF light skeleton + infrastructure performs a secondary kernel-originated program load that + does not carry a signature. + +All three properties are gated on ``CONFIG_IPE_PROP_BPF_SIGNATURE`` which +depends on ``CONFIG_SECURITY_HORNET``. + +The evaluation context (``struct ipe_eval_ctx``) carries three BPF-specific +fields: ``bpf_verdict`` (the integrity verdict enum), ``bpf_keyring_id`` +(the ``s32`` keyring ID from ``bpf_attr``), and ``bpf_kernel`` (bool +indicating kernel origin). + Tests ----- @@ -439,6 +506,7 @@ IPE has KUnit Tests for the policy parser. Recommended kunitconfig:: CONFIG_IPE_PROP_DM_VERITY_SIGNATURE=y CONFIG_IPE_PROP_FS_VERITY=y CONFIG_IPE_PROP_FS_VERITY_BUILTIN_SIG=y + CONFIG_IPE_PROP_BPF_SIGNATURE=y CONFIG_SECURITY_IPE_KUNIT_TEST=y In addition, IPE has a python based integration diff --git a/MAINTAINERS b/MAINTAINERS index e5157cf9d52fb..ed382ff688076 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -11845,6 +11845,15 @@ S: Maintained F: Documentation/devicetree/bindings/iio/pressure/honeywell,mprls0025pa.yaml F: drivers/iio/pressure/mprls0025pa* +HORNET SECURITY MODULE +M: Blaise Boscaccy <bboscaccy@linux.microsoft.com> +L: linux-security-module@vger.kernel.org +S: Supported +T: git https://github.com/blaiseboscaccy/hornet.git +F: Documentation/admin-guide/LSM/Hornet.rst +F: scripts/hornet/ +F: security/hornet/ + HP BIOSCFG DRIVER M: Jorge Lopez <jorge.lopez2@hp.com> L: platform-driver-x86@vger.kernel.org diff --git a/certs/system_keyring.c b/certs/system_keyring.c index e0761436ec7f4..e1268d0cffff0 100644 --- a/certs/system_keyring.c +++ b/certs/system_keyring.c @@ -380,6 +380,7 @@ error: pr_devel("<==%s() = %d\n", __func__, ret); return ret; } +EXPORT_SYMBOL_GPL(verify_pkcs7_message_sig); /** * verify_pkcs7_signature - Verify a PKCS#7-based signature on system data. diff --git a/crypto/asymmetric_keys/Makefile b/crypto/asymmetric_keys/Makefile index bc65d3b98dcbf..f99b7169ae7cd 100644 --- a/crypto/asymmetric_keys/Makefile +++ b/crypto/asymmetric_keys/Makefile @@ -53,12 +53,14 @@ clean-files += pkcs8.asn1.c pkcs8.asn1.h obj-$(CONFIG_PKCS7_MESSAGE_PARSER) += pkcs7_message.o pkcs7_message-y := \ pkcs7.asn1.o \ + pkcs7_aa.asn1.o \ pkcs7_parser.o \ pkcs7_trust.o \ pkcs7_verify.o -$(obj)/pkcs7_parser.o: $(obj)/pkcs7.asn1.h +$(obj)/pkcs7_parser.o: $(obj)/pkcs7.asn1.h $(obj)/pkcs7_aa.asn1.h $(obj)/pkcs7.asn1.o: $(obj)/pkcs7.asn1.c $(obj)/pkcs7.asn1.h +$(obj)/pkcs7_aa.asn1.o: $(obj)/pkcs7_aa.asn1.c $(obj)/pkcs7_aa.asn1.h # # PKCS#7 parser testing key diff --git a/crypto/asymmetric_keys/pkcs7_aa.asn1 b/crypto/asymmetric_keys/pkcs7_aa.asn1 new file mode 100644 index 0000000000000..7a8857bdf56e1 --- /dev/null +++ b/crypto/asymmetric_keys/pkcs7_aa.asn1 @@ -0,0 +1,18 @@ +-- SPDX-License-Identifier: BSD-3-Clause +-- +-- Copyright (C) 2009 IETF Trust and the persons identified as authors +-- of the code +-- +-- https://www.rfc-editor.org/rfc/rfc5652#section-3 + +AA ::= CHOICE { + aaSet [0] IMPLICIT AASet, + aaSequence [2] EXPLICIT SEQUENCE OF AuthenticatedAttribute +} + +AASet ::= SET OF AuthenticatedAttribute + +AuthenticatedAttribute ::= SEQUENCE { + type OBJECT IDENTIFIER ({ pkcs7_aa_note_OID }), + values SET OF ANY ({ pkcs7_aa_note_attr }) +} diff --git a/crypto/asymmetric_keys/pkcs7_key_type.c b/crypto/asymmetric_keys/pkcs7_key_type.c index b930d3bbf1af5..e0b1ce0202f6d 100644 --- a/crypto/asymmetric_keys/pkcs7_key_type.c +++ b/crypto/asymmetric_keys/pkcs7_key_type.c @@ -12,6 +12,7 @@ #include <linux/verification.h> #include <linux/key-type.h> #include <keys/user-type.h> +#include <crypto/pkcs7.h> MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("PKCS#7 testing key type"); @@ -51,16 +52,57 @@ static int pkcs7_view_content(void *ctx, const void *data, size_t len, static int pkcs7_preparse(struct key_preparsed_payload *prep) { enum key_being_used_for usage = pkcs7_usage; + int ret; + struct pkcs7_message *pkcs7; + const void *data; + size_t len; if (usage >= NR__KEY_BEING_USED_FOR) { pr_err("Invalid usage type %d\n", usage); return -EINVAL; } - return verify_pkcs7_signature(NULL, 0, + ret = verify_pkcs7_signature(NULL, 0, prep->data, prep->datalen, VERIFY_USE_SECONDARY_KEYRING, usage, pkcs7_view_content, prep); + if (ret) + return ret; + + pkcs7 = pkcs7_parse_message(prep->data, prep->datalen); + if (IS_ERR(pkcs7)) { + pr_err("pkcs7 parse error\n"); + return PTR_ERR(pkcs7); + } + + /* + * the parsed message has no trusted signer, so nothing should + * be returned here + */ + ret = pkcs7_get_authattr(pkcs7, OID_messageDigest, &data, &len); + if (ret == 0) { + pr_err("OID returned when no trust in signer\n"); + goto out; + } + /* add trust and check again */ + ret = verify_pkcs7_message_sig(NULL, 0, pkcs7, + VERIFY_USE_SECONDARY_KEYRING, usage, + NULL, NULL); + if (ret) { + pr_err("verify_pkcs7_message_sig failed!!\n"); + goto out; + } + /* now we should find the OID */ + ret = pkcs7_get_authattr(pkcs7, OID_messageDigest, &data, &len); + if (ret) { + pr_err("Failed to get message digest\n"); + goto out; + } + pr_info("Correctly Got message hash, size=%zu\n", len); + + out: + pkcs7_free_message(pkcs7); + return 0; } /* diff --git a/crypto/asymmetric_keys/pkcs7_parser.c b/crypto/asymmetric_keys/pkcs7_parser.c index 6e3ffdac83ace..d467866f7d930 100644 --- a/crypto/asymmetric_keys/pkcs7_parser.c +++ b/crypto/asymmetric_keys/pkcs7_parser.c @@ -15,6 +15,7 @@ #include <crypto/public_key.h> #include "pkcs7_parser.h" #include "pkcs7.asn1.h" +#include "pkcs7_aa.asn1.h" MODULE_DESCRIPTION("PKCS#7 parser"); MODULE_AUTHOR("Red Hat, Inc."); @@ -211,6 +212,86 @@ int pkcs7_get_content_data(const struct pkcs7_message *pkcs7, } EXPORT_SYMBOL_GPL(pkcs7_get_content_data); +struct pkcs7_aa_context { + bool found; + enum OID oid_to_find; + const void *data; + size_t len; +}; + +int pkcs7_aa_note_OID(void *context, size_t hdrlen, + unsigned char tag, + const void *value, size_t vlen) +{ + struct pkcs7_aa_context *ctx = context; + enum OID oid = look_up_OID(value, vlen); + + ctx->found = (oid == ctx->oid_to_find); + + return 0; +} + +int pkcs7_aa_note_attr(void *context, size_t hdrlen, + unsigned char tag, + const void *value, size_t vlen) +{ + struct pkcs7_aa_context *ctx = context; + + if (ctx->found) { + ctx->data = value; + ctx->len = vlen; + } + + return 0; +} + +/** + * pkcs7_get_authattr - get authenticated attribute by OID + * @pkcs7: The preparsed PKCS#7 message + * @oid: the enum value of the OID to find + * @_data: Place to return a pointer to the attribute value + * @_len: length of the attribute value + * + * Searches the authenticated attributes until one is found with a + * matching OID. Note that because the attributes are per signer + * there could be multiple signers with different values, but this + * routine will simply return the first one in parse order. + * + * Returns -ENODATA if the attribute can't be found + */ +int pkcs7_get_authattr(const struct pkcs7_message *pkcs7, + enum OID oid, + const void **_data, size_t *_len) +{ + struct pkcs7_signed_info *sinfo = pkcs7->signed_infos; + struct pkcs7_aa_context ctx; + + ctx.data = NULL; + ctx.oid_to_find = oid; + + for (; sinfo; sinfo = sinfo->next) { + int ret; + + /* only extract OIDs from validated signers */ + if (!sinfo->verified) + continue; + + ret = asn1_ber_decoder(&pkcs7_aa_decoder, &ctx, + sinfo->authattrs, sinfo->authattrs_len); + if (ret < 0 || ctx.data != NULL) + break; + } + + if (!ctx.data) + return -ENODATA; + + *_data = ctx.data; + *_len = ctx.len; + + return 0; +} +EXPORT_SYMBOL_GPL(pkcs7_get_authattr); + /* * Note an OID when we find one for later processing when we know how * to interpret it. diff --git a/crypto/asymmetric_keys/pkcs7_parser.h b/crypto/asymmetric_keys/pkcs7_parser.h index 6ef9f335bb17f..203062a33def6 100644 --- a/crypto/asymmetric_keys/pkcs7_parser.h +++ b/crypto/asymmetric_keys/pkcs7_parser.h @@ -20,6 +20,7 @@ struct pkcs7_signed_info { unsigned index; bool unsupported_crypto; /* T if not usable due to missing crypto */ bool blacklisted; + bool verified; /* T if this signer has validated trust */ /* Message digest - the digest of the Content Data (or NULL) */ const void *msgdigest; diff --git a/crypto/asymmetric_keys/pkcs7_trust.c b/crypto/asymmetric_keys/pkcs7_trust.c index 9a87c34ed1733..78ebfb6373b61 100644 --- a/crypto/asymmetric_keys/pkcs7_trust.c +++ b/crypto/asymmetric_keys/pkcs7_trust.c @@ -127,6 +127,7 @@ verified: for (p = sinfo->signer; p != x509; p = p->signer) p->verified = true; } + sinfo->verified = true; kleave(" = 0"); return 0; } diff --git a/fs/nfs/nfs4proc.c b/fs/nfs/nfs4proc.c index 0715a6745d1fa..c48281db38689 100644 --- a/fs/nfs/nfs4proc.c +++ b/fs/nfs/nfs4proc.c @@ -10564,13 +10564,10 @@ static ssize_t nfs4_listxattr(struct dentry *dentry, char *list, size_t size) left -= error; } - error2 = security_inode_listsecurity(d_inode(dentry), list, left); + error2 = security_inode_listsecurity(d_inode(dentry), &list, &left); if (error2 < 0) return error2; - if (list) { - list += error2; - left -= error2; - } + error2 = size - error - left; error3 = nfs4_listxattr_nfs4_user(d_inode(dentry), list, left); if (error3 < 0) diff --git a/fs/xattr.c b/fs/xattr.c index efdcf2a485857..46f5ef9f27ad3 100644 --- a/fs/xattr.c +++ b/fs/xattr.c @@ -510,9 +510,12 @@ vfs_listxattr(struct dentry *dentry, char *list, size_t size) if (inode->i_op->listxattr) { error = inode->i_op->listxattr(dentry, list, size); } else { - error = security_inode_listsecurity(inode, list, size); - if (size && error > size) - error = -ERANGE; + ssize_t remaining = size; + + error = security_inode_listsecurity(inode, &list, &remaining); + if (error) + return error; + error = size - remaining; } return error; } @@ -1540,7 +1543,7 @@ ssize_t simple_xattr_list(struct inode *inode, struct simple_xattrs *xattrs, if (err) return err; - err = security_inode_listsecurity(inode, buffer, remaining_size); + err = security_inode_listsecurity(inode, &buffer, &remaining_size); if (err < 0) return err; diff --git a/include/crypto/pkcs7.h b/include/crypto/pkcs7.h index 38ec7f5f90411..bd83202cd805c 100644 --- a/include/crypto/pkcs7.h +++ b/include/crypto/pkcs7.h @@ -25,6 +25,10 @@ extern void pkcs7_free_message(struct pkcs7_message *pkcs7); extern int pkcs7_get_content_data(const struct pkcs7_message *pkcs7, const void **_data, size_t *_datalen, size_t *_headerlen); +extern int pkcs7_get_authattr(const struct pkcs7_message *pkcs7, + enum OID oid, + const void **_data, size_t *_len); + /* * pkcs7_trust.c diff --git a/include/linux/lsm_hook_defs.h b/include/linux/lsm_hook_defs.h index 2b8dfb35caed3..c0e91727f87ef 100644 --- a/include/linux/lsm_hook_defs.h +++ b/include/linux/lsm_hook_defs.h @@ -176,8 +176,8 @@ LSM_HOOK(int, -EOPNOTSUPP, inode_getsecurity, struct mnt_idmap *idmap, struct inode *inode, const char *name, void **buffer, bool alloc) LSM_HOOK(int, -EOPNOTSUPP, inode_setsecurity, struct inode *inode, const char *name, const void *value, size_t size, int flags) -LSM_HOOK(int, 0, inode_listsecurity, struct inode *inode, char *buffer, - size_t buffer_size) +LSM_HOOK(int, 0, inode_listsecurity, struct inode *inode, char **buffer, + ssize_t *remaining_size) LSM_HOOK(void, LSM_RET_VOID, inode_getlsmprop, struct inode *inode, struct lsm_prop *prop) LSM_HOOK(int, 0, inode_copy_up, struct dentry *src, struct cred **new) @@ -444,6 +444,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/oid_registry.h b/include/linux/oid_registry.h index ebce402854de4..bf852715aaea4 100644 --- a/include/linux/oid_registry.h +++ b/include/linux/oid_registry.h @@ -150,6 +150,9 @@ enum OID { OID_id_ml_dsa_65, /* 2.16.840.1.101.3.4.3.18 */ OID_id_ml_dsa_87, /* 2.16.840.1.101.3.4.3.19 */ + /* Hornet LSM */ + OID_hornet_data, /* 2.25.316487325684022475439036912669789383960 */ + OID__NR }; diff --git a/include/linux/security.h b/include/linux/security.h index 41d7367cf4036..598cd2eb1dcd5 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,17 @@ 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_UNKNOWNKEY, + LSM_INT_VERDICT_UNEXPECTED, + LSM_INT_VERDICT_FAULT, + 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 @@ -459,7 +471,7 @@ int security_inode_getsecurity(struct mnt_idmap *idmap, struct inode *inode, const char *name, void **buffer, bool alloc); int security_inode_setsecurity(struct inode *inode, const char *name, const void *value, size_t size, int flags); -int security_inode_listsecurity(struct inode *inode, char *buffer, size_t buffer_size); +int security_inode_listsecurity(struct inode *inode, char **buffer, ssize_t *remaining_size); void security_inode_getlsmprop(struct inode *inode, struct lsm_prop *prop); int security_inode_copy_up(struct dentry *src, struct cred **new); int security_inode_copy_up_xattr(struct dentry *src, const char *name); @@ -1097,7 +1109,8 @@ static inline int security_inode_setsecurity(struct inode *inode, const char *na return -EOPNOTSUPP; } -static inline int security_inode_listsecurity(struct inode *inode, char *buffer, size_t buffer_size) +static inline int security_inode_listsecurity(struct inode *inode, + char **buffer, ssize_t *remaining_size) { return 0; } @@ -2303,6 +2316,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); @@ -2337,6 +2356,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/include/uapi/linux/lsm.h b/include/uapi/linux/lsm.h index 938593dfd5daf..2ff9bcdd551e2 100644 --- a/include/uapi/linux/lsm.h +++ b/include/uapi/linux/lsm.h @@ -65,6 +65,7 @@ struct lsm_ctx { #define LSM_ID_IMA 111 #define LSM_ID_EVM 112 #define LSM_ID_IPE 113 +#define LSM_ID_HORNET 114 /* * LSM_ATTR_XXX definitions identify different LSM attributes diff --git a/scripts/Makefile b/scripts/Makefile index 3434a82a119f0..b028214fe767b 100644 --- a/scripts/Makefile +++ b/scripts/Makefile @@ -64,6 +64,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/.gitignore b/scripts/hornet/.gitignore new file mode 100644 index 0000000000000..ce40d0acdb1bf --- /dev/null +++ b/scripts/hornet/.gitignore @@ -0,0 +1 @@ +gen_sig diff --git a/scripts/hornet/Makefile b/scripts/hornet/Makefile new file mode 100644 index 0000000000000..3ee41e5e9a9ff --- /dev/null +++ b/scripts/hornet/Makefile @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: GPL-2.0 +hostprogs-always-y := gen_sig + +HOSTCFLAGS_gen_sig.o = $(shell $(HOSTPKG_CONFIG) --cflags libcrypto 2> /dev/null) +HOSTLDLIBS_gen_sig = $(shell $(HOSTPKG_CONFIG) --libs libcrypto 2> /dev/null || echo -lcrypto) diff --git a/scripts/hornet/extract-insn.sh b/scripts/hornet/extract-insn.sh new file mode 100755 index 0000000000000..52338f057ff6b --- /dev/null +++ b/scripts/hornet/extract-insn.sh @@ -0,0 +1,27 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# Copyright (c) 2025 Microsoft Corporation +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of version 2 of the GNU General Public +# License as published by the Free Software Foundation. + +function usage() { + echo "Sample script for extracting instructions" + echo "autogenerated eBPF lskel headers" + echo "" + echo "USAGE: header_file" + exit +} + +ARGC=$# + +EXPECTED_ARGS=1 + +if [ $ARGC -ne $EXPECTED_ARGS ] ; then + usage +else + printf $(gcc -E $1 | grep "opts_insn" | \ + awk -F"=" '{print $2}' | sed 's/;\+$//' | sed 's/\"//g') +fi diff --git a/scripts/hornet/extract-map.sh b/scripts/hornet/extract-map.sh new file mode 100755 index 0000000000000..c309f505c6238 --- /dev/null +++ b/scripts/hornet/extract-map.sh @@ -0,0 +1,27 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# Copyright (c) 2025 Microsoft Corporation +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of version 2 of the GNU General Public +# License as published by the Free Software Foundation. + +function usage() { + echo "Sample script for extracting instructions" + echo "autogenerated eBPF lskel headers" + echo "" + echo "USAGE: header_file" + exit +} + +ARGC=$# + +EXPECTED_ARGS=1 + +if [ $ARGC -ne $EXPECTED_ARGS ] ; then + usage +else + printf $(gcc -E $1 | grep "opts_data" | \ + awk -F"=" '{print $2}' | sed 's/;\+$//' | sed 's/\"//g') +fi diff --git a/scripts/hornet/extract-skel.sh b/scripts/hornet/extract-skel.sh new file mode 100755 index 0000000000000..6550a86b89917 --- /dev/null +++ b/scripts/hornet/extract-skel.sh @@ -0,0 +1,27 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# Copyright (c) 2025 Microsoft Corporation +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of version 2 of the GNU General Public +# License as published by the Free Software Foundation. + +function usage() { + echo "Sample script for extracting instructions and map data out of" + echo "autogenerated eBPF lskel headers" + echo "" + echo "USAGE: header_file field" + exit +} + +ARGC=$# + +EXPECTED_ARGS=2 + +if [ $ARGC -ne $EXPECTED_ARGS ] ; then + usage +else + printf $(gcc -E $1 | grep "static const char opts_$2" | \ + awk -F"=" '{print $2}' | sed 's/;\+$//' | sed 's/\"//g') +fi diff --git a/scripts/hornet/gen_sig.c b/scripts/hornet/gen_sig.c new file mode 100644 index 0000000000000..8dd9ed66346a2 --- /dev/null +++ b/scripts/hornet/gen_sig.c @@ -0,0 +1,402 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * + * Generate a signature for an eBPF program along with appending + * map hashes as signed attributes + * + * Copyright © 2025 Microsoft Corporation. + * + * Authors: Blaise Boscaccy <bboscaccy@linux.microsoft.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 + * of the licence, or (at your option) any later version. + */ + +#define _GNU_SOURCE +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <errno.h> +#include <stdbool.h> +#include <stdint.h> +#include <err.h> +#include <getopt.h> + +#include <openssl/cms.h> +#include <openssl/err.h> +#include <openssl/evp.h> +#include <openssl/pkcs7.h> +#include <openssl/x509.h> +#include <openssl/pem.h> +#include <openssl/objects.h> +#include <openssl/asn1.h> +#include <openssl/asn1t.h> +#include <openssl/opensslv.h> +#include <openssl/bio.h> +#include <openssl/stack.h> + +#if OPENSSL_VERSION_MAJOR >= 3 +# define USE_PKCS11_PROVIDER +# include <openssl/provider.h> +# include <openssl/store.h> +#else +# if !defined(OPENSSL_NO_ENGINE) && !defined(OPENSSL_NO_DEPRECATED_3_0) +# define USE_PKCS11_ENGINE +# include <openssl/engine.h> +# endif +#endif +#include "../ssl-common.h" + +#define SHA256_LEN 32 +#define BUF_SIZE (1 << 15) // 32 KiB +#define MAX_HASHES 64 + +struct hash_spec { + char *file; + int index; +}; + +typedef struct { + ASN1_INTEGER *index; + ASN1_OCTET_STRING *hash; + +} HORNET_MAP; + +DECLARE_ASN1_FUNCTIONS(HORNET_MAP) +ASN1_SEQUENCE(HORNET_MAP) = { + ASN1_SIMPLE(HORNET_MAP, index, ASN1_INTEGER), + ASN1_SIMPLE(HORNET_MAP, hash, ASN1_OCTET_STRING) +} ASN1_SEQUENCE_END(HORNET_MAP); + +IMPLEMENT_ASN1_FUNCTIONS(HORNET_MAP) + +DEFINE_STACK_OF(HORNET_MAP) + +typedef struct { + STACK_OF(HORNET_MAP) * maps; +} MAP_SET; + +DECLARE_ASN1_FUNCTIONS(MAP_SET) +ASN1_SEQUENCE(MAP_SET) = { + ASN1_SET_OF(MAP_SET, maps, HORNET_MAP) +} ASN1_SEQUENCE_END(MAP_SET); + +IMPLEMENT_ASN1_FUNCTIONS(MAP_SET) + +#define DIE(...) do { fprintf(stderr, __VA_ARGS__); fputc('\n', stderr); \ + exit(EXIT_FAILURE); } while (0) + +static BIO *bio_open_wr(const char *path) +{ + BIO *b = BIO_new_file(path, "wb"); + + if (!b) { + perror(path); + ERR_print_errors_fp(stderr); + exit(EXIT_FAILURE); + } + return b; +} + +static void usage(const char *prog) +{ + fprintf(stderr, + "Usage:\n" + " %s --data content.bin --cert signer.crt --key signer.key [-pass pass]\n" + " --out newsig.p7b \n" + " --add FILE:index [--add FILE:index ...]\n", + prog); +} + +static const char *key_pass; + +static int pem_pw_cb(char *buf, int len, int w, void *v) +{ + int pwlen; + + if (!key_pass) + return -1; + + pwlen = strlen(key_pass); + if (pwlen >= len) + return -1; + + strcpy(buf, key_pass); + + key_pass = NULL; + + return pwlen; +} + +static EVP_PKEY *read_private_key(const char *private_key_name) +{ + EVP_PKEY *private_key; + BIO *b; + + b = BIO_new_file(private_key_name, "rb"); + ERR(!b, "%s", private_key_name); + private_key = PEM_read_bio_PrivateKey(b, NULL, pem_pw_cb, + NULL); + ERR(!private_key, "%s", private_key_name); + BIO_free(b); + + return private_key; +} + +static X509 *read_x509(const char *x509_name) +{ + unsigned char buf[2]; + X509 *x509; + BIO *b; + int n; + + b = BIO_new_file(x509_name, "rb"); + ERR(!b, "%s", x509_name); + + /* Look at the first two bytes of the file to determine the encoding */ + n = BIO_read(b, buf, 2); + if (n != 2) { + if (BIO_should_retry(b)) { + fprintf(stderr, "%s: Read wanted retry\n", x509_name); + exit(1); + } + if (n >= 0) { + fprintf(stderr, "%s: Short read\n", x509_name); + exit(1); + } + ERR(1, "%s", x509_name); + } + + ERR(BIO_reset(b) != 0, "%s", x509_name); + + if (buf[0] == 0x30 && buf[1] >= 0x81 && buf[1] <= 0x84) + /* Assume raw DER encoded X.509 */ + x509 = d2i_X509_bio(b, NULL); + else + /* Assume PEM encoded X.509 */ + x509 = PEM_read_bio_X509(b, NULL, NULL, NULL); + + BIO_free(b); + ERR(!x509, "%s", x509_name); + + return x509; +} + +static int sha256(const char *path, unsigned char out[SHA256_LEN], unsigned int *out_len) +{ + FILE *f; + int rc; + EVP_MD_CTX *ctx; + unsigned char buf[BUF_SIZE]; + size_t n; + unsigned int mdlen = 0; + + if (!path || !out) + return -1; + + f = fopen(path, "rb"); + if (!f) { + perror("fopen"); + return -2; + } + + ERR_load_crypto_strings(); + + rc = -3; + ctx = EVP_MD_CTX_new(); + if (!ctx) { + rc = -4; + goto done; + } + +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + if (EVP_DigestInit_ex2(ctx, EVP_sha256(), NULL) != 1) { + rc = -5; + goto done; + } +#else + if (EVP_DigestInit_ex(ctx, EVP_sha256(), NULL) != 1) { + rc = -5; + goto done; + } +#endif + while ((n = fread(buf, 1, sizeof(buf), f)) > 0) { + if (EVP_DigestUpdate(ctx, buf, n) != 1) { + rc = -6; + goto done; + } + } + if (ferror(f)) { + rc = -7; + goto done; + } + + if (EVP_DigestFinal_ex(ctx, out, &mdlen) != 1) { + rc = -8; + goto done; + } + if (mdlen != SHA256_LEN) { + rc = -9; + goto done; + } + + if (out_len) + *out_len = mdlen; + rc = 0; + +done: + EVP_MD_CTX_free(ctx); + fclose(f); + ERR_free_strings(); + return rc; +} + +static void add_hash(MAP_SET *set, unsigned char *buffer, int buffer_len, int index) +{ + HORNET_MAP *map = NULL; + + map = HORNET_MAP_new(); + ASN1_INTEGER_set(map->index, index); + ASN1_OCTET_STRING_set(map->hash, buffer, buffer_len); + sk_HORNET_MAP_push(set->maps, map); +} + +int main(int argc, char **argv) +{ + const char *cert_path = NULL; + const char *key_path = NULL; + const char *data_path = NULL; + const char *out_path = NULL; + + X509 *signer; + EVP_PKEY *pkey; + BIO *data_in; + CMS_ContentInfo *cms_out; + struct hash_spec hashes[MAX_HASHES]; + int hash_count = 0; + int flags; + CMS_SignerInfo *si; + MAP_SET *set; + unsigned char hash_buffer[SHA256_LEN]; + unsigned int hash_len; + ASN1_OBJECT *oid; + unsigned char *der = NULL; + int der_len; + int err; + BIO *b_out; + int i; + int opt; + + const char *short_opts = "C:K:P:O:A:Sh"; + + static const struct option long_opts[] = { + {"cert", required_argument, 0, 'C'}, + {"key", required_argument, 0, 'K'}, + {"pass", required_argument, 0, 'P'}, + {"out", required_argument, 0, 'O'}, + {"data", required_argument, 0, 'D'}, + {"add", required_argument, 0, 'A'}, + {"help", no_argument, 0, 'h'}, + {0, 0, 0, 0} + }; + + while ((opt = getopt_long_only(argc, argv, short_opts, long_opts, NULL)) != -1) { + switch (opt) { + case 'C': + cert_path = optarg; + break; + case 'K': + key_path = optarg; + break; + case 'P': + key_pass = optarg; + break; + case 'O': + out_path = optarg; + break; + case 'D': + data_path = optarg; + break; + case 'A': + if (strchr(optarg, ':')) { + hashes[hash_count].file = strsep(&optarg, ":"); + hashes[hash_count].index = atoi(optarg); + if (++hash_count >= MAX_HASHES) { + usage(argv[0]); + return EXIT_FAILURE; + } + } else { + usage(argv[0]); + return EXIT_FAILURE; + } + break; + default: + usage(argv[0]); + return EXIT_FAILURE; + } + } + + if (!cert_path || !key_path || !out_path || !data_path) { + usage(argv[0]); + return EXIT_FAILURE; + } + + OpenSSL_add_all_algorithms(); + ERR_load_crypto_strings(); + + signer = read_x509(cert_path); + ERR(!signer, "Load cert failed"); + + pkey = read_private_key(key_path); + ERR(!pkey, "Load key failed"); + + data_in = BIO_new_file(data_path, "rb"); + ERR(!data_in, "Load data failed"); + + cms_out = CMS_sign(NULL, NULL, NULL, NULL, + CMS_NOCERTS | CMS_PARTIAL | CMS_BINARY | CMS_DETACHED); + ERR(!cms_out, "create cms failed"); + + flags = CMS_NOCERTS | CMS_PARTIAL | CMS_BINARY | CMS_NOSMIMECAP | CMS_DETACHED; + + si = CMS_add1_signer(cms_out, signer, pkey, EVP_sha256(), flags); + ERR(!si, "add signer failed"); + + set = MAP_SET_new(); + set->maps = sk_HORNET_MAP_new_null(); + + for (i = 0; i < hash_count; i++) { + if (sha256(hashes[i].file, hash_buffer, &hash_len) != 0) { + DIE("failed to hash input"); + } + add_hash(set, hash_buffer, hash_len, hashes[i].index); + } + + oid = OBJ_txt2obj("2.25.316487325684022475439036912669789383960", 1); + if (!oid) { + ERR_print_errors_fp(stderr); + DIE("create oid failed"); + } + + der_len = ASN1_item_i2d((ASN1_VALUE *)set, &der, ASN1_ITEM_rptr(MAP_SET)); + CMS_signed_add1_attr_by_OBJ(si, oid, V_ASN1_SEQUENCE, der, der_len); + + err = CMS_final(cms_out, data_in, NULL, CMS_NOCERTS | CMS_BINARY); + ERR(!err, "cms final failed"); + + OPENSSL_free(der); + MAP_SET_free(set); + + b_out = bio_open_wr(out_path); + ERR(!b_out, "opening output path failed"); + + i2d_CMS_bio_stream(b_out, cms_out, NULL, 0); + + BIO_free(data_in); + BIO_free(b_out); + EVP_cleanup(); + ERR_free_strings(); + return 0; +} diff --git a/scripts/hornet/write-sig.sh b/scripts/hornet/write-sig.sh new file mode 100755 index 0000000000000..7eaabe3bab9aa --- /dev/null +++ b/scripts/hornet/write-sig.sh @@ -0,0 +1,27 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# Copyright (c) 2025 Microsoft Corporation +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of version 2 of the GNU General Public +# License as published by the Free Software Foundation. + +function usage() { + echo "Sample for rewriting an autogenerated eBPF lskel headers" + echo "with a new signature" + echo "" + echo "USAGE: header_file sig" + exit +} + +ARGC=$# + +EXPECTED_ARGS=2 + +if [ $ARGC -ne $EXPECTED_ARGS ] ; then + usage +else + SIG=$(xxd -p $2 | tr -d '\n' | sed 's/\(..\)/\\\\x\1/g') + sed '/const char opts_sig/,/;/c\\tstatic const char opts_sig[] __attribute__((__aligned__(8))) = "\\\n'"$(printf '%s\n' "$SIG")"'\";' $1 +fi 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); } /** diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile index 6e59b8f63e416..4b0a74a04fbef 100644 --- a/tools/testing/selftests/Makefile +++ b/tools/testing/selftests/Makefile @@ -47,6 +47,7 @@ TARGETS += ftrace TARGETS += futex TARGETS += gpio TARGETS += hid +TARGETS += hornet TARGETS += intel_pstate TARGETS += iommu TARGETS += ipc diff --git a/tools/testing/selftests/hornet/Makefile b/tools/testing/selftests/hornet/Makefile new file mode 100644 index 0000000000000..432bce59f54e7 --- /dev/null +++ b/tools/testing/selftests/hornet/Makefile @@ -0,0 +1,63 @@ +# SPDX-License-Identifier: GPL-2.0 +include ../../../build/Build.include +include ../../../scripts/Makefile.arch +include ../../../scripts/Makefile.include + +CLANG ?= clang +CFLAGS := -g -O2 -Wall +BPFTOOL ?= $(TOOLSDIR)/bpf/bpftool/bpftool +SCRIPTSDIR := $(abspath ../../../../scripts/hornet) +TOOLSDIR := $(abspath ../../..) +LIBDIR := $(TOOLSDIR)/lib +BPFDIR := $(LIBDIR)/bpf +TOOLSINCDIR := $(TOOLSDIR)/include +APIDIR := $(TOOLSINCDIR)/uapi +CERTDIR := $(abspath ../../../../certs) +PKG_CONFIG ?= $(CROSS_COMPILE)pkg-config + +TEST_GEN_PROGS := loader +TEST_GEN_FILES := vmlinux.h loader.h trivial.bpf.o map.bin sig.bin insn.bin signed_loader.h +$(TEST_GEN_PROGS): LDLIBS += -lbpf +$(TEST_GEN_PROGS): $(TEST_GEN_FILES) + +include ../lib.mk + +BPF_CFLAGS := -target bpf \ + -D__TARGET_ARCH_$(ARCH) \ + -I/usr/include/$(shell uname -m)-linux-gnu \ + $(KHDR_INCLUDES) + +vmlinux.h: + $(BPFTOOL) btf dump file /sys/kernel/btf/vmlinux format c > vmlinux.h + +trivial.bpf.o: trivial.bpf.c vmlinux.h + $(CLANG) $(CFLAGS) $(BPF_CFLAGS) -c $< -o $@ + +loader.h: trivial.bpf.o + $(BPFTOOL) gen skeleton -S -k $(CERTDIR)/signing_key.pem -i $(CERTDIR)/signing_key.x509 \ + -L $< name trivial > $@ + +insn.bin: loader.h + $(SCRIPTSDIR)/extract-insn.sh $< > $@ + +map.bin: loader.h + $(SCRIPTSDIR)/extract-map.sh $< > $@ + +$(OUTPUT)/gen_sig: ../../../../scripts/hornet/gen_sig.c + $(call msg,GEN_SIG,,$@) + $(Q)$(CC) $(shell $(PKG_CONFIG) --cflags libcrypto 2> /dev/null) \ + $< -o $@ \ + $(shell $(PKG_CONFIG) --libs libcrypto 2> /dev/null || echo -lcrypto) + +sig.bin: insn.bin map.bin $(OUTPUT)/gen_sig + $(OUTPUT)/gen_sig --key $(CERTDIR)/signing_key.pem --cert $(CERTDIR)/signing_key.x509 \ + --data insn.bin --add map.bin:0 --out sig.bin + +signed_loader.h: sig.bin + $(SCRIPTSDIR)/write-sig.sh loader.h sig.bin > $@ + +loader: loader.c signed_loader.h + $(CC) $(CFLAGS) -I$(LIBDIR) -I$(APIDIR) $< -o $@ -lbpf + + +EXTRA_CLEAN = $(OUTPUT)/gen_sig diff --git a/tools/testing/selftests/hornet/loader.c b/tools/testing/selftests/hornet/loader.c new file mode 100644 index 0000000000000..f27580c7262b3 --- /dev/null +++ b/tools/testing/selftests/hornet/loader.c @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause + +#include <stdio.h> +#include <unistd.h> +#include <stddef.h> +#include <sys/resource.h> +#include <bpf/libbpf.h> +#include <errno.h> +#include "signed_loader.h" + +int main(int argc, char **argv) +{ + struct trivial *skel; + + skel = trivial__open_and_load(); + if (!skel) + return -1; + + trivial__destroy(skel); + return 0; +} diff --git a/tools/testing/selftests/hornet/trivial.bpf.c b/tools/testing/selftests/hornet/trivial.bpf.c new file mode 100644 index 0000000000000..d38c5b53ff932 --- /dev/null +++ b/tools/testing/selftests/hornet/trivial.bpf.c @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause + +#include "vmlinux.h" + +#include <bpf/bpf_helpers.h> +#include <bpf/bpf_tracing.h> +#include <bpf/bpf_core_read.h> + +char LICENSE[] SEC("license") = "Dual BSD/GPL"; + +int monitored_pid = 0; + +SEC("tracepoint/syscalls/sys_enter_unlinkat") +int handle_enter_unlink(struct trace_event_raw_sys_enter *ctx) +{ + char filename[128] = { 0 }; + struct task_struct *task; + unsigned long start_time = 0; + int pid = bpf_get_current_pid_tgid() >> 32; + char *pathname_ptr = (char *) BPF_CORE_READ(ctx, args[1]); + + bpf_probe_read_str(filename, sizeof(filename), pathname_ptr); + task = (struct task_struct *)bpf_get_current_task(); + start_time = BPF_CORE_READ(task, start_time); + + bpf_printk("BPF triggered unlinkat by PID: %d, start_time %ld. pathname = %s", + pid, start_time, filename); + + if (monitored_pid == pid) + bpf_printk("target pid found"); + + return 0; +} |
