* [PATCH v3 7/9] hornet: Introduce gen_sig
From: Blaise Boscaccy @ 2026-03-26 6:06 UTC (permalink / raw)
To: Blaise Boscaccy, Jonathan Corbet, Paul Moore, James Morris,
Serge E. Hallyn, Mickaël Salaün, Günther Noack,
Dr. David Alan Gilbert, Andrew Morton, James.Bottomley, dhowells,
Fan Wu, Ryan Foster, Randy Dunlap, linux-security-module,
linux-doc, linux-kernel, bpf
In-Reply-To: <20260326060655.2550595-1-bboscaccy@linux.microsoft.com>
This introduces the gen_sig tool. It creates a pkcs#7 signature of a
data payload. Additionally it appends a signed attribute containing a
set of hashes.
Typical usage is to provide a payload containing the light skeleton
ebpf syscall program binary and it's associated maps, which can be
extracted from the auto-generated skeleton header.
Signed-off-by: Blaise Boscaccy <bboscaccy@linux.microsoft.com>
---
scripts/Makefile | 1 +
scripts/hornet/Makefile | 5 +
scripts/hornet/gen_sig.c | 392 ++++++++++++++++++++++++++++++++++++
scripts/hornet/write-sig.sh | 27 +++
4 files changed, 425 insertions(+)
create mode 100644 scripts/hornet/Makefile
create mode 100644 scripts/hornet/gen_sig.c
create mode 100755 scripts/hornet/write-sig.sh
diff --git a/scripts/Makefile b/scripts/Makefile
index 0941e5ce7b575..dea8ab91bbe4e 100644
--- a/scripts/Makefile
+++ b/scripts/Makefile
@@ -63,6 +63,7 @@ subdir-$(CONFIG_GENKSYMS) += genksyms
subdir-$(CONFIG_GENDWARFKSYMS) += gendwarfksyms
subdir-$(CONFIG_SECURITY_SELINUX) += selinux
subdir-$(CONFIG_SECURITY_IPE) += ipe
+subdir-$(CONFIG_SECURITY_HORNET) += hornet
# Let clean descend into subdirs
subdir- += basic dtc gdb kconfig mod
diff --git a/scripts/hornet/Makefile b/scripts/hornet/Makefile
new file mode 100644
index 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/gen_sig.c b/scripts/hornet/gen_sig.c
new file mode 100644
index 0000000000000..f966516ebc99b
--- /dev/null
+++ b/scripts/hornet/gen_sig.c
@@ -0,0 +1,392 @@
+/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
+ *
+ * Generate a signature for an eBPF program along with appending
+ * map hashes as signed attributes
+ *
+ * Copyright © 2025 Microsoft Corporation.
+ *
+ * Authors: Blaise Boscaccy <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;
+ char opt;
+
+ const char *short_opts = "C:K:P:O:A:Sh";
+
+ static const struct option long_opts[] = {
+ {"cert", required_argument, 0, 'C'},
+ {"key", required_argument, 0, 'K'},
+ {"pass", required_argument, 0, 'P'},
+ {"out", required_argument, 0, 'O'},
+ {"data", required_argument, 0, 'D'},
+ {"add", required_argument, 0, 'A'},
+ {"help", no_argument, 0, 'h'},
+ {0, 0, 0, 0}
+ };
+
+ while ((opt = getopt_long_only(argc, argv, short_opts, long_opts, NULL)) != -1) {
+ switch (opt) {
+ case 'C':
+ cert_path = optarg;
+ break;
+ case 'K':
+ key_path = optarg;
+ break;
+ case 'P':
+ key_pass = optarg;
+ break;
+ case 'O':
+ out_path = optarg;
+ break;
+ case 'D':
+ data_path = optarg;
+ break;
+ case 'A':
+ if (strchr(optarg, ':')) {
+ hashes[hash_count].file = strsep(&optarg, ":");
+ hashes[hash_count].index = atoi(optarg);
+ hash_count++;
+ } else {
+ usage(argv[0]);
+ return EXIT_FAILURE;
+ }
+ }
+ }
+
+ if (!cert_path || !key_path || !out_path || !data_path) {
+ usage(argv[0]);
+ return EXIT_FAILURE;
+ }
+
+ OpenSSL_add_all_algorithms();
+ ERR_load_crypto_strings();
+
+ signer = read_x509(cert_path);
+ ERR(!signer, "Load cert failed");
+
+ pkey = read_private_key(key_path);
+ ERR(!pkey, "Load key failed");
+
+ data_in = BIO_new_file(data_path, "rb");
+ ERR(!data_in, "Load data failed");
+
+ cms_out = CMS_sign(NULL, NULL, NULL, NULL,
+ CMS_NOCERTS | CMS_PARTIAL | CMS_BINARY | CMS_DETACHED);
+ ERR(!cms_out, "create cms failed");
+
+ flags = CMS_NOCERTS | CMS_PARTIAL | CMS_BINARY | CMS_NOSMIMECAP | CMS_DETACHED;
+
+ si = CMS_add1_signer(cms_out, signer, pkey, EVP_sha256(), flags);
+ ERR(!si, "add signer failed");
+
+ set = MAP_SET_new();
+ set->maps = sk_HORNET_MAP_new_null();
+
+ for (i = 0; i < hash_count; i++) {
+ sha256(hashes[i].file, hash_buffer, &hash_len);
+ add_hash(set, hash_buffer, hash_len, hashes[i].index);
+ }
+
+ oid = OBJ_txt2obj("2.25.316487325684022475439036912669789383960", 1);
+ if (!oid) {
+ ERR_print_errors_fp(stderr);
+ DIE("create oid failed");
+ }
+
+ der_len = ASN1_item_i2d((ASN1_VALUE *)set, &der, ASN1_ITEM_rptr(MAP_SET));
+ CMS_signed_add1_attr_by_OBJ(si, oid, V_ASN1_SEQUENCE, der, der_len);
+
+ err = CMS_final(cms_out, data_in, NULL, CMS_NOCERTS | CMS_BINARY);
+ ERR(!err, "cms final failed");
+
+ OPENSSL_free(der);
+ MAP_SET_free(set);
+
+ b_out = bio_open_wr(out_path);
+ ERR(!b_out, "opening output path failed");
+
+ i2d_CMS_bio_stream(b_out, cms_out, NULL, 0);
+
+ BIO_free(data_in);
+ BIO_free(b_out);
+ EVP_cleanup();
+ ERR_free_strings();
+ return 0;
+}
diff --git a/scripts/hornet/write-sig.sh b/scripts/hornet/write-sig.sh
new file mode 100755
index 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
--
2.53.0
^ permalink raw reply related
* [PATCH v3 6/9] security: Hornet LSM
From: Blaise Boscaccy @ 2026-03-26 6:06 UTC (permalink / raw)
To: Blaise Boscaccy, Jonathan Corbet, Paul Moore, James Morris,
Serge E. Hallyn, Mickaël Salaün, Günther Noack,
Dr. David Alan Gilbert, Andrew Morton, James.Bottomley, dhowells,
Fan Wu, Ryan Foster, Randy Dunlap, linux-security-module,
linux-doc, linux-kernel, bpf
In-Reply-To: <20260326060655.2550595-1-bboscaccy@linux.microsoft.com>
This adds the Hornet Linux Security Module which provides enhanced
signature verification and data validation for eBPF programs. This
allows users to continue to maintain an invariant that all code
running inside of the kernel has actually been signed and verified, by
the kernel.
This effort builds upon the currently excepted upstream solution. It
further hardens it by providing deterministic, in-kernel checking of
map hashes to solidify auditing along with preventing TOCTOU attacks
against lskel map hashes.
Target map hashes are passed in via PKCS#7 signed attributes. Hornet
determines the extent which the eBFP program is signed and defers to
other LSMs for policy decisions.
Signed-off-by: Blaise Boscaccy <bboscaccy@linux.microsoft.com>
Nacked-by: Alexei Starovoitov <alexei.starovoitov@gmail.com>
---
Documentation/admin-guide/LSM/Hornet.rst | 321 ++++++++++++++++++++++
Documentation/admin-guide/LSM/index.rst | 1 +
MAINTAINERS | 9 +
include/linux/oid_registry.h | 3 +
include/uapi/linux/lsm.h | 1 +
security/Kconfig | 3 +-
security/Makefile | 1 +
security/hornet/Kconfig | 11 +
security/hornet/Makefile | 7 +
security/hornet/hornet.asn1 | 13 +
security/hornet/hornet_lsm.c | 333 +++++++++++++++++++++++
11 files changed, 702 insertions(+), 1 deletion(-)
create mode 100644 Documentation/admin-guide/LSM/Hornet.rst
create mode 100644 security/hornet/Kconfig
create mode 100644 security/hornet/Makefile
create mode 100644 security/hornet/hornet.asn1
create mode 100644 security/hornet/hornet_lsm.c
diff --git a/Documentation/admin-guide/LSM/Hornet.rst b/Documentation/admin-guide/LSM/Hornet.rst
new file mode 100644
index 0000000000000..af5e9cd9d83a8
--- /dev/null
+++ b/Documentation/admin-guide/LSM/Hornet.rst
@@ -0,0 +1,321 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+======
+Hornet
+======
+
+Hornet is a Linux Security Module that provides extensible signature
+verification for eBPF programs. This is selectable at build-time with
+``CONFIG_SECURITY_HORNET``.
+
+Overview
+========
+
+Hornet addresses concerns from users who require strict audit trails and
+verification guarantees for eBPF programs, especially in
+security-sensitive environments. Many production systems need assurance
+that only authorized, unmodified eBPF programs are loaded into the
+kernel. Hornet provides this assurance through cryptographic signature
+verification.
+
+When an eBPF program is loaded via the ``bpf()`` syscall, Hornet
+verifies a PKCS#7 signature attached to the program instructions. The
+signature is checked against the kernel's secondary keyring using the
+existing kernel cryptographic infrastructure. In addition to signing the
+program bytecode, Hornet supports signing SHA-256 hashes of associated
+BPF maps, enabling integrity verification of map contents at load time
+and at runtime.
+
+After verification, Hornet classifies the program into one of the
+following integrity states and passes the result to a downstream LSM hook
+(``bpf_prog_load_post_integrity``), allowing other security modules to
+make policy decisions based on the verification outcome:
+
+``LSM_INT_VERDICT_OK``
+ The program signature and all map hashes verified successfully.
+
+``LSM_INT_VERDICT_UNSIGNED``
+ No signature was provided with the program.
+
+``LSM_INT_VERDICT_PARTIALSIG``
+ The program signature verified, but the signature did not contain
+ hornet map hash data.
+
+``LSM_INT_VERDICT_UNKNOWNKEY``
+ The signing certificate is not trusted in the secondary keyring,
+
+``LSM_INT_VERDICT_FAULT``
+ A system error occured during verification.
+
+``LSM_INT_VERDICT_UNEXPECTED``
+ An unexpected map hash value was encountered.
+
+``LSM_INT_VERDICT_BADSIG``
+ The signature or a map hash failed verification.
+
+Hornet itself does not enforce a policy on whether unsigned or partially
+signed programs should be rejected. It delegates that decision to
+downstream LSMs via the ``bpf_prog_load_post_integrity`` hook, making it
+a composable building block in a larger security architecture.
+
+Use Cases
+=========
+
+- **Locked-down production environments**: Ensure only eBPF programs
+ signed by a trusted authority can be loaded, preventing unauthorized
+ or tampered programs from running in the kernel.
+
+- **Audit and compliance**: Provide cryptographic evidence that loaded
+ eBPF programs match their expected build artifacts, supporting
+ compliance requirements in regulated industries.
+
+- **Supply chain integrity**: Verify that eBPF programs and their
+ associated map data have not been modified since they were built and
+ signed, protecting against supply chain attacks.
+
+Threat Model
+============
+
+Hornet protects against the following threats:
+
+- **Unauthorized eBPF program loading**: Programs that have not been
+ signed by a trusted key will be reported as unsigned or badly signed.
+
+- **Tampering with program instructions**: Any modification to the eBPF
+ bytecode after signing will cause signature verification to fail.
+
+- **Tampering with map data**: When map hashes are included in the
+ signature, Hornet verifies that frozen BPF maps match their expected
+ SHA-256 hashes at load time. Maps are also re-verified before program
+ execution via ``BPF_PROG_RUN``.
+
+Hornet does **not** protect against:
+
+- Compromise of the signing key itself.
+- Attacks that occur after a program has been loaded and verified.
+- Programs loaded by the kernel itself (kernel-internal loads bypass
+ the ``BPF_PROG_RUN`` map check).
+
+Known Limitations
+=================
+
+- Hornet requires programs to use :doc:`light skeletons
+ </bpf/libbpf/libbpf_naming_convention>` (lskels) for the signing
+ workflow, as the tooling operates on lskel-generated headers.
+
+- A maximum of 64 maps per program can be tracked for hash
+ verification.
+
+- Map hash verification requires the maps to be frozen before loading.
+ Maps that are not frozen at load time will cause verification to fail
+ when their hashes are included in the signature.
+
+- Hornet relies on the kernel's secondary keyring
+ (``VERIFY_USE_SECONDARY_KEYRING``) for certificate trust. Keys must
+ be provisioned into this keyring before programs can be verified.
+
+- The only hashing algorithm available is SHA256 due to it be hardcoded
+ in the bpf subsystem.
+
+Configuration
+=============
+
+Build Configuration
+-------------------
+
+Enable Hornet by setting the following kernel configuration option::
+
+ CONFIG_SECURITY_HORNET=y
+
+This option is found under :menuselection:`Security options --> Hornet
+support` and depends on ``CONFIG_SECURITY``.
+
+When enabled, Hornet is included in the default LSM initialization order
+and will appear in ``/sys/kernel/security/lsm``.
+
+Architecture
+============
+
+Signature Verification Flow
+---------------------------
+
+The following describes what happens when a userspace program calls
+``bpf(BPF_PROG_LOAD, ...)`` with a signature attached:
+
+1. The ``bpf_prog_load_integrity`` LSM hook is invoked.
+
+2. Hornet reads the signature from the userspace buffer specified by
+ ``attr->signature`` (with length ``attr->signature_size``).
+
+3. The PKCS#7 signature is verified against the program instructions
+ using ``verify_pkcs7_signature()`` with the kernel's secondary
+ keyring.
+
+4. The PKCS#7 message is parsed and its trust chain is validated via
+ ``validate_pkcs7_trust()``.
+
+5. Hornet extracts the authenticated attribute identified by
+ ``OID_hornet_data`` (OID ``2.25.316487325684022475439036912669789383960``)
+ from the PKCS#7 message. This attribute contains an ASN.1-encoded set
+ of map index/hash pairs.
+
+6. For each map hash entry, Hornet retrieves the corresponding BPF map
+ via its file descriptor, confirms it is frozen, computes its SHA-256
+ hash, and compares it against the signed hash.
+
+7. The resulting integrity verdict is passed to the
+ ``bpf_prog_load_post_integrity`` hook so that downstream LSMs can
+ enforce policy.
+
+Runtime Map Verification
+------------------------
+
+When ``bpf(BPF_PROG_RUN, ...)`` is called from userspace, Hornet
+re-verifies the hashes of all maps associated with the program. This
+ensures that map contents have not been modified between program load
+and execution. If any map hash no longer matches, the ``BPF_PROG_RUN``
+command is denied.
+
+Userspace Interface
+-------------------
+
+Signatures are passed to the kernel through fields in ``union bpf_attr``
+when using the ``BPF_PROG_LOAD`` command:
+
+``signature``
+ A pointer to a userspace buffer containing the PKCS#7 signature.
+
+``signature_size``
+ The size of the signature buffer in bytes.
+
+ASN.1 Schema
+------------
+
+Map hashes are encoded as a signed attribute in the PKCS#7 message using
+the following ASN.1 schema::
+
+ HornetData ::= SET OF Map
+
+ Map ::= SEQUENCE {
+ index INTEGER,
+ sha OCTET STRING
+ }
+
+Each ``Map`` entry contains the index of the map in the program's
+``fd_array`` and its expected SHA-256 hash. A zero-length ``sha`` field
+indicates that the map at that index should be skipped during
+verification.
+
+Tooling
+=======
+
+Helper scripts and a signature generation tool are provided in
+``scripts/hornet/`` to support the development of signed eBPF light
+skeletons.
+
+gen_sig
+-------
+
+``gen_sig`` is a C program (using OpenSSL) that creates a PKCS#7
+signature over eBPF program instructions and optionally includes
+SHA-256 hashes of BPF maps as signed attributes.
+
+Usage::
+
+ gen_sig --data <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/MAINTAINERS b/MAINTAINERS
index 55af015174a54..6e91234a9ba4e 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -11682,6 +11682,15 @@ S: Maintained
F: Documentation/devicetree/bindings/iio/pressure/honeywell,mprls0025pa.yaml
F: drivers/iio/pressure/mprls0025pa*
+HORNET SECURITY MODULE
+M: Blaise Boscaccy <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/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/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/security/Kconfig b/security/Kconfig
index 6a4393fce9a17..283c4a1032094 100644
--- a/security/Kconfig
+++ b/security/Kconfig
@@ -230,6 +230,7 @@ source "security/safesetid/Kconfig"
source "security/lockdown/Kconfig"
source "security/landlock/Kconfig"
source "security/ipe/Kconfig"
+source "security/hornet/Kconfig"
source "security/integrity/Kconfig"
@@ -274,7 +275,7 @@ config LSM
default "landlock,lockdown,yama,loadpin,safesetid,apparmor,selinux,smack,tomoyo,ipe,bpf" if DEFAULT_SECURITY_APPARMOR
default "landlock,lockdown,yama,loadpin,safesetid,tomoyo,ipe,bpf" if DEFAULT_SECURITY_TOMOYO
default "landlock,lockdown,yama,loadpin,safesetid,ipe,bpf" if DEFAULT_SECURITY_DAC
- default "landlock,lockdown,yama,loadpin,safesetid,selinux,smack,tomoyo,apparmor,ipe,bpf"
+ default "landlock,lockdown,yama,loadpin,safesetid,selinux,smack,tomoyo,apparmor,ipe,hornet,bpf"
help
A comma-separated list of LSMs, in initialization order.
Any LSMs left off this list, except for those with order
diff --git a/security/Makefile b/security/Makefile
index 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..19406aa237ac6
--- /dev/null
+++ b/security/hornet/Kconfig
@@ -0,0 +1,11 @@
+# SPDX-License-Identifier: GPL-2.0-only
+config SECURITY_HORNET
+ bool "Hornet support"
+ depends on SECURITY
+ default n
+ help
+ This selects Hornet.
+ Further information can be found in
+ Documentation/admin-guide/LSM/Hornet.rst.
+
+ If you are unsure how to answer this question, answer N.
diff --git a/security/hornet/Makefile b/security/hornet/Makefile
new file mode 100644
index 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..c8d47b16b65d7
--- /dev/null
+++ b/security/hornet/hornet.asn1
@@ -0,0 +1,13 @@
+-- SPDX-License-Identifier: BSD-3-Clause
+--
+-- Copyright (C) 2009 IETF Trust and the persons identified as authors
+-- of the code
+--
+-- https://www.rfc-editor.org/rfc/rfc5652#section-3
+
+HornetData ::= SET OF Map
+
+Map ::= SEQUENCE {
+ index INTEGER ({ hornet_map_index }),
+ sha OCTET STRING ({ hornet_map_hash })
+} ({ hornet_next_map })
diff --git a/security/hornet/hornet_lsm.c b/security/hornet/hornet_lsm.c
new file mode 100644
index 0000000000000..21355bb7b5583
--- /dev/null
+++ b/security/hornet/hornet_lsm.c
@@ -0,0 +1,333 @@
+// 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 {
+ bool checked[MAX_USED_MAPS];
+ unsigned char hashes[SHA256_DIGEST_SIZE * MAX_USED_MAPS];
+};
+
+struct hornet_map_security_struct {
+ bool checked;
+ int index;
+};
+
+struct lsm_blob_sizes hornet_blob_sizes __ro_after_init = {
+ .lbs_bpf_map = sizeof(struct hornet_map_security_struct),
+ .lbs_bpf_prog = sizeof(struct hornet_prog_security_struct),
+};
+
+static inline struct hornet_prog_security_struct *
+hornet_bpf_prog_security(struct bpf_prog *prog)
+{
+ return prog->aux->security + hornet_blob_sizes.lbs_bpf_prog;
+}
+
+static inline struct hornet_map_security_struct *
+hornet_bpf_map_security(struct bpf_map *map)
+{
+ return map->security + hornet_blob_sizes.lbs_bpf_map;
+}
+
+static int hornet_verify_hashes(struct hornet_maps *maps,
+ struct hornet_parse_context *ctx,
+ struct bpf_prog *prog)
+{
+ int map_fd;
+ u32 i;
+ struct bpf_map *map;
+ int err = 0;
+ unsigned char hash[SHA256_DIGEST_SIZE];
+ struct hornet_prog_security_struct *security = hornet_bpf_prog_security(prog);
+ struct hornet_map_security_struct *map_security;
+
+ for (i = 0; i < ctx->hash_count; i++) {
+ if (ctx->skips[i])
+ 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 (!map->frozen)
+ return LSM_INT_VERDICT_FAULT;
+
+ map->ops->map_get_hash(map, SHA256_DIGEST_SIZE, hash);
+
+ err = memcmp(hash, &ctx->hashes[i * SHA256_DIGEST_SIZE],
+ SHA256_DIGEST_SIZE);
+ if (err)
+ return LSM_INT_VERDICT_UNEXPECTED;
+
+ security->checked[i] = true;
+ memcpy(&security->hashes[i * SHA256_DIGEST_SIZE], hash, SHA256_DIGEST_SIZE);
+ map_security = hornet_bpf_map_security(map);
+ map_security->checked = true;
+ map_security->index = i;
+ }
+ return LSM_INT_VERDICT_OK;
+}
+
+int hornet_next_map(void *context, size_t hdrlen,
+ unsigned char tag,
+ const void *value, size_t vlen)
+{
+ struct hornet_parse_context *ctx = (struct hornet_parse_context *)context;
+
+ 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;
+
+ if (!attr->signature) {
+ *verdict = LSM_INT_VERDICT_UNSIGNED;
+ 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)
+ goto cleanup_sig;
+
+ msg = pkcs7_parse_message(sig, attr->signature_size);
+ if (IS_ERR(msg)) {
+ err = LSM_INT_VERDICT_BADSIG;
+ goto cleanup_sig;
+ }
+
+ if (verify_pkcs7_message_sig(prog->insnsi, prog->len * sizeof(struct bpf_insn), msg,
+ VERIFY_USE_SECONDARY_KEYRING,
+ VERIFYING_BPF_SIGNATURE,
+ NULL, NULL)) {
+ err = LSM_INT_VERDICT_UNKNOWNKEY;
+ goto cleanup_msg;
+ }
+
+ if (pkcs7_get_authattr(msg, OID_hornet_data,
+ &authattrs, &authattrs_len) == -ENODATA) {
+ err = LSM_INT_VERDICT_PARTIALSIG;
+ goto cleanup_msg;
+ }
+
+ err = asn1_ber_decoder(&hornet_decoder, ctx, authattrs, authattrs_len);
+ if (err < 0 || authattrs == NULL) {
+ err = LSM_INT_VERDICT_BADSIG;
+ goto cleanup_msg;
+ }
+
+ err = hornet_verify_hashes(&maps, ctx, prog);
+
+cleanup_msg:
+ pkcs7_free_message(msg);
+cleanup_sig:
+ kfree(sig);
+out:
+ kfree(ctx);
+ return err;
+}
+
+static const struct lsm_id hornet_lsmid = {
+ .name = "hornet",
+ .id = LSM_ID_HORNET,
+};
+
+static int hornet_bpf_prog_load_integrity(struct bpf_prog *prog, union bpf_attr *attr,
+ struct bpf_token *token, bool is_kernel)
+{
+ 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_verify_map(struct bpf_prog *prog, int index)
+{
+ unsigned char hash[SHA256_DIGEST_SIZE];
+ int i;
+ struct bpf_map *map;
+ struct hornet_prog_security_struct *security = hornet_bpf_prog_security(prog);
+ struct hornet_map_security_struct *map_security;
+
+ if (!security->checked[index])
+ return 0;
+
+ for (i = 0; i < prog->aux->used_map_cnt; i++) {
+ map = prog->aux->used_maps[i];
+ map_security = hornet_bpf_map_security(map);
+ if (map_security->index != index)
+ continue;
+
+ if (!map->frozen)
+ return -EPERM;
+
+ map->ops->map_get_hash(map, SHA256_DIGEST_SIZE, hash);
+ if (memcmp(hash, &security->hashes[index * SHA256_DIGEST_SIZE],
+ SHA256_DIGEST_SIZE) != 0)
+ return -EPERM;
+ else
+ return 0;
+ }
+ return -EINVAL;
+}
+
+static int hornet_check_prog_maps(u32 ufd)
+{
+ CLASS(fd, f)(ufd);
+ struct bpf_prog *prog;
+ int i, result = 0;
+
+ if (fd_empty(f))
+ return -EBADF;
+ if (fd_file(f)->f_op != &bpf_prog_fops)
+ return -EINVAL;
+
+ prog = fd_file(f)->private_data;
+
+ mutex_lock(&prog->aux->used_maps_mutex);
+ if (!prog->aux->used_map_cnt)
+ goto out;
+
+ for (i = 0; i < prog->aux->used_map_cnt; i++) {
+ result = hornet_verify_map(prog, i);
+ if (result)
+ goto out;
+ }
+out:
+ mutex_unlock(&prog->aux->used_maps_mutex);
+
+ return result;
+}
+
+static int hornet_bpf(int cmd, union bpf_attr *attr, unsigned int size, bool kernel)
+{
+ /* 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,
+};
--
2.53.0
^ permalink raw reply related
* [PATCH v3 5/9] lsm: security: Add additional enum values for bpf integrity checks
From: Blaise Boscaccy @ 2026-03-26 6:06 UTC (permalink / raw)
To: Blaise Boscaccy, Jonathan Corbet, Paul Moore, James Morris,
Serge E. Hallyn, Mickaël Salaün, Günther Noack,
Dr. David Alan Gilbert, Andrew Morton, James.Bottomley, dhowells,
Fan Wu, Ryan Foster, Randy Dunlap, linux-security-module,
linux-doc, linux-kernel, bpf
In-Reply-To: <20260326060655.2550595-1-bboscaccy@linux.microsoft.com>
First add a generic LSM_INT_VERDICT_FAULT value to indicate a system
failure during checking. Second, add a LSM_INT_VERDICT_UNKNOWNKEY to
signal that the payload was signed with a key other than one that
exists in the secondary keyring. And finally add an
LSM_INT_VERDICT_UNEXPECTED enum value to indicate that a unexpected
hash value was encountered at some stage of verification.
Signed-off-by: Blaise Boscaccy <bboscaccy@linux.microsoft.com>
---
include/linux/security.h | 3 +++
1 file changed, 3 insertions(+)
diff --git a/include/linux/security.h b/include/linux/security.h
index 298a43b7744a4..84c82c41b48c4 100644
--- a/include/linux/security.h
+++ b/include/linux/security.h
@@ -106,6 +106,9 @@ enum lsm_integrity_verdict {
LSM_INT_VERDICT_OK,
LSM_INT_VERDICT_UNSIGNED,
LSM_INT_VERDICT_PARTIALSIG,
+ LSM_INT_VERDICT_UNKNOWNKEY,
+ LSM_INT_VERDICT_UNEXPECTED,
+ LSM_INT_VERDICT_FAULT,
LSM_INT_VERDICT_BADSIG,
};
--
2.53.0
^ permalink raw reply related
* [PATCH v3 4/9] lsm: framework for BPF integrity verification
From: Blaise Boscaccy @ 2026-03-26 6:06 UTC (permalink / raw)
To: Blaise Boscaccy, Jonathan Corbet, Paul Moore, James Morris,
Serge E. Hallyn, Mickaël Salaün, Günther Noack,
Dr. David Alan Gilbert, Andrew Morton, James.Bottomley, dhowells,
Fan Wu, Ryan Foster, Randy Dunlap, linux-security-module,
linux-doc, linux-kernel, bpf
In-Reply-To: <20260326060655.2550595-1-bboscaccy@linux.microsoft.com>
From: Paul Moore <paul@paul-moore.com>
Add a new LSM hook and two new LSM hook callbacks to support LSMs that
perform integrity verification, e.g. digital signature verification,
of BPF programs.
While the BPF subsystem does implement a signature verification scheme,
it does not satisfy a number of existing requirements, adding support
for BPF program integrity verification to the LSM framework allows
administrators to select additional integrity verification mechanisms
to meet these needs while also providing a mechanism for future
expansion. Additional on why this is necessary can be found at the
lore archive link below:
https://lore.kernel.org/linux-security-module/CAHC9VhTQ_DR=ANzoDBjcCtrimV7XcCZVUsANPt=TjcvM4d-vjg@mail.gmail.com/
The LSM-based BPF integrity verification mechanism works within the
existing security_bpf_prog_load() hook called by the BPF subsystem.
It adds an additional dedicated integrity callback and a new LSM
hook/callback to be called from within LSMs implementing integrity
verification.
The first new callback, bpf_prog_load_integrity(), located within the
security_bpf_prog_load() hook, is necessary to ensure that the integrity
verification callbacks are executed before any of the existing LSMs
are executed via the bpf_prog_load() callback. Reusing the existing
bpf_prog_load() callback for integrity verification could result in LSMs
not having access to the integrity verification results when asked to
authorize the BPF program load in the bpf_prog_load() callback.
The new LSM hook, security_bpf_prog_load_post_integrity(), is intended
to be called from within LSMs performing BPF program integrity
verification. It is used to report the verdict of the integrity
verification to other LSMs enforcing access control policy on BPF
program loads. LSMs enforcing such access controls should register a
bpf_prog_load_post_integrity() callback to receive integrity verdicts.
More information on these new callbacks and hook can be found in the
code comments in this patch.
Signed-off-by: Paul Moore <paul@paul-moore.com>
Signed-off-by: Blaise Boscaccy <bboscaccy@linux.microsoft.com>
---
include/linux/lsm_hook_defs.h | 5 +++
include/linux/security.h | 25 ++++++++++++
security/security.c | 75 +++++++++++++++++++++++++++++++++--
3 files changed, 102 insertions(+), 3 deletions(-)
diff --git a/include/linux/lsm_hook_defs.h b/include/linux/lsm_hook_defs.h
index 8c42b4bde09c0..4971d3c36d5b4 100644
--- a/include/linux/lsm_hook_defs.h
+++ b/include/linux/lsm_hook_defs.h
@@ -434,6 +434,11 @@ LSM_HOOK(int, 0, bpf_prog, struct bpf_prog *prog)
LSM_HOOK(int, 0, bpf_map_create, struct bpf_map *map, union bpf_attr *attr,
struct bpf_token *token, bool kernel)
LSM_HOOK(void, LSM_RET_VOID, bpf_map_free, struct bpf_map *map)
+LSM_HOOK(int, 0, bpf_prog_load_post_integrity, struct bpf_prog *prog,
+ union bpf_attr *attr, struct bpf_token *token, bool kernel,
+ const struct lsm_id *lsmid, enum lsm_integrity_verdict verdict)
+LSM_HOOK(int, 0, bpf_prog_load_integrity, struct bpf_prog *prog,
+ union bpf_attr *attr, struct bpf_token *token, bool kernel)
LSM_HOOK(int, 0, bpf_prog_load, struct bpf_prog *prog, union bpf_attr *attr,
struct bpf_token *token, bool kernel)
LSM_HOOK(void, LSM_RET_VOID, bpf_prog_free, struct bpf_prog *prog)
diff --git a/include/linux/security.h b/include/linux/security.h
index 83a646d72f6f8..298a43b7744a4 100644
--- a/include/linux/security.h
+++ b/include/linux/security.h
@@ -67,6 +67,7 @@ enum fs_value_type;
struct watch;
struct watch_notification;
struct lsm_ctx;
+struct lsm_id;
/* Default (no) options for the capable function */
#define CAP_OPT_NONE 0x0
@@ -100,6 +101,14 @@ enum lsm_integrity_type {
LSM_INT_FSVERITY_BUILTINSIG_VALID,
};
+enum lsm_integrity_verdict {
+ LSM_INT_VERDICT_NONE = 0,
+ LSM_INT_VERDICT_OK,
+ LSM_INT_VERDICT_UNSIGNED,
+ LSM_INT_VERDICT_PARTIALSIG,
+ LSM_INT_VERDICT_BADSIG,
+};
+
/*
* These are reasons that can be passed to the security_locked_down()
* LSM hook. Lockdown reasons that protect kernel integrity (ie, the
@@ -2269,6 +2278,12 @@ extern int security_bpf_prog(struct bpf_prog *prog);
extern int security_bpf_map_create(struct bpf_map *map, union bpf_attr *attr,
struct bpf_token *token, bool kernel);
extern void security_bpf_map_free(struct bpf_map *map);
+extern int security_bpf_prog_load_post_integrity(struct bpf_prog *prog,
+ union bpf_attr *attr,
+ struct bpf_token *token,
+ bool kernel,
+ const struct lsm_id *lsmid,
+ enum lsm_integrity_verdict verdict);
extern int security_bpf_prog_load(struct bpf_prog *prog, union bpf_attr *attr,
struct bpf_token *token, bool kernel);
extern void security_bpf_prog_free(struct bpf_prog *prog);
@@ -2303,6 +2318,16 @@ static inline int security_bpf_map_create(struct bpf_map *map, union bpf_attr *a
static inline void security_bpf_map_free(struct bpf_map *map)
{ }
+static inline int security_bpf_prog_load_post_integrity(struct bpf_prog *prog,
+ union bpf_attr *attr,
+ struct bpf_token *token,
+ bool kernel,
+ const struct lsm_id *lsmid,
+ enum lsm_integrity_verdict verdict)
+{
+ return 0;
+}
+
static inline int security_bpf_prog_load(struct bpf_prog *prog, union bpf_attr *attr,
struct bpf_token *token, bool kernel)
{
diff --git a/security/security.c b/security/security.c
index 67af9228c4e94..2d8279bd4aae2 100644
--- a/security/security.c
+++ b/security/security.c
@@ -5232,6 +5232,50 @@ int security_bpf_map_create(struct bpf_map *map, union bpf_attr *attr,
return rc;
}
+/**
+ * security_bpf_prog_load_post_integrity() - Check if the BPF prog is allowed
+ * @prog: BPF program object
+ * @attr: BPF syscall attributes used to create BPF program
+ * @token: BPF token used to grant user access to BPF subsystem
+ * @kernel: whether or not call originated from kernel
+ * @lsmid: LSM ID of the LSM providing @verdict
+ * @verdict: result of the integrity verification
+ *
+ * See the comment block for the security_bpf_prog_load() LSM hook.
+ *
+ * This LSM hook is intended to be called from within the
+ * bpf_prog_load_integrity() callback that is part of the
+ * security_bpf_prog_load() hook; kernel subsystems outside the scope of the
+ * LSM framework should not call this hook directly.
+ *
+ * If the LSM calling into this hook receives a non-zero error code, it should
+ * return the same error code back to its caller. If this hook returns a zero,
+ * it does not necessarily mean that all of the enabled LSMs have authorized
+ * the BPF program load, as there may be other LSMs implementing BPF integrity
+ * checks which have yet to execute. However, if a zero is returned, the LSM
+ * calling into this hook should continue and return zero back to its caller.
+ *
+ * LSMs which implement the bpf_prog_load_post_integrity() callback and
+ * determine that a particular BPF program load is not authorized may choose to
+ * either return an error code for immediate rejection, or store their decision
+ * in their own LSM state attached to @prog, later returning an error code in
+ * the bpf_prog_load() callback. An immediate error code return is in keeping
+ * with the "fail fast" practice, but waiting until the bpf_prog_load()
+ * callback allows the LSM to consider multiple different integrity verdicts.
+ *
+ * Return: Returns 0 on success, error on failure.
+ */
+int security_bpf_prog_load_post_integrity(struct bpf_prog *prog,
+ union bpf_attr *attr,
+ struct bpf_token *token,
+ bool kernel,
+ const struct lsm_id *lsmid,
+ enum lsm_integrity_verdict verdict)
+{
+ return call_int_hook(bpf_prog_load_post_integrity, prog, attr, token,
+ kernel, lsmid, verdict);
+}
+
/**
* security_bpf_prog_load() - Check if loading of BPF program is allowed
* @prog: BPF program object
@@ -5240,8 +5284,24 @@ int security_bpf_map_create(struct bpf_map *map, union bpf_attr *attr,
* @kernel: whether or not call originated from kernel
*
* Perform an access control check when the kernel loads a BPF program and
- * allocates associated BPF program object. This hook is also responsible for
- * allocating any required LSM state for the BPF program.
+ * allocates the associated BPF program object. This hook is also responsible
+ * for allocating any required LSM state for the BPF program.
+ *
+ * This hook calls two LSM callbacks: bpf_prog_load_integrity() and
+ * bpf_prog_load(). The bpf_prog_load_integrity() callback is for those LSMs
+ * that wish to implement integrity verifications of BPF programs, e.g.
+ * signature verification, while the bpf_prog_load() callback is for general
+ * authorization of the BPF program load. Performing both verification and
+ * authorization in a single callback, with arbitrary LSM ordering, would be
+ * a challenge.
+ *
+ * LSMs which implement the bpf_prog_load_integrity() callback should call into
+ * the security_bpf_prog_load_post_integrity() hook with their integrity
+ * verdict. LSMs which implement BPF program integrity policy can register a
+ * callback for the security_bpf_prog_load_post_integrity() hook and
+ * either update their own internal state based on the verdict, or immediately
+ * reject the BPF program load with an error code. See the comment block for
+ * security_bpf_prog_load_post_integrity() for more information.
*
* Return: Returns 0 on success, error on failure.
*/
@@ -5254,9 +5314,18 @@ int security_bpf_prog_load(struct bpf_prog *prog, union bpf_attr *attr,
if (unlikely(rc))
return rc;
+ rc = call_int_hook(bpf_prog_load_integrity, prog, attr, token, kernel);
+ if (unlikely(rc))
+ goto err;
+
rc = call_int_hook(bpf_prog_load, prog, attr, token, kernel);
if (unlikely(rc))
- security_bpf_prog_free(prog);
+ goto err;
+
+ return rc;
+
+err:
+ security_bpf_prog_free(prog);
return rc;
}
--
2.53.0
^ permalink raw reply related
* [PATCH v3 3/9] crypto: pkcs7: add tests for pkcs7_get_authattr
From: Blaise Boscaccy @ 2026-03-26 6:06 UTC (permalink / raw)
To: Blaise Boscaccy, Jonathan Corbet, Paul Moore, James Morris,
Serge E. Hallyn, Mickaël Salaün, Günther Noack,
Dr. David Alan Gilbert, Andrew Morton, James.Bottomley, dhowells,
Fan Wu, Ryan Foster, Randy Dunlap, linux-security-module,
linux-doc, linux-kernel, bpf
In-Reply-To: <20260326060655.2550595-1-bboscaccy@linux.microsoft.com>
From: James Bottomley <James.Bottomley@HansenPartnership.com>
Add example code to the test module pkcs7_key_type.c that verifies a
message and then pulls out a known authenticated attribute.
Signed-off-by: James Bottomley <James.Bottomley@HansenPartnership.com>
Signed-off-by: Blaise Boscaccy <bboscaccy@linux.microsoft.com>
Acked-by: David Howells <dhowells@redhat.com>
---
crypto/asymmetric_keys/pkcs7_key_type.c | 44 ++++++++++++++++++++++++-
1 file changed, 43 insertions(+), 1 deletion(-)
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;
}
/*
--
2.53.0
^ permalink raw reply related
* [PATCH v3 2/9] crypto: pkcs7: add ability to extract signed attributes by OID
From: Blaise Boscaccy @ 2026-03-26 6:06 UTC (permalink / raw)
To: Blaise Boscaccy, Jonathan Corbet, Paul Moore, James Morris,
Serge E. Hallyn, Mickaël Salaün, Günther Noack,
Dr. David Alan Gilbert, Andrew Morton, James.Bottomley, dhowells,
Fan Wu, Ryan Foster, Randy Dunlap, linux-security-module,
linux-doc, linux-kernel, bpf
In-Reply-To: <20260326060655.2550595-1-bboscaccy@linux.microsoft.com>
From: James Bottomley <James.Bottomley@HansenPartnership.com>
Signers may add any information they like in signed attributes and
sometimes this information turns out to be relevant to specific
signing cases, so add an api pkcs7_get_authattr() to extract the value
of an authenticated attribute by specific OID. The current
implementation is designed for the single signer use case and simply
terminates the search when it finds the relevant OID.
Signed-off-by: James Bottomley <James.Bottomley@HansenPartnership.com>
Signed-off-by: Blaise Boscaccy <bboscaccy@linux.microsoft.com>
---
crypto/asymmetric_keys/Makefile | 4 +-
crypto/asymmetric_keys/pkcs7_aa.asn1 | 18 ++++++
crypto/asymmetric_keys/pkcs7_parser.c | 81 +++++++++++++++++++++++++++
include/crypto/pkcs7.h | 4 ++
4 files changed, 106 insertions(+), 1 deletion(-)
create mode 100644 crypto/asymmetric_keys/pkcs7_aa.asn1
diff --git a/crypto/asymmetric_keys/Makefile b/crypto/asymmetric_keys/Makefile
index 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_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/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
--
2.53.0
^ permalink raw reply related
* [PATCH v3 1/9] crypto: pkcs7: add flag for validated trust on a signed info block
From: Blaise Boscaccy @ 2026-03-26 6:06 UTC (permalink / raw)
To: Blaise Boscaccy, Jonathan Corbet, Paul Moore, James Morris,
Serge E. Hallyn, Mickaël Salaün, Günther Noack,
Dr. David Alan Gilbert, Andrew Morton, James.Bottomley, dhowells,
Fan Wu, Ryan Foster, Randy Dunlap, linux-security-module,
linux-doc, linux-kernel, bpf
In-Reply-To: <20260326060655.2550595-1-bboscaccy@linux.microsoft.com>
From: James Bottomley <James.Bottomley@HansenPartnership.com>
Allow consumers of struct pkcs7_message to tell if any of the sinfo
fields has passed a trust validation. Note that this does not happen
in parsing, pkcs7_validate_trust() must be explicitly called or called
via validate_pkcs7_trust(). Since the way to get this trusted pkcs7
object is via verify_pkcs7_message_sig, export that so modules can use
it.
Signed-off-by: James Bottomley <James.Bottomley@HansenPartnership.com>
Signed-off-by: Blaise Boscaccy <bboscaccy@linux.microsoft.com>
---
certs/system_keyring.c | 1 +
crypto/asymmetric_keys/pkcs7_parser.h | 1 +
crypto/asymmetric_keys/pkcs7_trust.c | 1 +
3 files changed, 3 insertions(+)
diff --git a/certs/system_keyring.c b/certs/system_keyring.c
index e0761436ec7f4..9bda49295bd02 100644
--- a/certs/system_keyring.c
+++ b/certs/system_keyring.c
@@ -380,6 +380,7 @@ int verify_pkcs7_message_sig(const void *data, size_t len,
pr_devel("<==%s() = %d\n", __func__, ret);
return ret;
}
+EXPORT_SYMBOL(verify_pkcs7_message_sig);
/**
* verify_pkcs7_signature - Verify a PKCS#7-based signature on system data.
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 @@ static int pkcs7_validate_trust_one(struct pkcs7_message *pkcs7,
for (p = sinfo->signer; p != x509; p = p->signer)
p->verified = true;
}
+ sinfo->verified = true;
kleave(" = 0");
return 0;
}
--
2.53.0
^ permalink raw reply related
* [PATCH v3 0/9] Reintrodce Hornet LSM
From: Blaise Boscaccy @ 2026-03-26 6:06 UTC (permalink / raw)
To: Blaise Boscaccy, Jonathan Corbet, Paul Moore, James Morris,
Serge E. Hallyn, Mickaël Salaün, Günther Noack,
Dr. David Alan Gilbert, Andrew Morton, James.Bottomley, dhowells,
Fan Wu, Ryan Foster, Randy Dunlap, linux-security-module,
linux-doc, linux-kernel, bpf
This patch series introduces the next iteration of the Hornet LSM.
Hornet’s goal is to provide a secure and extensible in-kernel
signature verification mechanism for eBPF programs.
Hornet addresses concerns from users who require strict audit trails and
verification guarantees for eBPF programs, especially in
security-sensitive environments. Many production systems need assurance
that only authorized, unmodified eBPF programs are loaded into the
kernel. Hornet provides this assurance through cryptographic signature
verification.
The currently accepted loader-plus-map signature verification scheme,
mandated by Alexei and KP, is simple to implement and generally
acceptable if users and administrators are satisfied with it. However,
verifying both the loader and the maps offers additional benefits
beyond verifying the loader alone:
1. Security and Audit Integrity
A key advantage is that the LSM hook for authorizing BPF program loads
can operate after signature verification. This ensures:
* Access control decisions are based on verified signature status.
* Accurate system state measurement and logging.
* Log entries claiming a verified signature are truthful, avoiding
misleading records where only the loader was verified while the actual
BPF program verification occurs later without logging.
2. TOCTOU Attack Prevention
The current map hash implementation may be vulnerable to a TOCTOU
attack because it allows unfrozen maps to cache a previously
calculated hash. The accepted “trusted loader” scheme cannot detect
this and may permit loading altered maps.
3. Supply Chain Integrity
Verify that eBPF programs and their associated map data have not been
modified since they were built and signed, in the kernel proper, may
aid in protecting against supply chain attacks.
This approach addresses concerns from users who require strict audit
trails and verification guarantees, especially in security-sensitive
environments. Map hashes for extended verification are passed via the
existing PKCS#7 UAPI and verified by the crypto subsystem. Hornet then
calculates the program’s verification state. Hornet itself does not
enforce a policy on whether unsigned or partially signed programs
should be rejected. It delegates that decision to downstream LSMs
hook, making it a composable building block in a larger security
architecture.
Changes in V3:
- Updated for signed attribute patch series changes
- Added some new result enum values
- Minor documentation clarification
- Misc style fixes
- Added missing signed-off-by tags
Link to V2: https://lore.kernel.org/linux-security-module/20260227233930.2418522-1-bboscaccy@linux.microsoft.com/
Changes in V2:
- Addressed possible TocTou races in hash verification
- Improved documentation and tooling
- Added Alexie's nack
Link to RFC: https://lore.kernel.org/linux-security-module/20251211021257.1208712-1-bboscaccy@linux.microsoft.com/
Blaise Boscaccy (5):
lsm: security: Add additional enum values for bpf integrity checks
security: Hornet LSM
hornet: Introduce gen_sig
hornet: Add a light skeleton data extractor scripts
selftests/hornet: Add a selftest for the Hornet LSM
James Bottomley (3):
crypto: pkcs7: add flag for validated trust on a signed info block
crypto: pkcs7: add ability to extract signed attributes by OID
crypto: pkcs7: add tests for pkcs7_get_authattr
Paul Moore (1):
lsm: framework for BPF integrity verification
Documentation/admin-guide/LSM/Hornet.rst | 321 +++++++++++++++
Documentation/admin-guide/LSM/index.rst | 1 +
MAINTAINERS | 9 +
certs/system_keyring.c | 1 +
crypto/asymmetric_keys/Makefile | 4 +-
crypto/asymmetric_keys/pkcs7_aa.asn1 | 18 +
crypto/asymmetric_keys/pkcs7_key_type.c | 44 ++-
crypto/asymmetric_keys/pkcs7_parser.c | 81 ++++
crypto/asymmetric_keys/pkcs7_parser.h | 1 +
crypto/asymmetric_keys/pkcs7_trust.c | 1 +
include/crypto/pkcs7.h | 4 +
include/linux/lsm_hook_defs.h | 5 +
include/linux/oid_registry.h | 3 +
include/linux/security.h | 28 ++
include/uapi/linux/lsm.h | 1 +
scripts/Makefile | 1 +
scripts/hornet/Makefile | 5 +
scripts/hornet/extract-insn.sh | 27 ++
scripts/hornet/extract-map.sh | 27 ++
scripts/hornet/extract-skel.sh | 27 ++
scripts/hornet/gen_sig.c | 392 +++++++++++++++++++
scripts/hornet/write-sig.sh | 27 ++
security/Kconfig | 3 +-
security/Makefile | 1 +
security/hornet/Kconfig | 11 +
security/hornet/Makefile | 7 +
security/hornet/hornet.asn1 | 13 +
security/hornet/hornet_lsm.c | 333 ++++++++++++++++
security/security.c | 75 +++-
tools/testing/selftests/Makefile | 1 +
tools/testing/selftests/hornet/Makefile | 63 +++
tools/testing/selftests/hornet/loader.c | 21 +
tools/testing/selftests/hornet/trivial.bpf.c | 33 ++
33 files changed, 1583 insertions(+), 6 deletions(-)
create mode 100644 Documentation/admin-guide/LSM/Hornet.rst
create mode 100644 crypto/asymmetric_keys/pkcs7_aa.asn1
create mode 100644 scripts/hornet/Makefile
create mode 100755 scripts/hornet/extract-insn.sh
create mode 100755 scripts/hornet/extract-map.sh
create mode 100755 scripts/hornet/extract-skel.sh
create mode 100644 scripts/hornet/gen_sig.c
create mode 100755 scripts/hornet/write-sig.sh
create mode 100644 security/hornet/Kconfig
create mode 100644 security/hornet/Makefile
create mode 100644 security/hornet/hornet.asn1
create mode 100644 security/hornet/hornet_lsm.c
create mode 100644 tools/testing/selftests/hornet/Makefile
create mode 100644 tools/testing/selftests/hornet/loader.c
create mode 100644 tools/testing/selftests/hornet/trivial.bpf.c
--
2.53.0
^ permalink raw reply
* Re: [RFC PATCH v2 1/2] lsm: add backing_file LSM hooks
From: Paul Moore @ 2026-03-25 17:36 UTC (permalink / raw)
To: Ryan Lee
Cc: linux-security-module, selinux, linux-fsdevel, linux-unionfs,
linux-erofs, Amir Goldstein, Gao Xiang
In-Reply-To: <CAKCV-6t=m-8eu1xoTORnLwhG4kQB5u1v5diJDQDFcat=tH8WgA@mail.gmail.com>
On Tue, Mar 24, 2026 at 7:01 PM Ryan Lee <ryan.lee@canonical.com> wrote:
>
> Hi Paul,
>
> I'm currently looking at the patch more closely to implement the hooks
> for AppArmor, but here are some typofixes and the like below:
Thanks Ryan, I appreciate the extra eyes.
> > diff --git a/include/linux/security.h b/include/linux/security.h
> > index 83a646d72f6f..1e4c68d5877f 100644
> > --- a/include/linux/security.h
> > +++ b/include/linux/security.h unsigned long prot);
> > @@ -1140,6 +1146,15 @@ static inline void security_file_release(struct file *file)
> > static inline void security_file_free(struct file *file)
> > { }
> >
> > +int security_backing_file_alloc(void **backing_file_blobp,
> > + const struct file *user_file)
> > +{
> > + return 0;
> > +}
> > +
> > +void security_backing_file_free(void **backing_file_blobp)
> > +{ }
> > +
>
> Should these two placeholders be static inline functions, like the
> other ones around them?
Yes :) The kernel test robot found the same problem yesterday, I've
already fixed it in my working branch.
> > diff --git a/security/lsm_init.c b/security/lsm_init.c
> > index 573e2a7250c4..020eace65973 100644
> > --- a/security/lsm_init.c
> > +++ b/security/lsm_init.c
> > @@ -293,6 +293,8 @@ static void __init lsm_prepare(struct lsm_info *lsm)
> > blobs = lsm->blobs;
> > lsm_blob_size_update(&blobs->lbs_cred, &blob_sizes.lbs_cred);
> > lsm_blob_size_update(&blobs->lbs_file, &blob_sizes.lbs_file);
> > + lsm_blob_size_update(&blobs->lbs_backing_file,
> > + &blob_sizes.lbs_backing_file);
> > lsm_blob_size_update(&blobs->lbs_ib, &blob_sizes.lbs_ib);
> > /* inode blob gets an rcu_head in addition to LSM blobs. */
> > if (blobs->lbs_inode && blob_sizes.lbs_inode == 0)
> > @@ -441,6 +443,8 @@ int __init security_init(void)
> > if (lsm_debug) {
> > lsm_pr("blob(cred) size %d\n", blob_sizes.lbs_cred);
> > lsm_pr("blob(file) size %d\n", blob_sizes.lbs_file);
> > + lsm_pr("blob(backing_file) size %d\n",
> > + blob_sizes.lbs_backing_file);
> > lsm_pr("blob(ib) size %d\n", blob_sizes.lbs_ib);
> > lsm_pr("blob(inode) size %d\n", blob_sizes.lbs_inode);
> > lsm_pr("blob(ipc) size %d\n", blob_sizes.lbs_ipc);
> > @@ -462,6 +466,11 @@ int __init security_init(void)
> > lsm_file_cache = kmem_cache_create("lsm_file_cache",
> > blob_sizes.lbs_file, 0,
> > SLAB_PANIC, NULL);
> > + if (blob_sizes.lbs_backing_file)
> > + lsm_backing_file_cache = kmem_cache_create(
> > + "lsm_backing_file_cache",
> > + blob_sizes.lbs_file, 0,
> > + SLAB_PANIC, NULL);
>
> Shouldn't blob_sizes.lbs_file here be blob_sizes.lbs_backing_file instead?
Good catch, thank you! I'll have the fix in the next posting. I'm
hoping to do some more testing today/tomorrow and post a non-RFC patch
by the end of the week. If you find anything else that looks awry, or
just doesn't work, please let me know.
--
paul-moore.com
^ permalink raw reply
* Re: [RFC PATCH v1 03/11] nsproxy: Add FOR_EACH_NS_TYPE() X-macro and CLONE_NS_ALL
From: Mickaël Salaün @ 2026-03-25 15:26 UTC (permalink / raw)
To: Christian Brauner
Cc: Günther Noack, Paul Moore, Serge E . Hallyn, Justin Suess,
Lennart Poettering, Mikhail Ivanov, Nicolas Bouchinet,
Shervin Oloumi, Tingmao Wang, kernel-team, linux-fsdevel,
linux-kernel, linux-security-module
In-Reply-To: <20260325-utensil-endung-6e28806ae92c@brauner>
On Wed, Mar 25, 2026 at 01:33:31PM +0100, Christian Brauner wrote:
> On Thu, Mar 12, 2026 at 11:04:36AM +0100, Mickaël Salaün wrote:
> > Introduce the FOR_EACH_NS_TYPE(X) macro as the single source of truth
> > for the set of (struct type, CLONE_NEW* flag) pairs that define Linux
> > namespace types.
> >
> > Currently, the list of CLONE_NEW* flags is duplicated inline in
> > multiple call sites and would need another copy in each new consumer.
> > This makes it easy to miss one when a new namespace type is added.
> >
> > Derive two things from the X-macro:
> >
> > - CLONE_NS_ALL: Bitmask of all known CLONE_NEW* flags, usable as a
> > validity mask or iteration bound.
> >
> > - ns_common_type(): Rewritten to use the X-macro via a leading-comma
> > _Generic pattern, so the struct-to-flag mapping stays in sync with the
> > flag set automatically.
> >
> > Replace the inline flag enumerations in copy_namespaces(),
> > unshare_nsproxy_namespaces(), check_setns_flags(), and
> > ksys_unshare() with CLONE_NS_ALL.
> >
> > When a new namespace type is added, only FOR_EACH_NS_TYPE needs to
> > be updated; CLONE_NS_ALL, ns_common_type(), and all the call sites
> > pick up the change automatically.
> >
> > Cc: Christian Brauner <brauner@kernel.org>
> > Cc: Günther Noack <gnoack@google.com>
> > Signed-off-by: Mickaël Salaün <mic@digikod.net>
> > ---
>
> Yeah, I love that. I can take that as a separate patch right now even.
Yes, please take it.
>
> Reviewed-by: Christian Brauner <brauner@kernel.org>
>
^ permalink raw reply
* Re: [RFC PATCH v1 00/11] Landlock: Namespace and capability control
From: Christian Brauner @ 2026-03-25 12:34 UTC (permalink / raw)
To: Mickaël Salaün
Cc: Günther Noack, Paul Moore, Serge E . Hallyn, Justin Suess,
Lennart Poettering, Mikhail Ivanov, Nicolas Bouchinet,
Shervin Oloumi, Tingmao Wang, kernel-team, linux-fsdevel,
linux-kernel, linux-security-module
In-Reply-To: <20260312100444.2609563-1-mic@digikod.net>
On Thu, Mar 12, 2026 at 11:04:33AM +0100, Mickaël Salaün wrote:
> Namespaces are a fundamental building block for containers and
> application sandboxes, but user namespace creation significantly widens
> the kernel attack surface. CVE-2022-0185 (filesystem mount parsing),
> CVE-2022-25636 and CVE-2023-32233 (netfilter), and CVE-2022-0492 (cgroup
> v1 release_agent) all demonstrate vulnerabilities exploitable only
> through capabilities gained via user namespaces. Some distributions
> block user namespace creation entirely, but this removes a useful
> isolation primitive. Fine-grained control allows trusted programs to
> use namespaces while preventing unnecessary exposure for programs that
> do not need them.
>
> Existing mechanisms (user.max_*_namespaces sysctls, userns_create LSM
> hook, PR_SET_NO_NEW_PRIVS, and capset) each address part of this threat
> but none provides per-process, fine-grained control over both namespace
> types and capabilities. Container runtimes resort to seccomp-based
> clone/unshare filtering, but seccomp cannot dereference clone3's flag
> structure, forcing runtimes to block clone3 entirely.
>
> Landlock's composable layer model enables several patterns: a user
> session manager can restrict namespace types and capabilities broadly
> while allowing trusted programs to create the namespaces they need, and
> each deeper layer can further restrict the allowed set. Container
> runtimes can similarly deny namespace creation inside managed
> containers.
>
> This series adds two new permission categories to Landlock:
>
> - LANDLOCK_PERM_NAMESPACE_ENTER: Restricts which namespace types a
> sandboxed process can acquire: both creation (unshare/clone) and entry
> (setns). User namespace creation has no capability check in the
> kernel, so this is the only enforcement mechanism for that entry
> point.
>
> - LANDLOCK_PERM_CAPABILITY_USE: Restricts which Linux capabilities a
> sandboxed process can use, regardless of how they were obtained
> (including through user namespace creation).
>
> Both use new handled_perm and LANDLOCK_RULE_* constants following the
> existing allow-list model. The UAPI uses raw CAP_* and CLONE_NEW*
> values directly; unknown values are silently accepted for forward
> compatibility (the allow-list denies them by default). The Landlock ABI
> version is bumped from 8 to 9.
>
> The handled_perm infrastructure is designed to be reusable by future
> permission categories. The last patch documents the design rationale
> for the permission model and the criteria for choosing between
> handled_access_*, handled_perm, and scoped. A patch series to add
> socket creation control is under review [2]; it could benefit from the
> same permission model to achieve complete deny-by-default coverage of
> socket creation.
>
> This series builds on Christian Brauner's namespace LSM blob RFC [1],
> included as patch 1.
>
> Christian, could you please review patch 3? It adds a FOR_EACH_NS_TYPE
> X-macro to ns_common_types.h and derives CLONE_NS_ALL, replacing inline
> CLONE_NEW* flag enumerations in nsproxy.c and fork.c.
This all looks good to me, thanks! I'd really love to see this go in.
^ permalink raw reply
* Re: [RFC PATCH v1 03/11] nsproxy: Add FOR_EACH_NS_TYPE() X-macro and CLONE_NS_ALL
From: Christian Brauner @ 2026-03-25 12:33 UTC (permalink / raw)
To: Mickaël Salaün
Cc: Günther Noack, Paul Moore, Serge E . Hallyn, Justin Suess,
Lennart Poettering, Mikhail Ivanov, Nicolas Bouchinet,
Shervin Oloumi, Tingmao Wang, kernel-team, linux-fsdevel,
linux-kernel, linux-security-module
In-Reply-To: <20260312100444.2609563-4-mic@digikod.net>
On Thu, Mar 12, 2026 at 11:04:36AM +0100, Mickaël Salaün wrote:
> Introduce the FOR_EACH_NS_TYPE(X) macro as the single source of truth
> for the set of (struct type, CLONE_NEW* flag) pairs that define Linux
> namespace types.
>
> Currently, the list of CLONE_NEW* flags is duplicated inline in
> multiple call sites and would need another copy in each new consumer.
> This makes it easy to miss one when a new namespace type is added.
>
> Derive two things from the X-macro:
>
> - CLONE_NS_ALL: Bitmask of all known CLONE_NEW* flags, usable as a
> validity mask or iteration bound.
>
> - ns_common_type(): Rewritten to use the X-macro via a leading-comma
> _Generic pattern, so the struct-to-flag mapping stays in sync with the
> flag set automatically.
>
> Replace the inline flag enumerations in copy_namespaces(),
> unshare_nsproxy_namespaces(), check_setns_flags(), and
> ksys_unshare() with CLONE_NS_ALL.
>
> When a new namespace type is added, only FOR_EACH_NS_TYPE needs to
> be updated; CLONE_NS_ALL, ns_common_type(), and all the call sites
> pick up the change automatically.
>
> Cc: Christian Brauner <brauner@kernel.org>
> Cc: Günther Noack <gnoack@google.com>
> Signed-off-by: Mickaël Salaün <mic@digikod.net>
> ---
Yeah, I love that. I can take that as a separate patch right now even.
Reviewed-by: Christian Brauner <brauner@kernel.org>
^ permalink raw reply
* Re: [RFC PATCH v1 02/11] security: Add LSM_AUDIT_DATA_NS for namespace audit records
From: Christian Brauner @ 2026-03-25 12:32 UTC (permalink / raw)
To: Mickaël Salaün
Cc: Günther Noack, Paul Moore, Serge E . Hallyn, Justin Suess,
Lennart Poettering, Mikhail Ivanov, Nicolas Bouchinet,
Shervin Oloumi, Tingmao Wang, kernel-team, linux-fsdevel,
linux-kernel, linux-security-module
In-Reply-To: <20260312100444.2609563-3-mic@digikod.net>
On Thu, Mar 12, 2026 at 11:04:35AM +0100, Mickaël Salaün wrote:
> Add a new LSM audit data type LSM_AUDIT_DATA_NS that logs namespace
> information in audit records. Two fields are provided, matching the
> field names of struct ns_common:
>
> - ns_type: the CLONE_NEW* flag identifying the namespace type, logged in
> hexadecimal.
>
> - inum: the proc inode number identifying a specific namespace instance.
> Namespace inode numbers are allocated by proc_alloc_inum() via
> ida_alloc_max() bounded to UINT_MAX, so the value always fits in 32
> bits.
>
> A new audit data type is needed because no existing LSM_AUDIT_DATA_*
> type carries namespace information. The closest alternatives (e.g.
> LSM_AUDIT_DATA_TASK or LSM_AUDIT_DATA_NONE with custom strings) would
> either lose the namespace type or require ad-hoc formatting that
> bypasses the structured audit data union.
>
> Cc: Christian Brauner <brauner@kernel.org>
> Cc: Günther Noack <gnoack@google.com>
> Cc: Paul Moore <paul@paul-moore.com>
> Signed-off-by: Mickaël Salaün <mic@digikod.net>
> ---
> include/linux/lsm_audit.h | 5 +++++
> security/lsm_audit.c | 4 ++++
> 2 files changed, 9 insertions(+)
>
> diff --git a/include/linux/lsm_audit.h b/include/linux/lsm_audit.h
> index 382c56a97bba..6e20a56b8c22 100644
> --- a/include/linux/lsm_audit.h
> +++ b/include/linux/lsm_audit.h
> @@ -78,6 +78,7 @@ struct common_audit_data {
> #define LSM_AUDIT_DATA_NOTIFICATION 16
> #define LSM_AUDIT_DATA_ANONINODE 17
> #define LSM_AUDIT_DATA_NLMSGTYPE 18
> +#define LSM_AUDIT_DATA_NS 19
> union {
> struct path path;
> struct dentry *dentry;
> @@ -100,6 +101,10 @@ struct common_audit_data {
> int reason;
> const char *anonclass;
> u16 nlmsg_type;
> + struct {
> + u32 ns_type;
> + unsigned int inum;
fwiw, you might want to start the 64-bit namespace id as well.
But either way:
Reviewed-by: Christian Brauner <brauner@kernel.org>
^ permalink raw reply
* Re: [RFC PATCH v1 01/11] security: add LSM blob and hooks for namespaces
From: Christian Brauner @ 2026-03-25 12:31 UTC (permalink / raw)
To: Mickaël Salaün
Cc: Günther Noack, Paul Moore, Serge E . Hallyn, Justin Suess,
Lennart Poettering, Mikhail Ivanov, Nicolas Bouchinet,
Shervin Oloumi, Tingmao Wang, kernel-team, linux-fsdevel,
linux-kernel, linux-security-module
In-Reply-To: <20260312100444.2609563-2-mic@digikod.net>
On Thu, Mar 12, 2026 at 11:04:34AM +0100, Mickaël Salaün wrote:
> From: Christian Brauner <brauner@kernel.org>
>
> All namespace types now share the same ns_common infrastructure. Extend
> this to include a security blob so LSMs can start managing namespaces
> uniformly without having to add one-off hooks or security fields to
> every individual namespace type.
>
> Add a ns_security pointer to ns_common and the corresponding lbs_ns
> blob size to lsm_blob_sizes. Allocation and freeing hooks are called
> from the common __ns_common_init() and __ns_common_free() paths so
> every namespace type gets covered in one go. All information about the
> namespace type and the appropriate casting helpers to get at the
> containing namespace are available via ns_common making it
> straightforward for LSMs to differentiate when they need to.
>
> A namespace_install hook is called from validate_ns() during setns(2)
> giving LSMs a chance to enforce policy on namespace transitions.
>
> Individual namespace types can still have their own specialized security
> hooks when needed. This is just the common baseline that makes it easy
> to track and manage namespaces from the security side without requiring
> every namespace type to reinvent the wheel.
>
> Cc: Günther Noack <gnoack@google.com>
> Cc: Paul Moore <paul@paul-moore.com>
> Cc: Serge E. Hallyn <serge@hallyn.com>
> Signed-off-by: Christian Brauner <brauner@kernel.org>
> Link: https://lore.kernel.org/r/20260216-work-security-namespace-v1-1-075c28758e1f@kernel.org
> ---
> include/linux/lsm_hook_defs.h | 3 ++
> include/linux/lsm_hooks.h | 1 +
> include/linux/ns/ns_common_types.h | 3 ++
> include/linux/security.h | 20 ++++++++
> kernel/nscommon.c | 12 +++++
> kernel/nsproxy.c | 8 +++-
> security/lsm_init.c | 2 +
> security/security.c | 76 ++++++++++++++++++++++++++++++
> 8 files changed, 124 insertions(+), 1 deletion(-)
>
> diff --git a/include/linux/lsm_hook_defs.h b/include/linux/lsm_hook_defs.h
> index 8c42b4bde09c..fefd3aa6d8f4 100644
> --- a/include/linux/lsm_hook_defs.h
> +++ b/include/linux/lsm_hook_defs.h
> @@ -260,6 +260,9 @@ LSM_HOOK(int, -ENOSYS, task_prctl, int option, unsigned long arg2,
> LSM_HOOK(void, LSM_RET_VOID, task_to_inode, struct task_struct *p,
> struct inode *inode)
> LSM_HOOK(int, 0, userns_create, const struct cred *cred)
> +LSM_HOOK(int, 0, namespace_alloc, struct ns_common *ns)
> +LSM_HOOK(void, LSM_RET_VOID, namespace_free, struct ns_common *ns)
> +LSM_HOOK(int, 0, namespace_install, const struct nsset *nsset, struct ns_common *ns)
> LSM_HOOK(int, 0, ipc_permission, struct kern_ipc_perm *ipcp, short flag)
> LSM_HOOK(void, LSM_RET_VOID, ipc_getlsmprop, struct kern_ipc_perm *ipcp,
> struct lsm_prop *prop)
> diff --git a/include/linux/lsm_hooks.h b/include/linux/lsm_hooks.h
> index d48bf0ad26f4..3e7afe76e86c 100644
> --- a/include/linux/lsm_hooks.h
> +++ b/include/linux/lsm_hooks.h
> @@ -111,6 +111,7 @@ struct lsm_blob_sizes {
> unsigned int lbs_ipc;
> unsigned int lbs_key;
> unsigned int lbs_msg_msg;
> + unsigned int lbs_ns;
> unsigned int lbs_perf_event;
> unsigned int lbs_task;
> unsigned int lbs_xattr_count; /* num xattr slots in new_xattrs array */
> diff --git a/include/linux/ns/ns_common_types.h b/include/linux/ns/ns_common_types.h
> index 0014fbc1c626..170288e2e895 100644
> --- a/include/linux/ns/ns_common_types.h
> +++ b/include/linux/ns/ns_common_types.h
> @@ -115,6 +115,9 @@ struct ns_common {
> struct dentry *stashed;
> const struct proc_ns_operations *ops;
> unsigned int inum;
> +#ifdef CONFIG_SECURITY
> + void *ns_security;
> +#endif
> union {
> struct ns_tree;
> struct rcu_head ns_rcu;
> diff --git a/include/linux/security.h b/include/linux/security.h
> index 83a646d72f6f..611b9098367d 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 nsset;
>
> /* Default (no) options for the capable function */
> #define CAP_OPT_NONE 0x0
> @@ -80,6 +81,7 @@ struct lsm_ctx;
>
> struct ctl_table;
> struct audit_krule;
> +struct ns_common;
> struct user_namespace;
> struct timezone;
>
> @@ -533,6 +535,9 @@ int security_task_prctl(int option, unsigned long arg2, unsigned long arg3,
> unsigned long arg4, unsigned long arg5);
> void security_task_to_inode(struct task_struct *p, struct inode *inode);
> int security_create_user_ns(const struct cred *cred);
> +int security_namespace_alloc(struct ns_common *ns);
> +void security_namespace_free(struct ns_common *ns);
> +int security_namespace_install(const struct nsset *nsset, struct ns_common *ns);
> int security_ipc_permission(struct kern_ipc_perm *ipcp, short flag);
> void security_ipc_getlsmprop(struct kern_ipc_perm *ipcp, struct lsm_prop *prop);
> int security_msg_msg_alloc(struct msg_msg *msg);
> @@ -1407,6 +1412,21 @@ static inline int security_create_user_ns(const struct cred *cred)
> return 0;
> }
>
> +static inline int security_namespace_alloc(struct ns_common *ns)
> +{
> + return 0;
> +}
> +
> +static inline void security_namespace_free(struct ns_common *ns)
> +{
> +}
> +
> +static inline int security_namespace_install(const struct nsset *nsset,
> + struct ns_common *ns)
> +{
> + return 0;
> +}
> +
> static inline int security_ipc_permission(struct kern_ipc_perm *ipcp,
> short flag)
> {
> diff --git a/kernel/nscommon.c b/kernel/nscommon.c
> index bdc3c86231d3..de774e374f9d 100644
> --- a/kernel/nscommon.c
> +++ b/kernel/nscommon.c
> @@ -4,6 +4,7 @@
> #include <linux/ns_common.h>
> #include <linux/nstree.h>
> #include <linux/proc_ns.h>
> +#include <linux/security.h>
> #include <linux/user_namespace.h>
> #include <linux/vfsdebug.h>
>
> @@ -59,6 +60,9 @@ int __ns_common_init(struct ns_common *ns, u32 ns_type, const struct proc_ns_ope
>
> refcount_set(&ns->__ns_ref, 1);
> ns->stashed = NULL;
> +#ifdef CONFIG_SECURITY
> + ns->ns_security = NULL;
> +#endif
> ns->ops = ops;
> ns->ns_id = 0;
> ns->ns_type = ns_type;
> @@ -77,6 +81,13 @@ int __ns_common_init(struct ns_common *ns, u32 ns_type, const struct proc_ns_ope
> ret = proc_alloc_inum(&ns->inum);
> if (ret)
> return ret;
> +
> + ret = security_namespace_alloc(ns);
> + if (ret) {
> + proc_free_inum(ns->inum);
ret = security_namespace_alloc(ns);
if (ret && !inum)
proc_free_inum(ns->inum);
return ret;
> + return ret;
> + }
> +
> /*
> * Tree ref starts at 0. It's incremented when namespace enters
> * active use (installed in nsproxy) and decremented when all
> @@ -91,6 +102,7 @@ int __ns_common_init(struct ns_common *ns, u32 ns_type, const struct proc_ns_ope
>
> void __ns_common_free(struct ns_common *ns)
> {
> + security_namespace_free(ns);
> proc_free_inum(ns->inum);
> }
>
> diff --git a/kernel/nsproxy.c b/kernel/nsproxy.c
> index 259c4b4f1eeb..f0b30d1907e7 100644
> --- a/kernel/nsproxy.c
> +++ b/kernel/nsproxy.c
> @@ -379,7 +379,13 @@ static int prepare_nsset(unsigned flags, struct nsset *nsset)
>
> static inline int validate_ns(struct nsset *nsset, struct ns_common *ns)
> {
> - return ns->ops->install(nsset, ns);
> + int ret;
> +
> + ret = ns->ops->install(nsset, ns);
> + if (ret)
> + return ret;
> +
> + return security_namespace_install(nsset, ns);
In my local tree I had that moved before the ->install() and I think
that's the correct thing to do. So please switch to that.
The rest looks good to me, thanks.
^ permalink raw reply
* Re: [PATCH 6/7] tomoyo: Convert from sb_mount to granular mount hooks
From: Song Liu @ 2026-03-25 1:35 UTC (permalink / raw)
To: Tetsuo Handa
Cc: Song Liu, Christian Brauner, viro@zeniv.linux.org.uk,
paul@paul-moore.com, jmorris@namei.org, serge@hallyn.com,
jack@suse.cz, john.johansen@canonical.com,
stephen.smalley.work@gmail.com, omosnace@redhat.com,
mic@digikod.net, gnoack@google.com, takedakn@nttdata.co.jp,
herton@canonical.com, Kernel Team, selinux@vger.kernel.org,
apparmor@lists.ubuntu.com, linux-fsdevel@vger.kernel.org,
linux-security-module@vger.kernel.org
In-Reply-To: <4f5d1b1f-ecb2-421a-8a46-36c7a12d48de@I-love.SAKURA.ne.jp>
On Tue, Mar 24, 2026 at 6:02 PM Tetsuo Handa
<penguin-kernel@i-love.sakura.ne.jp> wrote:
[...]
> >>>> I guess something like untested diff shown below would work.
> >>>
> >>> I think this doesn't work with erofs on file (requires
> >>> CONFIG_EROFS_FS_BACKED_BY_FILE). erofs may not be the
> >>> only one that has this problem.
> >>
> >> This is incomplete but I think this is better than now because currently
> >> mount() operation likely fails with -ENOENT if the requested filesystem
> >> does not interpret fc->source as a pathname despite tomoyo_mount_acl()
> >> always interprets fc->source as a pathname when FS_REQUIRES_DEV is set.
> >
> > If I understand Christian correctly, the main challenge here is that
> > FS_REQUIRES_DEV doesn't imply fc->source is the path of a device.
>
> Correct. FS_REQUIRES_DEV no longer implies that fc->source is a pathname.
>
> > Changing this assumption is a major change between VFS and many
> > filesystems.
>
> Wrong. I'm not trying to change this assumption. I'm trying to move LSM hook
> to a location after fc->source was interpreted by individual filesystem.
OK, I can understand your point now. And I don't see a big red flag with it.
> >
> > I was thinking about something like:
> >
> > diff --git i/fs/super.c w/fs/super.c
> > index 378e81efe643..91ce3003bc23 100644
> > --- i/fs/super.c
> > +++ w/fs/super.c
> > @@ -1676,6 +1676,9 @@ int get_tree_bdev_flags(struct fs_context *fc,
> > errorf(fc, "%s: Can't lookup blockdev", fc->source);
> > return error;
> > }
> > + error = security_mount_dev(fc, dev);
> > + if (error)
> > + return error;
> > fc->sb_flags |= SB_NOSEC;
> > s = sget_dev(fc, dev);
> > if (IS_ERR(s))
> >
> > This allows the LSMs to monitor the dev being mounted in a new mount.
>
> Splitting into multiple LSM hooks does not work, for TOMOYO wants to check
> all parameters (parameters currently passed to security_mount_new() + the
> "struct path" which was resolved by individual filesystem from fc->source
> parameter) in one location.
>
> I'm not sure how security_mount_new() is called for fsconfig() case.
> Does https://man7.org/linux/man-pages/man2/fsconfig.2.html#EXAMPLES mean
> TOMOYO cannot check all parameters until move_mount() is called?
We need to add hooks for fsopen(), fsconfig(), etc. I have some basic code
for these. But I would rather we address this set first. After this, the other
hooks should be more straightforward.
Thanks,
Song
^ permalink raw reply
* Re: [PATCH 6/7] tomoyo: Convert from sb_mount to granular mount hooks
From: Tetsuo Handa @ 2026-03-25 1:01 UTC (permalink / raw)
To: Song Liu
Cc: Song Liu, Christian Brauner, viro@zeniv.linux.org.uk,
paul@paul-moore.com, jmorris@namei.org, serge@hallyn.com,
jack@suse.cz, john.johansen@canonical.com,
stephen.smalley.work@gmail.com, omosnace@redhat.com,
mic@digikod.net, gnoack@google.com, takedakn@nttdata.co.jp,
herton@canonical.com, Kernel Team, selinux@vger.kernel.org,
apparmor@lists.ubuntu.com, linux-fsdevel@vger.kernel.org,
linux-security-module@vger.kernel.org
In-Reply-To: <CAPhsuW4E_BrF0ap5yg_6TbRAna=2Ajk2nuoT6WGwkS5cyyYB3w@mail.gmail.com>
On 2026/03/25 4:03, Song Liu wrote:
> On Tue, Mar 24, 2026 at 2:59 AM Tetsuo Handa
> <penguin-kernel@i-love.sakura.ne.jp> wrote:
>>
>> On 2026/03/24 16:46, Song Liu wrote:
>>> On Mon, Mar 23, 2026 at 11:12 PM Tetsuo Handa
>>> <penguin-kernel@i-love.sakura.ne.jp> wrote:
>>>>
>>>> On 2026/03/24 4:31, Song Liu wrote:
>>>>>> Then, how can LSM modules know that how the requested filesystem resolves
>>>>>> the dev_name argument, without embedding filesystem specific resolution
>>>>>> logic into individual LSM module?
>>>>>
>>>>> IIUC, if an LSM cares about the dev_name of a new mount, it will have to look
>>>>> into each individual filesystem. We can add a LSM hook for the filesystems to
>>>>> call. But this will require changes to individual filesystem code. OTOH,
>>>>> dev_name can probably bridge the gap as we change filesystems.
>>>>>
>>>>> Would this work?
>>>>
>>>> I guess something like untested diff shown below would work.
>>>
>>> I think this doesn't work with erofs on file (requires
>>> CONFIG_EROFS_FS_BACKED_BY_FILE). erofs may not be the
>>> only one that has this problem.
>>
>> This is incomplete but I think this is better than now because currently
>> mount() operation likely fails with -ENOENT if the requested filesystem
>> does not interpret fc->source as a pathname despite tomoyo_mount_acl()
>> always interprets fc->source as a pathname when FS_REQUIRES_DEV is set.
>
> If I understand Christian correctly, the main challenge here is that
> FS_REQUIRES_DEV doesn't imply fc->source is the path of a device.
Correct. FS_REQUIRES_DEV no longer implies that fc->source is a pathname.
> Changing this assumption is a major change between VFS and many
> filesystems.
Wrong. I'm not trying to change this assumption. I'm trying to move LSM hook
to a location after fc->source was interpreted by individual filesystem.
>
> I was thinking about something like:
>
> diff --git i/fs/super.c w/fs/super.c
> index 378e81efe643..91ce3003bc23 100644
> --- i/fs/super.c
> +++ w/fs/super.c
> @@ -1676,6 +1676,9 @@ int get_tree_bdev_flags(struct fs_context *fc,
> errorf(fc, "%s: Can't lookup blockdev", fc->source);
> return error;
> }
> + error = security_mount_dev(fc, dev);
> + if (error)
> + return error;
> fc->sb_flags |= SB_NOSEC;
> s = sget_dev(fc, dev);
> if (IS_ERR(s))
>
> This allows the LSMs to monitor the dev being mounted in a new mount.
Splitting into multiple LSM hooks does not work, for TOMOYO wants to check
all parameters (parameters currently passed to security_mount_new() + the
"struct path" which was resolved by individual filesystem from fc->source
parameter) in one location.
I'm not sure how security_mount_new() is called for fsconfig() case.
Does https://man7.org/linux/man-pages/man2/fsconfig.2.html#EXAMPLES mean
TOMOYO cannot check all parameters until move_mount() is called?
^ permalink raw reply
* Re: [RFC PATCH v2 1/2] lsm: add backing_file LSM hooks
From: Ryan Lee @ 2026-03-24 23:01 UTC (permalink / raw)
To: Paul Moore
Cc: linux-security-module, selinux, linux-fsdevel, linux-unionfs,
linux-erofs, Amir Goldstein, Gao Xiang
In-Reply-To: <20260323042510.3331778-5-paul@paul-moore.com>
Hi Paul,
I'm currently looking at the patch more closely to implement the hooks
for AppArmor, but
here are some typofixes and the like below:
On Sun, Mar 22, 2026 at 9:26 PM Paul Moore <paul@paul-moore.com> wrote:
>
> Stacked filesystems such as overlayfs do not currently provide the
> necessary mechanisms for LSMs to properly enforce access controls on the
> mmap() and mprotect() operations. In order to resolve this gap, a LSM
> security blob is being added to the backing_file struct and the following
> new LSM hooks are being created:
>
> security_backing_file_alloc()
> security_backing_file_free()
> security_mmap_backing_file()
>
> The first two hooks are to manage the lifecycle of the LSM security blob
> in the backing_file struct, while the third provides a new mmap() access
> control point for the underlying backing file. It is also expected that
> LSMs will likely want to update their security_file_mprotect() callback
> to address issues with their mprotect() controls, but that does not
> require a change to the security_file_mprotect() LSM hook.
>
> There are a two other small changes to support these new LSM hooks. We
> pass the user file associated with a backing file down to
> alloc_empty_backing_file() so it can be included in the
> security_backing_file_alloc() hook, and we constify the file struct field
> in the LSM common_audit_data struct to better support LSMs that need to
> pass a const file struct pointer into the common LSM audit code.
>
> Thanks to Arnd Bergmann for identifying the missing EXPORT_SYMBOL_GPL()
> and supplying a fixup.
>
> Cc: stable@vger.kernel.org
> Signed-off-by: Paul Moore <paul@paul-moore.com>
> ---
> fs/backing-file.c | 18 ++++--
> fs/erofs/ishare.c | 10 +++-
> fs/file_table.c | 21 ++++++-
> fs/fuse/passthrough.c | 2 +-
> fs/internal.h | 3 +-
> fs/overlayfs/dir.c | 2 +-
> fs/overlayfs/file.c | 2 +-
> include/linux/backing-file.h | 4 +-
> include/linux/fs.h | 1 +
> include/linux/lsm_audit.h | 2 +-
> include/linux/lsm_hook_defs.h | 5 ++
> include/linux/lsm_hooks.h | 1 +
> include/linux/security.h | 22 ++++++++
> security/lsm.h | 1 +
> security/lsm_init.c | 9 +++
> security/security.c | 100 ++++++++++++++++++++++++++++++++++
> 16 files changed, 187 insertions(+), 16 deletions(-)
>
> diff --git a/fs/backing-file.c b/fs/backing-file.c
> index 45da8600d564..1f3bbfc75882 100644
> --- a/fs/backing-file.c
> +++ b/fs/backing-file.c
> @@ -12,6 +12,7 @@
> #include <linux/backing-file.h>
> #include <linux/splice.h>
> #include <linux/mm.h>
> +#include <linux/security.h>
>
> #include "internal.h"
>
> @@ -29,14 +30,15 @@
> * returned file into a container structure that also stores the stacked
> * file's path, which can be retrieved using backing_file_user_path().
> */
> -struct file *backing_file_open(const struct path *user_path, int flags,
> +struct file *backing_file_open(const struct file *user_file, int flags,
> const struct path *real_path,
> const struct cred *cred)
> {
> + const struct path *user_path = &user_file->f_path;
> struct file *f;
> int error;
>
> - f = alloc_empty_backing_file(flags, cred);
> + f = alloc_empty_backing_file(flags, cred, user_file);
> if (IS_ERR(f))
> return f;
>
> @@ -52,15 +54,16 @@ struct file *backing_file_open(const struct path *user_path, int flags,
> }
> EXPORT_SYMBOL_GPL(backing_file_open);
>
> -struct file *backing_tmpfile_open(const struct path *user_path, int flags,
> +struct file *backing_tmpfile_open(const struct file *user_file, int flags,
> const struct path *real_parentpath,
> umode_t mode, const struct cred *cred)
> {
> struct mnt_idmap *real_idmap = mnt_idmap(real_parentpath->mnt);
> + const struct path *user_path = &user_file->f_path;
> struct file *f;
> int error;
>
> - f = alloc_empty_backing_file(flags, cred);
> + f = alloc_empty_backing_file(flags, cred, user_file);
> if (IS_ERR(f))
> return f;
>
> @@ -336,8 +339,13 @@ int backing_file_mmap(struct file *file, struct vm_area_struct *vma,
>
> vma_set_file(vma, file);
>
> - scoped_with_creds(ctx->cred)
> + scoped_with_creds(ctx->cred) {
> + ret = security_mmap_backing_file(vma, file, user_file);
> + if (ret)
> + return ret;
> +
> ret = vfs_mmap(vma->vm_file, vma);
> + }
>
> if (ctx->accessed)
> ctx->accessed(user_file);
> diff --git a/fs/erofs/ishare.c b/fs/erofs/ishare.c
> index 829d50d5c717..ec3fc5ac1a55 100644
> --- a/fs/erofs/ishare.c
> +++ b/fs/erofs/ishare.c
> @@ -4,6 +4,7 @@
> */
> #include <linux/xxhash.h>
> #include <linux/mount.h>
> +#include <linux/security.h>
> #include "internal.h"
> #include "xattr.h"
>
> @@ -106,7 +107,8 @@ static int erofs_ishare_file_open(struct inode *inode, struct file *file)
>
> if (file->f_flags & O_DIRECT)
> return -EINVAL;
> - realfile = alloc_empty_backing_file(O_RDONLY|O_NOATIME, current_cred());
> + realfile = alloc_empty_backing_file(O_RDONLY|O_NOATIME, current_cred(),
> + file);
> if (IS_ERR(realfile))
> return PTR_ERR(realfile);
> ihold(sharedinode);
> @@ -150,8 +152,14 @@ static ssize_t erofs_ishare_file_read_iter(struct kiocb *iocb,
> static int erofs_ishare_mmap(struct file *file, struct vm_area_struct *vma)
> {
> struct file *realfile = file->private_data;
> + int err;
>
> vma_set_file(vma, realfile);
> +
> + err = security_mmap_backing_file(vma, realfile, file);
> + if (err)
> + return err;
> +
> return generic_file_readonly_mmap(file, vma);
> }
>
> diff --git a/fs/file_table.c b/fs/file_table.c
> index aaa5faaace1e..0bdc26cae138 100644
> --- a/fs/file_table.c
> +++ b/fs/file_table.c
> @@ -50,6 +50,7 @@ struct backing_file {
> struct path user_path;
> freeptr_t bf_freeptr;
> };
> + void *security;
> };
>
> #define backing_file(f) container_of(f, struct backing_file, file)
> @@ -66,6 +67,11 @@ void backing_file_set_user_path(struct file *f, const struct path *path)
> }
> EXPORT_SYMBOL_GPL(backing_file_set_user_path);
>
> +void *backing_file_security(const struct file *f)
> +{
> + return backing_file(f)->security;
> +}
> +
> static inline void file_free(struct file *f)
> {
> security_file_free(f);
> @@ -73,8 +79,11 @@ static inline void file_free(struct file *f)
> percpu_counter_dec(&nr_files);
> put_cred(f->f_cred);
> if (unlikely(f->f_mode & FMODE_BACKING)) {
> - path_put(backing_file_user_path(f));
> - kmem_cache_free(bfilp_cachep, backing_file(f));
> + struct backing_file *ff = backing_file(f);
> +
> + security_backing_file_free(&ff->security);
> + path_put(&ff->user_path);
> + kmem_cache_free(bfilp_cachep, ff);
> } else {
> kmem_cache_free(filp_cachep, f);
> }
> @@ -290,7 +299,8 @@ struct file *alloc_empty_file_noaccount(int flags, const struct cred *cred)
> * This is only for kernel internal use, and the allocate file must not be
> * installed into file tables or such.
> */
> -struct file *alloc_empty_backing_file(int flags, const struct cred *cred)
> +struct file *alloc_empty_backing_file(int flags, const struct cred *cred,
> + const struct file *user_file)
> {
> struct backing_file *ff;
> int error;
> @@ -306,6 +316,11 @@ struct file *alloc_empty_backing_file(int flags, const struct cred *cred)
> }
>
> ff->file.f_mode |= FMODE_BACKING | FMODE_NOACCOUNT;
> + error = security_backing_file_alloc(&ff->security, user_file);
> + if (unlikely(error)) {
> + fput(&ff->file);
> + return ERR_PTR(error);
> + }
> return &ff->file;
> }
> EXPORT_SYMBOL_GPL(alloc_empty_backing_file);
> diff --git a/fs/fuse/passthrough.c b/fs/fuse/passthrough.c
> index 72de97c03d0e..f2d08ac2459b 100644
> --- a/fs/fuse/passthrough.c
> +++ b/fs/fuse/passthrough.c
> @@ -167,7 +167,7 @@ struct fuse_backing *fuse_passthrough_open(struct file *file, int backing_id)
> goto out;
>
> /* Allocate backing file per fuse file to store fuse path */
> - backing_file = backing_file_open(&file->f_path, file->f_flags,
> + backing_file = backing_file_open(file, file->f_flags,
> &fb->file->f_path, fb->cred);
> err = PTR_ERR(backing_file);
> if (IS_ERR(backing_file)) {
> diff --git a/fs/internal.h b/fs/internal.h
> index cbc384a1aa09..77e90e4124e0 100644
> --- a/fs/internal.h
> +++ b/fs/internal.h
> @@ -106,7 +106,8 @@ extern void chroot_fs_refs(const struct path *, const struct path *);
> */
> struct file *alloc_empty_file(int flags, const struct cred *cred);
> struct file *alloc_empty_file_noaccount(int flags, const struct cred *cred);
> -struct file *alloc_empty_backing_file(int flags, const struct cred *cred);
> +struct file *alloc_empty_backing_file(int flags, const struct cred *cred,
> + const struct file *user_file);
> void backing_file_set_user_path(struct file *f, const struct path *path);
>
> static inline void file_put_write_access(struct file *file)
> diff --git a/fs/overlayfs/dir.c b/fs/overlayfs/dir.c
> index ff3dbd1ca61f..f2f20a611af3 100644
> --- a/fs/overlayfs/dir.c
> +++ b/fs/overlayfs/dir.c
> @@ -1374,7 +1374,7 @@ static int ovl_create_tmpfile(struct file *file, struct dentry *dentry,
> return PTR_ERR(cred);
>
> ovl_path_upper(dentry->d_parent, &realparentpath);
> - realfile = backing_tmpfile_open(&file->f_path, flags, &realparentpath,
> + realfile = backing_tmpfile_open(file, flags, &realparentpath,
> mode, current_cred());
> err = PTR_ERR_OR_ZERO(realfile);
> pr_debug("tmpfile/open(%pd2, 0%o) = %i\n", realparentpath.dentry, mode, err);
> diff --git a/fs/overlayfs/file.c b/fs/overlayfs/file.c
> index 97bed2286030..27cc07738f33 100644
> --- a/fs/overlayfs/file.c
> +++ b/fs/overlayfs/file.c
> @@ -48,7 +48,7 @@ static struct file *ovl_open_realfile(const struct file *file,
> if (!inode_owner_or_capable(real_idmap, realinode))
> flags &= ~O_NOATIME;
>
> - realfile = backing_file_open(file_user_path(file),
> + realfile = backing_file_open(file,
> flags, realpath, current_cred());
> }
> }
> diff --git a/include/linux/backing-file.h b/include/linux/backing-file.h
> index 1476a6ed1bfd..c939cd222730 100644
> --- a/include/linux/backing-file.h
> +++ b/include/linux/backing-file.h
> @@ -18,10 +18,10 @@ struct backing_file_ctx {
> void (*end_write)(struct kiocb *iocb, ssize_t);
> };
>
> -struct file *backing_file_open(const struct path *user_path, int flags,
> +struct file *backing_file_open(const struct file *user_file, int flags,
> const struct path *real_path,
> const struct cred *cred);
> -struct file *backing_tmpfile_open(const struct path *user_path, int flags,
> +struct file *backing_tmpfile_open(const struct file *user_file, int flags,
> const struct path *real_parentpath,
> umode_t mode, const struct cred *cred);
> ssize_t backing_file_read_iter(struct file *file, struct iov_iter *iter,
> diff --git a/include/linux/fs.h b/include/linux/fs.h
> index 8b3dd145b25e..8f5702cfb5e0 100644
> --- a/include/linux/fs.h
> +++ b/include/linux/fs.h
> @@ -2474,6 +2474,7 @@ struct file *dentry_open_nonotify(const struct path *path, int flags,
> struct file *dentry_create(struct path *path, int flags, umode_t mode,
> const struct cred *cred);
> const struct path *backing_file_user_path(const struct file *f);
> +void *backing_file_security(const struct file *f);
>
> /*
> * When mmapping a file on a stackable filesystem (e.g., overlayfs), the file
> diff --git a/include/linux/lsm_audit.h b/include/linux/lsm_audit.h
> index 382c56a97bba..584db296e43b 100644
> --- a/include/linux/lsm_audit.h
> +++ b/include/linux/lsm_audit.h
> @@ -94,7 +94,7 @@ struct common_audit_data {
> #endif
> char *kmod_name;
> struct lsm_ioctlop_audit *op;
> - struct file *file;
> + const struct file *file;
> struct lsm_ibpkey_audit *ibpkey;
> struct lsm_ibendport_audit *ibendport;
> int reason;
> diff --git a/include/linux/lsm_hook_defs.h b/include/linux/lsm_hook_defs.h
> index 8c42b4bde09c..2c4da40757ad 100644
> --- a/include/linux/lsm_hook_defs.h
> +++ b/include/linux/lsm_hook_defs.h
> @@ -191,6 +191,9 @@ LSM_HOOK(int, 0, file_permission, struct file *file, int mask)
> LSM_HOOK(int, 0, file_alloc_security, struct file *file)
> LSM_HOOK(void, LSM_RET_VOID, file_release, struct file *file)
> LSM_HOOK(void, LSM_RET_VOID, file_free_security, struct file *file)
> +LSM_HOOK(int, 0, backing_file_alloc, void *backing_file_blobp,
> + const struct file *user_file)
> +LSM_HOOK(void, LSM_RET_VOID, backing_file_free, void *backing_file_blobp)
> LSM_HOOK(int, 0, file_ioctl, struct file *file, unsigned int cmd,
> unsigned long arg)
> LSM_HOOK(int, 0, file_ioctl_compat, struct file *file, unsigned int cmd,
> @@ -198,6 +201,8 @@ LSM_HOOK(int, 0, file_ioctl_compat, struct file *file, unsigned int cmd,
> LSM_HOOK(int, 0, mmap_addr, unsigned long addr)
> LSM_HOOK(int, 0, mmap_file, struct file *file, unsigned long reqprot,
> unsigned long prot, unsigned long flags)
> +LSM_HOOK(int, 0, mmap_backing_file, struct vm_area_struct *vma,
> + struct file *backing_file, struct file *user_file)
> LSM_HOOK(int, 0, file_mprotect, struct vm_area_struct *vma,
> unsigned long reqprot, unsigned long prot)
> LSM_HOOK(int, 0, file_lock, struct file *file, unsigned int cmd)
> diff --git a/include/linux/lsm_hooks.h b/include/linux/lsm_hooks.h
> index d48bf0ad26f4..b4f8cad53ddb 100644
> --- a/include/linux/lsm_hooks.h
> +++ b/include/linux/lsm_hooks.h
> @@ -104,6 +104,7 @@ struct security_hook_list {
> struct lsm_blob_sizes {
> unsigned int lbs_cred;
> unsigned int lbs_file;
> + unsigned int lbs_backing_file;
> unsigned int lbs_ib;
> unsigned int lbs_inode;
> unsigned int lbs_sock;
> diff --git a/include/linux/security.h b/include/linux/security.h
> index 83a646d72f6f..1e4c68d5877f 100644
> --- a/include/linux/security.h
> +++ b/include/linux/security.h
> @@ -471,11 +471,17 @@ int security_file_permission(struct file *file, int mask);
> int security_file_alloc(struct file *file);
> void security_file_release(struct file *file);
> void security_file_free(struct file *file);
> +int security_backing_file_alloc(void **backing_file_blobp,
> + const struct file *user_file);
> +void security_backing_file_free(void **backing_file_blobp);
> int security_file_ioctl(struct file *file, unsigned int cmd, unsigned long arg);
> int security_file_ioctl_compat(struct file *file, unsigned int cmd,
> unsigned long arg);
> int security_mmap_file(struct file *file, unsigned long prot,
> unsigned long flags);
> +int security_mmap_backing_file(struct vm_area_struct *vma,
> + struct file *backing_file,
> + struct file *user_file);
> int security_mmap_addr(unsigned long addr);
> int security_file_mprotect(struct vm_area_struct *vma, unsigned long reqprot,
> unsigned long prot);
> @@ -1140,6 +1146,15 @@ static inline void security_file_release(struct file *file)
> static inline void security_file_free(struct file *file)
> { }
>
> +int security_backing_file_alloc(void **backing_file_blobp,
> + const struct file *user_file)
> +{
> + return 0;
> +}
> +
> +void security_backing_file_free(void **backing_file_blobp)
> +{ }
> +
Should these two placeholders be static inline functions, like the
other ones around them?
> static inline int security_file_ioctl(struct file *file, unsigned int cmd,
> unsigned long arg)
> {
> @@ -1159,6 +1174,13 @@ static inline int security_mmap_file(struct file *file, unsigned long prot,
> return 0;
> }
>
> +static inline int security_mmap_backing_file(struct vm_area_struct *vma,
> + struct file *backing_file,
> + struct file *user_file)
> +{
> + return 0;
> +}
> +
> static inline int security_mmap_addr(unsigned long addr)
> {
> return cap_mmap_addr(addr);
> diff --git a/security/lsm.h b/security/lsm.h
> index db77cc83e158..32f808ad4335 100644
> --- a/security/lsm.h
> +++ b/security/lsm.h
> @@ -29,6 +29,7 @@ extern struct lsm_blob_sizes blob_sizes;
>
> /* LSM blob caches */
> extern struct kmem_cache *lsm_file_cache;
> +extern struct kmem_cache *lsm_backing_file_cache;
> extern struct kmem_cache *lsm_inode_cache;
>
> /* LSM blob allocators */
> diff --git a/security/lsm_init.c b/security/lsm_init.c
> index 573e2a7250c4..020eace65973 100644
> --- a/security/lsm_init.c
> +++ b/security/lsm_init.c
> @@ -293,6 +293,8 @@ static void __init lsm_prepare(struct lsm_info *lsm)
> blobs = lsm->blobs;
> lsm_blob_size_update(&blobs->lbs_cred, &blob_sizes.lbs_cred);
> lsm_blob_size_update(&blobs->lbs_file, &blob_sizes.lbs_file);
> + lsm_blob_size_update(&blobs->lbs_backing_file,
> + &blob_sizes.lbs_backing_file);
> lsm_blob_size_update(&blobs->lbs_ib, &blob_sizes.lbs_ib);
> /* inode blob gets an rcu_head in addition to LSM blobs. */
> if (blobs->lbs_inode && blob_sizes.lbs_inode == 0)
> @@ -441,6 +443,8 @@ int __init security_init(void)
> if (lsm_debug) {
> lsm_pr("blob(cred) size %d\n", blob_sizes.lbs_cred);
> lsm_pr("blob(file) size %d\n", blob_sizes.lbs_file);
> + lsm_pr("blob(backing_file) size %d\n",
> + blob_sizes.lbs_backing_file);
> lsm_pr("blob(ib) size %d\n", blob_sizes.lbs_ib);
> lsm_pr("blob(inode) size %d\n", blob_sizes.lbs_inode);
> lsm_pr("blob(ipc) size %d\n", blob_sizes.lbs_ipc);
> @@ -462,6 +466,11 @@ int __init security_init(void)
> lsm_file_cache = kmem_cache_create("lsm_file_cache",
> blob_sizes.lbs_file, 0,
> SLAB_PANIC, NULL);
> + if (blob_sizes.lbs_backing_file)
> + lsm_backing_file_cache = kmem_cache_create(
> + "lsm_backing_file_cache",
> + blob_sizes.lbs_file, 0,
> + SLAB_PANIC, NULL);
Shouldn't blob_sizes.lbs_file here be blob_sizes.lbs_backing_file instead?
> if (blob_sizes.lbs_inode)
> lsm_inode_cache = kmem_cache_create("lsm_inode_cache",
> blob_sizes.lbs_inode, 0,
> diff --git a/security/security.c b/security/security.c
> index 67af9228c4e9..651a0d643c9f 100644
> --- a/security/security.c
> +++ b/security/security.c
> @@ -81,6 +81,7 @@ const struct lsm_id *lsm_idlist[MAX_LSM_COUNT];
> struct lsm_blob_sizes blob_sizes;
>
> struct kmem_cache *lsm_file_cache;
> +struct kmem_cache *lsm_backing_file_cache;
> struct kmem_cache *lsm_inode_cache;
>
> #define SECURITY_HOOK_ACTIVE_KEY(HOOK, IDX) security_hook_active_##HOOK##_##IDX
> @@ -172,6 +173,28 @@ static int lsm_file_alloc(struct file *file)
> return 0;
> }
>
> +/**
> + * lsm_backing_file_alloc - allocate a composite backing file blob
> + * @backing_file_blobp: pointer to the backing file LSM blob pointer
> + *
> + * Allocate the backing file blob for all the modules.
> + *
> + * Returns 0, or -ENOMEM if memory can't be allocated.
> + */
> +static int lsm_backing_file_alloc(void **backing_file_blobp)
> +{
> + if (!lsm_backing_file_cache) {
> + *backing_file_blobp = NULL;
> + return 0;
> + }
> +
> + *backing_file_blobp = kmem_cache_zalloc(lsm_backing_file_cache,
> + GFP_KERNEL);
> + if (*backing_file_blobp == NULL)
> + return -ENOMEM;
> + return 0;
> +}
> +
> /**
> * lsm_blob_alloc - allocate a composite blob
> * @dest: the destination for the blob
> @@ -2417,6 +2440,57 @@ void security_file_free(struct file *file)
> }
> }
>
> +/**
> + * security_backing_file_alloc() - Allocate and setup a backing file blob
> + * @backing_file_blobp: pointer to the backing file LSM blob pointer
> + * @user_file: the associated user visible file
> + *
> + * Allocate a backing file LSM blob and perform any necessary initialization of
> + * the LSM blob. There will be some operations where the LSM will not have
> + * access to @user_file after this point, so any important state associated
> + * with @user_file that is important to the LSM should be captured in the
> + * backing file's LSM blob.
> + *
> + * LSM's should avoid taking a reference to @user_file in this hook as it will
> + * result in problems later when the system attempts to drop/put the file
> + * references due to a circular dependency.
> + *
> + * Return: Return 0 if the hook is successful, negative values otherwise.
> + */
> +int security_backing_file_alloc(void **backing_file_blobp,
> + const struct file *user_file)
> +{
> + int rc;
> +
> + rc = lsm_backing_file_alloc(backing_file_blobp);
> + if (rc)
> + return rc;
> + rc = call_int_hook(backing_file_alloc, *backing_file_blobp, user_file);
> + if (unlikely(rc))
> + security_backing_file_free(backing_file_blobp);
> +
> + return rc;
> +}
> +
> +/**
> + * security_backing_file_free() - Free a backing file blob
> + * @backing_file_blobp: pointer to the backing file LSM blob pointer
> + *
> + * Free any LSM state associate with a backing file's LSM blob, including the
> + * blob itself.
> + */
> +void security_backing_file_free(void **backing_file_blobp)
> +{
> + void *backing_file_blob = *backing_file_blobp;
> +
> + call_void_hook(backing_file_free, backing_file_blob);
> +
> + if (backing_file_blob) {
> + *backing_file_blobp = NULL;
> + kmem_cache_free(lsm_backing_file_cache, backing_file_blob);
> + }
> +}
> +
> /**
> * security_file_ioctl() - Check if an ioctl is allowed
> * @file: associated file
> @@ -2505,6 +2579,32 @@ int security_mmap_file(struct file *file, unsigned long prot,
> flags);
> }
>
> +/**
> + * security_mmap_backing_file - Check if mmap'ing a backing file is allowed
> + * @vma: the vm_area_struct for the mmap'd region
> + * @backing_file: the backing file being mmap'd
> + * @user_file: the user file being mmap'd
> + *
> + * Check permissions for a mmap operation on a stacked filesystem. This hook
> + * is called after the security_mmap_file() and is responsible for authorizing
> + * the mmap on @backing_file. It is important to note that the mmap operation
> + * on @user_file has already been authorized and the @vma->vm_file has been
> + * set to @backing_file.
> + *
> + * Return: Returns 0 if permission is granted.
> + */
> +int security_mmap_backing_file(struct vm_area_struct *vma,
> + struct file *backing_file,
> + struct file *user_file)
> +{
> + /* recommended by the stackable filesystem devs */
> + if (WARN_ON_ONCE(!(backing_file->f_mode & FMODE_BACKING)))
> + return -EIO;
> +
> + return call_int_hook(mmap_backing_file, vma, backing_file, user_file);
> +}
> +EXPORT_SYMBOL_GPL(security_mmap_backing_file);
> +
> /**
> * security_mmap_addr() - Check if mmap'ing an address is allowed
> * @addr: address
> --
> 2.53.0
>
>
^ permalink raw reply
* Re: LSM namespacing API
From: Paul Moore @ 2026-03-24 21:31 UTC (permalink / raw)
To: Stephen Smalley
Cc: Ondrej Mosnacek, linux-security-module, selinux, John Johansen
In-Reply-To: <CAHC9VhTGruOPJ+NWZT8vw1bjXzkB4DSPFmWd1pC=J2jTYHP5BA@mail.gmail.com>
On Tue, Mar 3, 2026 at 11:46 AM Paul Moore <paul@paul-moore.com> wrote:
>
> I'd really like to hear from some of the other LSMs before we start
> diving into the code. It may sound funny, but from my perspective
> doing the work to get the API definition "right" is far more important
> than implementing it.
It's been three weeks now, and I haven't seen any strong arguments for
supporting the clone() API at this time, so we can leave that out for
now and stick with just the unshare() API for an initial attempt. We
can always add a clone() API at a later date if needed; going small
and expanding over time is usually a better decision anyway.
So to quickly summarize, here is where I think the discussion landed:
* Implement the lsm_unshare() syscall
I expect it would look something like 'lsm_unshare(struct lsm_ctx
*ctx, u32 size, u32 flags)' with @ctx specifying the particular LSM
being unshared, and @flags being 0/unused at this point in time
(unless we can think of something we want to specify here). Like
lsm_set_self_attr(), only one @ctx can be specified at a time, so you
can only unshare one LSM at a time.
* Implement /proc/pid/ns/lsm and setns(CLONE_NEWLSM)
As discussed previously, this allows us to move a process into an
existing, established LSM namespace set. The caller cannot
selectively choose which individual LSM namespaces they join from the
given LSM namespace set, they receive the same LSM namespace
configuration as the target process.
Any comments, corrections, etc.? If not, if someone wants to send me
a patch{set} implementing these changes we can merge them into
lsm/dev-staging until we have a LSM which implements support for the
new API.
--
paul-moore.com
^ permalink raw reply
* Re: [PATCH 6/7] tomoyo: Convert from sb_mount to granular mount hooks
From: Song Liu @ 2026-03-24 19:03 UTC (permalink / raw)
To: Tetsuo Handa
Cc: Song Liu, Christian Brauner, viro@zeniv.linux.org.uk,
paul@paul-moore.com, jmorris@namei.org, serge@hallyn.com,
jack@suse.cz, john.johansen@canonical.com,
stephen.smalley.work@gmail.com, omosnace@redhat.com,
mic@digikod.net, gnoack@google.com, takedakn@nttdata.co.jp,
herton@canonical.com, Kernel Team, selinux@vger.kernel.org,
apparmor@lists.ubuntu.com, linux-fsdevel@vger.kernel.org,
linux-security-module@vger.kernel.org
In-Reply-To: <6c298238-8d87-4c41-84a7-e0373d466a15@I-love.SAKURA.ne.jp>
On Tue, Mar 24, 2026 at 2:59 AM Tetsuo Handa
<penguin-kernel@i-love.sakura.ne.jp> wrote:
>
> On 2026/03/24 16:46, Song Liu wrote:
> > On Mon, Mar 23, 2026 at 11:12 PM Tetsuo Handa
> > <penguin-kernel@i-love.sakura.ne.jp> wrote:
> >>
> >> On 2026/03/24 4:31, Song Liu wrote:
> >>>> Then, how can LSM modules know that how the requested filesystem resolves
> >>>> the dev_name argument, without embedding filesystem specific resolution
> >>>> logic into individual LSM module?
> >>>
> >>> IIUC, if an LSM cares about the dev_name of a new mount, it will have to look
> >>> into each individual filesystem. We can add a LSM hook for the filesystems to
> >>> call. But this will require changes to individual filesystem code. OTOH,
> >>> dev_name can probably bridge the gap as we change filesystems.
> >>>
> >>> Would this work?
> >>
> >> I guess something like untested diff shown below would work.
> >
> > I think this doesn't work with erofs on file (requires
> > CONFIG_EROFS_FS_BACKED_BY_FILE). erofs may not be the
> > only one that has this problem.
>
> This is incomplete but I think this is better than now because currently
> mount() operation likely fails with -ENOENT if the requested filesystem
> does not interpret fc->source as a pathname despite tomoyo_mount_acl()
> always interprets fc->source as a pathname when FS_REQUIRES_DEV is set.
If I understand Christian correctly, the main challenge here is that
FS_REQUIRES_DEV doesn't imply fc->source is the path of a device.
Changing this assumption is a major change between VFS and many
filesystems.
I was thinking about something like:
diff --git i/fs/super.c w/fs/super.c
index 378e81efe643..91ce3003bc23 100644
--- i/fs/super.c
+++ w/fs/super.c
@@ -1676,6 +1676,9 @@ int get_tree_bdev_flags(struct fs_context *fc,
errorf(fc, "%s: Can't lookup blockdev", fc->source);
return error;
}
+ error = security_mount_dev(fc, dev);
+ if (error)
+ return error;
fc->sb_flags |= SB_NOSEC;
s = sget_dev(fc, dev);
if (IS_ERR(s))
This allows the LSMs to monitor the dev being mounted in a new mount.
If a filesystem doesn't use get_tree_bdev*(), we will need something else
to cover this specific filesystem. I am not sure whether this is acceptable
for VFS and LSM, specifically tomoyo and apparmor.
Also, before we go too deep into the hook for new mounts, can we focus
on this set, which will fix some existing TOCTOU issues?
Thanks,
Song
^ permalink raw reply related
* Re: [PATCH v4] landlock: Expand restrict flags example for ABI version 8
From: Günther Noack @ 2026-03-24 15:30 UTC (permalink / raw)
To: Mickaël Salaün
Cc: Panagiotis "Ivory" Vasilopoulos, Jonathan Corbet,
Shuah Khan, linux-security-module, linux-doc, linux-kernel,
Dan Cojocaru
In-Reply-To: <20260324.aqu5Eic7Thee@digikod.net>
On Tue, Mar 24, 2026 at 04:06:01PM +0100, Mickaël Salaün wrote:
> On Tue, Mar 24, 2026 at 10:48:29AM +0100, Günther Noack wrote:
> > Apologies for the delay, this must have slipped through the cracks.
> > Thanks for bringing it up again. Yes, this looks good.
> >
> > Signed-off-by: Günther Noack <gnoack@google.com>
>
> Shouldn't it be a Reviewed-by?
Absolutely, thanks! I meant to send a Reviewed-by.
Please ignore the previous message.
Reviewed-by: Günther Noack <gnoack@google.com>
—Günther
^ permalink raw reply
* Re: [PATCH v4] landlock: Expand restrict flags example for ABI version 8
From: Mickaël Salaün @ 2026-03-24 15:06 UTC (permalink / raw)
To: Günther Noack
Cc: Panagiotis "Ivory" Vasilopoulos, Jonathan Corbet,
Shuah Khan, linux-security-module, linux-doc, linux-kernel,
Dan Cojocaru
In-Reply-To: <acJa_56LtPeeH956@google.com>
On Tue, Mar 24, 2026 at 10:48:29AM +0100, Günther Noack wrote:
> Hello!
>
> On Mon, Mar 23, 2026 at 07:56:21PM +0100, Mickaël Salaün wrote:
> > Thanks! I pushed your patch in next with a minor fix.
> >
> > Günther, does it look good to you?
> >
> > On Wed, Mar 04, 2026 at 07:13:04PM +0100, Panagiotis "Ivory" Vasilopoulos wrote:
> > > Add LANDLOCK_RESTRICT_SELF_TSYNC to the backwards compatibility example
> > > for restrict flags. This introduces completeness, similar to that of
> > > the ruleset attributes example. However, as the new example can impact
> > > enforcement in certain cases, an appropriate warning is also included.
> > >
> > > Additionally, I modified the two comments of the example to make them
> > > more consistent with the ruleset attributes example's.
> > >
> > > Signed-off-by: Panagiotis 'Ivory' Vasilopoulos <git@n0toose.net>
> > > Co-developed-by: Dan Cojocaru <dan@dcdev.ro>
> > > Signed-off-by: Dan Cojocaru <dan@dcdev.ro>
> > > ---
> > > Changes in v4:
> > > - Make warning somewhat more terse, merge comments.
> > > - Remove some sensationalization. ("Don't copy-paste this just yet!")
> > > - Apply Günther's suggestion (v3 "recycled" some phrases, was long)
> > > - ... but also retain some of the wording on ABI differences
> > > - Provide a brief overview that contextualizes the example further:
> > > - Clarify the difference behind ABI < 8 & ABI v8, to avoid
> > > misunderstandings on which option is the default.
> > > - Make "linear reading" easier.
> > > - Based on Mickaël's feedback: Avoid cans of worms w.r.t. use cases
> > > - Link to v3: https://lore.kernel.org/r/20260228-landlock-docs-add-tsync-example-v3-1-140ab50f0524@n0toose.net
> > >
> > > Changes in v3:
> > > - Add __attribute__((fallthrough)) like in earlier example.
> > > - Improve comment for LANDLOCK_RESTRICT_SELF_TSYNC (ABI < 8) example.
> > > - Add relevant warning for ABI < 8 example based on Günther's feedback.
> > > - Link to v2: https://lore.kernel.org/r/20260221-landlock-docs-add-tsync-example-v2-1-60990986bba5@n0toose.net
> > >
> > > Changes in v2:
> > > - Fix formatting error.
> > > - Link to v1: https://lore.kernel.org/r/20260221-landlock-docs-add-tsync-example-v1-1-f89383809eb4@n0toose.net
> > > ---
> > > Documentation/userspace-api/landlock.rst | 22 ++++++++++++++++++----
> > > 1 file changed, 18 insertions(+), 4 deletions(-)
> > >
> > > diff --git a/Documentation/userspace-api/landlock.rst b/Documentation/userspace-api/landlock.rst
> > > index 13134bccdd39d78ddce3daf454f32dda162ce91b..64c7138a788d74f99da0a71428da392b3d873bf8 100644
> > > --- a/Documentation/userspace-api/landlock.rst
> > > +++ b/Documentation/userspace-api/landlock.rst
> > > @@ -196,13 +196,27 @@ similar backwards compatibility check is needed for the restrict flags
> > > (see sys_landlock_restrict_self() documentation for available flags):
> > >
> > > .. code-block:: c
> > > -
> > > - __u32 restrict_flags = LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON;
> > > - if (abi < 7) {
> > > - /* Clear logging flags unsupported before ABI 7. */
> > > + __u32 restrict_flags =
> > > + LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON |
> > > + LANDLOCK_RESTRICT_SELF_TSYNC;
> > > + switch (abi) {
> > > + case 1 ... 6:
> > > + /* Clear logging flags unsupported for ABI < 7 */
> > > restrict_flags &= ~(LANDLOCK_RESTRICT_SELF_LOG_SAME_EXEC_OFF |
> > > LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON |
> > > LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF);
> > > + __attribute__((fallthrough));
> > > + case 7:
> > > + /*
> > > + * Removes multithreaded enforcement flag unsupported for ABI < 8
> > > + *
> > > + * WARNING: Without this flag, calling landlock_restrict_self(2) is
> > > + * only equivalent if the calling process is single-threaded. Below
> > > + * ABI v8 (and as of ABI v8, when not using this flag), a Landlock
> > > + * policy would only be enforced for the calling thread and its
> > > + * children (and not for all threads, including parents and siblings).
> > > + */
> > > + restrict_flags &= ~LANDLOCK_RESTRICT_SELF_TSYNC;
> > > }
> > >
> > > The next step is to restrict the current thread from gaining more privileges
> > >
> > > ---
> > > base-commit: ceb977bfe9e8715e6cd3a4785c7aab8ea5cd2b77
> > > change-id: 20260221-landlock-docs-add-tsync-example-e8fd5c64a366
> > >
> > > Best regards,
> > > --
> > > Panagiotis "Ivory" Vasilopoulos <git@n0toose.net>
> > >
> > >
>
> Apologies for the delay, this must have slipped through the cracks.
> Thanks for bringing it up again. Yes, this looks good.
>
> Signed-off-by: Günther Noack <gnoack@google.com>
Shouldn't it be a Reviewed-by?
^ permalink raw reply
* Re: [RFC PATCH v1 07/11] selftests/landlock: Drain stale audit records on init
From: Günther Noack @ 2026-03-24 13:27 UTC (permalink / raw)
To: Mickaël Salaün
Cc: Christian Brauner, Paul Moore, Serge E . Hallyn, Justin Suess,
Lennart Poettering, Mikhail Ivanov, Nicolas Bouchinet,
Shervin Oloumi, Tingmao Wang, kernel-team, linux-fsdevel,
linux-kernel, linux-security-module
In-Reply-To: <20260312100444.2609563-8-mic@digikod.net>
On Thu, Mar 12, 2026 at 11:04:40AM +0100, Mickaël Salaün wrote:
> Non-audit Landlock tests generate audit records as side effects when
> audit_enabled is non-zero (e.g. from boot configuration). These records
> accumulate in the kernel audit backlog while no audit daemon socket is
> open. When the next test opens a new netlink socket and registers as
> the audit daemon, the stale backlog is delivered, causing baseline
> record count checks to fail spuriously.
>
> Fix this by draining all pending records in audit_init() right after
> setting the receive timeout. The 1-usec SO_RCVTIMEO causes audit_recv()
> to return -EAGAIN once the backlog is empty, naturally terminating the
> drain loop.
>
> Domain deallocation records are emitted asynchronously from a work
> queue, so they may still arrive after the drain. Remove records.domain
> == 0 checks from tests where a stale deallocation record from a previous
> test could cause spurious failures.
>
> Also fix a socket file descriptor leak on error paths in audit_init():
> if audit_set_status() or setsockopt() fails (e.g. when another audit
> daemon is already registered), close the socket before returning.
>
> Fix off-by-one checks in matches_log_domain_allocated() and
> matches_log_domain_deallocated() where snprintf() truncation was
> detected with ">" instead of ">=" (snprintf() returns the length
> excluding the NUL terminator, so equality means truncation).
>
> Cc: Günther Noack <gnoack@google.com>
> Fixes: 6a500b22971c ("selftests/landlock: Add tests for audit flags and domain IDs")
> Signed-off-by: Mickaël Salaün <mic@digikod.net>
> ---
> tools/testing/selftests/landlock/audit.h | 29 +++++++++++++++----
> tools/testing/selftests/landlock/audit_test.c | 2 --
> 2 files changed, 23 insertions(+), 8 deletions(-)
>
> diff --git a/tools/testing/selftests/landlock/audit.h b/tools/testing/selftests/landlock/audit.h
> index 44eb433e9666..550acaafcc1e 100644
> --- a/tools/testing/selftests/landlock/audit.h
> +++ b/tools/testing/selftests/landlock/audit.h
> @@ -309,7 +309,7 @@ static int __maybe_unused matches_log_domain_allocated(int audit_fd, pid_t pid,
>
> log_match_len =
> snprintf(log_match, sizeof(log_match), log_template, pid);
> - if (log_match_len > sizeof(log_match))
> + if (log_match_len >= sizeof(log_match))
> return -E2BIG;
>
> return audit_match_record(audit_fd, AUDIT_LANDLOCK_DOMAIN, log_match,
> @@ -326,7 +326,7 @@ static int __maybe_unused matches_log_domain_deallocated(
>
> log_match_len = snprintf(log_match, sizeof(log_match), log_template,
> num_denials);
> - if (log_match_len > sizeof(log_match))
> + if (log_match_len >= sizeof(log_match))
> return -E2BIG;
>
> return audit_match_record(audit_fd, AUDIT_LANDLOCK_DOMAIN, log_match,
> @@ -379,19 +379,36 @@ static int audit_init(void)
>
> err = audit_set_status(fd, AUDIT_STATUS_ENABLED, 1);
> if (err)
> - return err;
> + goto err_close;
>
> err = audit_set_status(fd, AUDIT_STATUS_PID, getpid());
> if (err)
> - return err;
> + goto err_close;
>
> /* Sets a timeout for negative tests. */
> err = setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &audit_tv_default,
> sizeof(audit_tv_default));
> - if (err)
> - return -errno;
> + if (err) {
> + err = -errno;
> + goto err_close;
> + }
> +
> + /*
> + * Drains stale audit records that accumulated in the kernel backlog
> + * while no audit daemon socket was open. This happens when
> + * non-audit Landlock tests create domains or trigger denials while
> + * audit_enabled is non-zero (e.g. from boot configuration), or when
> + * domain deallocation records arrive asynchronously after a
> + * previous test's socket was closed.
> + */
> + while (audit_recv(fd, NULL) == 0)
> + ;
>
> return fd;
> +
> +err_close:
> + close(fd);
> + return err;
> }
>
> static int audit_init_filter_exe(struct audit_filter *filter, const char *path)
> diff --git a/tools/testing/selftests/landlock/audit_test.c b/tools/testing/selftests/landlock/audit_test.c
> index 46d02d49835a..f92ba6774faa 100644
> --- a/tools/testing/selftests/landlock/audit_test.c
> +++ b/tools/testing/selftests/landlock/audit_test.c
> @@ -412,7 +412,6 @@ TEST_F(audit_flags, signal)
> } else {
> EXPECT_EQ(1, records.access);
> }
> - EXPECT_EQ(0, records.domain);
>
> /* Updates filter rules to match the drop record. */
> set_cap(_metadata, CAP_AUDIT_CONTROL);
> @@ -601,7 +600,6 @@ TEST_F(audit_exec, signal_and_open)
> /* Tests that there was no denial until now. */
> EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
> EXPECT_EQ(0, records.access);
> - EXPECT_EQ(0, records.domain);
>
> /*
> * Wait for the child to do a first denied action by layer1 and
> --
> 2.53.0
>
Ooh, nice catch! I have definitely stumbled across this bug in the
past (especially when the kernel is compiled with more debugging
options), and I know from Justin that he ran into it as well.
Draining the audit logs before sending a new stimulus for audit
logging looks like a good approach.
Reviewed-by: Günther Noack <gnoack@google.com>
—Günther
^ permalink raw reply
* Re: [PATCH v2] KEYS: trusted: Debugging as a feature
From: Jarkko Sakkinen @ 2026-03-24 11:05 UTC (permalink / raw)
To: linux-integrity
Cc: keyrings, Srish Srinivasan, Nayna Jain, James Bottomley,
Mimi Zohar, David Howells, Paul Moore, James Morris,
Serge E. Hallyn, Ahmad Fatoum, Pengutronix Kernel Team, open list,
open list:SECURITY SUBSYSTEM
In-Reply-To: <20260324110018.67081-1-jarkko@kernel.org>
On Tue, Mar 24, 2026 at 01:00:15PM +0200, Jarkko Sakkinen wrote:
> TPM_DEBUG, and other similar flags, are a non-standard way to specify a
> feature in Linux kernel. Introduce CONFIG_TRUSTED_KEYS_DEBUG for
> trusted keys, and use it to replace these ad-hoc feature flags.
>
> Given that trusted keys debug dumps can contain sensitive data, harden
> the feature as follows:
>
> 1. In the Kconfig description postulate that pr_debug() statements must be
> used.
> 2. Use pr_debug() statements in TPM 1.x driver to print the protocol dump.
>
> Traces, when actually needed, can be easily enabled by providing
> trusted.dyndbg='+p' in the kernel command-line.
>
> Cc: Srish Srinivasan <ssrish@linux.ibm.com>
> Reported-by: Nayna Jain <nayna@linux.ibm.com>
> Closes: https://lore.kernel.org/all/7f8b8478-5cd8-4d97-bfd0-341fd5cf10f9@linux.ibm.com/
> Signed-off-by: Jarkko Sakkinen <jarkko@kernel.org>
> ---
> v2:
> - Implement for all trusted keys backends.
> - Add HAVE_TRUSTED_KEYS_DEBUG as it is a good practice despite full
> coverage.
> ---
Sorry came out 3x, I messed up in terminal :-) They are equal.
> include/keys/trusted-type.h | 18 +++++-------
> security/keys/trusted-keys/Kconfig | 19 ++++++++++++
> security/keys/trusted-keys/trusted_caam.c | 4 +--
> security/keys/trusted-keys/trusted_tpm1.c | 36 +++++++++++------------
> 4 files changed, 46 insertions(+), 31 deletions(-)
>
> diff --git a/include/keys/trusted-type.h b/include/keys/trusted-type.h
> index 03527162613f..620a1f890b6b 100644
> --- a/include/keys/trusted-type.h
> +++ b/include/keys/trusted-type.h
> @@ -83,18 +83,16 @@ struct trusted_key_source {
>
> extern struct key_type key_type_trusted;
>
> -#define TRUSTED_DEBUG 0
> -
> -#if TRUSTED_DEBUG
> +#ifdef CONFIG_TRUSTED_KEYS_DEBUG
> static inline void dump_payload(struct trusted_key_payload *p)
> {
> - pr_info("key_len %d\n", p->key_len);
> - print_hex_dump(KERN_INFO, "key ", DUMP_PREFIX_NONE,
> - 16, 1, p->key, p->key_len, 0);
> - pr_info("bloblen %d\n", p->blob_len);
> - print_hex_dump(KERN_INFO, "blob ", DUMP_PREFIX_NONE,
> - 16, 1, p->blob, p->blob_len, 0);
> - pr_info("migratable %d\n", p->migratable);
> + pr_debug("key_len %d\n", p->key_len);
> + print_hex_dump_debug("key ", DUMP_PREFIX_NONE,
> + 16, 1, p->key, p->key_len, 0);
> + pr_debug("bloblen %d\n", p->blob_len);
> + print_hex_dump_debug("blob ", DUMP_PREFIX_NONE,
> + 16, 1, p->blob, p->blob_len, 0);
> + pr_debug("migratable %d\n", p->migratable);
> }
> #else
> static inline void dump_payload(struct trusted_key_payload *p)
> diff --git a/security/keys/trusted-keys/Kconfig b/security/keys/trusted-keys/Kconfig
> index 9e00482d886a..2ad9ba0e03f1 100644
> --- a/security/keys/trusted-keys/Kconfig
> +++ b/security/keys/trusted-keys/Kconfig
> @@ -1,10 +1,25 @@
> config HAVE_TRUSTED_KEYS
> bool
>
> +config HAVE_TRUSTED_KEYS_DEBUG
> + bool
> +
> +config TRUSTED_KEYS_DEBUG
> + bool "Debug trusted keys"
> + depends on HAVE_TRUSTED_KEYS_DEBUG
> + default n
> + help
> + Trusted keys backends and core code that support debug dumps
> + can opt-in that feature here. Dumps must only use DEBUG
> + level output, as sensitive data may pass by. In the
> + kernel-command line traces can be enabled via
> + trusted.dyndbg='+p'.
> +
> config TRUSTED_KEYS_TPM
> bool "TPM-based trusted keys"
> depends on TCG_TPM >= TRUSTED_KEYS
> default y
> + select HAVE_TRUSTED_KEYS_DEBUG
> select CRYPTO_HASH_INFO
> select CRYPTO_LIB_SHA1
> select CRYPTO_LIB_UTILS
> @@ -23,6 +38,7 @@ config TRUSTED_KEYS_TEE
> bool "TEE-based trusted keys"
> depends on TEE >= TRUSTED_KEYS
> default y
> + select HAVE_TRUSTED_KEYS_DEBUG
> select HAVE_TRUSTED_KEYS
> help
> Enable use of the Trusted Execution Environment (TEE) as trusted
> @@ -33,6 +49,7 @@ config TRUSTED_KEYS_CAAM
> depends on CRYPTO_DEV_FSL_CAAM_JR >= TRUSTED_KEYS
> select CRYPTO_DEV_FSL_CAAM_BLOB_GEN
> default y
> + select HAVE_TRUSTED_KEYS_DEBUG
> select HAVE_TRUSTED_KEYS
> help
> Enable use of NXP's Cryptographic Accelerator and Assurance Module
> @@ -42,6 +59,7 @@ config TRUSTED_KEYS_DCP
> bool "DCP-based trusted keys"
> depends on CRYPTO_DEV_MXS_DCP >= TRUSTED_KEYS
> default y
> + select HAVE_TRUSTED_KEYS_DEBUG
> select HAVE_TRUSTED_KEYS
> help
> Enable use of NXP's DCP (Data Co-Processor) as trusted key backend.
> @@ -50,6 +68,7 @@ config TRUSTED_KEYS_PKWM
> bool "PKWM-based trusted keys"
> depends on PSERIES_PLPKS >= TRUSTED_KEYS
> default y
> + select HAVE_TRUSTED_KEYS_DEBUG
> select HAVE_TRUSTED_KEYS
> help
> Enable use of IBM PowerVM Key Wrapping Module (PKWM) as a trusted key backend.
> diff --git a/security/keys/trusted-keys/trusted_caam.c b/security/keys/trusted-keys/trusted_caam.c
> index 601943ce0d60..015cddc6b53c 100644
> --- a/security/keys/trusted-keys/trusted_caam.c
> +++ b/security/keys/trusted-keys/trusted_caam.c
> @@ -28,10 +28,10 @@ static const match_table_t key_tokens = {
> {opt_err, NULL}
> };
>
> -#ifdef CAAM_DEBUG
> +#ifdef CONFIG_TRUSTED_KEYS_DEBUG
> static inline void dump_options(const struct caam_pkey_info *pkey_info)
> {
> - pr_info("key encryption algo %d\n", pkey_info->key_enc_algo);
> + pr_debug("key encryption algo %d\n", pkey_info->key_enc_algo);
> }
> #else
> static inline void dump_options(const struct caam_pkey_info *pkey_info)
> diff --git a/security/keys/trusted-keys/trusted_tpm1.c b/security/keys/trusted-keys/trusted_tpm1.c
> index c865c97aa1b4..8fe889c7cdd1 100644
> --- a/security/keys/trusted-keys/trusted_tpm1.c
> +++ b/security/keys/trusted-keys/trusted_tpm1.c
> @@ -46,38 +46,36 @@ enum {
> SRK_keytype = 4
> };
>
> -#define TPM_DEBUG 0
> -
> -#if TPM_DEBUG
> +#ifdef CONFIG_TRUSTED_KEYS_DEBUG
> static inline void dump_options(struct trusted_key_options *o)
> {
> - pr_info("sealing key type %d\n", o->keytype);
> - pr_info("sealing key handle %0X\n", o->keyhandle);
> - pr_info("pcrlock %d\n", o->pcrlock);
> - pr_info("pcrinfo %d\n", o->pcrinfo_len);
> - print_hex_dump(KERN_INFO, "pcrinfo ", DUMP_PREFIX_NONE,
> - 16, 1, o->pcrinfo, o->pcrinfo_len, 0);
> + pr_debug("sealing key type %d\n", o->keytype);
> + pr_debug("sealing key handle %0X\n", o->keyhandle);
> + pr_debug("pcrlock %d\n", o->pcrlock);
> + pr_debug("pcrinfo %d\n", o->pcrinfo_len);
> + print_hex_dump_debug("pcrinfo ", DUMP_PREFIX_NONE,
> + 16, 1, o->pcrinfo, o->pcrinfo_len, 0);
> }
>
> static inline void dump_sess(struct osapsess *s)
> {
> - print_hex_dump(KERN_INFO, "trusted-key: handle ", DUMP_PREFIX_NONE,
> - 16, 1, &s->handle, 4, 0);
> - pr_info("secret:\n");
> - print_hex_dump(KERN_INFO, "", DUMP_PREFIX_NONE,
> - 16, 1, &s->secret, SHA1_DIGEST_SIZE, 0);
> - pr_info("trusted-key: enonce:\n");
> - print_hex_dump(KERN_INFO, "", DUMP_PREFIX_NONE,
> - 16, 1, &s->enonce, SHA1_DIGEST_SIZE, 0);
> + print_hex_dump_debug("trusted-key: handle ", DUMP_PREFIX_NONE,
> + 16, 1, &s->handle, 4, 0);
> + pr_debug("secret:\n");
> + print_hex_dump_debug("", DUMP_PREFIX_NONE,
> + 16, 1, &s->secret, SHA1_DIGEST_SIZE, 0);
> + pr_debug("trusted-key: enonce:\n");
> + print_hex_dump_debug("", DUMP_PREFIX_NONE,
> + 16, 1, &s->enonce, SHA1_DIGEST_SIZE, 0);
> }
>
> static inline void dump_tpm_buf(unsigned char *buf)
> {
> int len;
>
> - pr_info("\ntpm buffer\n");
> + pr_debug("\ntpm buffer\n");
> len = LOAD32(buf, TPM_SIZE_OFFSET);
> - print_hex_dump(KERN_INFO, "", DUMP_PREFIX_NONE, 16, 1, buf, len, 0);
> + print_hex_dump_debug("", DUMP_PREFIX_NONE, 16, 1, buf, len, 0);
> }
> #else
> static inline void dump_options(struct trusted_key_options *o)
> --
> 2.47.3
>
>
BR, Jarkko
^ permalink raw reply
* [PATCH v2] KEYS: trusted: Debugging as a feature
From: Jarkko Sakkinen @ 2026-03-24 11:01 UTC (permalink / raw)
To: linux-integrity
Cc: keyrings, Jarkko Sakkinen, Srish Srinivasan, Nayna Jain,
James Bottomley, Mimi Zohar, David Howells, Paul Moore,
James Morris, Serge E. Hallyn, Ahmad Fatoum,
Pengutronix Kernel Team, open list, open list:SECURITY SUBSYSTEM
TPM_DEBUG, and other similar flags, are a non-standard way to specify a
feature in Linux kernel. Introduce CONFIG_TRUSTED_KEYS_DEBUG for
trusted keys, and use it to replace these ad-hoc feature flags.
Given that trusted keys debug dumps can contain sensitive data, harden
the feature as follows:
1. In the Kconfig description postulate that pr_debug() statements must be
used.
2. Use pr_debug() statements in TPM 1.x driver to print the protocol dump.
Traces, when actually needed, can be easily enabled by providing
trusted.dyndbg='+p' in the kernel command-line.
Cc: Srish Srinivasan <ssrish@linux.ibm.com>
Reported-by: Nayna Jain <nayna@linux.ibm.com>
Closes: https://lore.kernel.org/all/7f8b8478-5cd8-4d97-bfd0-341fd5cf10f9@linux.ibm.com/
Signed-off-by: Jarkko Sakkinen <jarkko@kernel.org>
---
v2:
- Implement for all trusted keys backends.
- Add HAVE_TRUSTED_KEYS_DEBUG as it is a good practice despite full
coverage.
---
include/keys/trusted-type.h | 18 +++++-------
security/keys/trusted-keys/Kconfig | 19 ++++++++++++
security/keys/trusted-keys/trusted_caam.c | 4 +--
security/keys/trusted-keys/trusted_tpm1.c | 36 +++++++++++------------
4 files changed, 46 insertions(+), 31 deletions(-)
diff --git a/include/keys/trusted-type.h b/include/keys/trusted-type.h
index 03527162613f..620a1f890b6b 100644
--- a/include/keys/trusted-type.h
+++ b/include/keys/trusted-type.h
@@ -83,18 +83,16 @@ struct trusted_key_source {
extern struct key_type key_type_trusted;
-#define TRUSTED_DEBUG 0
-
-#if TRUSTED_DEBUG
+#ifdef CONFIG_TRUSTED_KEYS_DEBUG
static inline void dump_payload(struct trusted_key_payload *p)
{
- pr_info("key_len %d\n", p->key_len);
- print_hex_dump(KERN_INFO, "key ", DUMP_PREFIX_NONE,
- 16, 1, p->key, p->key_len, 0);
- pr_info("bloblen %d\n", p->blob_len);
- print_hex_dump(KERN_INFO, "blob ", DUMP_PREFIX_NONE,
- 16, 1, p->blob, p->blob_len, 0);
- pr_info("migratable %d\n", p->migratable);
+ pr_debug("key_len %d\n", p->key_len);
+ print_hex_dump_debug("key ", DUMP_PREFIX_NONE,
+ 16, 1, p->key, p->key_len, 0);
+ pr_debug("bloblen %d\n", p->blob_len);
+ print_hex_dump_debug("blob ", DUMP_PREFIX_NONE,
+ 16, 1, p->blob, p->blob_len, 0);
+ pr_debug("migratable %d\n", p->migratable);
}
#else
static inline void dump_payload(struct trusted_key_payload *p)
diff --git a/security/keys/trusted-keys/Kconfig b/security/keys/trusted-keys/Kconfig
index 9e00482d886a..2ad9ba0e03f1 100644
--- a/security/keys/trusted-keys/Kconfig
+++ b/security/keys/trusted-keys/Kconfig
@@ -1,10 +1,25 @@
config HAVE_TRUSTED_KEYS
bool
+config HAVE_TRUSTED_KEYS_DEBUG
+ bool
+
+config TRUSTED_KEYS_DEBUG
+ bool "Debug trusted keys"
+ depends on HAVE_TRUSTED_KEYS_DEBUG
+ default n
+ help
+ Trusted keys backends and core code that support debug dumps
+ can opt-in that feature here. Dumps must only use DEBUG
+ level output, as sensitive data may pass by. In the
+ kernel-command line traces can be enabled via
+ trusted.dyndbg='+p'.
+
config TRUSTED_KEYS_TPM
bool "TPM-based trusted keys"
depends on TCG_TPM >= TRUSTED_KEYS
default y
+ select HAVE_TRUSTED_KEYS_DEBUG
select CRYPTO_HASH_INFO
select CRYPTO_LIB_SHA1
select CRYPTO_LIB_UTILS
@@ -23,6 +38,7 @@ config TRUSTED_KEYS_TEE
bool "TEE-based trusted keys"
depends on TEE >= TRUSTED_KEYS
default y
+ select HAVE_TRUSTED_KEYS_DEBUG
select HAVE_TRUSTED_KEYS
help
Enable use of the Trusted Execution Environment (TEE) as trusted
@@ -33,6 +49,7 @@ config TRUSTED_KEYS_CAAM
depends on CRYPTO_DEV_FSL_CAAM_JR >= TRUSTED_KEYS
select CRYPTO_DEV_FSL_CAAM_BLOB_GEN
default y
+ select HAVE_TRUSTED_KEYS_DEBUG
select HAVE_TRUSTED_KEYS
help
Enable use of NXP's Cryptographic Accelerator and Assurance Module
@@ -42,6 +59,7 @@ config TRUSTED_KEYS_DCP
bool "DCP-based trusted keys"
depends on CRYPTO_DEV_MXS_DCP >= TRUSTED_KEYS
default y
+ select HAVE_TRUSTED_KEYS_DEBUG
select HAVE_TRUSTED_KEYS
help
Enable use of NXP's DCP (Data Co-Processor) as trusted key backend.
@@ -50,6 +68,7 @@ config TRUSTED_KEYS_PKWM
bool "PKWM-based trusted keys"
depends on PSERIES_PLPKS >= TRUSTED_KEYS
default y
+ select HAVE_TRUSTED_KEYS_DEBUG
select HAVE_TRUSTED_KEYS
help
Enable use of IBM PowerVM Key Wrapping Module (PKWM) as a trusted key backend.
diff --git a/security/keys/trusted-keys/trusted_caam.c b/security/keys/trusted-keys/trusted_caam.c
index 601943ce0d60..015cddc6b53c 100644
--- a/security/keys/trusted-keys/trusted_caam.c
+++ b/security/keys/trusted-keys/trusted_caam.c
@@ -28,10 +28,10 @@ static const match_table_t key_tokens = {
{opt_err, NULL}
};
-#ifdef CAAM_DEBUG
+#ifdef CONFIG_TRUSTED_KEYS_DEBUG
static inline void dump_options(const struct caam_pkey_info *pkey_info)
{
- pr_info("key encryption algo %d\n", pkey_info->key_enc_algo);
+ pr_debug("key encryption algo %d\n", pkey_info->key_enc_algo);
}
#else
static inline void dump_options(const struct caam_pkey_info *pkey_info)
diff --git a/security/keys/trusted-keys/trusted_tpm1.c b/security/keys/trusted-keys/trusted_tpm1.c
index c865c97aa1b4..8fe889c7cdd1 100644
--- a/security/keys/trusted-keys/trusted_tpm1.c
+++ b/security/keys/trusted-keys/trusted_tpm1.c
@@ -46,38 +46,36 @@ enum {
SRK_keytype = 4
};
-#define TPM_DEBUG 0
-
-#if TPM_DEBUG
+#ifdef CONFIG_TRUSTED_KEYS_DEBUG
static inline void dump_options(struct trusted_key_options *o)
{
- pr_info("sealing key type %d\n", o->keytype);
- pr_info("sealing key handle %0X\n", o->keyhandle);
- pr_info("pcrlock %d\n", o->pcrlock);
- pr_info("pcrinfo %d\n", o->pcrinfo_len);
- print_hex_dump(KERN_INFO, "pcrinfo ", DUMP_PREFIX_NONE,
- 16, 1, o->pcrinfo, o->pcrinfo_len, 0);
+ pr_debug("sealing key type %d\n", o->keytype);
+ pr_debug("sealing key handle %0X\n", o->keyhandle);
+ pr_debug("pcrlock %d\n", o->pcrlock);
+ pr_debug("pcrinfo %d\n", o->pcrinfo_len);
+ print_hex_dump_debug("pcrinfo ", DUMP_PREFIX_NONE,
+ 16, 1, o->pcrinfo, o->pcrinfo_len, 0);
}
static inline void dump_sess(struct osapsess *s)
{
- print_hex_dump(KERN_INFO, "trusted-key: handle ", DUMP_PREFIX_NONE,
- 16, 1, &s->handle, 4, 0);
- pr_info("secret:\n");
- print_hex_dump(KERN_INFO, "", DUMP_PREFIX_NONE,
- 16, 1, &s->secret, SHA1_DIGEST_SIZE, 0);
- pr_info("trusted-key: enonce:\n");
- print_hex_dump(KERN_INFO, "", DUMP_PREFIX_NONE,
- 16, 1, &s->enonce, SHA1_DIGEST_SIZE, 0);
+ print_hex_dump_debug("trusted-key: handle ", DUMP_PREFIX_NONE,
+ 16, 1, &s->handle, 4, 0);
+ pr_debug("secret:\n");
+ print_hex_dump_debug("", DUMP_PREFIX_NONE,
+ 16, 1, &s->secret, SHA1_DIGEST_SIZE, 0);
+ pr_debug("trusted-key: enonce:\n");
+ print_hex_dump_debug("", DUMP_PREFIX_NONE,
+ 16, 1, &s->enonce, SHA1_DIGEST_SIZE, 0);
}
static inline void dump_tpm_buf(unsigned char *buf)
{
int len;
- pr_info("\ntpm buffer\n");
+ pr_debug("\ntpm buffer\n");
len = LOAD32(buf, TPM_SIZE_OFFSET);
- print_hex_dump(KERN_INFO, "", DUMP_PREFIX_NONE, 16, 1, buf, len, 0);
+ print_hex_dump_debug("", DUMP_PREFIX_NONE, 16, 1, buf, len, 0);
}
#else
static inline void dump_options(struct trusted_key_options *o)
--
2.47.3
^ permalink raw reply related
* [PATCH v2] KEYS: trusted: Debugging as a feature
From: Jarkko Sakkinen @ 2026-03-24 11:00 UTC (permalink / raw)
To: linux-integrity
Cc: keyrings, Jarkko Sakkinen, Srish Srinivasan, Nayna Jain,
James Bottomley, Mimi Zohar, David Howells, Paul Moore,
James Morris, Serge E. Hallyn, Ahmad Fatoum,
Pengutronix Kernel Team, open list, open list:SECURITY SUBSYSTEM
TPM_DEBUG, and other similar flags, are a non-standard way to specify a
feature in Linux kernel. Introduce CONFIG_TRUSTED_KEYS_DEBUG for
trusted keys, and use it to replace these ad-hoc feature flags.
Given that trusted keys debug dumps can contain sensitive data, harden
the feature as follows:
1. In the Kconfig description postulate that pr_debug() statements must be
used.
2. Use pr_debug() statements in TPM 1.x driver to print the protocol dump.
Traces, when actually needed, can be easily enabled by providing
trusted.dyndbg='+p' in the kernel command-line.
Cc: Srish Srinivasan <ssrish@linux.ibm.com>
Reported-by: Nayna Jain <nayna@linux.ibm.com>
Closes: https://lore.kernel.org/all/7f8b8478-5cd8-4d97-bfd0-341fd5cf10f9@linux.ibm.com/
Signed-off-by: Jarkko Sakkinen <jarkko@kernel.org>
---
v2:
- Implement for all trusted keys backends.
- Add HAVE_TRUSTED_KEYS_DEBUG as it is a good practice despite full
coverage.
---
include/keys/trusted-type.h | 18 +++++-------
security/keys/trusted-keys/Kconfig | 19 ++++++++++++
security/keys/trusted-keys/trusted_caam.c | 4 +--
security/keys/trusted-keys/trusted_tpm1.c | 36 +++++++++++------------
4 files changed, 46 insertions(+), 31 deletions(-)
diff --git a/include/keys/trusted-type.h b/include/keys/trusted-type.h
index 03527162613f..620a1f890b6b 100644
--- a/include/keys/trusted-type.h
+++ b/include/keys/trusted-type.h
@@ -83,18 +83,16 @@ struct trusted_key_source {
extern struct key_type key_type_trusted;
-#define TRUSTED_DEBUG 0
-
-#if TRUSTED_DEBUG
+#ifdef CONFIG_TRUSTED_KEYS_DEBUG
static inline void dump_payload(struct trusted_key_payload *p)
{
- pr_info("key_len %d\n", p->key_len);
- print_hex_dump(KERN_INFO, "key ", DUMP_PREFIX_NONE,
- 16, 1, p->key, p->key_len, 0);
- pr_info("bloblen %d\n", p->blob_len);
- print_hex_dump(KERN_INFO, "blob ", DUMP_PREFIX_NONE,
- 16, 1, p->blob, p->blob_len, 0);
- pr_info("migratable %d\n", p->migratable);
+ pr_debug("key_len %d\n", p->key_len);
+ print_hex_dump_debug("key ", DUMP_PREFIX_NONE,
+ 16, 1, p->key, p->key_len, 0);
+ pr_debug("bloblen %d\n", p->blob_len);
+ print_hex_dump_debug("blob ", DUMP_PREFIX_NONE,
+ 16, 1, p->blob, p->blob_len, 0);
+ pr_debug("migratable %d\n", p->migratable);
}
#else
static inline void dump_payload(struct trusted_key_payload *p)
diff --git a/security/keys/trusted-keys/Kconfig b/security/keys/trusted-keys/Kconfig
index 9e00482d886a..2ad9ba0e03f1 100644
--- a/security/keys/trusted-keys/Kconfig
+++ b/security/keys/trusted-keys/Kconfig
@@ -1,10 +1,25 @@
config HAVE_TRUSTED_KEYS
bool
+config HAVE_TRUSTED_KEYS_DEBUG
+ bool
+
+config TRUSTED_KEYS_DEBUG
+ bool "Debug trusted keys"
+ depends on HAVE_TRUSTED_KEYS_DEBUG
+ default n
+ help
+ Trusted keys backends and core code that support debug dumps
+ can opt-in that feature here. Dumps must only use DEBUG
+ level output, as sensitive data may pass by. In the
+ kernel-command line traces can be enabled via
+ trusted.dyndbg='+p'.
+
config TRUSTED_KEYS_TPM
bool "TPM-based trusted keys"
depends on TCG_TPM >= TRUSTED_KEYS
default y
+ select HAVE_TRUSTED_KEYS_DEBUG
select CRYPTO_HASH_INFO
select CRYPTO_LIB_SHA1
select CRYPTO_LIB_UTILS
@@ -23,6 +38,7 @@ config TRUSTED_KEYS_TEE
bool "TEE-based trusted keys"
depends on TEE >= TRUSTED_KEYS
default y
+ select HAVE_TRUSTED_KEYS_DEBUG
select HAVE_TRUSTED_KEYS
help
Enable use of the Trusted Execution Environment (TEE) as trusted
@@ -33,6 +49,7 @@ config TRUSTED_KEYS_CAAM
depends on CRYPTO_DEV_FSL_CAAM_JR >= TRUSTED_KEYS
select CRYPTO_DEV_FSL_CAAM_BLOB_GEN
default y
+ select HAVE_TRUSTED_KEYS_DEBUG
select HAVE_TRUSTED_KEYS
help
Enable use of NXP's Cryptographic Accelerator and Assurance Module
@@ -42,6 +59,7 @@ config TRUSTED_KEYS_DCP
bool "DCP-based trusted keys"
depends on CRYPTO_DEV_MXS_DCP >= TRUSTED_KEYS
default y
+ select HAVE_TRUSTED_KEYS_DEBUG
select HAVE_TRUSTED_KEYS
help
Enable use of NXP's DCP (Data Co-Processor) as trusted key backend.
@@ -50,6 +68,7 @@ config TRUSTED_KEYS_PKWM
bool "PKWM-based trusted keys"
depends on PSERIES_PLPKS >= TRUSTED_KEYS
default y
+ select HAVE_TRUSTED_KEYS_DEBUG
select HAVE_TRUSTED_KEYS
help
Enable use of IBM PowerVM Key Wrapping Module (PKWM) as a trusted key backend.
diff --git a/security/keys/trusted-keys/trusted_caam.c b/security/keys/trusted-keys/trusted_caam.c
index 601943ce0d60..015cddc6b53c 100644
--- a/security/keys/trusted-keys/trusted_caam.c
+++ b/security/keys/trusted-keys/trusted_caam.c
@@ -28,10 +28,10 @@ static const match_table_t key_tokens = {
{opt_err, NULL}
};
-#ifdef CAAM_DEBUG
+#ifdef CONFIG_TRUSTED_KEYS_DEBUG
static inline void dump_options(const struct caam_pkey_info *pkey_info)
{
- pr_info("key encryption algo %d\n", pkey_info->key_enc_algo);
+ pr_debug("key encryption algo %d\n", pkey_info->key_enc_algo);
}
#else
static inline void dump_options(const struct caam_pkey_info *pkey_info)
diff --git a/security/keys/trusted-keys/trusted_tpm1.c b/security/keys/trusted-keys/trusted_tpm1.c
index c865c97aa1b4..8fe889c7cdd1 100644
--- a/security/keys/trusted-keys/trusted_tpm1.c
+++ b/security/keys/trusted-keys/trusted_tpm1.c
@@ -46,38 +46,36 @@ enum {
SRK_keytype = 4
};
-#define TPM_DEBUG 0
-
-#if TPM_DEBUG
+#ifdef CONFIG_TRUSTED_KEYS_DEBUG
static inline void dump_options(struct trusted_key_options *o)
{
- pr_info("sealing key type %d\n", o->keytype);
- pr_info("sealing key handle %0X\n", o->keyhandle);
- pr_info("pcrlock %d\n", o->pcrlock);
- pr_info("pcrinfo %d\n", o->pcrinfo_len);
- print_hex_dump(KERN_INFO, "pcrinfo ", DUMP_PREFIX_NONE,
- 16, 1, o->pcrinfo, o->pcrinfo_len, 0);
+ pr_debug("sealing key type %d\n", o->keytype);
+ pr_debug("sealing key handle %0X\n", o->keyhandle);
+ pr_debug("pcrlock %d\n", o->pcrlock);
+ pr_debug("pcrinfo %d\n", o->pcrinfo_len);
+ print_hex_dump_debug("pcrinfo ", DUMP_PREFIX_NONE,
+ 16, 1, o->pcrinfo, o->pcrinfo_len, 0);
}
static inline void dump_sess(struct osapsess *s)
{
- print_hex_dump(KERN_INFO, "trusted-key: handle ", DUMP_PREFIX_NONE,
- 16, 1, &s->handle, 4, 0);
- pr_info("secret:\n");
- print_hex_dump(KERN_INFO, "", DUMP_PREFIX_NONE,
- 16, 1, &s->secret, SHA1_DIGEST_SIZE, 0);
- pr_info("trusted-key: enonce:\n");
- print_hex_dump(KERN_INFO, "", DUMP_PREFIX_NONE,
- 16, 1, &s->enonce, SHA1_DIGEST_SIZE, 0);
+ print_hex_dump_debug("trusted-key: handle ", DUMP_PREFIX_NONE,
+ 16, 1, &s->handle, 4, 0);
+ pr_debug("secret:\n");
+ print_hex_dump_debug("", DUMP_PREFIX_NONE,
+ 16, 1, &s->secret, SHA1_DIGEST_SIZE, 0);
+ pr_debug("trusted-key: enonce:\n");
+ print_hex_dump_debug("", DUMP_PREFIX_NONE,
+ 16, 1, &s->enonce, SHA1_DIGEST_SIZE, 0);
}
static inline void dump_tpm_buf(unsigned char *buf)
{
int len;
- pr_info("\ntpm buffer\n");
+ pr_debug("\ntpm buffer\n");
len = LOAD32(buf, TPM_SIZE_OFFSET);
- print_hex_dump(KERN_INFO, "", DUMP_PREFIX_NONE, 16, 1, buf, len, 0);
+ print_hex_dump_debug("", DUMP_PREFIX_NONE, 16, 1, buf, len, 0);
}
#else
static inline void dump_options(struct trusted_key_options *o)
--
2.47.3
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox