aboutsummaryrefslogtreecommitdiffstats
diff options
authorMark Brown <broonie@kernel.org>2026-05-29 22:43:00 +0100
committerMark Brown <broonie@kernel.org>2026-05-29 22:43:00 +0100
commitc6c23350fbf64731e47f4d1576ac2ba6c03b1ed5 (patch)
treeec124a98ce774bf0bc31f9c73359122d65ed06e9
parente114941bfd16ae83f7075511371dad18aa62ad73 (diff)
parentc67f6e6d2c141f202211fa9fea844f577eb233a8 (diff)
downloadlinux-next-history-c6c23350fbf64731e47f4d1576ac2ba6c03b1ed5.tar.gz
Merge branch 'next' of https://git.kernel.org/pub/scm/linux/kernel/git/pcmoore/lsm.git
-rw-r--r--Documentation/admin-guide/LSM/Hornet.rst323
-rw-r--r--Documentation/admin-guide/LSM/index.rst1
-rw-r--r--Documentation/admin-guide/LSM/ipe.rst162
-rw-r--r--Documentation/security/ipe.rst68
-rw-r--r--MAINTAINERS9
-rw-r--r--certs/system_keyring.c1
-rw-r--r--crypto/asymmetric_keys/Makefile4
-rw-r--r--crypto/asymmetric_keys/pkcs7_aa.asn118
-rw-r--r--crypto/asymmetric_keys/pkcs7_key_type.c44
-rw-r--r--crypto/asymmetric_keys/pkcs7_parser.c81
-rw-r--r--crypto/asymmetric_keys/pkcs7_parser.h1
-rw-r--r--crypto/asymmetric_keys/pkcs7_trust.c1
-rw-r--r--fs/nfs/nfs4proc.c7
-rw-r--r--fs/xattr.c11
-rw-r--r--include/crypto/pkcs7.h4
-rw-r--r--include/linux/lsm_hook_defs.h9
-rw-r--r--include/linux/oid_registry.h3
-rw-r--r--include/linux/security.h33
-rw-r--r--include/uapi/linux/lsm.h1
-rw-r--r--scripts/Makefile1
-rw-r--r--scripts/hornet/.gitignore1
-rw-r--r--scripts/hornet/Makefile5
-rwxr-xr-xscripts/hornet/extract-insn.sh27
-rwxr-xr-xscripts/hornet/extract-map.sh27
-rwxr-xr-xscripts/hornet/extract-skel.sh27
-rw-r--r--scripts/hornet/gen_sig.c402
-rwxr-xr-xscripts/hornet/write-sig.sh27
-rw-r--r--security/Kconfig3
-rw-r--r--security/Makefile1
-rw-r--r--security/hornet/Kconfig14
-rw-r--r--security/hornet/Makefile7
-rw-r--r--security/hornet/hornet.asn112
-rw-r--r--security/hornet/hornet_lsm.c352
-rw-r--r--security/ipe/Kconfig15
-rw-r--r--security/ipe/audit.c15
-rw-r--r--security/ipe/eval.c93
-rw-r--r--security/ipe/eval.h11
-rw-r--r--security/ipe/hooks.c63
-rw-r--r--security/ipe/hooks.h15
-rw-r--r--security/ipe/ipe.c14
-rw-r--r--security/ipe/ipe.h3
-rw-r--r--security/ipe/policy.h14
-rw-r--r--security/ipe/policy_parser.c27
-rw-r--r--security/security.c91
-rw-r--r--security/selinux/hooks.c10
-rw-r--r--security/smack/smack_lsm.c13
-rw-r--r--tools/testing/selftests/Makefile1
-rw-r--r--tools/testing/selftests/hornet/Makefile63
-rw-r--r--tools/testing/selftests/hornet/loader.c21
-rw-r--r--tools/testing/selftests/hornet/trivial.bpf.c33
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;
+}