summaryrefslogtreecommitdiff
path: root/security
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2026-06-19 11:32:05 -0700
committerLinus Torvalds <torvalds@linux-foundation.org>2026-06-19 11:32:05 -0700
commit0798268aa4c26ece25020b3ddeeef9a5941209c0 (patch)
tree57ad9c020ece170dcca8d56a379d03545a61d137 /security
parenta552c81ff4a16738ca5a44a177d552eb38d552ce (diff)
parent35d6f5e788dae0dcc4c42d1280360f19aef9ab52 (diff)
Merge tag 'integrity-v7.2' of git://git.kernel.org/pub/scm/linux/kernel/git/zohar/linux-integrity
Pull IMA updates from Mimi Zohar: - Introduce IMA and EVM post-quantum ML-DSA signature support ML-DSA signature support for IMA and EVM is limited to sigv3 signatures, which calculates and verifies a hash of a compact structure containing the file data/metadata hash, hash type, and hash algorithm. IMA and EVM still calculate the file data/metadata hashes respectively. - Introduce support for removing IMA measurement list records stored in kernel memory The IMA measurement list can grow large depending on policy, but removing records breaks remote attestation, unless they are safely preserved and made available for attestation requests. Until environments are prepared to preserve the measurement records, a new CONFIG_IMA_STAGING Kconfig option is introduced to guard against deletion. Several approaches for removing measurement list records were evaluated but rejected due to filesystem constraints, the introduction of a new critical data record, and locking concerns. Two methods are being upstreamed: staged deletion with confirmation, and staged deletion of N records without confirmation. Both methods minimize the period during which new measurements are blocked from being appended to the measurement list by staging the measurement list. A comparison of the two methods is included in the documentation. - Some code cleanup, and a couple of bug fixes * tag 'integrity-v7.2' of git://git.kernel.org/pub/scm/linux/kernel/git/zohar/linux-integrity: doc: security: Add documentation of exporting and deleting IMA measurements ima: Support staging and deleting N measurements records ima: Add support for flushing the hash table when staging measurements ima: Add support for staging measurements with prompt ima: Introduce ima_dump_measurement() ima: Use snprintf() in create_securityfs_measurement_lists ima: Mediate open/release method of the measurements list ima: Introduce _ima_measurements_start() and _ima_measurements_next() ima: Introduce per binary measurements list type binary_runtime_size value ima: Introduce per binary measurements list type ima_num_records counter ima: Replace static htable queue with dynamically allocated array ima: Remove ima_h_table structure evm: terminate and bound the evm_xattrs read buffer integrity: Add support for sigv3 verification using ML-DSA keys integrity: Refactor asymmetric_verify for reusability integrity: Check that algo parameter is within valid range integrity: Check for NULL returned by asymmetric_key_public_key ima: return error early if file xattr cannot be changed ima: Fix sigv3 signature handling for EVM_IMA_XATTR_DIGSIG
Diffstat (limited to 'security')
-rw-r--r--security/integrity/digsig_asymmetric.c152
-rw-r--r--security/integrity/evm/evm_secfs.c16
-rw-r--r--security/integrity/ima/Kconfig15
-rw-r--r--security/integrity/ima/ima.h28
-rw-r--r--security/integrity/ima/ima_api.c2
-rw-r--r--security/integrity/ima/ima_appraise.c10
-rw-r--r--security/integrity/ima/ima_fs.c346
-rw-r--r--security/integrity/ima/ima_init.c5
-rw-r--r--security/integrity/ima/ima_kexec.c42
-rw-r--r--security/integrity/ima/ima_policy.c3
-rw-r--r--security/integrity/ima/ima_queue.c327
11 files changed, 845 insertions, 101 deletions
diff --git a/security/integrity/digsig_asymmetric.c b/security/integrity/digsig_asymmetric.c
index 6e68ec3becbd..b4c23a0ed68f 100644
--- a/security/integrity/digsig_asymmetric.c
+++ b/security/integrity/digsig_asymmetric.c
@@ -79,18 +79,25 @@ static struct key *request_asymmetric_key(struct key *keyring, uint32_t keyid)
return key;
}
-int asymmetric_verify(struct key *keyring, const char *sig,
- int siglen, const char *data, int datalen)
+/**
+ * asymmetric_verify_common -- sigv2 and sigv3 common verify function
+ * @key: The key to use for signature verification; caller must free it
+ * @pk: The associated public key; must not be NULL
+ * @sig: The xattr signature
+ * @siglen: The length of the xattr signature; must be at least
+ * sizeof(struct signature_v2_hdr)
+ * @data: The data to verify the signature on
+ * @datalen: Length of @data
+ */
+static int asymmetric_verify_common(const struct key *key,
+ const struct public_key *pk,
+ const char *sig, int siglen,
+ const char *data, int datalen)
{
- struct public_key_signature pks;
struct signature_v2_hdr *hdr = (struct signature_v2_hdr *)sig;
- const struct public_key *pk;
- struct key *key;
+ struct public_key_signature pks;
int ret;
- if (siglen <= sizeof(*hdr))
- return -EBADMSG;
-
siglen -= sizeof(*hdr);
if (siglen != be16_to_cpu(hdr->sig_size))
@@ -99,15 +106,9 @@ int asymmetric_verify(struct key *keyring, const char *sig,
if (hdr->hash_algo >= HASH_ALGO__LAST)
return -ENOPKG;
- key = request_asymmetric_key(keyring, be32_to_cpu(hdr->keyid));
- if (IS_ERR(key))
- return PTR_ERR(key);
-
memset(&pks, 0, sizeof(pks));
pks.hash_algo = hash_algo_name[hdr->hash_algo];
-
- pk = asymmetric_key_public_key(key);
pks.pkey_algo = pk->pkey_algo;
if (!strcmp(pk->pkey_algo, "rsa")) {
pks.encoding = "pkcs1";
@@ -127,15 +128,42 @@ int asymmetric_verify(struct key *keyring, const char *sig,
pks.s_size = siglen;
ret = verify_signature(key, &pks);
out:
- key_put(key);
pr_debug("%s() = %d\n", __func__, ret);
return ret;
}
+int asymmetric_verify(struct key *keyring, const char *sig,
+ int siglen, const char *data, int datalen)
+{
+ struct signature_v2_hdr *hdr = (struct signature_v2_hdr *)sig;
+ const struct public_key *pk;
+ struct key *key;
+ int ret;
+
+ if (siglen <= sizeof(*hdr))
+ return -EBADMSG;
+
+ key = request_asymmetric_key(keyring, be32_to_cpu(hdr->keyid));
+ if (IS_ERR(key))
+ return PTR_ERR(key);
+ pk = asymmetric_key_public_key(key);
+ if (!pk) {
+ ret = -ENOKEY;
+ goto out;
+ }
+
+ ret = asymmetric_verify_common(key, pk, sig, siglen, data, datalen);
+
+out:
+ key_put(key);
+
+ return ret;
+}
+
/*
* calc_file_id_hash - calculate the hash of the ima_file_id struct data
* @type: xattr type [enum evm_ima_xattr_type]
- * @algo: hash algorithm [enum hash_algo]
+ * @algo: hash algorithm [enum hash_algo]; caller must ensure valid value
* @digest: pointer to the digest to be hashed
* @hash: (out) pointer to the hash
*
@@ -176,17 +204,99 @@ static int calc_file_id_hash(enum evm_ima_xattr_type type,
return rc;
}
+/**
+ * asymmetric_verify_v3_hashless - Use hashless signature verification on sigv3
+ * @key: The key to use for signature verification; caller must free it
+ * @pk: The associated public key; must not be NULL
+ * @encoding: The encoding the key type uses
+ * @sig: The xattr signature
+ * @siglen: The length of the xattr signature; must be at least
+ * sizeof(struct signature_v2_hdr)
+ * @algo: hash algorithm [enum hash_algo]; caller must ensure valid value
+ * @digest: The file digest
+ *
+ * Create an ima_file_id structure and use it for signature verification
+ * directly. This can be used for ML-DSA in pure mode for example.
+ */
+static int asymmetric_verify_v3_hashless(struct key *key,
+ const struct public_key *pk,
+ const char *encoding,
+ const char *sig, int siglen,
+ u8 algo,
+ const u8 *digest)
+{
+ struct signature_v2_hdr *hdr = (struct signature_v2_hdr *)sig;
+ struct ima_file_id file_id = {
+ .hash_type = hdr->type,
+ .hash_algorithm = algo,
+ };
+ size_t digest_size = hash_digest_size[algo];
+ struct public_key_signature pks = {
+ .m = (u8 *)&file_id,
+ .m_size = sizeof(file_id) - (HASH_MAX_DIGESTSIZE - digest_size),
+ .s = hdr->sig,
+ .s_size = siglen - sizeof(*hdr),
+ .pkey_algo = pk->pkey_algo,
+ .hash_algo = "none",
+ .encoding = encoding,
+ };
+ int ret;
+
+ if (hdr->type != IMA_VERITY_DIGSIG &&
+ hdr->type != EVM_IMA_XATTR_DIGSIG &&
+ hdr->type != EVM_XATTR_PORTABLE_DIGSIG)
+ return -EINVAL;
+
+ if (pks.s_size != be16_to_cpu(hdr->sig_size))
+ return -EBADMSG;
+
+ memcpy(file_id.hash, digest, digest_size);
+
+ ret = verify_signature(key, &pks);
+ pr_debug("%s() = %d\n", __func__, ret);
+ return ret;
+}
+
int asymmetric_verify_v3(struct key *keyring, const char *sig, int siglen,
const char *data, int datalen, u8 algo)
{
struct signature_v2_hdr *hdr = (struct signature_v2_hdr *)sig;
struct ima_max_digest_data hash;
+ const struct public_key *pk;
+ struct key *key;
int rc;
- rc = calc_file_id_hash(hdr->type, algo, data, &hash);
- if (rc)
- return -EINVAL;
+ if (algo >= HASH_ALGO__LAST)
+ return -ENOPKG;
+
+ if (siglen <= sizeof(*hdr))
+ return -EBADMSG;
+
+ key = request_asymmetric_key(keyring, be32_to_cpu(hdr->keyid));
+ if (IS_ERR(key))
+ return PTR_ERR(key);
+
+ pk = asymmetric_key_public_key(key);
+ if (!pk) {
+ rc = -ENOKEY;
+ goto out;
+ }
+ if (!strncmp(pk->pkey_algo, "mldsa", 5)) {
+ rc = asymmetric_verify_v3_hashless(key, pk, "raw",
+ sig, siglen, algo, data);
+ } else {
+ rc = calc_file_id_hash(hdr->type, algo, data, &hash);
+ if (rc) {
+ rc = -EINVAL;
+ goto out;
+ }
+
+ rc = asymmetric_verify_common(key, pk, sig, siglen, hash.digest,
+ hash.hdr.length);
+ }
- return asymmetric_verify(keyring, sig, siglen, hash.digest,
- hash.hdr.length);
+out:
+ key_put(key);
+
+ return rc;
}
diff --git a/security/integrity/evm/evm_secfs.c b/security/integrity/evm/evm_secfs.c
index acd840461902..4baf5e23bc97 100644
--- a/security/integrity/evm/evm_secfs.c
+++ b/security/integrity/evm/evm_secfs.c
@@ -127,8 +127,8 @@ static ssize_t evm_read_xattrs(struct file *filp, char __user *buf,
size_t count, loff_t *ppos)
{
char *temp;
- int offset = 0;
- ssize_t rc, size = 0;
+ size_t offset = 0, size = 0;
+ ssize_t rc;
struct xattr_list *xattr;
if (*ppos != 0)
@@ -151,16 +151,22 @@ static ssize_t evm_read_xattrs(struct file *filp, char __user *buf,
return -ENOMEM;
}
+ temp[size] = '\0';
+
+ /*
+ * No truncation possible: size is computed over the same enabled
+ * xattrs under xattr_list_mutex, so offset never exceeds size.
+ */
list_for_each_entry(xattr, &evm_config_xattrnames, list) {
if (!xattr->enabled)
continue;
- sprintf(temp + offset, "%s\n", xattr->name);
- offset += strlen(xattr->name) + 1;
+ offset += snprintf(temp + offset, size + 1 - offset, "%s\n",
+ xattr->name);
}
mutex_unlock(&xattr_list_mutex);
- rc = simple_read_from_buffer(buf, count, ppos, temp, strlen(temp));
+ rc = simple_read_from_buffer(buf, count, ppos, temp, offset);
kfree(temp);
diff --git a/security/integrity/ima/Kconfig b/security/integrity/ima/Kconfig
index 862fbee2b174..f4d25e045808 100644
--- a/security/integrity/ima/Kconfig
+++ b/security/integrity/ima/Kconfig
@@ -332,4 +332,19 @@ 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 n
+ 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.
+
+ Or, alternatively, it allows user space to specify N measurements
+ records to stage internally, so that they can be immediately deleted.
+
+ On kexec, staging is aborted and any staged measurement records are
+ copied to the secondary kernel.
+
endif
diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h
index 69e9bf0b82c6..caaedd4b58fd 100644
--- a/security/integrity/ima/ima.h
+++ b/security/integrity/ima/ima.h
@@ -28,6 +28,15 @@ enum ima_show_type { IMA_SHOW_BINARY, IMA_SHOW_BINARY_NO_FIELD_LEN,
IMA_SHOW_BINARY_OLD_STRING_FMT, IMA_SHOW_ASCII };
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_STAGED, BINARY_FULL, BINARY__LAST
+};
+
/* digest size for IMA, fits SHA1 or MD5 */
#define IMA_DIGEST_SIZE SHA1_DIGEST_SIZE
#define IMA_EVENT_NAME_LEN_MAX 255
@@ -118,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 {
@@ -308,10 +318,14 @@ 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_queue_delete_partial(unsigned long req_value);
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);
-unsigned long ima_get_binary_runtime_size(void);
+int __init ima_init_htable(void);
+unsigned long ima_get_binary_runtime_size(enum binary_lists binary_list);
int ima_init_template(void);
void ima_init_template_list(void);
int __init ima_init_digests(void);
@@ -324,12 +338,12 @@ int ima_lsm_policy_change(struct notifier_block *nb, unsigned long event,
*/
extern spinlock_t ima_queue_lock;
-struct ima_h_table {
- atomic_long_t len; /* number of stored measurements in the list */
- atomic_long_t violations;
- struct hlist_head queue[IMA_MEASURE_HTABLE_SIZE];
-};
-extern struct ima_h_table ima_htable;
+/* Total number of measurement list records since hard boot. */
+extern atomic_long_t ima_num_records[BINARY__LAST];
+/* Total number of violations since hard boot. */
+extern atomic_long_t ima_num_violations;
+extern struct hlist_head __rcu *ima_htable;
+extern bool ima_flush_htable;
static inline unsigned int ima_hash_key(u8 *digest)
{
diff --git a/security/integrity/ima/ima_api.c b/security/integrity/ima/ima_api.c
index 0916f24f005f..122d127e108d 100644
--- a/security/integrity/ima/ima_api.c
+++ b/security/integrity/ima/ima_api.c
@@ -146,7 +146,7 @@ void ima_add_violation(struct file *file, const unsigned char *filename,
int result;
/* can overflow, only indicator */
- atomic_long_inc(&ima_htable.violations);
+ atomic_long_inc(&ima_num_violations);
result = ima_alloc_init_template(&event_data, &entry, NULL);
if (result < 0) {
diff --git a/security/integrity/ima/ima_appraise.c b/security/integrity/ima/ima_appraise.c
index de963b9f3634..18d0d9154317 100644
--- a/security/integrity/ima/ima_appraise.c
+++ b/security/integrity/ima/ima_appraise.c
@@ -90,6 +90,11 @@ static int ima_fix_xattr(struct dentry *dentry, struct ima_iint_cache *iint)
int rc, offset;
u8 algo = iint->ima_hash->algo;
+ if (IS_RDONLY(d_inode(dentry)))
+ return -EROFS;
+ if (IS_IMMUTABLE(d_inode(dentry)))
+ return -EPERM;
+
if (algo <= HASH_ALGO_SHA1) {
offset = 1;
iint->ima_hash->xattr.sha1.type = IMA_XATTR_DIGEST;
@@ -195,8 +200,9 @@ enum hash_algo ima_get_hash_algo(const struct evm_ima_xattr_data *xattr_value,
return sig->hash_algo;
case EVM_IMA_XATTR_DIGSIG:
sig = (typeof(sig))xattr_value;
- if (sig->version != 2 || xattr_len <= sizeof(*sig)
- || sig->hash_algo >= HASH_ALGO__LAST)
+ if ((sig->version != 2 && sig->version != 3) ||
+ xattr_len <= sizeof(*sig) ||
+ sig->hash_algo >= HASH_ALGO__LAST)
return ima_hash_algo;
return sig->hash_algo;
case IMA_XATTR_DIGEST_NG:
diff --git a/security/integrity/ima/ima_fs.c b/security/integrity/ima/ima_fs.c
index ca4931a95098..174a94740da1 100644
--- a/security/integrity/ima/ima_fs.c
+++ b/security/integrity/ima/ima_fs.c
@@ -24,7 +24,19 @@
#include "ima.h"
+/*
+ * Requests:
+ * 'A\n': stage the entire measurements list
+ * 'D\n': delete all staged measurements
+ * '[1, ULONG_MAX]\n' delete N measurements records
+ */
+#define STAGED_REQ_LENGTH 21
+
static DEFINE_MUTEX(ima_write_mutex);
+static DEFINE_MUTEX(ima_measure_mutex);
+static long ima_measure_users;
+static struct task_struct *measure_writer;
+static long measure_writer_extra_writes;
bool ima_canonical_fmt;
static int __init default_canonical_fmt_setup(char *str)
@@ -38,8 +50,8 @@ __setup("ima_canonical_fmt", default_canonical_fmt_setup);
static int valid_policy = 1;
-static ssize_t ima_show_htable_value(char __user *buf, size_t count,
- loff_t *ppos, atomic_long_t *val)
+static ssize_t ima_show_counter(char __user *buf, size_t count, loff_t *ppos,
+ atomic_long_t *val)
{
char tmpbuf[32]; /* greater than largest 'long' string value */
ssize_t len;
@@ -48,15 +60,14 @@ static ssize_t ima_show_htable_value(char __user *buf, size_t count,
return simple_read_from_buffer(buf, count, ppos, tmpbuf, len);
}
-static ssize_t ima_show_htable_violations(struct file *filp,
- char __user *buf,
- size_t count, loff_t *ppos)
+static ssize_t ima_show_num_violations(struct file *filp, char __user *buf,
+ size_t count, loff_t *ppos)
{
- return ima_show_htable_value(buf, count, ppos, &ima_htable.violations);
+ return ima_show_counter(buf, count, ppos, &ima_num_violations);
}
-static const struct file_operations ima_htable_violations_ops = {
- .read = ima_show_htable_violations,
+static const struct file_operations ima_num_violations_ops = {
+ .read = ima_show_num_violations,
.llseek = generic_file_llseek,
};
@@ -64,8 +75,7 @@ static ssize_t ima_show_measurements_count(struct file *filp,
char __user *buf,
size_t count, loff_t *ppos)
{
- return ima_show_htable_value(buf, count, ppos, &ima_htable.len);
-
+ return ima_show_counter(buf, count, ppos, &ima_num_records[BINARY]);
}
static const struct file_operations ima_measurements_count_ops = {
@@ -74,14 +84,15 @@ static const struct file_operations ima_measurements_count_ops = {
};
/* returns pointer to hlist_node */
-static void *ima_measurements_start(struct seq_file *m, loff_t *pos)
+static void *_ima_measurements_start(struct seq_file *m, loff_t *pos,
+ struct list_head *head)
{
loff_t l = *pos;
struct ima_queue_entry *qe;
/* we need a lock since pos could point beyond last element */
rcu_read_lock();
- list_for_each_entry_rcu(qe, &ima_measurements, later) {
+ list_for_each_entry_rcu(qe, head, later) {
if (!l--) {
rcu_read_unlock();
return qe;
@@ -91,7 +102,18 @@ static void *ima_measurements_start(struct seq_file *m, loff_t *pos)
return NULL;
}
-static void *ima_measurements_next(struct seq_file *m, void *v, loff_t *pos)
+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)
{
struct ima_queue_entry *qe = v;
@@ -103,7 +125,18 @@ static void *ima_measurements_next(struct seq_file *m, void *v, loff_t *pos)
rcu_read_unlock();
(*pos)++;
- return (&qe->later == &ima_measurements) ? NULL : qe;
+ return (&qe->later == head) ? NULL : qe;
+}
+
+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)
@@ -199,16 +232,199 @@ 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);
+ /* Overflow check. */
+ if (!write && ima_measure_users == LONG_MAX) {
+ mutex_unlock(&ima_measure_mutex);
+ return -ENFILE;
+ }
+
+ /* Same writer can do additional writes or read/writes. */
+ if (write && current == measure_writer) {
+ measure_writer_extra_writes++;
+ mutex_unlock(&ima_measure_mutex);
+ return 0;
+ }
+
+ /*
+ * ima_measure_users: > 0 open readers
+ * ima_measure_users: == -1 open writer
+ */
+ if ((write && ima_measure_users != 0) ||
+ (!write && ima_measure_users < 0)) {
+ mutex_unlock(&ima_measure_mutex);
+ return -EBUSY;
+ }
+
+ if (write) {
+ ima_measure_users--;
+ /* Pointer valid, no reuse while the file descriptor is open. */
+ measure_writer = current;
+ } else {
+ ima_measure_users++;
+ }
+ mutex_unlock(&ima_measure_mutex);
+ return 0;
+}
+
+static void ima_measure_unlock(bool write)
+{
+ mutex_lock(&ima_measure_mutex);
+ /* Decrement additional writes or read/writes. */
+ if (write && current == measure_writer &&
+ measure_writer_extra_writes != 0) {
+ measure_writer_extra_writes--;
+ mutex_unlock(&ima_measure_mutex);
+ return;
+ }
+ if (write) {
+ ima_measure_users++;
+ measure_writer = NULL;
+ } else {
+ ima_measure_users--;
+ }
+ mutex_unlock(&ima_measure_mutex);
+}
+
+static int _ima_measurements_open(struct inode *inode, struct file *file,
+ const struct seq_operations *seq_ops)
+{
+ bool write = (file->f_mode & FMODE_WRITE);
+ int ret;
+
+ if (write && !capable(CAP_SYS_ADMIN))
+ return -EPERM;
+
+ ret = ima_measure_lock(write);
+ if (ret < 0)
+ return ret;
+
+ ret = seq_open(file, seq_ops);
+ if (ret < 0)
+ ima_measure_unlock(write);
+
+ return ret;
+}
+
static int ima_measurements_open(struct inode *inode, struct file *file)
{
- return seq_open(file, &ima_measurments_seqops);
+ return _ima_measurements_open(inode, file, &ima_measurments_seqops);
+}
+
+static int ima_measurements_release(struct inode *inode, struct file *file)
+{
+ bool write = (file->f_mode & FMODE_WRITE);
+ int ret;
+
+ /* seq_release() always returns zero. */
+ ret = seq_release(inode, file);
+
+ ima_measure_unlock(write);
+
+ 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];
+ unsigned long req_value;
+ int ret;
+
+ if (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:
+ if (staged_interface)
+ return -EINVAL;
+
+ if (ima_flush_htable) {
+ pr_debug("Deleting staged N measurements not supported when flushing the hash table is requested\n");
+ return -EINVAL;
+ }
+
+ ret = kstrtoul(req, 10, &req_value);
+ if (ret < 0)
+ return ret;
+
+ if (req_value == 0) {
+ pr_debug("Must delete at least one entry\n");
+ return -EINVAL;
+ }
+
+ ret = ima_queue_delete_partial(req_value);
+ }
+
+ 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 = seq_release,
+ .release = ima_measurements_release,
};
void ima_print_digest(struct seq_file *m, u8 *digest, u32 size)
@@ -273,14 +489,38 @@ static const struct seq_operations ima_ascii_measurements_seqops = {
static int ima_ascii_measurements_open(struct inode *inode, struct file *file)
{
- return seq_open(file, &ima_ascii_measurements_seqops);
+ return _ima_measurements_open(inode, file,
+ &ima_ascii_measurements_seqops);
}
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 = seq_release,
+ .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,
};
static ssize_t ima_read_policy(char *path)
@@ -386,10 +626,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;
+ umode_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++;
@@ -399,26 +649,33 @@ static int __init create_securityfs_measurement_lists(void)
struct dentry *dentry;
if (algo == HASH_ALGO__LAST)
- sprintf(file_name, "ascii_runtime_measurements_tpm_alg_%x",
- ima_tpm_chip->allocated_banks[i].alg_id);
+ snprintf(file_name, sizeof(file_name),
+ "ascii_runtime_measurements_tpm_alg_%x%s",
+ ima_tpm_chip->allocated_banks[i].alg_id,
+ file_suffix);
else
- sprintf(file_name, "ascii_runtime_measurements_%s",
- hash_algo_name[algo]);
- dentry = securityfs_create_file(file_name, S_IRUSR | S_IRGRP,
+ snprintf(file_name, sizeof(file_name),
+ "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)
- sprintf(file_name, "binary_runtime_measurements_tpm_alg_%x",
- ima_tpm_chip->allocated_banks[i].alg_id);
+ snprintf(file_name, sizeof(file_name),
+ "binary_runtime_measurements_tpm_alg_%x%s",
+ ima_tpm_chip->allocated_banks[i].alg_id,
+ file_suffix);
else
- sprintf(file_name, "binary_runtime_measurements_%s",
- hash_algo_name[algo]);
- dentry = securityfs_create_file(file_name, S_IRUSR | S_IRGRP,
+ snprintf(file_name, sizeof(file_name),
+ "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);
}
@@ -426,6 +683,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
*/
@@ -518,7 +792,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;
@@ -545,7 +825,7 @@ int __init ima_fs_init(void)
}
dentry = securityfs_create_file("violations", S_IRUSR | S_IRGRP,
- ima_dir, NULL, &ima_htable_violations_ops);
+ ima_dir, NULL, &ima_num_violations_ops);
if (IS_ERR(dentry)) {
ret = PTR_ERR(dentry);
goto out;
diff --git a/security/integrity/ima/ima_init.c b/security/integrity/ima/ima_init.c
index a2f34f2d8ad7..7e0aa09a12e6 100644
--- a/security/integrity/ima/ima_init.c
+++ b/security/integrity/ima/ima_init.c
@@ -140,6 +140,11 @@ int __init ima_init(void)
rc = ima_init_digests();
if (rc != 0)
return rc;
+
+ rc = ima_init_htable();
+ if (rc != 0)
+ return rc;
+
rc = ima_add_boot_aggregate(); /* boot aggregate must be first entry */
if (rc != 0)
return rc;
diff --git a/security/integrity/ima/ima_kexec.c b/security/integrity/ima/ima_kexec.c
index 36a34c54de58..0d845693a1f7 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();
- len = atomic_long_read(&ima_htable.len);
+ buf_size = ima_get_binary_runtime_size(BINARY_FULL);
+ len = atomic_long_read(&ima_num_records[BINARY_FULL]);
n = scnprintf(ima_kexec_event, IMA_KEXEC_EVENT_LEN,
"kexec_segment_size=%lu;ima_binary_runtime_size=%lu;"
@@ -80,6 +80,17 @@ out:
return 0;
}
+static int ima_dump_measurement(struct ima_kexec_hdr *khdr,
+ struct ima_queue_entry *qe)
+{
+ if (ima_kexec_file.count >= ima_kexec_file.size)
+ return -EINVAL;
+
+ khdr->count++;
+ ima_measurements_show(&ima_kexec_file, qe);
+ return 0;
+}
+
static int ima_dump_measurement_list(unsigned long *buffer_size, void **buffer,
unsigned long segment_size)
{
@@ -95,15 +106,22 @@ 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) {
- if (ima_kexec_file.count < ima_kexec_file.size) {
- khdr.count++;
- ima_measurements_show(&ima_kexec_file, qe);
- } else {
- ret = -EINVAL;
+ /*
+ * Lockless walks possible due to strict ordering of the reboot
+ * notifiers, suspending measurement before dump, and forbidding
+ * staging/deleting (list mutations) after suspend.
+ */
+ list_for_each_entry(qe, &ima_measurements_staged, later) {
+ ret = ima_dump_measurement(&khdr, qe);
+ if (ret < 0)
+ break;
+ }
+
+ list_for_each_entry(qe, &ima_measurements, later) {
+ if (!ret)
+ ret = ima_dump_measurement(&khdr, qe);
+ if (ret < 0)
break;
- }
}
/*
@@ -159,7 +177,9 @@ void ima_add_kexec_buffer(struct kimage *image)
else
extra_memory = CONFIG_IMA_KEXEC_EXTRA_MEMORY_KB * 1024;
- binary_runtime_size = ima_get_binary_runtime_size() + extra_memory;
+ 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)
kexec_segment_size = ULONG_MAX;
diff --git a/security/integrity/ima/ima_policy.c b/security/integrity/ima/ima_policy.c
index f7f940a76922..b1c010e8eb13 100644
--- a/security/integrity/ima/ima_policy.c
+++ b/security/integrity/ima/ima_policy.c
@@ -1313,7 +1313,8 @@ static bool ima_validate_rule(struct ima_rule_entry *entry)
IMA_GID | IMA_EGID |
IMA_FGROUP | IMA_DIGSIG_REQUIRED |
IMA_PERMIT_DIRECTIO | IMA_MODSIG_ALLOWED |
- IMA_CHECK_BLACKLIST | IMA_VALIDATE_ALGOS))
+ IMA_CHECK_BLACKLIST | IMA_VALIDATE_ALGOS |
+ IMA_SIGV3_REQUIRED))
return false;
break;
diff --git a/security/integrity/ima/ima_queue.c b/security/integrity/ima/ima_queue.c
index 319522450854..f89f0ca3d4ed 100644
--- a/security/integrity/ima/ima_queue.c
+++ b/security/integrity/ima/ima_queue.c
@@ -22,24 +22,42 @@
#define AUDIT_CAUSE_LEN_MAX 32
+bool ima_flush_htable;
+
+static int __init ima_flush_htable_setup(char *str)
+{
+ if (IS_ENABLED(CONFIG_IMA_DISABLE_HTABLE)) {
+ pr_warn("Hash table not enabled, ignoring request to flush\n");
+ return 1;
+ }
+
+ ima_flush_htable = true;
+ return 1;
+}
+__setup("ima_flush_htable", ima_flush_htable_setup);
+
/* pre-allocated array of tpm_digest structures to extend a PCR */
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;
+static unsigned long binary_runtime_size[BINARY__LAST];
#else
-static unsigned long binary_runtime_size = ULONG_MAX;
+static unsigned long binary_runtime_size[BINARY__LAST] = {
+ [0 ... BINARY__LAST - 1] = ULONG_MAX
+};
#endif
-/* key: inode (before secure-hashing a file) */
-struct ima_h_table ima_htable = {
- .len = ATOMIC_LONG_INIT(0),
- .violations = ATOMIC_LONG_INIT(0),
- .queue[0 ... IMA_MEASURE_HTABLE_SIZE - 1] = HLIST_HEAD_INIT
+atomic_long_t ima_num_records[BINARY__LAST] = {
+ [0 ... BINARY__LAST - 1] = ATOMIC_LONG_INIT(0)
};
+atomic_long_t ima_num_violations = ATOMIC_LONG_INIT(0);
-/* mutex protects atomicity of extending measurement list
+/* key: inode (before secure-hashing a file) */
+struct hlist_head __rcu *ima_htable;
+
+/* 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.
*/
@@ -51,17 +69,53 @@ static DEFINE_MUTEX(ima_extend_list_mutex);
*/
static bool ima_measurements_suspended;
+/* Callers must call synchronize_rcu() and free the hash table. */
+static struct hlist_head *ima_alloc_replace_htable(void)
+{
+ struct hlist_head *old_htable, *new_htable;
+
+ /* Initializing to zeros is equivalent to call HLIST_HEAD_INIT. */
+ new_htable = kcalloc(IMA_MEASURE_HTABLE_SIZE, sizeof(struct hlist_head),
+ GFP_KERNEL);
+ if (!new_htable)
+ return ERR_PTR(-ENOMEM);
+
+ old_htable = rcu_replace_pointer(ima_htable, new_htable,
+ lockdep_is_held(&ima_extend_list_mutex));
+
+ return old_htable;
+}
+
+int __init ima_init_htable(void)
+{
+ struct hlist_head *old_htable;
+
+ mutex_lock(&ima_extend_list_mutex);
+ old_htable = ima_alloc_replace_htable();
+ mutex_unlock(&ima_extend_list_mutex);
+
+ if (IS_ERR(old_htable))
+ return PTR_ERR(old_htable);
+
+ /* Synchronize_rcu() and kfree() not necessary, only for robustness. */
+ synchronize_rcu();
+ kfree(old_htable);
+ return 0;
+}
+
/* lookup up the digest value in the hash table, and return the entry */
static struct ima_queue_entry *ima_lookup_digest_entry(u8 *digest_value,
int pcr)
{
struct ima_queue_entry *qe, *ret = NULL;
+ struct hlist_head *htable;
unsigned int key;
int rc;
key = ima_hash_key(digest_value);
rcu_read_lock();
- hlist_for_each_entry_rcu(qe, &ima_htable.queue[key], hnext) {
+ htable = rcu_dereference(ima_htable);
+ hlist_for_each_entry_rcu(qe, &htable[key], hnext) {
rc = memcmp(qe->entry->digests[ima_hash_algo_idx].digest,
digest_value, hash_digest_size[ima_hash_algo]);
if ((rc == 0) && (qe->entry->pcr == pcr)) {
@@ -91,6 +145,20 @@ static int get_binary_runtime_size(struct ima_template_entry *entry)
return size;
}
+static void ima_update_binary_runtime_size(struct ima_template_entry *entry,
+ enum binary_lists binary_list)
+{
+ int size;
+
+ if (binary_runtime_size[binary_list] == ULONG_MAX)
+ return;
+
+ size = get_binary_runtime_size(entry);
+ binary_runtime_size[binary_list] =
+ (binary_runtime_size[binary_list] < ULONG_MAX - size) ?
+ binary_runtime_size[binary_list] + size : ULONG_MAX;
+}
+
/* ima_add_template_entry helper function:
* - Add template entry to the measurement list and hash table, for
* all entries except those carried across kexec.
@@ -101,6 +169,7 @@ static int ima_add_digest_entry(struct ima_template_entry *entry,
bool update_htable)
{
struct ima_queue_entry *qe;
+ struct hlist_head *htable;
unsigned int key;
qe = kmalloc_obj(*qe);
@@ -113,19 +182,20 @@ static int ima_add_digest_entry(struct ima_template_entry *entry,
INIT_LIST_HEAD(&qe->later);
list_add_tail_rcu(&qe->later, &ima_measurements);
- atomic_long_inc(&ima_htable.len);
+ htable = rcu_dereference_protected(ima_htable,
+ lockdep_is_held(&ima_extend_list_mutex));
+
+ atomic_long_inc(&ima_num_records[BINARY]);
+ atomic_long_inc(&ima_num_records[BINARY_FULL]);
+
if (update_htable) {
key = ima_hash_key(entry->digests[ima_hash_algo_idx].digest);
- hlist_add_head_rcu(&qe->hnext, &ima_htable.queue[key]);
+ hlist_add_head_rcu(&qe->hnext, &htable[key]);
}
- if (binary_runtime_size != ULONG_MAX) {
- int size;
+ ima_update_binary_runtime_size(entry, BINARY);
+ ima_update_binary_runtime_size(entry, BINARY_FULL);
- size = get_binary_runtime_size(entry);
- binary_runtime_size = (binary_runtime_size < ULONG_MAX - size) ?
- binary_runtime_size + size : ULONG_MAX;
- }
return 0;
}
@@ -134,12 +204,18 @@ static int ima_add_digest_entry(struct ima_template_entry *entry,
* entire binary_runtime_measurement list, including the ima_kexec_hdr
* structure.
*/
-unsigned long ima_get_binary_runtime_size(void)
+unsigned long ima_get_binary_runtime_size(enum binary_lists binary_list)
{
- if (binary_runtime_size >= (ULONG_MAX - sizeof(struct ima_kexec_hdr)))
+ unsigned long val;
+
+ mutex_lock(&ima_extend_list_mutex);
+ val = binary_runtime_size[binary_list];
+ mutex_unlock(&ima_extend_list_mutex);
+
+ if (val >= (ULONG_MAX - sizeof(struct ima_kexec_hdr)))
return ULONG_MAX;
else
- return binary_runtime_size + sizeof(struct ima_kexec_hdr);
+ return val + sizeof(struct ima_kexec_hdr);
}
static int ima_pcr_extend(struct tpm_digest *digests_arg, int pcr)
@@ -220,6 +296,217 @@ out:
return result;
}
+/**
+ * ima_queue_stage - Stage all measurements
+ *
+ * If the staged measurements list is empty, the current measurements list is
+ * not empty, and measurement is not suspended, move the measurements from the
+ * current list to the staged one, and update the number of records and binary
+ * run-time size accordingly.
+ *
+ * Do not allow staging after measurement is suspended, so that dumping
+ * measurements can be done in a lockless way.
+ *
+ * Return: Zero on success, a negative value otherwise.
+ */
+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;
+ }
+
+ if (ima_measurements_suspended) {
+ ret = -EACCES;
+ goto out_unlock;
+ }
+
+ list_replace(&ima_measurements, &ima_measurements_staged);
+ INIT_LIST_HEAD(&ima_measurements);
+
+ atomic_long_set(&ima_num_records[BINARY_STAGED],
+ atomic_long_read(&ima_num_records[BINARY]));
+ atomic_long_set(&ima_num_records[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, bool flush_htable);
+
+/**
+ * ima_queue_staged_delete_all - Delete staged measurements
+ *
+ * Move staged measurements to a temporary list, ima_measurements_trim, update
+ * the number of records and the binary run-time size accordingly. Finally,
+ * delete measurements in the temporary list.
+ *
+ * Refuse to delete staged measurements if measurement is suspended, so that
+ * dump can be done in a lockless way and user space is notified about staged
+ * measurements being carried over to the secondary kernel, so that it does not
+ * save them twice.
+ *
+ * Return: Zero on success, a negative value otherwise.
+ */
+int ima_queue_staged_delete_all(void)
+{
+ struct hlist_head *old_queue = NULL;
+ 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;
+ }
+
+ if (ima_measurements_suspended) {
+ mutex_unlock(&ima_extend_list_mutex);
+ return -ESTALE;
+ }
+
+ list_replace(&ima_measurements_staged, &ima_measurements_trim);
+ INIT_LIST_HEAD(&ima_measurements_staged);
+
+ atomic_long_set(&ima_num_records[BINARY_STAGED], 0);
+
+ if (IS_ENABLED(CONFIG_IMA_KEXEC))
+ binary_runtime_size[BINARY_STAGED] = 0;
+
+ if (ima_flush_htable) {
+ old_queue = ima_alloc_replace_htable();
+ if (IS_ERR(old_queue)) {
+ mutex_unlock(&ima_extend_list_mutex);
+ return PTR_ERR(old_queue);
+ }
+ }
+
+ mutex_unlock(&ima_extend_list_mutex);
+
+ if (ima_flush_htable) {
+ synchronize_rcu();
+ kfree(old_queue);
+ }
+
+ ima_queue_delete(&ima_measurements_trim, ima_flush_htable);
+ return 0;
+}
+
+/**
+ * ima_queue_delete_partial - Delete current measurements
+ * @req_value: Number of measurements to delete
+ *
+ * Delete the requested number of measurements from the current measurements
+ * list, and update the number of records and the binary run-time size
+ * accordingly.
+ *
+ * Refuse to delete current measurements if measurement is suspended, so that
+ * dump can be done in a lockless way and user space is notified about current
+ * measurements being carried over to the secondary kernel, so that it does not
+ * save them twice.
+ *
+ * Return: Zero on success, a negative value otherwise.
+ */
+int ima_queue_delete_partial(unsigned long req_value)
+{
+ unsigned long req_value_copy = req_value;
+ unsigned long size_to_remove = 0, num_to_remove = 0;
+ LIST_HEAD(ima_measurements_trim);
+ struct ima_queue_entry *qe;
+ int ret = 0;
+
+ /*
+ * list_for_each_entry_rcu() without rcu_read_lock() is fine because
+ * only list append can happen concurrently. No list replace due to the
+ * staging/delete writers mutual exclusion.
+ */
+ list_for_each_entry_rcu(qe, &ima_measurements, later, true) {
+ size_to_remove += get_binary_runtime_size(qe->entry);
+ num_to_remove++;
+
+ if (--req_value_copy == 0)
+ break;
+ }
+
+ /* Not enough records to delete. */
+ if (req_value_copy > 0)
+ return -ENOENT;
+
+ mutex_lock(&ima_extend_list_mutex);
+ if (ima_measurements_suspended) {
+ mutex_unlock(&ima_extend_list_mutex);
+ return -ESTALE;
+ }
+
+ /*
+ * qe remains valid because ima_fs.c enforces single-writer exclusion.
+ */
+ __list_cut_position(&ima_measurements_trim, &ima_measurements,
+ &qe->later);
+
+ atomic_long_sub(num_to_remove, &ima_num_records[BINARY]);
+
+ if (IS_ENABLED(CONFIG_IMA_KEXEC))
+ binary_runtime_size[BINARY] -= size_to_remove;
+
+ mutex_unlock(&ima_extend_list_mutex);
+
+ ima_queue_delete(&ima_measurements_trim, false);
+ return ret;
+}
+
+/**
+ * ima_queue_delete - Delete measurements
+ * @head: List head measurements are deleted from
+ * @flush_htable: Whether or not the hash table is being flushed
+ *
+ * Delete the measurements from the passed list head completely if the
+ * hash table is not enabled or is being flushed, or partially (only the
+ * template data), if the hash table is used.
+ */
+static void ima_queue_delete(struct list_head *head, bool flush_htable)
+{
+ 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) || flush_htable) {
+ kfree(qe->entry->digests);
+ kfree(qe->entry);
+ kfree(qe);
+ }
+ }
+}
+
int ima_restore_measurement_entry(struct ima_template_entry *entry)
{
int result = 0;