* Re: [PATCH] ima: debugging late_initcall_sync measurements
From: Mimi Zohar @ 2026-05-04 12:02 UTC (permalink / raw)
To: Paul Moore
Cc: Yeoreum Yun, Jonathan McDowell, linux-security-module,
linux-kernel, linux-integrity, linux-arm-kernel, kvmarm, jmorris,
serge, roberto.sassu, dmitry.kasatkin, eric.snowberg, jarkko, jgg,
sudeep.holla, maz, oupton, joey.gouly, suzuki.poulose, yuzenghui,
catalin.marinas, will, noodles, sebastianene
In-Reply-To: <CAHC9VhRE2kRr1fdDf6xgQgpSrtvqtP8Vy9LVGJhDZFUbzLKGmQ@mail.gmail.com>
On Sun, 2026-05-03 at 12:46 -0400, Paul Moore wrote:
> Regardless, assuming you always want IMA to leverage a TPMs when they
> exist, your reply suggests that using an initcall based IMA init
> scheme, even a late-sync initcall, may not be sufficient because
> deferred TPM initialization could happen later, yes?
Well yeah. The TPM could be configured as a module, but that scenario is not of
interest. That's way too late. The case being addressed in this patch set is
when the TPM driver tries to initialize at device_initcall, returns
EPROBE_DEFER, and is retried at deferred_probe_initcall (late_initcall). Since
ordering within an initcall is not supported, this patch attempts to initialize
IMA at late_initcall and similarly retries, in this case, at late_initcall_sync.
Mimi
^ permalink raw reply
* Re: [PATCH v5 09/13] ima: Add support for staging measurements with prompt
From: Roberto Sassu @ 2026-05-04 12:51 UTC (permalink / raw)
To: corbet, skhan, zohar, dmitry.kasatkin, eric.snowberg, paul,
jmorris, serge
Cc: linux-doc, linux-kernel, linux-integrity, linux-security-module,
gregorylumen, chenste, nramas, Roberto Sassu
In-Reply-To: <20260429160319.4162918-10-roberto.sassu@huaweicloud.com>
On Wed, 2026-04-29 at 18:03 +0200, Roberto Sassu wrote:
> From: Roberto Sassu <roberto.sassu@huawei.com>
>
> Introduce the ability of staging the IMA measurement list and deleting them
> with a prompt.
>
> Staging means moving the current content of the measurement list to a
> separate location, and allowing users to read and delete it. This causes
> the measurement list to be atomically truncated before new measurements can
> be added. Staging can be done only once at a time. In the event of kexec(),
> staging is reverted and staged entries will be carried over to the new
> kernel.
>
> Introduce ascii_runtime_measurements_<algo>_staged and
> binary_runtime_measurements_<algo>_staged interfaces to access and delete
> the measurements. Also, add write permission to the original measurement
> interfaces.
>
> Use 'echo A > <IMA original interface>' and
> 'echo D > <IMA _staged interface>' to respectively stage and delete the
> entire measurements list. Locking of these interfaces is also mediated with
> a call to _ima_measurements_open() and with ima_measurements_release().
While doing the staging in the original interface looks more intuitive,
since it is interface the user operates on, it causes loss of
transaction atomicity.
An agent opening the original interface has to close it, open the
staged interface to read and delete the staged measurement. Other
agents can open the staged interface first and do operations the
original agent didn't intend to do.
Will restore the previous behavior of staging/reading/deleting on the
staged interface. Will keep deleting N entries on the original
interface, since there is no risk of races.
Roberto
> Implement the staging functionality by introducing the new global
> measurements list ima_measurements_staged, and ima_queue_stage() and
> ima_queue_staged_delete_all() to respectively move measurements from the
> current measurements list to the staged one, and to move staged
> measurements to the ima_measurements_trim list for deletion. Introduce
> ima_queue_delete() to delete the measurements.
>
> Finally, introduce the BINARY_STAGED and BINARY_FULL binary measurements
> list types, to maintain the counters and the binary size of staged
> measurements and the full measurements list (including entries that were
> staged). BINARY still represents the current binary measurements list.
>
> Use the binary size for the BINARY + BINARY_STAGED types in
> ima_add_kexec_buffer(), since both measurements list types are copied to
> the secondary kernel during kexec. Use BINARY_FULL in
> ima_measure_kexec_event(), to generate a critical data record.
>
> It should be noted that the BINARY_FULL counter is not passed through
> kexec. Thus, the number of entries included in the kexec critical data
> records refers to the entries since the previous kexec records.
>
> Note: This code derives from the Alt-IMA Huawei project, whose license is
> GPL-2.0 OR MIT.
>
> Link: https://github.com/linux-integrity/linux/issues/1
> Suggested-by: Gregory Lumen <gregorylumen@linux.microsoft.com> (staging revert)
> Signed-off-by: Roberto Sassu <roberto.sassu@huawei.com>
> ---
> security/integrity/ima/Kconfig | 13 +++
> security/integrity/ima/ima.h | 8 +-
> security/integrity/ima/ima_fs.c | 181 ++++++++++++++++++++++++++---
> security/integrity/ima/ima_kexec.c | 24 +++-
> security/integrity/ima/ima_queue.c | 97 +++++++++++++++-
> 5 files changed, 302 insertions(+), 21 deletions(-)
>
> diff --git a/security/integrity/ima/Kconfig b/security/integrity/ima/Kconfig
> index 862fbee2b174..48c906793efb 100644
> --- a/security/integrity/ima/Kconfig
> +++ b/security/integrity/ima/Kconfig
> @@ -332,4 +332,17 @@ config IMA_KEXEC_EXTRA_MEMORY_KB
> If set to the default value of 0, an extra half page of memory for those
> additional measurements will be allocated.
>
> +config IMA_STAGING
> + bool "Support for staging the measurements list"
> + default y
> + help
> + Add support for staging the measurements list.
> +
> + It allows user space to stage the measurements list for deletion and
> + to delete the staged measurements after confirmation.
> +
> + On kexec, staging is reverted and staged measurements are prepended
> + to the current measurements list when measurements are copied to the
> + secondary kernel.
> +
> endif
> diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h
> index f8ab6b604c0d..ca8fa43ec72b 100644
> --- a/security/integrity/ima/ima.h
> +++ b/security/integrity/ima/ima.h
> @@ -30,9 +30,11 @@ enum tpm_pcrs { TPM_PCR0 = 0, TPM_PCR8 = 8, TPM_PCR10 = 10 };
>
> /*
> * BINARY: current binary measurements list
> + * BINARY_STAGED: staged binary measurements list
> + * BINARY_FULL: binary measurements list since IMA init (lost after kexec)
> */
> enum binary_lists {
> - BINARY, BINARY__LAST
> + BINARY, BINARY_STAGED, BINARY_FULL, BINARY__LAST
> };
>
> /* digest size for IMA, fits SHA1 or MD5 */
> @@ -125,6 +127,7 @@ struct ima_queue_entry {
> struct ima_template_entry *entry;
> };
> extern struct list_head ima_measurements; /* list of all measurements */
> +extern struct list_head ima_measurements_staged; /* list of staged meas. */
>
> /* Some details preceding the binary serialized measurement list */
> struct ima_kexec_hdr {
> @@ -315,6 +318,8 @@ struct ima_template_desc *ima_template_desc_current(void);
> struct ima_template_desc *ima_template_desc_buf(void);
> struct ima_template_desc *lookup_template_desc(const char *name);
> bool ima_template_has_modsig(const struct ima_template_desc *ima_template);
> +int ima_queue_stage(void);
> +int ima_queue_staged_delete_all(void);
> int ima_restore_measurement_entry(struct ima_template_entry *entry);
> int ima_restore_measurement_list(loff_t bufsize, void *buf);
> int ima_measurements_show(struct seq_file *m, void *v);
> @@ -335,6 +340,7 @@ extern spinlock_t ima_queue_lock;
> extern atomic_long_t ima_num_entries[BINARY__LAST];
> extern atomic_long_t ima_num_violations;
> extern struct hlist_head __rcu *ima_htable;
> +extern struct mutex ima_extend_list_mutex;
>
> static inline unsigned int ima_hash_key(u8 *digest)
> {
> diff --git a/security/integrity/ima/ima_fs.c b/security/integrity/ima/ima_fs.c
> index 7709a4576322..088d5a69aa92 100644
> --- a/security/integrity/ima/ima_fs.c
> +++ b/security/integrity/ima/ima_fs.c
> @@ -24,6 +24,13 @@
>
> #include "ima.h"
>
> +/*
> + * Requests:
> + * 'A\n': stage the entire measurements list
> + * 'D\n': delete all staged measurements
> + */
> +#define STAGED_REQ_LENGTH 21
> +
> static DEFINE_MUTEX(ima_write_mutex);
> static DEFINE_MUTEX(ima_measure_mutex);
> static long ima_measure_users;
> @@ -97,6 +104,11 @@ static void *ima_measurements_start(struct seq_file *m, loff_t *pos)
> return _ima_measurements_start(m, pos, &ima_measurements);
> }
>
> +static void *ima_measurements_staged_start(struct seq_file *m, loff_t *pos)
> +{
> + return _ima_measurements_start(m, pos, &ima_measurements_staged);
> +}
> +
> static void *_ima_measurements_next(struct seq_file *m, void *v, loff_t *pos,
> struct list_head *head)
> {
> @@ -118,6 +130,12 @@ static void *ima_measurements_next(struct seq_file *m, void *v, loff_t *pos)
> return _ima_measurements_next(m, v, pos, &ima_measurements);
> }
>
> +static void *ima_measurements_staged_next(struct seq_file *m, void *v,
> + loff_t *pos)
> +{
> + return _ima_measurements_next(m, v, pos, &ima_measurements_staged);
> +}
> +
> static void ima_measurements_stop(struct seq_file *m, void *v)
> {
> }
> @@ -211,6 +229,13 @@ static const struct seq_operations ima_measurments_seqops = {
> .show = ima_measurements_show
> };
>
> +static const struct seq_operations ima_measurments_staged_seqops = {
> + .start = ima_measurements_staged_start,
> + .next = ima_measurements_staged_next,
> + .stop = ima_measurements_stop,
> + .show = ima_measurements_show
> +};
> +
> static int ima_measure_lock(bool write)
> {
> mutex_lock(&ima_measure_mutex);
> @@ -276,9 +301,78 @@ static int ima_measurements_release(struct inode *inode, struct file *file)
> return ret;
> }
>
> +static int ima_measurements_staged_open(struct inode *inode, struct file *file)
> +{
> + return _ima_measurements_open(inode, file,
> + &ima_measurments_staged_seqops);
> +}
> +
> +static ssize_t _ima_measurements_write(struct file *file,
> + const char __user *buf, size_t datalen,
> + loff_t *ppos, bool staged_interface)
> +{
> + char req[STAGED_REQ_LENGTH];
> + int ret;
> +
> + if (*ppos > 0 || datalen < 2 || datalen > STAGED_REQ_LENGTH)
> + return -EINVAL;
> +
> + if (copy_from_user(req, buf, datalen) != 0)
> + return -EFAULT;
> +
> + if (req[datalen - 1] != '\n')
> + return -EINVAL;
> +
> + req[datalen - 1] = '\0';
> +
> + switch (req[0]) {
> + case 'A':
> + if (datalen != 2 || staged_interface)
> + return -EINVAL;
> +
> + ret = ima_queue_stage();
> + break;
> + case 'D':
> + if (datalen != 2 || !staged_interface)
> + return -EINVAL;
> +
> + ret = ima_queue_staged_delete_all();
> + break;
> + default:
> + ret = -EINVAL;
> + }
> +
> + if (ret < 0)
> + return ret;
> +
> + return datalen;
> +}
> +
> +static ssize_t ima_measurements_write(struct file *file, const char __user *buf,
> + size_t datalen, loff_t *ppos)
> +{
> + return _ima_measurements_write(file, buf, datalen, ppos, false);
> +}
> +
> +static ssize_t ima_measurements_staged_write(struct file *file,
> + const char __user *buf,
> + size_t datalen, loff_t *ppos)
> +{
> + return _ima_measurements_write(file, buf, datalen, ppos, true);
> +}
> +
> static const struct file_operations ima_measurements_ops = {
> .open = ima_measurements_open,
> .read = seq_read,
> + .write = ima_measurements_write,
> + .llseek = seq_lseek,
> + .release = ima_measurements_release,
> +};
> +
> +static const struct file_operations ima_measurements_staged_ops = {
> + .open = ima_measurements_staged_open,
> + .read = seq_read,
> + .write = ima_measurements_staged_write,
> .llseek = seq_lseek,
> .release = ima_measurements_release,
> };
> @@ -352,6 +446,29 @@ static int ima_ascii_measurements_open(struct inode *inode, struct file *file)
> static const struct file_operations ima_ascii_measurements_ops = {
> .open = ima_ascii_measurements_open,
> .read = seq_read,
> + .write = ima_measurements_write,
> + .llseek = seq_lseek,
> + .release = ima_measurements_release,
> +};
> +
> +static const struct seq_operations ima_ascii_measurements_staged_seqops = {
> + .start = ima_measurements_staged_start,
> + .next = ima_measurements_staged_next,
> + .stop = ima_measurements_stop,
> + .show = ima_ascii_measurements_show
> +};
> +
> +static int ima_ascii_measurements_staged_open(struct inode *inode,
> + struct file *file)
> +{
> + return _ima_measurements_open(inode, file,
> + &ima_ascii_measurements_staged_seqops);
> +}
> +
> +static const struct file_operations ima_ascii_measurements_staged_ops = {
> + .open = ima_ascii_measurements_staged_open,
> + .read = seq_read,
> + .write = ima_measurements_staged_write,
> .llseek = seq_lseek,
> .release = ima_measurements_release,
> };
> @@ -459,10 +576,20 @@ static const struct seq_operations ima_policy_seqops = {
> };
> #endif
>
> -static int __init create_securityfs_measurement_lists(void)
> +static int __init create_securityfs_measurement_lists(bool staging)
> {
> + const struct file_operations *ascii_ops = &ima_ascii_measurements_ops;
> + const struct file_operations *binary_ops = &ima_measurements_ops;
> + mode_t permissions = (S_IRUSR | S_IRGRP | S_IWUSR | S_IWGRP);
> + const char *file_suffix = "";
> int count = NR_BANKS(ima_tpm_chip);
>
> + if (staging) {
> + ascii_ops = &ima_ascii_measurements_staged_ops;
> + binary_ops = &ima_measurements_staged_ops;
> + file_suffix = "_staged";
> + }
> +
> if (ima_sha1_idx >= NR_BANKS(ima_tpm_chip))
> count++;
>
> @@ -473,29 +600,32 @@ static int __init create_securityfs_measurement_lists(void)
>
> if (algo == HASH_ALGO__LAST)
> snprintf(file_name, sizeof(file_name),
> - "ascii_runtime_measurements_tpm_alg_%x",
> - ima_tpm_chip->allocated_banks[i].alg_id);
> + "ascii_runtime_measurements_tpm_alg_%x%s",
> + ima_tpm_chip->allocated_banks[i].alg_id,
> + file_suffix);
> else
> snprintf(file_name, sizeof(file_name),
> - "ascii_runtime_measurements_%s",
> - hash_algo_name[algo]);
> - dentry = securityfs_create_file(file_name, S_IRUSR | S_IRGRP,
> + "ascii_runtime_measurements_%s%s",
> + hash_algo_name[algo], file_suffix);
> + dentry = securityfs_create_file(file_name, permissions,
> ima_dir, (void *)(uintptr_t)i,
> - &ima_ascii_measurements_ops);
> + ascii_ops);
> if (IS_ERR(dentry))
> return PTR_ERR(dentry);
>
> if (algo == HASH_ALGO__LAST)
> snprintf(file_name, sizeof(file_name),
> - "binary_runtime_measurements_tpm_alg_%x",
> - ima_tpm_chip->allocated_banks[i].alg_id);
> + "binary_runtime_measurements_tpm_alg_%x%s",
> + ima_tpm_chip->allocated_banks[i].alg_id,
> + file_suffix);
> else
> snprintf(file_name, sizeof(file_name),
> - "binary_runtime_measurements_%s",
> - hash_algo_name[algo]);
> - dentry = securityfs_create_file(file_name, S_IRUSR | S_IRGRP,
> + "binary_runtime_measurements_%s%s",
> + hash_algo_name[algo], file_suffix);
> +
> + dentry = securityfs_create_file(file_name, permissions,
> ima_dir, (void *)(uintptr_t)i,
> - &ima_measurements_ops);
> + binary_ops);
> if (IS_ERR(dentry))
> return PTR_ERR(dentry);
> }
> @@ -503,6 +633,23 @@ static int __init create_securityfs_measurement_lists(void)
> return 0;
> }
>
> +static int __init create_securityfs_staging_links(void)
> +{
> + struct dentry *dentry;
> +
> + dentry = securityfs_create_symlink("binary_runtime_measurements_staged",
> + ima_dir, "binary_runtime_measurements_sha1_staged", NULL);
> + if (IS_ERR(dentry))
> + return PTR_ERR(dentry);
> +
> + dentry = securityfs_create_symlink("ascii_runtime_measurements_staged",
> + ima_dir, "ascii_runtime_measurements_sha1_staged", NULL);
> + if (IS_ERR(dentry))
> + return PTR_ERR(dentry);
> +
> + return 0;
> +}
> +
> /*
> * ima_open_policy: sequentialize access to the policy file
> */
> @@ -595,7 +742,13 @@ int __init ima_fs_init(void)
> goto out;
> }
>
> - ret = create_securityfs_measurement_lists();
> + ret = create_securityfs_measurement_lists(false);
> + if (ret == 0 && IS_ENABLED(CONFIG_IMA_STAGING)) {
> + ret = create_securityfs_measurement_lists(true);
> + if (ret == 0)
> + ret = create_securityfs_staging_links();
> + }
> +
> if (ret != 0)
> goto out;
>
> diff --git a/security/integrity/ima/ima_kexec.c b/security/integrity/ima/ima_kexec.c
> index d7d0fb639d99..064cfce0c318 100644
> --- a/security/integrity/ima/ima_kexec.c
> +++ b/security/integrity/ima/ima_kexec.c
> @@ -42,8 +42,8 @@ void ima_measure_kexec_event(const char *event_name)
> long len;
> int n;
>
> - buf_size = ima_get_binary_runtime_size(BINARY);
> - len = atomic_long_read(&ima_num_entries[BINARY]);
> + buf_size = ima_get_binary_runtime_size(BINARY_FULL);
> + len = atomic_long_read(&ima_num_entries[BINARY_FULL]);
>
> n = scnprintf(ima_kexec_event, IMA_KEXEC_EVENT_LEN,
> "kexec_segment_size=%lu;ima_binary_runtime_size=%lu;"
> @@ -106,13 +106,28 @@ static int ima_dump_measurement_list(unsigned long *buffer_size, void **buffer,
>
> memset(&khdr, 0, sizeof(khdr));
> khdr.version = 1;
> - /* This is an append-only list, no need to hold the RCU read lock */
> - list_for_each_entry_rcu(qe, &ima_measurements, later, true) {
> + /*
> + * It can race with ima_queue_stage() and ima_queue_staged_delete_all().
> + */
> + mutex_lock(&ima_extend_list_mutex);
> +
> + list_for_each_entry_rcu(qe, &ima_measurements_staged, later,
> + lockdep_is_held(&ima_extend_list_mutex)) {
> ret = ima_dump_measurement(&khdr, qe);
> if (ret < 0)
> break;
> }
>
> + list_for_each_entry_rcu(qe, &ima_measurements, later,
> + lockdep_is_held(&ima_extend_list_mutex)) {
> + if (!ret)
> + ret = ima_dump_measurement(&khdr, qe);
> + if (ret < 0)
> + break;
> + }
> +
> + mutex_unlock(&ima_extend_list_mutex);
> +
> /*
> * fill in reserved space with some buffer details
> * (eg. version, buffer size, number of measurements)
> @@ -167,6 +182,7 @@ void ima_add_kexec_buffer(struct kimage *image)
> extra_memory = CONFIG_IMA_KEXEC_EXTRA_MEMORY_KB * 1024;
>
> binary_runtime_size = ima_get_binary_runtime_size(BINARY) +
> + ima_get_binary_runtime_size(BINARY_STAGED) +
> extra_memory;
>
> if (binary_runtime_size >= ULONG_MAX - PAGE_SIZE)
> diff --git a/security/integrity/ima/ima_queue.c b/security/integrity/ima/ima_queue.c
> index b6d10dceb669..50519ed837d4 100644
> --- a/security/integrity/ima/ima_queue.c
> +++ b/security/integrity/ima/ima_queue.c
> @@ -26,6 +26,7 @@
> static struct tpm_digest *digests;
>
> LIST_HEAD(ima_measurements); /* list of all measurements */
> +LIST_HEAD(ima_measurements_staged); /* list of staged measurements */
> #ifdef CONFIG_IMA_KEXEC
> static unsigned long binary_runtime_size[BINARY__LAST];
> #else
> @@ -45,11 +46,11 @@ atomic_long_t ima_num_violations = ATOMIC_LONG_INIT(0);
> /* key: inode (before secure-hashing a file) */
> struct hlist_head __rcu *ima_htable;
>
> -/* mutex protects atomicity of extending measurement list
> +/* mutex protects atomicity of extending and staging measurement list
> * and extending the TPM PCR aggregate. Since tpm_extend can take
> * long (and the tpm driver uses a mutex), we can't use the spinlock.
> */
> -static DEFINE_MUTEX(ima_extend_list_mutex);
> +DEFINE_MUTEX(ima_extend_list_mutex);
>
> /*
> * Used internally by the kernel to suspend measurements.
> @@ -174,12 +175,16 @@ static int ima_add_digest_entry(struct ima_template_entry *entry,
> lockdep_is_held(&ima_extend_list_mutex));
>
> atomic_long_inc(&ima_num_entries[BINARY]);
> + atomic_long_inc(&ima_num_entries[BINARY_FULL]);
> +
> if (update_htable) {
> key = ima_hash_key(entry->digests[ima_hash_algo_idx].digest);
> hlist_add_head_rcu(&qe->hnext, &htable[key]);
> }
>
> ima_update_binary_runtime_size(entry, BINARY);
> + ima_update_binary_runtime_size(entry, BINARY_FULL);
> +
> return 0;
> }
>
> @@ -280,6 +285,94 @@ int ima_add_template_entry(struct ima_template_entry *entry, int violation,
> return result;
> }
>
> +int ima_queue_stage(void)
> +{
> + int ret = 0;
> +
> + mutex_lock(&ima_extend_list_mutex);
> + if (!list_empty(&ima_measurements_staged)) {
> + ret = -EEXIST;
> + goto out_unlock;
> + }
> +
> + if (list_empty(&ima_measurements)) {
> + ret = -ENOENT;
> + goto out_unlock;
> + }
> +
> + list_replace(&ima_measurements, &ima_measurements_staged);
> + INIT_LIST_HEAD(&ima_measurements);
> +
> + atomic_long_set(&ima_num_entries[BINARY_STAGED],
> + atomic_long_read(&ima_num_entries[BINARY]));
> + atomic_long_set(&ima_num_entries[BINARY], 0);
> +
> + if (IS_ENABLED(CONFIG_IMA_KEXEC)) {
> + binary_runtime_size[BINARY_STAGED] =
> + binary_runtime_size[BINARY];
> + binary_runtime_size[BINARY] = 0;
> + }
> +out_unlock:
> + mutex_unlock(&ima_extend_list_mutex);
> + return ret;
> +}
> +
> +static void ima_queue_delete(struct list_head *head);
> +
> +int ima_queue_staged_delete_all(void)
> +{
> + LIST_HEAD(ima_measurements_trim);
> +
> + mutex_lock(&ima_extend_list_mutex);
> + if (list_empty(&ima_measurements_staged)) {
> + mutex_unlock(&ima_extend_list_mutex);
> + return -ENOENT;
> + }
> +
> + list_replace(&ima_measurements_staged, &ima_measurements_trim);
> + INIT_LIST_HEAD(&ima_measurements_staged);
> +
> + atomic_long_set(&ima_num_entries[BINARY_STAGED], 0);
> +
> + if (IS_ENABLED(CONFIG_IMA_KEXEC))
> + binary_runtime_size[BINARY_STAGED] = 0;
> +
> + mutex_unlock(&ima_extend_list_mutex);
> +
> + ima_queue_delete(&ima_measurements_trim);
> + return 0;
> +}
> +
> +static void ima_queue_delete(struct list_head *head)
> +{
> + struct ima_queue_entry *qe, *qe_tmp;
> + unsigned int i;
> +
> + list_for_each_entry_safe(qe, qe_tmp, head, later) {
> + /*
> + * Safe to free template_data here without synchronize_rcu()
> + * because the only htable reader, ima_lookup_digest_entry(),
> + * accesses only entry->digests, not template_data. If new
> + * htable readers are added that access template_data, a
> + * synchronize_rcu() is required here.
> + */
> + for (i = 0; i < qe->entry->template_desc->num_fields; i++) {
> + kfree(qe->entry->template_data[i].data);
> + qe->entry->template_data[i].data = NULL;
> + qe->entry->template_data[i].len = 0;
> + }
> +
> + list_del(&qe->later);
> +
> + /* No leak if condition is false, referenced by ima_htable. */
> + if (IS_ENABLED(CONFIG_IMA_DISABLE_HTABLE)) {
> + kfree(qe->entry->digests);
> + kfree(qe->entry);
> + kfree(qe);
> + }
> + }
> +}
> +
> int ima_restore_measurement_entry(struct ima_template_entry *entry)
> {
> int result = 0;
^ permalink raw reply
* Re: [PATCH 1/3] apparmor: Fix return in ns_mkdir_op
From: Ryan Lee @ 2026-05-04 18:22 UTC (permalink / raw)
To: Hongling Zeng
Cc: john.johansen, paul, jmorris, serge, neil, brauner, jlayton, jack,
apparmor, linux-security-module, linux-kernel, zhongling0719
In-Reply-To: <20260503041243.200895-1-zenghongling@kylinos.cn>
On Sat, May 2, 2026 at 9:13 PM Hongling Zeng <zenghongling@kylinos.cn> wrote:
>
> Return NULL instead of passing to ERR_PTR while error is zero.
> Fixes smatch warning:
> - security/apparmor/apparmorfs.c:1846 ns_mkdir_op() warn:
> passing zero to 'ERR_PTR'
>
> Fixes: 88d5baf69082 ("Change inode_operations.mkdir to return struct dentry *")
> Signed-off-by: Hongling Zeng <zenghongling@kylinos.cn>
> ---
> security/apparmor/apparmorfs.c | 2 +-
> 1 file changed, 1 insertion(+), 1 deletion(-)
>
> diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c
> index ededaf46f3ca..1d7b1c70f22a 100644
> --- a/security/apparmor/apparmorfs.c
> +++ b/security/apparmor/apparmorfs.c
> @@ -1922,7 +1922,7 @@ static struct dentry *ns_mkdir_op(struct mnt_idmap *idmap, struct inode *dir,
> mutex_unlock(&parent->lock);
> aa_put_ns(parent);
>
> - return ERR_PTR(error);
> + return error ? ERR_PTR(error) : NULL;
> }
>
> static int ns_rmdir_op(struct inode *dir, struct dentry *dentry)
> --
> 2.25.1
>
>
Reviewed-by: Ryan Lee <ryan.lee@canonical.com>
^ permalink raw reply
* Re: [PATCH v2 1/2] bpf: add bpf_init_inode_xattr kfunc for atomic inode labeling
From: Paul Moore @ 2026-05-04 20:14 UTC (permalink / raw)
To: David Windsor
Cc: Alexander Viro, Christian Brauner, Alexei Starovoitov,
Daniel Borkmann, Andrii Nakryiko, Eduard Zingerman,
Kumar Kartikeya Dwivedi, KP Singh, Matt Bobrowski, James Morris,
Serge E. Hallyn, Mimi Zohar, Roberto Sassu, Dmitry Kasatkin,
Stephen Smalley, Casey Schaufler, Song Liu, Jan Kara,
John Fastabend, Martin KaFai Lau, Yonghong Song, Jiri Olsa,
Eric Snowberg, Ondrej Mosnacek, linux-fsdevel, linux-kernel, bpf,
linux-security-module, linux-integrity, selinux
In-Reply-To: <20260503211835.16103-2-dwindsor@gmail.com>
On Sun, May 3, 2026 at 5:18 PM David Windsor <dwindsor@gmail.com> wrote:
>
> Add bpf_init_inode_xattr() kfunc for BPF LSM programs to atomically set
> xattrs via the inode_init_security hook using lsm_get_xattr_slot().
>
> The inode_init_security hook previously took the xattr array and count
> as two separate output parameters (struct xattr *xattrs, int
> *xattr_count), which BPF programs cannot write to. Pass the xattr state
> as a single context object (struct lsm_xattr_ctx) instead, and have
> bpf_init_inode_xattr() take that context directly. Update the existing
> in-tree callers of inode_init_security to take and forward the new
> lsm_xattr_ctx.
>
> Because we rely on the hook-specific ctx layout, the kfunc is
> restricted to lsm/inode_init_security. Restrict the xattr names that
> may be set via this kfunc to the bpf.* namespace.
>
> Suggested-by: Song Liu <song@kernel.org>
> Signed-off-by: David Windsor <dwindsor@gmail.com>
> ---
> fs/bpf_fs_kfuncs.c | 106 +++++++++++++++++++++++++++++-
> include/linux/bpf_lsm.h | 3 +
> include/linux/evm.h | 9 +--
> include/linux/lsm_hook_defs.h | 4 +-
> include/linux/lsm_hooks.h | 16 ++---
> include/linux/security.h | 5 ++
> kernel/bpf/bpf_lsm.c | 1 +
> security/bpf/hooks.c | 1 +
> security/integrity/evm/evm_main.c | 8 ++-
> security/security.c | 7 +-
> security/selinux/hooks.c | 4 +-
> security/smack/smack_lsm.c | 13 ++--
> 12 files changed, 147 insertions(+), 30 deletions(-)
Comments below ...
> diff --git a/fs/bpf_fs_kfuncs.c b/fs/bpf_fs_kfuncs.c
> index 9d27be058494..193accc00796 100644
> --- a/fs/bpf_fs_kfuncs.c
> +++ b/fs/bpf_fs_kfuncs.c
> @@ -10,6 +10,7 @@
> #include <linux/fsnotify.h>
> #include <linux/file.h>
> #include <linux/kernfs.h>
> +#include <linux/lsm_hooks.h>
> #include <linux/mm.h>
> #include <linux/xattr.h>
>
> @@ -353,6 +354,97 @@ __bpf_kfunc int bpf_cgroup_read_xattr(struct cgroup *cgroup, const char *name__s
> }
> #endif /* CONFIG_CGROUPS */
>
> +static int bpf_xattrs_used(const struct lsm_xattr_ctx *ctx)
> +{
> + const size_t prefix_len = sizeof(XATTR_BPF_LSM_SUFFIX) - 1;
> + int i, n = 0;
> +
> + for (i = 0; i < *ctx->xattr_count; i++) {
> + const char *name = ctx->xattrs[i].name;
> +
> + if (name && !strncmp(name, XATTR_BPF_LSM_SUFFIX, prefix_len))
> + n++;
> + }
> + return n;
> +}
> +
> +static int __bpf_init_inode_xattr(struct lsm_xattr_ctx *xattr_ctx,
> + const char *name__str,
> + const struct bpf_dynptr *value_p)
> +{
> + struct bpf_dynptr_kern *value_ptr = (struct bpf_dynptr_kern *)value_p;
> + size_t name_len;
> + void *xattr_value;
> + struct xattr *xattr;
> + struct xattr *xattrs;
> + int *xattr_count;
> + const void *value;
> + u32 value_len;
> +
> + if (!xattr_ctx || !name__str)
> + return -EINVAL;
> +
> + xattrs = xattr_ctx->xattrs;
> + xattr_count = xattr_ctx->xattr_count;
> + if (!xattrs || !xattr_count)
> + return -EINVAL;
> + if (bpf_xattrs_used(xattr_ctx) >= BPF_LSM_INODE_INIT_XATTRS)
> + return -ENOSPC;
> +
> + name_len = strlen(name__str);
> + if (name_len == 0 || name_len > XATTR_NAME_MAX)
> + return -EINVAL;
> + if (strncmp(name__str, XATTR_BPF_LSM_SUFFIX,
> + sizeof(XATTR_BPF_LSM_SUFFIX) - 1))
> + return -EPERM;
> +
> + value_len = __bpf_dynptr_size(value_ptr);
> + if (value_len == 0 || value_len > XATTR_SIZE_MAX)
> + return -EINVAL;
> +
> + value = __bpf_dynptr_data(value_ptr, value_len);
> + if (!value)
> + return -EINVAL;
> +
> + /* Combine xattr value + name into one allocation. */
> + xattr_value = kmalloc(value_len + name_len + 1, GFP_KERNEL);
> + if (!xattr_value)
> + return -ENOMEM;
> +
> + memcpy(xattr_value, value, value_len);
> + memcpy(xattr_value + value_len, name__str, name_len);
> + ((char *)xattr_value)[value_len + name_len] = '\0';
> +
> + xattr = lsm_get_xattr_slot(xattr_ctx);
> + if (!xattr) {
> + kfree(xattr_value);
> + return -ENOSPC;
> + }
> +
> + xattr->value = xattr_value;
> + xattr->name = (const char *)xattr_value + value_len;
> + xattr->value_len = value_len;
> +
> + return 0;
> +}
> +
> +/**
> + * bpf_init_inode_xattr - set an xattr on a new inode from inode_init_security
> + * @xattr_ctx: inode_init_security xattr state from the hook context
> + * @name__str: xattr name (e.g., "bpf.file_label")
> + * @value_p: dynptr containing the xattr value
> + *
> + * Only callable from lsm/inode_init_security programs.
> + *
> + * Return: 0 on success, negative error on failure.
> + */
> +__bpf_kfunc int bpf_init_inode_xattr(struct lsm_xattr_ctx *xattr_ctx,
> + const char *name__str,
> + const struct bpf_dynptr *value_p)
> +{
> + return __bpf_init_inode_xattr(xattr_ctx, name__str, value_p);
> +}
> +
> __bpf_kfunc_end_defs();
>
> BTF_KFUNCS_START(bpf_fs_kfunc_set_ids)
> @@ -363,13 +455,25 @@ BTF_ID_FLAGS(func, bpf_get_dentry_xattr, KF_SLEEPABLE)
> BTF_ID_FLAGS(func, bpf_get_file_xattr, KF_SLEEPABLE)
> BTF_ID_FLAGS(func, bpf_set_dentry_xattr, KF_SLEEPABLE)
> BTF_ID_FLAGS(func, bpf_remove_dentry_xattr, KF_SLEEPABLE)
> +BTF_ID_FLAGS(func, bpf_init_inode_xattr, KF_SLEEPABLE)
> BTF_KFUNCS_END(bpf_fs_kfunc_set_ids)
>
> +BTF_ID_LIST(bpf_lsm_inode_init_security_btf_ids)
> +BTF_ID(func, bpf_lsm_inode_init_security)
> +
> +BTF_ID_LIST(bpf_init_inode_xattr_btf_ids)
> +BTF_ID(func, bpf_init_inode_xattr)
> +
> static int bpf_fs_kfuncs_filter(const struct bpf_prog *prog, u32 kfunc_id)
> {
> if (!btf_id_set8_contains(&bpf_fs_kfunc_set_ids, kfunc_id) ||
> - prog->type == BPF_PROG_TYPE_LSM)
> + prog->type == BPF_PROG_TYPE_LSM) {
> + /* bpf_init_inode_xattr only attaches to inode_init_security. */
> + if (kfunc_id == bpf_init_inode_xattr_btf_ids[0] &&
> + prog->aux->attach_btf_id != bpf_lsm_inode_init_security_btf_ids[0])
> + return -EACCES;
> return 0;
> + }
> return -EACCES;
> }
Perhaps I'm simply not seeing it, but is there a check to ensure that
there is only one BPF LSM calling into security_inode_init_security()
at any given time? With the BPF LSM only reserving a single xattr
slot, multiple loaded BPF LSM programs providing
security_inode_init_security() callbacks will be a problem.
> diff --git a/include/linux/security.h b/include/linux/security.h
> index 41d7367cf403..a2fc72e63ada 100644
> --- a/include/linux/security.h
> +++ b/include/linux/security.h
> @@ -68,6 +68,11 @@ struct watch;
> struct watch_notification;
> struct lsm_ctx;
>
> +struct lsm_xattr_ctx {
> + struct xattr *xattrs;
> + int *xattr_count;
> +};
I'd prefer this to be simply "struct lsm_xattrs" as "ctx" is an
overloaded term in the LSM space.
> diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c
> index 97801966bf32..dca81a22bf83 100644
> --- a/security/selinux/hooks.c
> +++ b/security/selinux/hooks.c
> @@ -2962,11 +2962,11 @@ static int selinux_dentry_create_files_as(struct dentry *dentry, int mode,
>
> static int selinux_inode_init_security(struct inode *inode, struct inode *dir,
> const struct qstr *qstr,
> - struct xattr *xattrs, int *xattr_count)
> + struct lsm_xattr_ctx *xattr_ctx)
> {
> const struct cred_security_struct *crsec = selinux_cred(current_cred());
> struct superblock_security_struct *sbsec;
> - struct xattr *xattr = lsm_get_xattr_slot(xattrs, xattr_count);
> + struct xattr *xattr = lsm_get_xattr_slot(xattr_ctx);
> u32 newsid, clen;
> u16 newsclass;
> int rc;
In case you didn't see it, your fix for the above lsm_get_xattr_slot()
usage is now in Linus' tree. It's a trivial bit of merge fuzz, but
you might want to rebase your next revision.
--
paul-moore.com
^ permalink raw reply
* Re: [PATCH] ima: debugging late_initcall_sync measurements
From: Paul Moore @ 2026-05-04 20:51 UTC (permalink / raw)
To: Mimi Zohar
Cc: Yeoreum Yun, Jonathan McDowell, linux-security-module,
linux-kernel, linux-integrity, linux-arm-kernel, kvmarm, jmorris,
serge, roberto.sassu, dmitry.kasatkin, eric.snowberg, jarkko, jgg,
sudeep.holla, maz, oupton, joey.gouly, suzuki.poulose, yuzenghui,
catalin.marinas, will, noodles, sebastianene
In-Reply-To: <ff28c6dcb60c357c752724927addaa8c4fd3bf2c.camel@linux.ibm.com>
On Mon, May 4, 2026 at 8:03 AM Mimi Zohar <zohar@linux.ibm.com> wrote:
> On Sun, 2026-05-03 at 12:46 -0400, Paul Moore wrote:
> > Regardless, assuming you always want IMA to leverage a TPMs when they
> > exist, your reply suggests that using an initcall based IMA init
> > scheme, even a late-sync initcall, may not be sufficient because
> > deferred TPM initialization could happen later, yes?
>
> Well yeah. The TPM could be configured as a module, but that scenario is not of
> interest. That's way too late. The case being addressed in this patch set is
> when the TPM driver tries to initialize at device_initcall, returns
> EPROBE_DEFER, and is retried at deferred_probe_initcall (late_initcall). Since
> ordering within an initcall is not supported, this patch attempts to initialize
> IMA at late_initcall and similarly retries, in this case, at late_initcall_sync.
Okay, so from a TPM initialization perspective you are satisfied with
a late-sync IMA initialization, yes?
--
paul-moore.com
^ permalink raw reply
* Re: [PATCH v2 1/2] bpf: add bpf_init_inode_xattr kfunc for atomic inode labeling
From: Song Liu @ 2026-05-04 21:40 UTC (permalink / raw)
To: Paul Moore
Cc: David Windsor, Alexander Viro, Christian Brauner,
Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
Eduard Zingerman, Kumar Kartikeya Dwivedi, KP Singh,
Matt Bobrowski, James Morris, Serge E. Hallyn, Mimi Zohar,
Roberto Sassu, Dmitry Kasatkin, Stephen Smalley, Casey Schaufler,
Jan Kara, John Fastabend, Martin KaFai Lau, Yonghong Song,
Jiri Olsa, Eric Snowberg, Ondrej Mosnacek, linux-fsdevel,
linux-kernel, bpf, linux-security-module, linux-integrity,
selinux
In-Reply-To: <CAHC9VhSy5K5nQTtFUE4BScy1Ur61v7eZW067vTcUYDQeJb13Bw@mail.gmail.com>
On Mon, May 4, 2026 at 10:14 PM Paul Moore <paul@paul-moore.com> wrote:
[...]
> > diff --git a/fs/bpf_fs_kfuncs.c b/fs/bpf_fs_kfuncs.c
> > index 9d27be058494..193accc00796 100644
> > --- a/fs/bpf_fs_kfuncs.c
> > +++ b/fs/bpf_fs_kfuncs.c
> > @@ -10,6 +10,7 @@
> > #include <linux/fsnotify.h>
> > #include <linux/file.h>
> > #include <linux/kernfs.h>
> > +#include <linux/lsm_hooks.h>
> > #include <linux/mm.h>
> > #include <linux/xattr.h>
> >
> > @@ -353,6 +354,97 @@ __bpf_kfunc int bpf_cgroup_read_xattr(struct cgroup *cgroup, const char *name__s
> > }
> > #endif /* CONFIG_CGROUPS */
> >
> > +static int bpf_xattrs_used(const struct lsm_xattr_ctx *ctx)
> > +{
> > + const size_t prefix_len = sizeof(XATTR_BPF_LSM_SUFFIX) - 1;
> > + int i, n = 0;
> > +
> > + for (i = 0; i < *ctx->xattr_count; i++) {
> > + const char *name = ctx->xattrs[i].name;
> > +
> > + if (name && !strncmp(name, XATTR_BPF_LSM_SUFFIX, prefix_len))
> > + n++;
> > + }
> > + return n;
> > +}
[...]
> > +
> > static int bpf_fs_kfuncs_filter(const struct bpf_prog *prog, u32 kfunc_id)
> > {
> > if (!btf_id_set8_contains(&bpf_fs_kfunc_set_ids, kfunc_id) ||
> > - prog->type == BPF_PROG_TYPE_LSM)
> > + prog->type == BPF_PROG_TYPE_LSM) {
> > + /* bpf_init_inode_xattr only attaches to inode_init_security. */
> > + if (kfunc_id == bpf_init_inode_xattr_btf_ids[0] &&
> > + prog->aux->attach_btf_id != bpf_lsm_inode_init_security_btf_ids[0])
> > + return -EACCES;
We need to mark bpf_init_inode_xattr with KF_RCU (requires a trusted
pointer), then we can remove this check above.
> > return 0;
> > + }
> > return -EACCES;
> > }
>
> Perhaps I'm simply not seeing it, but is there a check to ensure that
> there is only one BPF LSM calling into security_inode_init_security()
> at any given time? With the BPF LSM only reserving a single xattr
> slot, multiple loaded BPF LSM programs providing
> security_inode_init_security() callbacks will be a problem.
I don't think there is such a check. Also, a single BPF LSM function
may call the kfunc multiple times, which is also problematic.
I think we will need to make the default bigger, and also introduce
some realloc mechanism for the worst case scenario. This should
work, but the code might be a bit messy.
Thanks,
Song
>
> > diff --git a/include/linux/security.h b/include/linux/security.h
> > index 41d7367cf403..a2fc72e63ada 100644
> > --- a/include/linux/security.h
> > +++ b/include/linux/security.h
> > @@ -68,6 +68,11 @@ struct watch;
> > struct watch_notification;
> > struct lsm_ctx;
> >
[...]
^ permalink raw reply
* Re: [PATCH v2 0/4] Firmware LSM hook
From: Paul Moore @ 2026-05-04 22:33 UTC (permalink / raw)
To: Jason Gunthorpe
Cc: Leon Romanovsky, Roberto Sassu, KP Singh, Matt Bobrowski,
Alexei Starovoitov, Daniel Borkmann, John Fastabend,
Andrii Nakryiko, Martin KaFai Lau, Eduard Zingerman, Song Liu,
Yonghong Song, Stanislav Fomichev, Hao Luo, Jiri Olsa, Shuah Khan,
Saeed Mahameed, Itay Avraham, Dave Jiang, Jonathan Cameron, bpf,
linux-kernel, linux-kselftest, linux-rdma, Chiara Meiohas,
Maher Sanalla, linux-security-module
In-Reply-To: <20260424221310.GA804026@ziepe.ca>
On Fri, Apr 24, 2026 at 6:13 PM Jason Gunthorpe <jgg@ziepe.ca> wrote:
>
> ... I wonder if we are even speaking the same language.
Let's reset the conversation.
As I understand it, based on our discussion in this thread and Leon's
previous patchsets, the basic idea is to enable LSMs to enforce access
control over fwctl requests/commands sent from userspace. I'm going
to start with that as a basis.
Using the kernel's docs on fwctl, the userspace API appears to consist
mostly of ioctls with some basic sysfs interfaces. It looks like we
can mostly ignore the sysfs interface and focus on the ioctl side of
the API, do you agree?
https://docs.kernel.org/userspace-api/fwctl/fwctl.html
While normally I would suggest simply using the existing
security_file_ioctl() hook, Leon previously mentioned that the hook is
too early for fwctl as the userspace copy happens much later. Looking
at the code, it appears that the copy happens in fwctl_fops_ioctl()
for all fwctl ioctls regardless of the device or ioctl, is that
correct?
Assuming the above is correct, how about the following LSM hook,
called after the copy_struct_from_user() in fwctl_fops_ioctl()?
union fwctl_data {
struct fwctl_info info;
struct fwctl_rpc rpc;
}
int security_fwctl_ioctl(struct file *filep, unsigned int cmd, union
fwctl_data *arg)
Where @filep is the file/device being sent the ioctl, @cmd is the
ioctl command number (e.g. FWCTL_RPC), and @arg is the copied ioctl
data (e.g. ucmd.cmd in fwctl_fops_ioctl). In addition to applying
access controls based on the ioctl command number, a capability that
already exists via the security_file_ioctl() hook, LSMs could also
apply access controls based on the RPC scope as well as any other well
defined data in the ioctl payload.
I expect most of the existing LSMs would implement callbacks for this
new hook with the subject being the process submitting the ioctl, the
object being the file/device that is being operated on with the
ioctl() call, and the access/privilege/verb/etc. being something along
the lines of INFO, RPC_CONFIG, RPC_DEBUG_READ, RPC_DEBUG_WRITE, or
RPC_DEBUG_WRITE_FULL. Of course these are just quick examples to
demonstrate a point, please don't take those names as hard
requirements. Each LSM is free to characterize the access request
however they like, in a way that best aligns with their security
model.
--
paul-moore.com
^ permalink raw reply
* Re: [PATCH v2 1/2] bpf: add bpf_init_inode_xattr kfunc for atomic inode labeling
From: Paul Moore @ 2026-05-04 22:42 UTC (permalink / raw)
To: Song Liu
Cc: David Windsor, Alexander Viro, Christian Brauner,
Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
Eduard Zingerman, Kumar Kartikeya Dwivedi, KP Singh,
Matt Bobrowski, James Morris, Serge E. Hallyn, Mimi Zohar,
Roberto Sassu, Dmitry Kasatkin, Stephen Smalley, Casey Schaufler,
Jan Kara, John Fastabend, Martin KaFai Lau, Yonghong Song,
Jiri Olsa, Eric Snowberg, Ondrej Mosnacek, linux-fsdevel,
linux-kernel, bpf, linux-security-module, linux-integrity,
selinux
In-Reply-To: <CAPhsuW6sy2cdC4B7Z48-5A-yVX6fmVWxS_fWVjQxiX95KeUguw@mail.gmail.com>
On Mon, May 4, 2026 at 5:40 PM Song Liu <song@kernel.org> wrote:
> On Mon, May 4, 2026 at 10:14 PM Paul Moore <paul@paul-moore.com> wrote:
> [...]
> > > diff --git a/fs/bpf_fs_kfuncs.c b/fs/bpf_fs_kfuncs.c
> > > index 9d27be058494..193accc00796 100644
> > > --- a/fs/bpf_fs_kfuncs.c
> > > +++ b/fs/bpf_fs_kfuncs.c
> > > @@ -10,6 +10,7 @@
> > > #include <linux/fsnotify.h>
> > > #include <linux/file.h>
> > > #include <linux/kernfs.h>
> > > +#include <linux/lsm_hooks.h>
> > > #include <linux/mm.h>
> > > #include <linux/xattr.h>
> > >
> > > @@ -353,6 +354,97 @@ __bpf_kfunc int bpf_cgroup_read_xattr(struct cgroup *cgroup, const char *name__s
> > > }
> > > #endif /* CONFIG_CGROUPS */
> > >
> > > +static int bpf_xattrs_used(const struct lsm_xattr_ctx *ctx)
> > > +{
> > > + const size_t prefix_len = sizeof(XATTR_BPF_LSM_SUFFIX) - 1;
> > > + int i, n = 0;
> > > +
> > > + for (i = 0; i < *ctx->xattr_count; i++) {
> > > + const char *name = ctx->xattrs[i].name;
> > > +
> > > + if (name && !strncmp(name, XATTR_BPF_LSM_SUFFIX, prefix_len))
> > > + n++;
> > > + }
> > > + return n;
> > > +}
> [...]
> > > +
> > > static int bpf_fs_kfuncs_filter(const struct bpf_prog *prog, u32 kfunc_id)
> > > {
> > > if (!btf_id_set8_contains(&bpf_fs_kfunc_set_ids, kfunc_id) ||
> > > - prog->type == BPF_PROG_TYPE_LSM)
> > > + prog->type == BPF_PROG_TYPE_LSM) {
> > > + /* bpf_init_inode_xattr only attaches to inode_init_security. */
> > > + if (kfunc_id == bpf_init_inode_xattr_btf_ids[0] &&
> > > + prog->aux->attach_btf_id != bpf_lsm_inode_init_security_btf_ids[0])
> > > + return -EACCES;
>
> We need to mark bpf_init_inode_xattr with KF_RCU (requires a trusted
> pointer), then we can remove this check above.
>
> > > return 0;
> > > + }
> > > return -EACCES;
> > > }
> >
> > Perhaps I'm simply not seeing it, but is there a check to ensure that
> > there is only one BPF LSM calling into security_inode_init_security()
> > at any given time? With the BPF LSM only reserving a single xattr
> > slot, multiple loaded BPF LSM programs providing
> > security_inode_init_security() callbacks will be a problem.
>
> I don't think there is such a check. Also, a single BPF LSM function
> may call the kfunc multiple times, which is also problematic.
>
> I think we will need to make the default bigger, and also introduce
> some realloc mechanism for the worst case scenario. This should
> work, but the code might be a bit messy.
Thanks for the clarification, that is what I was afraid of when
looking at the code, but I was hoping I was just missing it.
Increasing the default is an option, but I don't think we want to
support a dynamic reallocation scheme for the xattr slots, that will
likely get extremely messy with synchronization between the LSM
framework and BPF LSM hook registrations as well as special code to
handle inodes with lifetimes that are disjoint from the BPF LSM
programs ... I suppose there may be a way to do it, but it will surely
be ugly and come at a cost.
--
paul-moore.com
^ permalink raw reply
* Re: [PATCH v2 1/2] bpf: add bpf_init_inode_xattr kfunc for atomic inode labeling
From: Song Liu @ 2026-05-04 23:09 UTC (permalink / raw)
To: Paul Moore
Cc: David Windsor, Alexander Viro, Christian Brauner,
Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
Eduard Zingerman, Kumar Kartikeya Dwivedi, KP Singh,
Matt Bobrowski, James Morris, Serge E. Hallyn, Mimi Zohar,
Roberto Sassu, Dmitry Kasatkin, Stephen Smalley, Casey Schaufler,
Jan Kara, John Fastabend, Martin KaFai Lau, Yonghong Song,
Jiri Olsa, Eric Snowberg, Ondrej Mosnacek, linux-fsdevel,
linux-kernel, bpf, linux-security-module, linux-integrity,
selinux
In-Reply-To: <CAHC9VhQLN5NA_ZMMNyUdMCZVdwC3VM4PUnzka8xDK5rpR2a3sw@mail.gmail.com>
On Tue, May 5, 2026 at 12:42 AM Paul Moore <paul@paul-moore.com> wrote:
[...]
> > > Perhaps I'm simply not seeing it, but is there a check to ensure that
> > > there is only one BPF LSM calling into security_inode_init_security()
> > > at any given time? With the BPF LSM only reserving a single xattr
> > > slot, multiple loaded BPF LSM programs providing
> > > security_inode_init_security() callbacks will be a problem.
> >
> > I don't think there is such a check. Also, a single BPF LSM function
> > may call the kfunc multiple times, which is also problematic.
> >
> > I think we will need to make the default bigger, and also introduce
> > some realloc mechanism for the worst case scenario. This should
> > work, but the code might be a bit messy.
>
> Thanks for the clarification, that is what I was afraid of when
> looking at the code, but I was hoping I was just missing it.
>
> Increasing the default is an option, but I don't think we want to
> support a dynamic reallocation scheme for the xattr slots, that will
> likely get extremely messy with synchronization between the LSM
> framework and BPF LSM hook registrations as well as special code to
> handle inodes with lifetimes that are disjoint from the BPF LSM
> programs ... I suppose there may be a way to do it, but it will surely
> be ugly and come at a cost.
BPF trampoline already handles all the synchronizations, such as
add hook, remove hook, etc. properly. So this is not that hard.
All we really need is to allocate a new array, copy pointers, and free
the old array. And we only really need this in the worst case
scenarios.
Thanks,
Song
^ permalink raw reply
* Re: [v6 10/10] ipe: Add BPF program load policy enforcement via Hornet integration
From: Fan Wu @ 2026-05-04 23:52 UTC (permalink / raw)
To: Blaise Boscaccy
Cc: 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, Song Liu
In-Reply-To: <20260429191431.2345448-11-bboscaccy@linux.microsoft.com>
On Wed, Apr 29, 2026 at 12:15 PM Blaise Boscaccy
<bboscaccy@linux.microsoft.com> wrote:
>
> Add support for the bpf_prog_load_post_integrity LSM hook, enabling IPE
> to make policy decisions about BPF program loading based on integrity
> verdicts provided by the Hornet LSM.
>
> New policy operation:
> op=BPF_PROG_LOAD - Matches BPF program load events
>
> New policy properties:
> bpf_signature=NONE - No Verdict
> bpf_signature=OK - Program signature and map hashes verified
> bpf_signature=UNSIGNED - No signature provided
> bpf_signature=PARTIALSIG - Signature OK but no map hash data
> bpf_signature=UNKNOWNKEY - Cert not trusted
This one should be: The keyring requested by the user is invalid.
> bpf_signature=UNEXPECTED - An unexpected hash value was encountered
> bpf_signature=FAULT - System error during verification
> bpf_signature=BADSIG - Signature or map hash verification failed
> bpf_keyring=BUILTIN - Program was signed using a builtin keyring
> bpf_keyring=SECONDARY - Program was signed using the secondary keyring
> bpf_keyring=PLATFORM - Program was signed using the platform keyring
> bpf_kernel=TRUE - Program originated from kernelspace
> bpf_kernel=FALSE - Program originated from userspace
>
> These properties map directly to the lsm_integrity_verdict enum values
> provided by the Hornet LSM through security_bpf_prog_load_post_integrity.
>
> The feature is gated on CONFIG_IPE_PROP_BPF_SIGNATURE which depends on
> CONFIG_SECURITY_HORNET.
>
> Signed-off-by: Blaise Boscaccy <bboscaccy@linux.microsoft.com>
> ---
> Documentation/admin-guide/LSM/ipe.rst | 162 +++++++++++++++++++++++++-
> Documentation/security/ipe.rst | 39 +++++++
> security/ipe/Kconfig | 14 +++
> security/ipe/audit.c | 15 +++
> security/ipe/eval.c | 73 +++++++++++-
> security/ipe/eval.h | 11 ++
> security/ipe/hooks.c | 63 ++++++++++
> security/ipe/hooks.h | 15 +++
> security/ipe/ipe.c | 14 +++
> security/ipe/ipe.h | 3 +
> security/ipe/policy.h | 14 +++
> security/ipe/policy_parser.c | 27 +++++
> 12 files changed, 448 insertions(+), 2 deletions(-)
>
> diff --git a/Documentation/admin-guide/LSM/ipe.rst b/Documentation/admin-guide/LSM/ipe.rst
> index a756d81585317..4dfbf0d325a8a 100644
> --- a/Documentation/admin-guide/LSM/ipe.rst
> +++ b/Documentation/admin-guide/LSM/ipe.rst
> @@ -559,7 +559,8 @@ policy. Two properties are built-into the policy parser: 'op' and 'action'.
> The other properties are used to restrict immutable security properties
> about the files being evaluated. Currently those properties are:
> '``boot_verified``', '``dmverity_signature``', '``dmverity_roothash``',
> -'``fsverity_signature``', '``fsverity_digest``'. A description of all
> +'``fsverity_signature``', '``fsverity_digest``', '``bpf_signature``',
> +'``bpf_keyring``', '``bpf_kernel``'. A description of all
> properties supported by IPE are listed below:
>
> op
> @@ -603,6 +604,14 @@ as the first token. IPE supports the following operations:
> Controls loading IMA certificates through the Kconfigs,
> ``CONFIG_IMA_X509_PATH`` and ``CONFIG_EVM_X509_PATH``.
>
> + ``BPF_PROG_LOAD``:
> +
> + Pertains to BPF programs being loaded via the ``bpf()`` syscall.
> + This operation is used in conjunction with the ``bpf_signature``,
> + ``bpf_keyring``, and ``bpf_kernel`` properties to control BPF
> + program loading based on integrity verification provided by the
> + Hornet LSM.
> +
> action
> ~~~~~~
>
> @@ -713,6 +722,105 @@ fsverity_signature
>
> fsverity_signature=(TRUE|FALSE)
>
> +bpf_signature
> +~~~~~~~~~~~~~
> +
> + This property can be utilized for authorization of BPF program loads based
> + on the integrity verdict provided by the Hornet LSM. When a BPF program is
> + loaded, Hornet performs cryptographic verification of the program's PKCS#7
> + signature (if present) and passes an integrity verdict to IPE via the
> + ``security_bpf_prog_load_post_integrity`` hook. IPE can then allow or deny
> + the load based on the verdict.
> +
> + This property depends on ``SECURITY_HORNET`` and is controlled by the
> + ``IPE_PROP_BPF_SIGNATURE`` config option.
> + The format of this property is::
> +
> + bpf_signature=(NONE|OK|UNSIGNED|PARTIALSIG|UNKNOWNKEY|UNEXPECTED|FAULT|BADSIG)
> +
> + The possible values correspond to the integrity verdicts from Hornet:
> +
> + ``NONE``
> +
> + No integrity verdict was set (default/uninitialized).
> +
> + ``OK``
> +
> + The BPF program's signature and all map hashes were successfully
> + verified.
> +
> + ``UNSIGNED``
> +
> + No signature was provided with the BPF program.
> +
> + ``PARTIALSIG``
> +
> + The program signature was verified, but no authenticated map hash
> + data was present.
> +
> + ``UNKNOWNKEY``
> +
> + The signing certificate is not trusted by the specified keyring.
Same above.
> +
> + ``UNEXPECTED``
> +
> + An unexpected map hash value was encountered during verification.
> +
> + ``FAULT``
> +
> + A system error occurred during signature verification.
> +
> + ``BADSIG``
> +
> + The signature or hash verification failed.
> +
> +bpf_keyring
> +~~~~~~~~~~~~
> +
> + This property can be utilized for authorization of BPF program loads based
> + on the keyring specified in the ``bpf_attr`` during the ``BPF_PROG_LOAD``
> + syscall. This allows policies to restrict which keyring must be used for
> + signature verification of BPF programs.
> +
> + This property shares the ``IPE_PROP_BPF_SIGNATURE`` config option with
> + ``bpf_signature``.
> + The format of this property is::
> +
> + bpf_keyring=(BUILTIN|SECONDARY|PLATFORM)
> +
> + The possible values correspond to the system keyrings:
> +
> + ``BUILTIN``
> +
> + The builtin trusted keyring (``.builtin_trusted_keys``), which
> + contains keys embedded at kernel compile time.
> +
> + ``SECONDARY``
> +
> + The secondary trusted keyring (``.secondary_trusted_keys``), which
> + includes both builtin trusted keys and keys added at runtime.
> +
> + ``PLATFORM``
> +
> + The platform keyring (``.platform``), which contains keys provided
> + by the platform firmware (e.g. UEFI db keys).
> +
> +bpf_kernel
> +~~~~~~~~~~
> +
> + This property can be utilized for authorization of BPF program loads based
> + on whether the load originated from kernel space or user space. The BPF
> + light skeleton infrastructure performs a secondary kernel-originated program
> + load that will not carry a signature. This property allows policies to
> + permit such kernel-originated loads while still requiring signatures for
> + user-space loads.
> +
> + This property shares the ``IPE_PROP_BPF_SIGNATURE`` config option with
> + ``bpf_signature``.
> + The format of this property is::
> +
> + bpf_kernel=(TRUE|FALSE)
> +
> Policy Examples
> ---------------
>
> @@ -788,6 +896,58 @@ Allow execution of a specific fs-verity file
>
> op=EXECUTE fsverity_digest=sha256:fd88f2b8824e197f850bf4c5109bea5cf0ee38104f710843bb72da796ba5af9e action=ALLOW
>
> +Allow only signed BPF programs
> +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> +
> +::
> +
> + policy_name=Allow_Signed_BPF policy_version=0.0.0
> + DEFAULT action=ALLOW
> +
> + DEFAULT op=BPF_PROG_LOAD action=DENY
> + op=BPF_PROG_LOAD bpf_kernel=TRUE action=ALLOW
> + op=BPF_PROG_LOAD bpf_signature=OK action=ALLOW
> +
> +This policy allows all other operations but restricts BPF program loading
> +to only programs that either originate from kernel space (e.g. light skeleton
> +reloads) or have a valid signature verified by the Hornet LSM. Unsigned or
> +improperly signed BPF programs from user space will be denied.
> +
> +Allow signed BPF programs from a specific keyring
> +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> +
> +::
> +
> + policy_name=Allow_BPF_Builtin_Keyring policy_version=0.0.0
> + DEFAULT action=ALLOW
> +
> + DEFAULT op=BPF_PROG_LOAD action=DENY
> + op=BPF_PROG_LOAD bpf_kernel=TRUE action=ALLOW
> + op=BPF_PROG_LOAD bpf_signature=OK bpf_keyring=BUILTIN action=ALLOW
> +
> +This policy further restricts BPF program loading to only accept programs
> +whose signatures were verified using the builtin trusted keyring. Programs
> +signed against the secondary or platform keyrings will be denied, providing
> +tighter control over which signing keys are acceptable.
> +
> +Allow signed BPF programs with relaxed partial signatures
> +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> +
> +::
> +
> + policy_name=Allow_BPF_Partial policy_version=0.0.0
> + DEFAULT action=ALLOW
> +
> + DEFAULT op=BPF_PROG_LOAD action=DENY
> + op=BPF_PROG_LOAD bpf_kernel=TRUE action=ALLOW
> + op=BPF_PROG_LOAD bpf_signature=OK action=ALLOW
> + op=BPF_PROG_LOAD bpf_signature=PARTIALSIG action=ALLOW
> +
> +This policy allows BPF programs that have been fully verified (``OK``) as
> +well as programs with a valid program signature but without authenticated
> +map hash data (``PARTIALSIG``). This can be useful during development or
> +for programs that do not use maps.
> +
> Additional Information
> ----------------------
>
> diff --git a/Documentation/security/ipe.rst b/Documentation/security/ipe.rst
> index 4a7d953abcdc3..de8fcf1dc173d 100644
> --- a/Documentation/security/ipe.rst
> +++ b/Documentation/security/ipe.rst
> @@ -412,6 +412,44 @@ a standard securityfs policy tree::
>
> The policy is stored in the ``->i_private`` data of the MyPolicy inode.
>
> +BPF/Hornet Integration
> +~~~~~~~~~~~~~~~~~~~~~~
> +
> +IPE integrates with the Hornet LSM to enforce integrity policies on BPF
> +program loading. Hornet performs cryptographic verification of BPF program
> +signatures (PKCS#7 with authenticated attributes containing map hashes) and
> +provides an integrity verdict to IPE via the
> +``security_bpf_prog_load_post_integrity`` hook.
> +
> +The hook flow is:
> +
> + 1. User space invokes ``BPF_PROG_LOAD`` via the ``bpf()`` syscall.
> + 2. Hornet's ``bpf_prog_load_integrity`` hook calls ``hornet_check_program()``
> + to verify the program's signature and map hashes.
> + 3. Hornet calls ``security_bpf_prog_load_post_integrity()`` with the
> + resulting ``lsm_integrity_verdict``.
> + 4. IPE evaluates the verdict against the active policy's ``BPF_PROG_LOAD``
> + rules and returns ``-EACCES`` if denied.
> +
This part needs to be updated.
> +Three properties are available for BPF policy rules:
> +
> + - ``bpf_signature``: Matches against the integrity verdict (OK, UNSIGNED,
> + BADSIG, etc.)
> + - ``bpf_keyring``: Matches against the keyring specified in ``bpf_attr``
> + (BUILTIN, SECONDARY, PLATFORM)
> + - ``bpf_kernel``: Matches whether the load originated from kernel space
> + (TRUE/FALSE). This is important because the BPF light skeleton
> + infrastructure performs a secondary kernel-originated program load that
> + does not carry a signature.
> +
> +All three properties are gated on ``CONFIG_IPE_PROP_BPF_SIGNATURE`` which
> +depends on ``CONFIG_SECURITY_HORNET``.
> +
> +The evaluation context (``struct ipe_eval_ctx``) carries three BPF-specific
> +fields: ``bpf_verdict`` (the integrity verdict enum), ``bpf_keyring_id``
> +(the ``s32`` keyring ID from ``bpf_attr``), and ``bpf_kernel`` (bool
> +indicating kernel origin).
> +
> Tests
> -----
>
> @@ -439,6 +477,7 @@ IPE has KUnit Tests for the policy parser. Recommended kunitconfig::
> CONFIG_IPE_PROP_DM_VERITY_SIGNATURE=y
> CONFIG_IPE_PROP_FS_VERITY=y
> CONFIG_IPE_PROP_FS_VERITY_BUILTIN_SIG=y
> + CONFIG_IPE_PROP_BPF_SIGNATURE=y
> CONFIG_SECURITY_IPE_KUNIT_TEST=y
>
> In addition, IPE has a python based integration
> diff --git a/security/ipe/Kconfig b/security/ipe/Kconfig
> index a110a6cd848b7..4c1d46847582b 100644
> --- a/security/ipe/Kconfig
> +++ b/security/ipe/Kconfig
> @@ -95,6 +95,20 @@ config IPE_PROP_FS_VERITY_BUILTIN_SIG
>
> if unsure, answer Y.
>
> +config IPE_PROP_BPF_SIGNATURE
> + bool "Enable support for Hornet BPF program signature verification"
> + depends on SECURITY_HORNET
> + help
> + This option enables the 'bpf_signature' and 'bpf_keyring'
bpf_kernel is missing.
> + properties within IPE policies. The 'bpf_signature' property
> + allows IPE to make policy decisions based on the integrity
> + verdict provided by the Hornet LSM when a BPF program is loaded.
> + Verdicts include OK, UNSIGNED, PARTIALSIG, BADSIG, and others.
> + The 'bpf_keyring' property allows policies to match against the
> + keyring specified in bpf_attr (BUILTIN, SECONDARY, PLATFORM).
> +
> + If unsure, answer Y.
> +
> endmenu
>
> config SECURITY_IPE_KUNIT_TEST
> diff --git a/security/ipe/audit.c b/security/ipe/audit.c
> index 3f0deeb549127..251c6ec2f8423 100644
> --- a/security/ipe/audit.c
> +++ b/security/ipe/audit.c
> @@ -41,6 +41,7 @@ static const char *const audit_op_names[__IPE_OP_MAX + 1] = {
> "KEXEC_INITRAMFS",
> "POLICY",
> "X509_CERT",
> + "BPF_PROG_LOAD",
> "UNKNOWN",
> };
>
> @@ -51,6 +52,7 @@ static const char *const audit_hook_names[__IPE_HOOK_MAX] = {
> "MPROTECT",
> "KERNEL_READ",
> "KERNEL_LOAD",
> + "BPF_PROG_LOAD",
> };
>
> static const char *const audit_prop_names[__IPE_PROP_MAX] = {
> @@ -62,6 +64,19 @@ static const char *const audit_prop_names[__IPE_PROP_MAX] = {
> "fsverity_digest=",
> "fsverity_signature=FALSE",
> "fsverity_signature=TRUE",
> + "bpf_signature=NONE",
> + "bpf_signature=OK",
> + "bpf_signature=UNSIGNED",
> + "bpf_signature=PARTIALSIG",
> + "bpf_signature=UNKNOWNKEY",
> + "bpf_signature=UNEXPECTED",
> + "bpf_signature=FAULT",
> + "bpf_signature=BADSIG",
> + "bpf_keyring=BUILTIN",
> + "bpf_keyring=SECONDARY",
> + "bpf_keyring=PLATFORM",
> + "bpf_kernel=FALSE",
> + "bpf_kernel=TRUE",
> };
>
> /**
> diff --git a/security/ipe/eval.c b/security/ipe/eval.c
> index 21439c5be3364..9a6d583fea125 100644
> --- a/security/ipe/eval.c
> +++ b/security/ipe/eval.c
> @@ -11,6 +11,7 @@
> #include <linux/rcupdate.h>
> #include <linux/moduleparam.h>
> #include <linux/fsverity.h>
> +#include <linux/verification.h>
>
> #include "ipe.h"
> #include "eval.h"
> @@ -265,8 +266,52 @@ static bool evaluate_fsv_sig_true(const struct ipe_eval_ctx *const ctx)
> }
> #endif /* CONFIG_IPE_PROP_FS_VERITY_BUILTIN_SIG */
>
> +#ifdef CONFIG_IPE_PROP_BPF_SIGNATURE
> +/**
> + * evaluate_bpf_sig() - Evaluate @ctx against a bpf_signature property.
> + * @ctx: Supplies a pointer to the context being evaluated.
> + * @expected: The expected lsm_integrity_verdict to match against.
> + *
> + * Return:
> + * * %true - The current @ctx matches the expected verdict
> + * * %false - The current @ctx doesn't match the expected verdict
> + */
> +static bool evaluate_bpf_sig(const struct ipe_eval_ctx *const ctx,
> + enum lsm_integrity_verdict expected)
> +{
> + return ctx->bpf_verdict == expected;
> +}
> +#else
> +static bool evaluate_bpf_sig(const struct ipe_eval_ctx *const ctx,
> + enum lsm_integrity_verdict expected)
> +{
> + return false;
> +}
> +#endif /* CONFIG_IPE_PROP_BPF_SIGNATURE */
> +
> +#ifdef CONFIG_IPE_PROP_BPF_SIGNATURE
> +/**
> + * evaluate_bpf_keyring() - Evaluate @ctx against a bpf_keyring property.
> + * @ctx: Supplies a pointer to the context being evaluated.
> + * @expected: The expected keyring_id to match against.
> + *
> + * Return:
> + * * %true - The current @ctx matches the expected keyring
> + * * %false - The current @ctx doesn't match the expected keyring
> + */
> +static bool evaluate_bpf_keyring(const struct ipe_eval_ctx *const ctx,
> + s32 expected)
> +{
> + return ctx->bpf_keyring_id == expected;
> +}
> +#else
> +static bool evaluate_bpf_keyring(const struct ipe_eval_ctx *const ctx,
> + s32 expected)
> +{
> + return false;
> +}
> +#endif /* CONFIG_IPE_PROP_BPF_SIGNATURE */
> /**
> - * evaluate_property() - Analyze @ctx against a rule property.
> * @ctx: Supplies a pointer to the context to be evaluated.
> * @p: Supplies a pointer to the property to be evaluated.
> *
> @@ -297,6 +342,32 @@ static bool evaluate_property(const struct ipe_eval_ctx *const ctx,
> return evaluate_fsv_sig_false(ctx);
> case IPE_PROP_FSV_SIG_TRUE:
> return evaluate_fsv_sig_true(ctx);
> + case IPE_PROP_BPF_SIG_NONE:
> + return evaluate_bpf_sig(ctx, LSM_INT_VERDICT_NONE);
> + case IPE_PROP_BPF_SIG_OK:
> + return evaluate_bpf_sig(ctx, LSM_INT_VERDICT_OK);
> + case IPE_PROP_BPF_SIG_UNSIGNED:
> + return evaluate_bpf_sig(ctx, LSM_INT_VERDICT_UNSIGNED);
> + case IPE_PROP_BPF_SIG_PARTIALSIG:
> + return evaluate_bpf_sig(ctx, LSM_INT_VERDICT_PARTIALSIG);
> + case IPE_PROP_BPF_SIG_UNKNOWNKEY:
> + return evaluate_bpf_sig(ctx, LSM_INT_VERDICT_UNKNOWNKEY);
> + case IPE_PROP_BPF_SIG_UNEXPECTED:
> + return evaluate_bpf_sig(ctx, LSM_INT_VERDICT_UNEXPECTED);
> + case IPE_PROP_BPF_SIG_FAULT:
> + return evaluate_bpf_sig(ctx, LSM_INT_VERDICT_FAULT);
> + case IPE_PROP_BPF_SIG_BADSIG:
> + return evaluate_bpf_sig(ctx, LSM_INT_VERDICT_BADSIG);
> + case IPE_PROP_BPF_KEYRING_BUILTIN:
> + return evaluate_bpf_keyring(ctx, 0);
> + case IPE_PROP_BPF_KEYRING_SECONDARY:
> + return evaluate_bpf_keyring(ctx, (s32)(unsigned long)VERIFY_USE_SECONDARY_KEYRING);
> + case IPE_PROP_BPF_KEYRING_PLATFORM:
> + return evaluate_bpf_keyring(ctx, (s32)(unsigned long)VERIFY_USE_PLATFORM_KEYRING);
> + case IPE_PROP_BPF_KERNEL_FALSE:
> + return !ctx->bpf_kernel;
> + case IPE_PROP_BPF_KERNEL_TRUE:
> + return ctx->bpf_kernel;
bpf_kernel part needs to be guarded by #ifdef, like the other two.
-Fan
> default:
> return false;
> }
^ permalink raw reply
* Re: [PATCH v2 1/2] bpf: add bpf_init_inode_xattr kfunc for atomic inode labeling
From: David Windsor @ 2026-05-05 1:07 UTC (permalink / raw)
To: Song Liu
Cc: Paul Moore, Alexander Viro, Christian Brauner, Alexei Starovoitov,
Daniel Borkmann, Andrii Nakryiko, Eduard Zingerman,
Kumar Kartikeya Dwivedi, KP Singh, Matt Bobrowski, James Morris,
Serge E. Hallyn, Mimi Zohar, Roberto Sassu, Dmitry Kasatkin,
Stephen Smalley, Casey Schaufler, Jan Kara, John Fastabend,
Martin KaFai Lau, Yonghong Song, Jiri Olsa, Eric Snowberg,
Ondrej Mosnacek, linux-fsdevel, linux-kernel, bpf,
linux-security-module, linux-integrity, selinux
In-Reply-To: <CAPhsuW5nDaLAV5UfAHeX6QPeF6bs-WDkFYOzYO7Q9_O6v=jEHA@mail.gmail.com>
On Mon, May 4, 2026 at 7:09 PM Song Liu <song@kernel.org> wrote:
>
> On Tue, May 5, 2026 at 12:42 AM Paul Moore <paul@paul-moore.com> wrote:
> [...]
> > > > Perhaps I'm simply not seeing it, but is there a check to ensure that
> > > > there is only one BPF LSM calling into security_inode_init_security()
> > > > at any given time? With the BPF LSM only reserving a single xattr
> > > > slot, multiple loaded BPF LSM programs providing
> > > > security_inode_init_security() callbacks will be a problem.
> > >
> > > I don't think there is such a check. Also, a single BPF LSM function
> > > may call the kfunc multiple times, which is also problematic.
> > >
bpf_xattrs_used() guards against this. The lsm_xattr_ctx is shared
between all callers, so xattr additions by another LSM (or by calling
it multiple times in the same function) will be tracked by this.
> > > I think we will need to make the default bigger, and also introduce
> > > some realloc mechanism for the worst case scenario. This should
> > > work, but the code might be a bit messy.
> >
> > Thanks for the clarification, that is what I was afraid of when
> > looking at the code, but I was hoping I was just missing it.
> >
> > Increasing the default is an option, but I don't think we want to
> > support a dynamic reallocation scheme for the xattr slots, that will
> > likely get extremely messy with synchronization between the LSM
> > framework and BPF LSM hook registrations as well as special code to
> > handle inodes with lifetimes that are disjoint from the BPF LSM
> > programs ... I suppose there may be a way to do it, but it will surely
> > be ugly and come at a cost.
>
> BPF trampoline already handles all the synchronizations, such as
> add hook, remove hook, etc. properly. So this is not that hard.
> All we really need is to allocate a new array, copy pointers, and free
> the old array. And we only really need this in the worst case
> scenarios.
>
How many bpf-lsm programs do we envision being attached at once? I'd
think that stacking of bpf-lsms would be difficult to reason about
(moreso than static LSMs) and won't work that well in practice, but
may be wrong. Most LSMs use 1 xattr, Smack is the only one who uses 2
IIRC.
> Thanks,
> Song
^ permalink raw reply
* Re: [PATCH v2 1/2] bpf: add bpf_init_inode_xattr kfunc for atomic inode labeling
From: Paul Moore @ 2026-05-05 2:02 UTC (permalink / raw)
To: Song Liu
Cc: David Windsor, Alexander Viro, Christian Brauner,
Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
Eduard Zingerman, Kumar Kartikeya Dwivedi, KP Singh,
Matt Bobrowski, James Morris, Serge E. Hallyn, Mimi Zohar,
Roberto Sassu, Dmitry Kasatkin, Stephen Smalley, Casey Schaufler,
Jan Kara, John Fastabend, Martin KaFai Lau, Yonghong Song,
Jiri Olsa, Eric Snowberg, Ondrej Mosnacek, linux-fsdevel,
linux-kernel, bpf, linux-security-module, linux-integrity,
selinux
In-Reply-To: <CAPhsuW5nDaLAV5UfAHeX6QPeF6bs-WDkFYOzYO7Q9_O6v=jEHA@mail.gmail.com>
On Mon, May 4, 2026 at 7:09 PM Song Liu <song@kernel.org> wrote:
> On Tue, May 5, 2026 at 12:42 AM Paul Moore <paul@paul-moore.com> wrote:
> [...]
> > > > Perhaps I'm simply not seeing it, but is there a check to ensure that
> > > > there is only one BPF LSM calling into security_inode_init_security()
> > > > at any given time? With the BPF LSM only reserving a single xattr
> > > > slot, multiple loaded BPF LSM programs providing
> > > > security_inode_init_security() callbacks will be a problem.
> > >
> > > I don't think there is such a check. Also, a single BPF LSM function
> > > may call the kfunc multiple times, which is also problematic.
> > >
> > > I think we will need to make the default bigger, and also introduce
> > > some realloc mechanism for the worst case scenario. This should
> > > work, but the code might be a bit messy.
> >
> > Thanks for the clarification, that is what I was afraid of when
> > looking at the code, but I was hoping I was just missing it.
> >
> > Increasing the default is an option, but I don't think we want to
> > support a dynamic reallocation scheme for the xattr slots, that will
> > likely get extremely messy with synchronization between the LSM
> > framework and BPF LSM hook registrations as well as special code to
> > handle inodes with lifetimes that are disjoint from the BPF LSM
> > programs ... I suppose there may be a way to do it, but it will surely
> > be ugly and come at a cost.
>
> BPF trampoline already handles all the synchronizations, such as
> add hook, remove hook, etc. properly. So this is not that hard.
How do you plan to handle the issue of disjoint lifetimes?
> All we really need is to allocate a new array, copy pointers, and free
> the old array. And we only really need this in the worst case
> scenarios.
Oh, is that all? :D
Keep in mind that the code must also handle arbitrary ordering of
LSMs; in other words, you must handle a BPF LSM that isn't at the end
of the LSM order. While a BPF LSM at the end of the LSM list is the
most common, and recommended ordering for the vast majority of users,
we've been working to make the ordering as generalized as possible.
--
paul-moore.com
^ permalink raw reply
* Re: [PATCH v2 1/2] bpf: add bpf_init_inode_xattr kfunc for atomic inode labeling
From: Paul Moore @ 2026-05-05 2:05 UTC (permalink / raw)
To: Song Liu
Cc: David Windsor, Alexander Viro, Christian Brauner,
Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
Eduard Zingerman, Kumar Kartikeya Dwivedi, KP Singh,
Matt Bobrowski, James Morris, Serge E. Hallyn, Mimi Zohar,
Roberto Sassu, Dmitry Kasatkin, Stephen Smalley, Casey Schaufler,
Jan Kara, John Fastabend, Martin KaFai Lau, Yonghong Song,
Jiri Olsa, Eric Snowberg, Ondrej Mosnacek, linux-fsdevel,
linux-kernel, bpf, linux-security-module, linux-integrity,
selinux
In-Reply-To: <CAHC9VhRx7L1WXYgQvWmGN0a7ssSaDKx4JPhup2E3W161sdp74A@mail.gmail.com>
On Mon, May 4, 2026 at 10:02 PM Paul Moore <paul@paul-moore.com> wrote:
> On Mon, May 4, 2026 at 7:09 PM Song Liu <song@kernel.org> wrote:
> > On Tue, May 5, 2026 at 12:42 AM Paul Moore <paul@paul-moore.com> wrote:
> > [...]
> > > > > Perhaps I'm simply not seeing it, but is there a check to ensure that
> > > > > there is only one BPF LSM calling into security_inode_init_security()
> > > > > at any given time? With the BPF LSM only reserving a single xattr
> > > > > slot, multiple loaded BPF LSM programs providing
> > > > > security_inode_init_security() callbacks will be a problem.
> > > >
> > > > I don't think there is such a check. Also, a single BPF LSM function
> > > > may call the kfunc multiple times, which is also problematic.
> > > >
> > > > I think we will need to make the default bigger, and also introduce
> > > > some realloc mechanism for the worst case scenario. This should
> > > > work, but the code might be a bit messy.
> > >
> > > Thanks for the clarification, that is what I was afraid of when
> > > looking at the code, but I was hoping I was just missing it.
> > >
> > > Increasing the default is an option, but I don't think we want to
> > > support a dynamic reallocation scheme for the xattr slots, that will
> > > likely get extremely messy with synchronization between the LSM
> > > framework and BPF LSM hook registrations as well as special code to
> > > handle inodes with lifetimes that are disjoint from the BPF LSM
> > > programs ... I suppose there may be a way to do it, but it will surely
> > > be ugly and come at a cost.
> >
> > BPF trampoline already handles all the synchronizations, such as
> > add hook, remove hook, etc. properly. So this is not that hard.
>
> How do you plan to handle the issue of disjoint lifetimes?
>
> > All we really need is to allocate a new array, copy pointers, and free
> > the old array. And we only really need this in the worst case
> > scenarios.
>
> Oh, is that all? :D
>
> Keep in mind that the code must also handle arbitrary ordering of
> LSMs; in other words, you must handle a BPF LSM that isn't at the end
> of the LSM order. While a BPF LSM at the end of the LSM list is the
> most common, and recommended ordering for the vast majority of users,
> we've been working to make the ordering as generalized as possible.
I just realized I probably wasn't as clear as I should have been ... I
really don't like telling people not to go experiment with things and
play with the code as that feels wrong for many reasons, but I do want
to warn you that if the code to handle this ends up looking like I
think it will, I'm not going to want to support it in the LSM
framework.
--
paul-moore.com
^ permalink raw reply
* Re: [PATCH v2 1/2] bpf: add bpf_init_inode_xattr kfunc for atomic inode labeling
From: Song Liu @ 2026-05-05 9:00 UTC (permalink / raw)
To: Paul Moore
Cc: David Windsor, Alexander Viro, Christian Brauner,
Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
Eduard Zingerman, Kumar Kartikeya Dwivedi, KP Singh,
Matt Bobrowski, James Morris, Serge E. Hallyn, Mimi Zohar,
Roberto Sassu, Dmitry Kasatkin, Stephen Smalley, Casey Schaufler,
Jan Kara, John Fastabend, Martin KaFai Lau, Yonghong Song,
Jiri Olsa, Eric Snowberg, Ondrej Mosnacek, linux-fsdevel,
linux-kernel, bpf, linux-security-module, linux-integrity,
selinux
In-Reply-To: <CAHC9VhRx7L1WXYgQvWmGN0a7ssSaDKx4JPhup2E3W161sdp74A@mail.gmail.com>
On Tue, May 5, 2026 at 4:02 AM Paul Moore <paul@paul-moore.com> wrote:
>
> On Mon, May 4, 2026 at 7:09 PM Song Liu <song@kernel.org> wrote:
> > On Tue, May 5, 2026 at 12:42 AM Paul Moore <paul@paul-moore.com> wrote:
> > [...]
> > > > > Perhaps I'm simply not seeing it, but is there a check to ensure that
> > > > > there is only one BPF LSM calling into security_inode_init_security()
> > > > > at any given time? With the BPF LSM only reserving a single xattr
> > > > > slot, multiple loaded BPF LSM programs providing
> > > > > security_inode_init_security() callbacks will be a problem.
> > > >
> > > > I don't think there is such a check. Also, a single BPF LSM function
> > > > may call the kfunc multiple times, which is also problematic.
> > > >
> > > > I think we will need to make the default bigger, and also introduce
> > > > some realloc mechanism for the worst case scenario. This should
> > > > work, but the code might be a bit messy.
> > >
> > > Thanks for the clarification, that is what I was afraid of when
> > > looking at the code, but I was hoping I was just missing it.
> > >
> > > Increasing the default is an option, but I don't think we want to
> > > support a dynamic reallocation scheme for the xattr slots, that will
> > > likely get extremely messy with synchronization between the LSM
> > > framework and BPF LSM hook registrations as well as special code to
> > > handle inodes with lifetimes that are disjoint from the BPF LSM
> > > programs ... I suppose there may be a way to do it, but it will surely
> > > be ugly and come at a cost.
> >
> > BPF trampoline already handles all the synchronizations, such as
> > add hook, remove hook, etc. properly. So this is not that hard.
>
> How do you plan to handle the issue of disjoint lifetimes?
>
> > All we really need is to allocate a new array, copy pointers, and free
> > the old array. And we only really need this in the worst case
> > scenarios.
>
> Oh, is that all? :D
>
> Keep in mind that the code must also handle arbitrary ordering of
> LSMs; in other words, you must handle a BPF LSM that isn't at the end
> of the LSM order. While a BPF LSM at the end of the LSM list is the
> most common, and recommended ordering for the vast majority of users,
> we've been working to make the ordering as generalized as possible.
All the BPF LSM hooks are called together, so it should be fine.
Maybe I missed some corner cases.
Either way, I agree with David that we don't need too many xattrs.
Since BPF LSM is reserved to the privileged users only, it is safe
to put a reasonable limit, say 4 or 8, and do not handle the realloc.
If the admin would like to brick a system with BPF LSM, there are
many other ways to do it.
Thanks,
Song
^ permalink raw reply
* [PATCH v5 01/14] kbuild: generate module BTF based on vmlinux.unstripped
From: Thomas Weißschuh @ 2026-05-05 9:05 UTC (permalink / raw)
To: Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
Eduard Zingerman, Kumar Kartikeya Dwivedi, Nathan Chancellor,
Nicolas Schier, Arnd Bergmann, Luis Chamberlain, Petr Pavlu,
Sami Tolvanen, Daniel Gomez, Paul Moore, James Morris,
Serge E. Hallyn, Jonathan Corbet, Madhavan Srinivasan,
Michael Ellerman, Nicholas Piggin, Naveen N Rao, Mimi Zohar,
Roberto Sassu, Dmitry Kasatkin, Eric Snowberg, Nicolas Schier,
Daniel Gomez, Aaron Tomlin, Christophe Leroy (CS GROUP),
Nicolas Bouchinet, Xiu Jianfeng, Christophe Leroy
Cc: Martin KaFai Lau, Song Liu, Yonghong Song, Jiri Olsa, bpf,
Fabian Grünbichler, Arnout Engelen, Mattia Rizzolo, kpcyrd,
Christian Heusel, Câju Mihai-Drosi, Eric Biggers,
Sebastian Andrzej Siewior, linux-kbuild, linux-kernel, linux-arch,
linux-modules, linux-security-module, linux-doc, linuxppc-dev,
linux-integrity, debian-kernel, Thomas Weißschuh
In-Reply-To: <20260505-module-hashes-v5-0-e174a5a49fce@weissschuh.net>
The upcoming module hashes functionality will build the modules in
between the generation of the BTF data and the final link of vmlinux.
At this point vmlinux is not yet built and therefore can't be used for
module BTF generation. vmlinux.unstripped however is usable and
sufficient for BTF generation.
Signed-off-by: Thomas Weißschuh <linux@weissschuh.net>
Reviewed-by: Nicolas Schier <nsc@kernel.org>
---
scripts/Makefile.modfinal | 12 +++++++-----
1 file changed, 7 insertions(+), 5 deletions(-)
diff --git a/scripts/Makefile.modfinal b/scripts/Makefile.modfinal
index adcbcde16a07..b09040ccddd2 100644
--- a/scripts/Makefile.modfinal
+++ b/scripts/Makefile.modfinal
@@ -38,12 +38,14 @@ quiet_cmd_ld_ko_o = LD [M] $@
$(KBUILD_LDFLAGS_MODULE) $(LDFLAGS_MODULE) \
-T $(objtree)/scripts/module.lds -o $@ $(filter %.o, $^)
+btf-vmlinux := $(if $(KBUILD_EXTMOD),vmlinux,vmlinux.unstripped)
+
quiet_cmd_btf_ko = BTF [M] $@
cmd_btf_ko = \
- if [ ! -f $(objtree)/vmlinux ]; then \
- printf "Skipping BTF generation for %s due to unavailability of vmlinux\n" $@ 1>&2; \
+ if [ ! -f $(objtree)/$(btf-vmlinux) ]; then \
+ printf "Skipping BTF generation for %s due to unavailability of $(btf-vmlinux)\n" $@ 1>&2; \
else \
- $(CONFIG_SHELL) $(srctree)/scripts/gen-btf.sh --btf_base $(objtree)/vmlinux $@; \
+ $(CONFIG_SHELL) $(srctree)/scripts/gen-btf.sh --btf_base $(objtree)/$(btf-vmlinux) $@; \
fi;
# Same as newer-prereqs, but allows to exclude specified extra dependencies
@@ -55,8 +57,8 @@ if_changed_except = $(if $(call newer_prereqs_except,$(2))$(cmd-check), \
printf '%s\n' 'savedcmd_$@ := $(make-cmd)' > $(dot-target).cmd, @:)
# Re-generate module BTFs if either module's .ko or vmlinux changed
-%.ko: %.o %.mod.o .module-common.o $(objtree)/scripts/module.lds $(and $(CONFIG_DEBUG_INFO_BTF_MODULES),$(KBUILD_BUILTIN),$(objtree)/vmlinux) FORCE
- +$(call if_changed_except,ld_ko_o,$(objtree)/vmlinux)
+%.ko: %.o %.mod.o .module-common.o $(objtree)/scripts/module.lds $(and $(CONFIG_DEBUG_INFO_BTF_MODULES),$(KBUILD_BUILTIN),$(objtree)/$(btf-vmlinux)) FORCE
+ +$(call if_changed_except,ld_ko_o,$(objtree)/$(btf-vmlinux))
ifdef CONFIG_DEBUG_INFO_BTF_MODULES
+$(if $(newer-prereqs),$(call cmd,btf_ko))
endif
--
2.54.0
^ permalink raw reply related
* [PATCH v5 02/14] lockdown: Make the relationship to MODULE_SIG a dependency
From: Thomas Weißschuh @ 2026-05-05 9:05 UTC (permalink / raw)
To: Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
Eduard Zingerman, Kumar Kartikeya Dwivedi, Nathan Chancellor,
Nicolas Schier, Arnd Bergmann, Luis Chamberlain, Petr Pavlu,
Sami Tolvanen, Daniel Gomez, Paul Moore, James Morris,
Serge E. Hallyn, Jonathan Corbet, Madhavan Srinivasan,
Michael Ellerman, Nicholas Piggin, Naveen N Rao, Mimi Zohar,
Roberto Sassu, Dmitry Kasatkin, Eric Snowberg, Nicolas Schier,
Daniel Gomez, Aaron Tomlin, Christophe Leroy (CS GROUP),
Nicolas Bouchinet, Xiu Jianfeng, Christophe Leroy
Cc: Martin KaFai Lau, Song Liu, Yonghong Song, Jiri Olsa, bpf,
Fabian Grünbichler, Arnout Engelen, Mattia Rizzolo, kpcyrd,
Christian Heusel, Câju Mihai-Drosi, Eric Biggers,
Sebastian Andrzej Siewior, linux-kbuild, linux-kernel, linux-arch,
linux-modules, linux-security-module, linux-doc, linuxppc-dev,
linux-integrity, debian-kernel, Thomas Weißschuh
In-Reply-To: <20260505-module-hashes-v5-0-e174a5a49fce@weissschuh.net>
The new hash-based module integrity checking will also be able to
satisfy the requirements of lockdown.
Such an alternative is not representable with "select", so use
"depends on" instead.
Acked-by: Paul Moore <paul@paul-moore.com>
Reviewed-by: Nicolas Schier <nsc@kernel.org>
Signed-off-by: Thomas Weißschuh <linux@weissschuh.net>
---
security/lockdown/Kconfig | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/security/lockdown/Kconfig b/security/lockdown/Kconfig
index e84ddf484010..155959205b8e 100644
--- a/security/lockdown/Kconfig
+++ b/security/lockdown/Kconfig
@@ -1,7 +1,7 @@
config SECURITY_LOCKDOWN_LSM
bool "Basic module for enforcing kernel lockdown"
depends on SECURITY
- select MODULE_SIG if MODULES
+ depends on !MODULES || MODULE_SIG
help
Build support for an LSM that enforces a coarse kernel lockdown
behaviour.
--
2.54.0
^ permalink raw reply related
* [PATCH v5 04/14] module: Drop pointless debugging message
From: Thomas Weißschuh @ 2026-05-05 9:05 UTC (permalink / raw)
To: Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
Eduard Zingerman, Kumar Kartikeya Dwivedi, Nathan Chancellor,
Nicolas Schier, Arnd Bergmann, Luis Chamberlain, Petr Pavlu,
Sami Tolvanen, Daniel Gomez, Paul Moore, James Morris,
Serge E. Hallyn, Jonathan Corbet, Madhavan Srinivasan,
Michael Ellerman, Nicholas Piggin, Naveen N Rao, Mimi Zohar,
Roberto Sassu, Dmitry Kasatkin, Eric Snowberg, Nicolas Schier,
Daniel Gomez, Aaron Tomlin, Christophe Leroy (CS GROUP),
Nicolas Bouchinet, Xiu Jianfeng, Christophe Leroy
Cc: Martin KaFai Lau, Song Liu, Yonghong Song, Jiri Olsa, bpf,
Fabian Grünbichler, Arnout Engelen, Mattia Rizzolo, kpcyrd,
Christian Heusel, Câju Mihai-Drosi, Eric Biggers,
Sebastian Andrzej Siewior, linux-kbuild, linux-kernel, linux-arch,
linux-modules, linux-security-module, linux-doc, linuxppc-dev,
linux-integrity, debian-kernel, Thomas Weißschuh
In-Reply-To: <20260505-module-hashes-v5-0-e174a5a49fce@weissschuh.net>
This is the only instance of pr_devel() in the whole module subsystem
and essentially useless.
Drop it.
Signed-off-by: Thomas Weißschuh <linux@weissschuh.net>
---
kernel/module/signing.c | 2 --
1 file changed, 2 deletions(-)
diff --git a/kernel/module/signing.c b/kernel/module/signing.c
index 590ba29c85ab..4a5e4eef250d 100644
--- a/kernel/module/signing.c
+++ b/kernel/module/signing.c
@@ -46,8 +46,6 @@ int mod_verify_sig(const void *mod, struct load_info *info)
size_t sig_len, modlen = info->len;
int ret;
- pr_devel("==>%s(,%zu)\n", __func__, modlen);
-
if (modlen <= sizeof(ms))
return -EBADMSG;
--
2.54.0
^ permalink raw reply related
* [PATCH v5 00/14] module: Introduce hash-based integrity checking
From: Thomas Weißschuh @ 2026-05-05 9:05 UTC (permalink / raw)
To: Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
Eduard Zingerman, Kumar Kartikeya Dwivedi, Nathan Chancellor,
Nicolas Schier, Arnd Bergmann, Luis Chamberlain, Petr Pavlu,
Sami Tolvanen, Daniel Gomez, Paul Moore, James Morris,
Serge E. Hallyn, Jonathan Corbet, Madhavan Srinivasan,
Michael Ellerman, Nicholas Piggin, Naveen N Rao, Mimi Zohar,
Roberto Sassu, Dmitry Kasatkin, Eric Snowberg, Nicolas Schier,
Daniel Gomez, Aaron Tomlin, Christophe Leroy (CS GROUP),
Nicolas Bouchinet, Xiu Jianfeng, Christophe Leroy
Cc: Martin KaFai Lau, Song Liu, Yonghong Song, Jiri Olsa, bpf,
Fabian Grünbichler, Arnout Engelen, Mattia Rizzolo, kpcyrd,
Christian Heusel, Câju Mihai-Drosi, Eric Biggers,
Sebastian Andrzej Siewior, linux-kbuild, linux-kernel, linux-arch,
linux-modules, linux-security-module, linux-doc, linuxppc-dev,
linux-integrity, debian-kernel, Thomas Weißschuh
The current signature-based module integrity checking has some drawbacks
in combination with reproducible builds. Either the module signing key
is generated at build time, which makes the build unreproducible, or a
static signing key is used, which precludes rebuilds by third parties
and makes the whole build and packaging process much more complicated.
The goal is to reach bit-for-bit reproducibility. Excluding certain
parts of the build output from the reproducibility analysis would be
error-prone and force each downstream consumer to introduce new tooling.
Introduce a new mechanism to ensure only well-known modules are loaded
by embedding a merkle tree root of all modules built as part of the full
kernel build into vmlinux.
Interest has been proclaimed by Arch Linux, Debian, Proxmox, SUSE, NixOS
and the general reproducible builds community.
Compatibility with IMA modsig is not provided yet. It is still unclear
to me if it should be hooked up transparently without any changes to the
policy or it should require new policy options.
BPF/BTF folks, please take a look at patch 1.
Further improvements:
* Use MODULE_SIG_HASH for configuration
* UAPI for discovery?
To: Nathan Chancellor <nathan@kernel.org>
To: Nicolas Schier <nsc@kernel.org>
To: Arnd Bergmann <arnd@arndb.de>
To: Luis Chamberlain <mcgrof@kernel.org>
To: Petr Pavlu <petr.pavlu@suse.com>
To: Sami Tolvanen <samitolvanen@google.com>
To: Daniel Gomez <da.gomez@samsung.com>
To: Paul Moore <paul@paul-moore.com>
To: James Morris <jmorris@namei.org>
To: Serge E. Hallyn <serge@hallyn.com>
To: Jonathan Corbet <corbet@lwn.net>
To: Madhavan Srinivasan <maddy@linux.ibm.com>
To: Michael Ellerman <mpe@ellerman.id.au>
To: Nicholas Piggin <npiggin@gmail.com>
To: Christophe Leroy <christophe.leroy@csgroup.eu>
To: Naveen N Rao <naveen@kernel.org>
To: Mimi Zohar <zohar@linux.ibm.com>
To: Roberto Sassu <roberto.sassu@huawei.com>
To: Dmitry Kasatkin <dmitry.kasatkin@gmail.com>
To: Eric Snowberg <eric.snowberg@oracle.com>
To: Nicolas Schier <nicolas.schier@linux.dev>
To: Daniel Gomez <da.gomez@kernel.org>
To: Aaron Tomlin <atomlin@atomlin.com>
To: Christophe Leroy (CS GROUP) <chleroy@kernel.org>
To: Nicolas Schier <nsc@kernel.org>
To: Nicolas Bouchinet <nicolas.bouchinet@oss.cyber.gouv.fr>
To: Xiu Jianfeng <xiujianfeng@huawei.com>
Cc: Fabian Grünbichler <f.gruenbichler@proxmox.com>
Cc: Arnout Engelen <arnout@bzzt.net>
Cc: Mattia Rizzolo <mattia@mapreri.org>
Cc: kpcyrd <kpcyrd@archlinux.org>
Cc: Christian Heusel <christian@heusel.eu>
Cc: Câju Mihai-Drosi <mcaju95@gmail.com>
Cc: Eric Biggers <ebiggers@kernel.org>
Cc: Sebastian Andrzej Siewior <bigeasy@linutronix.de>
Cc: linux-kbuild@vger.kernel.org
Cc: linux-kernel@vger.kernel.org
Cc: linux-arch@vger.kernel.org
Cc: linux-modules@vger.kernel.org
Cc: linux-security-module@vger.kernel.org
Cc: linux-doc@vger.kernel.org
Cc: linuxppc-dev@lists.ozlabs.org
Cc: linux-integrity@vger.kernel.org
Cc: debian-kernel@lists.debian.org
Signed-off-by: Thomas Weißschuh <linux@weissschuh.net>
---
Changes in v5:
- Document tree layout.
- Make scripts/module-merkle-tree more robust.
- Remove all changes to link-vmlinux.sh, use vmlinux.unstripped instead.
- Clean up types and logic in modules-merkle-tree.c.
- Use "auth" over "integrity" naming scheme.
- Reduce the changes to the existing authentication flow.
- Explicitly send the series to BTF folks for review of BTF changes.
- Link to v4: https://patch.msgid.link/20260113-module-hashes-v4-0-0b932db9b56b@weissschuh.net
Changes in v4:
- Use as Merkle tree over a linera list of hashes.
- Provide compatibilith with INSTALL_MOD_STRIP
- Rework commit messages.
- Use vmlinux.unstripped over plain "vmlinux".
- Link to v3: https://lore.kernel.org/r/20250429-module-hashes-v3-0-00e9258def9e@weissschuh.net
Changes in v3:
- Rebase on v6.15-rc1
- Use openssl to calculate hash
- Avoid warning if no modules are built
- Simplify module_integrity_check() a bit
- Make incompatibility with INSTALL_MOD_STRIP explicit
- Update docs
- Add IMA cleanups
- Link to v2: https://lore.kernel.org/r/20250120-module-hashes-v2-0-ba1184e27b7f@weissschuh.net
Changes in v2:
- Drop RFC state
- Mention interested parties in cover letter
- Expand Kconfig description
- Add compatibility with CONFIG_MODULE_SIG
- Parallelize module-hashes.sh
- Update Documentation/kbuild/reproducible-builds.rst
- Link to v1: https://lore.kernel.org/r/20241225-module-hashes-v1-0-d710ce7a3fd1@weissschuh.net
---
Thomas Weißschuh (14):
kbuild: generate module BTF based on vmlinux.unstripped
lockdown: Make the relationship to MODULE_SIG a dependency
kbuild: rename the strip_relocs command
module: Drop pointless debugging message
module: Make mod_verify_sig() static
module: Switch load_info::len to size_t
module: Make module authentication usable without MODULE_SIG
module: Move authentication logic into dedicated new file
module: Move signature type check out of mod_check_sig()
module: Prepare for additional module authentication mechanisms
module: update timestamp of modules.order after modules are built
module: Introduce hash-based integrity checking
kbuild: move handling of module stripping to Makefile.lib
kbuild: make CONFIG_MODULE_HASHES compatible with module stripping
.gitignore | 2 +
Documentation/kbuild/reproducible-builds.rst | 5 +-
Makefile | 7 +-
crypto/algapi.c | 4 +-
include/asm-generic/vmlinux.lds.h | 11 +
include/linux/module.h | 18 +-
include/linux/module_hashes.h | 29 ++
include/uapi/linux/module_signature.h | 1 +
kernel/module/Kconfig | 29 +-
kernel/module/Makefile | 2 +
kernel/module/auth.c | 139 +++++++++
kernel/module/hashes.c | 95 ++++++
kernel/module/hashes_root.c | 6 +
kernel/module/internal.h | 18 +-
kernel/module/main.c | 16 +-
kernel/module/signing.c | 113 +-------
kernel/module_signature.c | 8 +-
scripts/.gitignore | 1 +
scripts/Makefile | 4 +
scripts/Makefile.lib | 32 +++
scripts/Makefile.modfinal | 28 +-
scripts/Makefile.modinst | 44 +--
scripts/Makefile.vmlinux | 40 ++-
scripts/include/xalloc.h | 29 ++
scripts/link-vmlinux.sh | 3 +-
scripts/modules-merkle-tree.c | 416 +++++++++++++++++++++++++++
security/integrity/ima/ima_modsig.c | 5 +
security/lockdown/Kconfig | 2 +-
tools/include/uapi/linux/module_signature.h | 1 +
29 files changed, 919 insertions(+), 189 deletions(-)
---
base-commit: 585c2e775b12ef45bdf9cef5f679dcb1220e0d65
change-id: 20241225-module-hashes-7a50a7cc2a30
Best regards,
--
Thomas Weißschuh <linux@weissschuh.net>
^ permalink raw reply
* [PATCH v5 03/14] kbuild: rename the strip_relocs command
From: Thomas Weißschuh @ 2026-05-05 9:05 UTC (permalink / raw)
To: Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
Eduard Zingerman, Kumar Kartikeya Dwivedi, Nathan Chancellor,
Nicolas Schier, Arnd Bergmann, Luis Chamberlain, Petr Pavlu,
Sami Tolvanen, Daniel Gomez, Paul Moore, James Morris,
Serge E. Hallyn, Jonathan Corbet, Madhavan Srinivasan,
Michael Ellerman, Nicholas Piggin, Naveen N Rao, Mimi Zohar,
Roberto Sassu, Dmitry Kasatkin, Eric Snowberg, Nicolas Schier,
Daniel Gomez, Aaron Tomlin, Christophe Leroy (CS GROUP),
Nicolas Bouchinet, Xiu Jianfeng, Christophe Leroy
Cc: Martin KaFai Lau, Song Liu, Yonghong Song, Jiri Olsa, bpf,
Fabian Grünbichler, Arnout Engelen, Mattia Rizzolo, kpcyrd,
Christian Heusel, Câju Mihai-Drosi, Eric Biggers,
Sebastian Andrzej Siewior, linux-kbuild, linux-kernel, linux-arch,
linux-modules, linux-security-module, linux-doc, linuxppc-dev,
linux-integrity, debian-kernel, Thomas Weißschuh
In-Reply-To: <20260505-module-hashes-v5-0-e174a5a49fce@weissschuh.net>
This command is doing more than just stripping relocations.
With the introduction of CONFIG_MODULE_HASHES it will do even more.
Use a more generic name for the variable.
Signed-off-by: Thomas Weißschuh <linux@weissschuh.net>
---
scripts/Makefile.vmlinux | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/scripts/Makefile.vmlinux b/scripts/Makefile.vmlinux
index fcae1e432d9a..6cc661e5292b 100644
--- a/scripts/Makefile.vmlinux
+++ b/scripts/Makefile.vmlinux
@@ -91,13 +91,13 @@ remove-symbols := -w --strip-unneeded-symbol='__mod_device_table__*'
# To avoid warnings: "empty loadable segment detected at ..." from GNU objcopy,
# it is necessary to remove the PT_LOAD flag from the segment.
-quiet_cmd_strip_relocs = OBJCOPY $@
- cmd_strip_relocs = $(OBJCOPY) $(patsubst %,--set-section-flags %=noload,$(remove-section-y)) $< $@; \
- $(OBJCOPY) $(addprefix --remove-section=,$(remove-section-y)) $(remove-symbols) $@
+quiet_cmd_objcopy_vmlinux = OBJCOPY $@
+ cmd_objcopy_vmlinux = $(OBJCOPY) $(patsubst %,--set-section-flags %=noload,$(remove-section-y)) $< $@; \
+ $(OBJCOPY) $(addprefix --remove-section=,$(remove-section-y)) $(remove-symbols) $@
targets += vmlinux
vmlinux: vmlinux.unstripped FORCE
- $(call if_changed,strip_relocs)
+ $(call if_changed,objcopy_vmlinux)
# modules.builtin.modinfo
# ---------------------------------------------------------------------------
--
2.54.0
^ permalink raw reply related
* [PATCH v5 05/14] module: Make mod_verify_sig() static
From: Thomas Weißschuh @ 2026-05-05 9:05 UTC (permalink / raw)
To: Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
Eduard Zingerman, Kumar Kartikeya Dwivedi, Nathan Chancellor,
Nicolas Schier, Arnd Bergmann, Luis Chamberlain, Petr Pavlu,
Sami Tolvanen, Daniel Gomez, Paul Moore, James Morris,
Serge E. Hallyn, Jonathan Corbet, Madhavan Srinivasan,
Michael Ellerman, Nicholas Piggin, Naveen N Rao, Mimi Zohar,
Roberto Sassu, Dmitry Kasatkin, Eric Snowberg, Nicolas Schier,
Daniel Gomez, Aaron Tomlin, Christophe Leroy (CS GROUP),
Nicolas Bouchinet, Xiu Jianfeng, Christophe Leroy
Cc: Martin KaFai Lau, Song Liu, Yonghong Song, Jiri Olsa, bpf,
Fabian Grünbichler, Arnout Engelen, Mattia Rizzolo, kpcyrd,
Christian Heusel, Câju Mihai-Drosi, Eric Biggers,
Sebastian Andrzej Siewior, linux-kbuild, linux-kernel, linux-arch,
linux-modules, linux-security-module, linux-doc, linuxppc-dev,
linux-integrity, debian-kernel, Thomas Weißschuh
In-Reply-To: <20260505-module-hashes-v5-0-e174a5a49fce@weissschuh.net>
It is not used outside of signing.c.
Signed-off-by: Thomas Weißschuh <linux@weissschuh.net>
Reviewed-by: Aaron Tomlin <atomlin@atomlin.com>
Reviewed-by: Nicolas Schier <nsc@kernel.org>
Reviewed-by: Eric Biggers <ebiggers@kernel.org>
---
kernel/module/internal.h | 1 -
kernel/module/signing.c | 2 +-
2 files changed, 1 insertion(+), 2 deletions(-)
diff --git a/kernel/module/internal.h b/kernel/module/internal.h
index 061161cc79d9..071999743341 100644
--- a/kernel/module/internal.h
+++ b/kernel/module/internal.h
@@ -117,7 +117,6 @@ struct module_use {
struct module *source, *target;
};
-int mod_verify_sig(const void *mod, struct load_info *info);
int try_to_force_load(struct module *mod, const char *reason);
bool find_symbol(struct find_symbol_arg *fsa);
struct module *find_module_all(const char *name, size_t len, bool even_unformed);
diff --git a/kernel/module/signing.c b/kernel/module/signing.c
index 4a5e4eef250d..69d4b1758540 100644
--- a/kernel/module/signing.c
+++ b/kernel/module/signing.c
@@ -40,7 +40,7 @@ void set_module_sig_enforced(void)
/*
* Verify the signature on a module.
*/
-int mod_verify_sig(const void *mod, struct load_info *info)
+static int mod_verify_sig(const void *mod, struct load_info *info)
{
struct module_signature ms;
size_t sig_len, modlen = info->len;
--
2.54.0
^ permalink raw reply related
* [PATCH v5 11/14] module: update timestamp of modules.order after modules are built
From: Thomas Weißschuh @ 2026-05-05 9:05 UTC (permalink / raw)
To: Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
Eduard Zingerman, Kumar Kartikeya Dwivedi, Nathan Chancellor,
Nicolas Schier, Arnd Bergmann, Luis Chamberlain, Petr Pavlu,
Sami Tolvanen, Daniel Gomez, Paul Moore, James Morris,
Serge E. Hallyn, Jonathan Corbet, Madhavan Srinivasan,
Michael Ellerman, Nicholas Piggin, Naveen N Rao, Mimi Zohar,
Roberto Sassu, Dmitry Kasatkin, Eric Snowberg, Nicolas Schier,
Daniel Gomez, Aaron Tomlin, Christophe Leroy (CS GROUP),
Nicolas Bouchinet, Xiu Jianfeng, Christophe Leroy
Cc: Martin KaFai Lau, Song Liu, Yonghong Song, Jiri Olsa, bpf,
Fabian Grünbichler, Arnout Engelen, Mattia Rizzolo, kpcyrd,
Christian Heusel, Câju Mihai-Drosi, Eric Biggers,
Sebastian Andrzej Siewior, linux-kbuild, linux-kernel, linux-arch,
linux-modules, linux-security-module, linux-doc, linuxppc-dev,
linux-integrity, debian-kernel, Thomas Weißschuh
In-Reply-To: <20260505-module-hashes-v5-0-e174a5a49fce@weissschuh.net>
Make sure that modules.order is always newer than the module .ko files.
Other rules can then use modules.order as a prerequisite and rerun if
any module is (re-)built.
Signed-off-by: Thomas Weißschuh <linux@weissschuh.net>
---
scripts/Makefile.modfinal | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/scripts/Makefile.modfinal b/scripts/Makefile.modfinal
index b09040ccddd2..44a382689a5a 100644
--- a/scripts/Makefile.modfinal
+++ b/scripts/Makefile.modfinal
@@ -66,6 +66,13 @@ endif
targets += $(modules:%.o=%.ko) $(modules:%.o=%.mod.o) .module-common.o
+# Update modules.order when a module is (re-)built.
+# Allow using it as target dependency.
+targets += modules.order
+__modfinal: modules.order
+modules.order: $(modules:%.o=%.ko)
+ @touch $@
+
# Add FORCE to the prerequisites of a target to force it to be always rebuilt.
# ---------------------------------------------------------------------------
--
2.54.0
^ permalink raw reply related
* [PATCH v5 08/14] module: Move authentication logic into dedicated new file
From: Thomas Weißschuh @ 2026-05-05 9:05 UTC (permalink / raw)
To: Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
Eduard Zingerman, Kumar Kartikeya Dwivedi, Nathan Chancellor,
Nicolas Schier, Arnd Bergmann, Luis Chamberlain, Petr Pavlu,
Sami Tolvanen, Daniel Gomez, Paul Moore, James Morris,
Serge E. Hallyn, Jonathan Corbet, Madhavan Srinivasan,
Michael Ellerman, Nicholas Piggin, Naveen N Rao, Mimi Zohar,
Roberto Sassu, Dmitry Kasatkin, Eric Snowberg, Nicolas Schier,
Daniel Gomez, Aaron Tomlin, Christophe Leroy (CS GROUP),
Nicolas Bouchinet, Xiu Jianfeng, Christophe Leroy
Cc: Martin KaFai Lau, Song Liu, Yonghong Song, Jiri Olsa, bpf,
Fabian Grünbichler, Arnout Engelen, Mattia Rizzolo, kpcyrd,
Christian Heusel, Câju Mihai-Drosi, Eric Biggers,
Sebastian Andrzej Siewior, linux-kbuild, linux-kernel, linux-arch,
linux-modules, linux-security-module, linux-doc, linuxppc-dev,
linux-integrity, debian-kernel, Thomas Weißschuh
In-Reply-To: <20260505-module-hashes-v5-0-e174a5a49fce@weissschuh.net>
The module authentication functionality will also be used by the
hash-based module authentication. To make it usable even if
CONFIG_MODULE_SIG is disabled, move it to a new file.
Signed-off-by: Thomas Weißschuh <linux@weissschuh.net>
---
kernel/module/auth.c | 85 +++++++++++++++++++++++++++++++++++++++++++++
kernel/module/internal.h | 14 ++++++--
kernel/module/main.c | 6 ++--
kernel/module/signing.c | 90 ++----------------------------------------------
4 files changed, 103 insertions(+), 92 deletions(-)
diff --git a/kernel/module/auth.c b/kernel/module/auth.c
index 956ac63d9d33..831a13eb0c9b 100644
--- a/kernel/module/auth.c
+++ b/kernel/module/auth.c
@@ -5,10 +5,16 @@
* Written by David Howells (dhowells@redhat.com)
*/
+#include <linux/errno.h>
#include <linux/export.h>
#include <linux/module.h>
+#include <linux/module_signature.h>
#include <linux/moduleparam.h>
+#include <linux/security.h>
+#include <linux/string.h>
#include <linux/types.h>
+#include <uapi/linux/module.h>
+#include "internal.h"
#undef MODULE_PARAM_PREFIX
#define MODULE_PARAM_PREFIX "module."
@@ -30,3 +36,82 @@ void set_module_sig_enforced(void)
{
sig_enforce = true;
}
+
+static int mod_verify_sig(const void *mod, struct load_info *info)
+{
+ struct module_signature ms;
+ size_t sig_len, modlen = info->len;
+ int ret;
+
+ if (modlen <= sizeof(ms))
+ return -EBADMSG;
+
+ memcpy(&ms, mod + (modlen - sizeof(ms)), sizeof(ms));
+
+ ret = mod_check_sig(&ms, modlen, "module");
+ if (ret)
+ return ret;
+
+ sig_len = be32_to_cpu(ms.sig_len);
+ modlen -= sig_len + sizeof(ms);
+ info->len = modlen;
+
+ return module_sig_check(mod, modlen, mod + modlen, sig_len);
+}
+
+int module_auth_check(struct load_info *info, int flags)
+{
+ int err = -ENODATA;
+ const unsigned long markerlen = sizeof(MODULE_SIGNATURE_MARKER) - 1;
+ const char *reason;
+ const void *mod = info->hdr;
+ bool mangled_module = flags & (MODULE_INIT_IGNORE_MODVERSIONS |
+ MODULE_INIT_IGNORE_VERMAGIC);
+ /*
+ * Do not allow mangled modules as a module with version information
+ * removed is no longer the module that was signed.
+ */
+ if (!mangled_module &&
+ info->len > markerlen &&
+ memcmp(mod + info->len - markerlen, MODULE_SIGNATURE_MARKER, markerlen) == 0) {
+ /* We truncate the module to discard the signature */
+ info->len -= markerlen;
+ err = mod_verify_sig(mod, info);
+ if (!err) {
+ info->auth_ok = true;
+ return 0;
+ }
+ }
+
+ /*
+ * We don't permit modules to be loaded into the trusted kernels
+ * without a valid signature on them, but if we're not enforcing,
+ * certain errors are non-fatal.
+ */
+ switch (err) {
+ case -ENODATA:
+ reason = "unsigned module";
+ break;
+ case -ENOPKG:
+ reason = "module with unsupported crypto";
+ break;
+ case -ENOKEY:
+ reason = "module with unavailable key";
+ break;
+
+ default:
+ /*
+ * All other errors are fatal, including lack of memory,
+ * unparseable signatures, and signature check failures --
+ * even if signatures aren't required.
+ */
+ return err;
+ }
+
+ if (is_module_sig_enforced()) {
+ pr_notice("Loading of %s is rejected\n", reason);
+ return -EKEYREJECTED;
+ }
+
+ return security_locked_down(LOCKDOWN_MODULE_SIGNATURE);
+}
diff --git a/kernel/module/internal.h b/kernel/module/internal.h
index f8f425b167f1..d923e31a5d8e 100644
--- a/kernel/module/internal.h
+++ b/kernel/module/internal.h
@@ -336,14 +336,24 @@ void module_mark_ro_after_init(const Elf_Ehdr *hdr, Elf_Shdr *sechdrs,
const char *secstrings);
#ifdef CONFIG_MODULE_SIG
-int module_sig_check(struct load_info *info, int flags);
+int module_sig_check(const void *mod, size_t mod_len, const void *sig, size_t sig_len);
#else /* !CONFIG_MODULE_SIG */
-static inline int module_sig_check(struct load_info *info, int flags)
+static inline int module_sig_check(const void *mod, size_t mod_len,
+ const void *sig, size_t sig_len)
{
return 0;
}
#endif /* !CONFIG_MODULE_SIG */
+#ifdef CONFIG_MODULE_AUTH
+int module_auth_check(struct load_info *info, int flags);
+#else /* !CONFIG_MODULE_AUTH */
+static inline int module_auth_check(struct load_info *info, int flags)
+{
+ return 0;
+}
+#endif /* !CONFIG_MODULE_AUTH */
+
#ifdef CONFIG_DEBUG_KMEMLEAK
void kmemleak_load_module(const struct module *mod, const struct load_info *info);
#else /* !CONFIG_DEBUG_KMEMLEAK */
diff --git a/kernel/module/main.c b/kernel/module/main.c
index cd8a74df117e..55a010383a8d 100644
--- a/kernel/module/main.c
+++ b/kernel/module/main.c
@@ -3428,8 +3428,8 @@ static int load_module(struct load_info *info, const char __user *uargs,
char *after_dashes;
/*
- * Do the signature check (if any) first. All that
- * the signature check needs is info->len, it does
+ * Do the authentication checks (if any) first. All that
+ * the authentication checks need is info->len, it does
* not need any of the section info. That can be
* set up later. This will minimize the chances
* of a corrupt module causing problems before
@@ -3439,7 +3439,7 @@ static int load_module(struct load_info *info, const char __user *uargs,
* off the sig length at the end of the module, making
* checks against info->len more correct.
*/
- err = module_sig_check(info, flags);
+ err = module_auth_check(info, flags);
if (err)
goto free_copy;
diff --git a/kernel/module/signing.c b/kernel/module/signing.c
index 07a786723221..a49317e3c66f 100644
--- a/kernel/module/signing.c
+++ b/kernel/module/signing.c
@@ -5,98 +5,14 @@
* Written by David Howells (dhowells@redhat.com)
*/
-#include <linux/kernel.h>
-#include <linux/errno.h>
-#include <linux/module.h>
-#include <linux/module_signature.h>
-#include <linux/string.h>
+#include <linux/types.h>
#include <linux/verification.h>
-#include <linux/security.h>
-#include <crypto/public_key.h>
-#include <uapi/linux/module.h>
#include "internal.h"
-/*
- * Verify the signature on a module.
- */
-static int mod_verify_sig(const void *mod, struct load_info *info)
+int module_sig_check(const void *mod, size_t mod_len, const void *sig, size_t sig_len)
{
- struct module_signature ms;
- size_t sig_len, modlen = info->len;
- int ret;
-
- if (modlen <= sizeof(ms))
- return -EBADMSG;
-
- memcpy(&ms, mod + (modlen - sizeof(ms)), sizeof(ms));
-
- ret = mod_check_sig(&ms, modlen, "module");
- if (ret)
- return ret;
-
- sig_len = be32_to_cpu(ms.sig_len);
- modlen -= sig_len + sizeof(ms);
- info->len = modlen;
-
- return verify_pkcs7_signature(mod, modlen, mod + modlen, sig_len,
+ return verify_pkcs7_signature(mod, mod_len, sig, sig_len,
VERIFY_USE_SECONDARY_KEYRING,
VERIFYING_MODULE_SIGNATURE,
NULL, NULL);
}
-
-int module_sig_check(struct load_info *info, int flags)
-{
- int err = -ENODATA;
- const unsigned long markerlen = sizeof(MODULE_SIGNATURE_MARKER) - 1;
- const char *reason;
- const void *mod = info->hdr;
- bool mangled_module = flags & (MODULE_INIT_IGNORE_MODVERSIONS |
- MODULE_INIT_IGNORE_VERMAGIC);
- /*
- * Do not allow mangled modules as a module with version information
- * removed is no longer the module that was signed.
- */
- if (!mangled_module &&
- info->len > markerlen &&
- memcmp(mod + info->len - markerlen, MODULE_SIGNATURE_MARKER, markerlen) == 0) {
- /* We truncate the module to discard the signature */
- info->len -= markerlen;
- err = mod_verify_sig(mod, info);
- if (!err) {
- info->auth_ok = true;
- return 0;
- }
- }
-
- /*
- * We don't permit modules to be loaded into the trusted kernels
- * without a valid signature on them, but if we're not enforcing,
- * certain errors are non-fatal.
- */
- switch (err) {
- case -ENODATA:
- reason = "unsigned module";
- break;
- case -ENOPKG:
- reason = "module with unsupported crypto";
- break;
- case -ENOKEY:
- reason = "module with unavailable key";
- break;
-
- default:
- /*
- * All other errors are fatal, including lack of memory,
- * unparseable signatures, and signature check failures --
- * even if signatures aren't required.
- */
- return err;
- }
-
- if (is_module_sig_enforced()) {
- pr_notice("Loading of %s is rejected\n", reason);
- return -EKEYREJECTED;
- }
-
- return security_locked_down(LOCKDOWN_MODULE_SIGNATURE);
-}
--
2.54.0
^ permalink raw reply related
* [PATCH v5 09/14] module: Move signature type check out of mod_check_sig()
From: Thomas Weißschuh @ 2026-05-05 9:05 UTC (permalink / raw)
To: Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
Eduard Zingerman, Kumar Kartikeya Dwivedi, Nathan Chancellor,
Nicolas Schier, Arnd Bergmann, Luis Chamberlain, Petr Pavlu,
Sami Tolvanen, Daniel Gomez, Paul Moore, James Morris,
Serge E. Hallyn, Jonathan Corbet, Madhavan Srinivasan,
Michael Ellerman, Nicholas Piggin, Naveen N Rao, Mimi Zohar,
Roberto Sassu, Dmitry Kasatkin, Eric Snowberg, Nicolas Schier,
Daniel Gomez, Aaron Tomlin, Christophe Leroy (CS GROUP),
Nicolas Bouchinet, Xiu Jianfeng, Christophe Leroy
Cc: Martin KaFai Lau, Song Liu, Yonghong Song, Jiri Olsa, bpf,
Fabian Grünbichler, Arnout Engelen, Mattia Rizzolo, kpcyrd,
Christian Heusel, Câju Mihai-Drosi, Eric Biggers,
Sebastian Andrzej Siewior, linux-kbuild, linux-kernel, linux-arch,
linux-modules, linux-security-module, linux-doc, linuxppc-dev,
linux-integrity, debian-kernel, Thomas Weißschuh
In-Reply-To: <20260505-module-hashes-v5-0-e174a5a49fce@weissschuh.net>
Additional signature types are about to be added.
As each caller of mod_check_sig() can have different support for these,
move the type validation into the callers.
Signed-off-by: Thomas Weißschuh <linux@weissschuh.net>
---
kernel/module/auth.c | 5 +++++
kernel/module_signature.c | 8 +-------
security/integrity/ima/ima_modsig.c | 5 +++++
3 files changed, 11 insertions(+), 7 deletions(-)
diff --git a/kernel/module/auth.c b/kernel/module/auth.c
index 831a13eb0c9b..21e49eb4967c 100644
--- a/kernel/module/auth.c
+++ b/kernel/module/auth.c
@@ -48,6 +48,11 @@ static int mod_verify_sig(const void *mod, struct load_info *info)
memcpy(&ms, mod + (modlen - sizeof(ms)), sizeof(ms));
+ if (ms.id_type != MODULE_SIGNATURE_TYPE_PKCS7) {
+ pr_err("module: not signed with expected PKCS#7 message\n");
+ return -ENOPKG;
+ }
+
ret = mod_check_sig(&ms, modlen, "module");
if (ret)
return ret;
diff --git a/kernel/module_signature.c b/kernel/module_signature.c
index a0eee2fe4368..4d0476bcdb72 100644
--- a/kernel/module_signature.c
+++ b/kernel/module_signature.c
@@ -24,12 +24,6 @@ int mod_check_sig(const struct module_signature *ms, size_t file_len,
if (be32_to_cpu(ms->sig_len) >= file_len - sizeof(*ms))
return -EBADMSG;
- if (ms->id_type != MODULE_SIGNATURE_TYPE_PKCS7) {
- pr_err("%s: not signed with expected PKCS#7 message\n",
- name);
- return -ENOPKG;
- }
-
if (ms->algo != 0 ||
ms->hash != 0 ||
ms->signer_len != 0 ||
@@ -37,7 +31,7 @@ int mod_check_sig(const struct module_signature *ms, size_t file_len,
ms->__pad[0] != 0 ||
ms->__pad[1] != 0 ||
ms->__pad[2] != 0) {
- pr_err("%s: PKCS#7 signature info has unexpected non-zero params\n",
+ pr_err("%s: signature info has unexpected non-zero params\n",
name);
return -EBADMSG;
}
diff --git a/security/integrity/ima/ima_modsig.c b/security/integrity/ima/ima_modsig.c
index 632c746fd81e..ebfcdd368a2a 100644
--- a/security/integrity/ima/ima_modsig.c
+++ b/security/integrity/ima/ima_modsig.c
@@ -57,6 +57,11 @@ int ima_read_modsig(enum ima_hooks func, const void *buf, loff_t buf_len,
buf_len -= marker_len;
sig = (const struct module_signature *)(p - sizeof(*sig));
+ if (sig->id_type != MODULE_SIGNATURE_TYPE_PKCS7) {
+ pr_err("%s: not signed with expected PKCS#7 message\n", func_tokens[func]);
+ return -ENOPKG;
+ }
+
rc = mod_check_sig(sig, buf_len, func_tokens[func]);
if (rc)
return rc;
--
2.54.0
^ permalink raw reply related
* [PATCH v5 06/14] module: Switch load_info::len to size_t
From: Thomas Weißschuh @ 2026-05-05 9:05 UTC (permalink / raw)
To: Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
Eduard Zingerman, Kumar Kartikeya Dwivedi, Nathan Chancellor,
Nicolas Schier, Arnd Bergmann, Luis Chamberlain, Petr Pavlu,
Sami Tolvanen, Daniel Gomez, Paul Moore, James Morris,
Serge E. Hallyn, Jonathan Corbet, Madhavan Srinivasan,
Michael Ellerman, Nicholas Piggin, Naveen N Rao, Mimi Zohar,
Roberto Sassu, Dmitry Kasatkin, Eric Snowberg, Nicolas Schier,
Daniel Gomez, Aaron Tomlin, Christophe Leroy (CS GROUP),
Nicolas Bouchinet, Xiu Jianfeng, Christophe Leroy
Cc: Martin KaFai Lau, Song Liu, Yonghong Song, Jiri Olsa, bpf,
Fabian Grünbichler, Arnout Engelen, Mattia Rizzolo, kpcyrd,
Christian Heusel, Câju Mihai-Drosi, Eric Biggers,
Sebastian Andrzej Siewior, linux-kbuild, linux-kernel, linux-arch,
linux-modules, linux-security-module, linux-doc, linuxppc-dev,
linux-integrity, debian-kernel, Thomas Weißschuh
In-Reply-To: <20260505-module-hashes-v5-0-e174a5a49fce@weissschuh.net>
Switching the types will make some later changes cleaner.
size_t is also the semantically correct type for this field.
As both 'size_t' and 'unsigned long' are always the same size, this
should be risk-free.
Reviewed-by: Nicolas Schier <nsc@kernel.org>
Acked-by: Nicolas Schier <nsc@kernel.org>
Signed-off-by: Thomas Weißschuh <linux@weissschuh.net>
---
kernel/module/internal.h | 2 +-
kernel/module/main.c | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/kernel/module/internal.h b/kernel/module/internal.h
index 071999743341..006ada7d4e6e 100644
--- a/kernel/module/internal.h
+++ b/kernel/module/internal.h
@@ -64,7 +64,7 @@ struct load_info {
/* pointer to module in temporary copy, freed at end of load_module() */
struct module *mod;
Elf_Ehdr *hdr;
- unsigned long len;
+ size_t len;
Elf_Shdr *sechdrs;
char *secstrings, *strtab;
unsigned long symoffs, stroffs, init_typeoffs, core_typeoffs;
diff --git a/kernel/module/main.c b/kernel/module/main.c
index 46dd8d25a605..17a352198016 100644
--- a/kernel/module/main.c
+++ b/kernel/module/main.c
@@ -1898,7 +1898,7 @@ static int validate_section_offset(const struct load_info *info, Elf_Shdr *shdr)
static int elf_validity_ehdr(const struct load_info *info)
{
if (info->len < sizeof(*(info->hdr))) {
- pr_err("Invalid ELF header len %lu\n", info->len);
+ pr_err("Invalid ELF header len %zu\n", info->len);
return -ENOEXEC;
}
if (memcmp(info->hdr->e_ident, ELFMAG, SELFMAG) != 0) {
--
2.54.0
^ permalink raw reply related
* [PATCH v5 07/14] module: Make module authentication usable without MODULE_SIG
From: Thomas Weißschuh @ 2026-05-05 9:05 UTC (permalink / raw)
To: Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
Eduard Zingerman, Kumar Kartikeya Dwivedi, Nathan Chancellor,
Nicolas Schier, Arnd Bergmann, Luis Chamberlain, Petr Pavlu,
Sami Tolvanen, Daniel Gomez, Paul Moore, James Morris,
Serge E. Hallyn, Jonathan Corbet, Madhavan Srinivasan,
Michael Ellerman, Nicholas Piggin, Naveen N Rao, Mimi Zohar,
Roberto Sassu, Dmitry Kasatkin, Eric Snowberg, Nicolas Schier,
Daniel Gomez, Aaron Tomlin, Christophe Leroy (CS GROUP),
Nicolas Bouchinet, Xiu Jianfeng, Christophe Leroy
Cc: Martin KaFai Lau, Song Liu, Yonghong Song, Jiri Olsa, bpf,
Fabian Grünbichler, Arnout Engelen, Mattia Rizzolo, kpcyrd,
Christian Heusel, Câju Mihai-Drosi, Eric Biggers,
Sebastian Andrzej Siewior, linux-kbuild, linux-kernel, linux-arch,
linux-modules, linux-security-module, linux-doc, linuxppc-dev,
linux-integrity, debian-kernel, Thomas Weißschuh
In-Reply-To: <20260505-module-hashes-v5-0-e174a5a49fce@weissschuh.net>
The module authentication functionality will also be used by the
hash-based module authentication. Split it out from CONFIG_MODULE_SIG
so it is usable by both.
Signed-off-by: Thomas Weißschuh <linux@weissschuh.net>
---
crypto/algapi.c | 4 ++--
include/linux/module.h | 18 +++++++++---------
kernel/module/Kconfig | 5 ++++-
kernel/module/Makefile | 1 +
kernel/module/auth.c | 32 ++++++++++++++++++++++++++++++++
kernel/module/internal.h | 2 +-
kernel/module/main.c | 8 ++++----
kernel/module/signing.c | 23 +----------------------
8 files changed, 54 insertions(+), 39 deletions(-)
diff --git a/crypto/algapi.c b/crypto/algapi.c
index 37de377719ae..14252b780266 100644
--- a/crypto/algapi.c
+++ b/crypto/algapi.c
@@ -24,8 +24,8 @@ static LIST_HEAD(crypto_template_list);
static inline void crypto_check_module_sig(struct module *mod)
{
- if (fips_enabled && mod && !module_sig_ok(mod))
- panic("Module %s signature verification failed in FIPS mode\n",
+ if (fips_enabled && mod && !module_auth_ok(mod))
+ panic("Module %s authentication failed in FIPS mode\n",
module_name(mod));
}
diff --git a/include/linux/module.h b/include/linux/module.h
index 7566815fabbe..b4760777daad 100644
--- a/include/linux/module.h
+++ b/include/linux/module.h
@@ -437,9 +437,9 @@ struct module {
/* GPL-only exported symbols. */
bool using_gplonly_symbols;
-#ifdef CONFIG_MODULE_SIG
- /* Signature was verified. */
- bool sig_ok;
+#ifdef CONFIG_MODULE_AUTH
+ /* Module was authenticated. */
+ bool auth_ok;
#endif
bool async_probe_requested;
@@ -918,16 +918,16 @@ static inline bool retpoline_module_ok(bool has_retpoline)
}
#endif
-#ifdef CONFIG_MODULE_SIG
+#ifdef CONFIG_MODULE_AUTH
bool is_module_sig_enforced(void);
void set_module_sig_enforced(void);
-static inline bool module_sig_ok(struct module *module)
+static inline bool module_auth_ok(struct module *module)
{
- return module->sig_ok;
+ return module->auth_ok;
}
-#else /* !CONFIG_MODULE_SIG */
+#else /* !CONFIG_MODULE_AUTH */
static inline bool is_module_sig_enforced(void)
{
return false;
@@ -937,11 +937,11 @@ static inline void set_module_sig_enforced(void)
{
}
-static inline bool module_sig_ok(struct module *module)
+static inline bool module_auth_ok(struct module *module)
{
return true;
}
-#endif /* CONFIG_MODULE_SIG */
+#endif /* CONFIG_MODULE_AUTH */
#if defined(CONFIG_MODULES) && defined(CONFIG_KALLSYMS)
int module_kallsyms_on_each_symbol(const char *modname,
diff --git a/kernel/module/Kconfig b/kernel/module/Kconfig
index f535181e0d98..84297da666ff 100644
--- a/kernel/module/Kconfig
+++ b/kernel/module/Kconfig
@@ -271,9 +271,12 @@ config MODULE_SIG
debuginfo strip done by some packagers (such as rpmbuild) and
inclusion into an initramfs that wants the module size reduced.
+config MODULE_AUTH
+ def_bool MODULE_SIG
+
config MODULE_SIG_FORCE
bool "Require modules to be validly signed"
- depends on MODULE_SIG
+ depends on MODULE_AUTH
help
Reject unsigned modules or signed modules for which we don't have a
key. Without this, such modules will simply taint the kernel.
diff --git a/kernel/module/Makefile b/kernel/module/Makefile
index d9e8759a7b05..c7200e293d04 100644
--- a/kernel/module/Makefile
+++ b/kernel/module/Makefile
@@ -14,6 +14,7 @@ obj-y += strict_rwx.o
obj-y += kmod.o
obj-$(CONFIG_MODULE_DEBUG_AUTOLOAD_DUPS) += dups.o
obj-$(CONFIG_MODULE_DECOMPRESS) += decompress.o
+obj-$(CONFIG_MODULE_AUTH) += auth.o
obj-$(CONFIG_MODULE_SIG) += signing.o
obj-$(CONFIG_LIVEPATCH) += livepatch.o
obj-$(CONFIG_MODULES_TREE_LOOKUP) += tree_lookup.o
diff --git a/kernel/module/auth.c b/kernel/module/auth.c
new file mode 100644
index 000000000000..956ac63d9d33
--- /dev/null
+++ b/kernel/module/auth.c
@@ -0,0 +1,32 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/* Module authentication checker
+ *
+ * Copyright (C) 2012 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells (dhowells@redhat.com)
+ */
+
+#include <linux/export.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/types.h>
+
+#undef MODULE_PARAM_PREFIX
+#define MODULE_PARAM_PREFIX "module."
+
+static bool sig_enforce = IS_ENABLED(CONFIG_MODULE_SIG_FORCE);
+module_param(sig_enforce, bool_enable_only, 0644);
+
+/*
+ * Export sig_enforce kernel cmdline parameter to allow other subsystems rely
+ * on that instead of directly to CONFIG_MODULE_SIG_FORCE config.
+ */
+bool is_module_sig_enforced(void)
+{
+ return sig_enforce;
+}
+EXPORT_SYMBOL(is_module_sig_enforced);
+
+void set_module_sig_enforced(void)
+{
+ sig_enforce = true;
+}
diff --git a/kernel/module/internal.h b/kernel/module/internal.h
index 006ada7d4e6e..f8f425b167f1 100644
--- a/kernel/module/internal.h
+++ b/kernel/module/internal.h
@@ -68,7 +68,7 @@ struct load_info {
Elf_Shdr *sechdrs;
char *secstrings, *strtab;
unsigned long symoffs, stroffs, init_typeoffs, core_typeoffs;
- bool sig_ok;
+ bool auth_ok;
#ifdef CONFIG_KALLSYMS
unsigned long mod_kallsyms_init_off;
#endif
diff --git a/kernel/module/main.c b/kernel/module/main.c
index 17a352198016..cd8a74df117e 100644
--- a/kernel/module/main.c
+++ b/kernel/module/main.c
@@ -2601,10 +2601,10 @@ static void module_augment_kernel_taints(struct module *mod, struct load_info *i
mod->name);
add_taint_module(mod, TAINT_TEST, LOCKDEP_STILL_OK);
}
-#ifdef CONFIG_MODULE_SIG
- mod->sig_ok = info->sig_ok;
- if (!mod->sig_ok) {
- pr_notice_once("%s: module verification failed: signature "
+#ifdef CONFIG_MODULE_AUTH
+ mod->auth_ok = info->auth_ok;
+ if (!mod->auth_ok) {
+ pr_notice_once("%s: module authentication failed: signature "
"and/or required key missing - tainting "
"kernel\n", mod->name);
add_taint_module(mod, TAINT_UNSIGNED_MODULE, LOCKDEP_STILL_OK);
diff --git a/kernel/module/signing.c b/kernel/module/signing.c
index 69d4b1758540..07a786723221 100644
--- a/kernel/module/signing.c
+++ b/kernel/module/signing.c
@@ -16,27 +16,6 @@
#include <uapi/linux/module.h>
#include "internal.h"
-#undef MODULE_PARAM_PREFIX
-#define MODULE_PARAM_PREFIX "module."
-
-static bool sig_enforce = IS_ENABLED(CONFIG_MODULE_SIG_FORCE);
-module_param(sig_enforce, bool_enable_only, 0644);
-
-/*
- * Export sig_enforce kernel cmdline parameter to allow other subsystems rely
- * on that instead of directly to CONFIG_MODULE_SIG_FORCE config.
- */
-bool is_module_sig_enforced(void)
-{
- return sig_enforce;
-}
-EXPORT_SYMBOL(is_module_sig_enforced);
-
-void set_module_sig_enforced(void)
-{
- sig_enforce = true;
-}
-
/*
* Verify the signature on a module.
*/
@@ -84,7 +63,7 @@ int module_sig_check(struct load_info *info, int flags)
info->len -= markerlen;
err = mod_verify_sig(mod, info);
if (!err) {
- info->sig_ok = true;
+ info->auth_ok = true;
return 0;
}
}
--
2.54.0
^ 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