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
This commit is contained in:
Linus Torvalds
2026-06-19 11:32:05 -07:00
15 changed files with 1057 additions and 101 deletions

View File

@@ -2311,6 +2311,12 @@ Kernel parameters
Use the canonical format for the binary runtime
measurements, instead of host native format.
ima_flush_htable [IMA]
Flush the IMA hash table when deleting all the
staged measurement records, to achieve maximum
memory saving at the cost of having duplicate
records across the staged measurement lists.
ima_hash= [IMA]
Format: { md5 | sha1 | rmd160 | sha256 | sha384
| sha512 | ... }

View File

@@ -0,0 +1,203 @@
.. SPDX-License-Identifier: GPL-2.0
==================================
IMA Measurements Export and Delete
==================================
Introduction
============
The IMA measurements list is currently stored in the kernel memory. Memory
occupation grows linearly with the number of records, and can become a
problem especially in environments with reduced resources.
While there is an advantage in keeping the IMA measurements list in kernel
memory, so that it is always available for reading from the securityfs
interfaces, storing it elsewhere would make it possible to free precious
memory for other kernel usage.
The IMA measurements list needs to be retained and safely stored for new
attestation servers to validate it. Assuming the IMA measurements list is
properly saved, storing it outside the kernel does not introduce security
issues, since its integrity is anyway protected by the TPM.
Hence, the new IMA staging mechanism is introduced to export IMA
measurements to user space and delete them from kernel space.
Staging consists in atomically moving the current measurements list to a
temporary list, so that measurements can be deleted afterwards. The staging
operation locks the hot path (racing with addition of new measurements) for
a very short time, only for swapping the list pointers. Deletion of the
measurements instead is done locklessly, away from the hot path.
There are two flavors of the staging mechanism. In the staging with prompt,
all current measurements are staged, read and deleted upon confirmation. In
the staging and deleting flavor, N measurements are staged from the
beginning of the current measurements list and immediately deleted without
confirmation.
Management of Staged Measurements
=================================
Since with the staging mechanism measurement records are removed from the
kernel, the staged measurements need to be saved in a storage and
concatenated together, so that they can be presented during remote
attestation as if staging was never done. This task can be accomplished by
a remote attestation agent modified to support staging, or a system
service.
Coordination is necessary in the case where there are multiple actors
requesting measurements to be staged.
In the staging with prompt case, the measurement interfaces can be accessed
only by one actor (writer) at a time, so the others will get an error until
the former closes it. Since the actors don't care about N, when they gain
access to the interface, they will get all the staged measurements at the
time of their request.
In the case of staging and deleting, coordination is more important, since
there is the risk that two actors unaware of each other compute the value N
on the current measurements list and request IMA to stage N twice.
Remote Attestation Agent Workflow
=================================
Remote attestation agents can be configured to always present all the
measurements to the remote verifiers or, alternatively, to only provide the
measurements that have not been verified yet by the remote verifiers.
In the latter case, determining which measurements need to be sent and
verified must solely depend on the remote verifier. The remote attestation
agent can proactively send partial measurements, at the condition that they
are the ones that the remote verifier needs.
An agent can rely on one of the supported staging methods to proactively
send to a remote verifier the measurements since the previous request up
to the ones that verify the TPM quote obtained in the current request.
The workflow with each staging method is the following.
With staging with prompt, the agent stages the current measurements list,
reads and stores the measurements in a storage and immediately requests
IMA to delete the staged measurements from kernel memory. Afterwards, it
calculates N by replaying the PCR extend on the stored measurements until
the calculated PCRs match the quoted PCRs. It then keeps the measurements
in excess for the next attestation request.
At the next attestation request, the agent performs the same steps above,
and concatenates the new measurements to the ones in excess from the
previous request. Also in this case, the agent replays the PCR extend until
it matches the currently quoted PCRs, keeps the measurements in excess and
presents the new N measurement records to the remote attestation server.
With the staging and deleting method, the agent reads the current
measurements list, calculates N and requests IMA to delete only those. The
measurements in excess are kept in the IMA measurements list and can be
retrieved at the next remote attestation request.
While keeping only the excess measurements in the storage could be
sufficient to serve the requests of a remote verifier, it is advised to
keep all the obtained measurements locally, as they might be needed for the
attestation with a different remote verifier.
Usage
=====
The IMA staging mechanism can be enabled from the kernel configuration with
the CONFIG_IMA_STAGING option. This option prevents inadvertently removing
the IMA measurement list on systems which do not properly save it.
If the option is enabled, IMA duplicates the current securityfs
measurements interfaces (both binary and ASCII), by adding the ``_staged``
file suffix. Both the original and the staging interfaces gain the write
permission for the root user and group, but require the process to have
CAP_SYS_ADMIN set.
The staging mechanism supports two flavors.
Staging with prompt
~~~~~~~~~~~~~~~~~~~
The current measurements list is moved to a temporary staging area,
allowing it to be saved to external storage, before being deleted upon
confirmation.
This staging process is achieved with the following steps.
1. ``echo A > <_staged interface>``: the user requests IMA to stage the
entire measurements list;
2. ``cat <_staged interface>``: the user reads the staged measurements;
3. ``echo D > <_staged interface>``: the user requests IMA to delete
staged measurements.
Staging and deleting
~~~~~~~~~~~~~~~~~~~~
N measurements are staged to a temporary staging area, and immediately
deleted without further confirmation.
This staging process is achieved with the following steps.
1. ``cat <original interface>``: the user reads the current measurements
list and determines what the value N for staging should be;
2. ``echo N > <original interface>``: the user requests IMA to delete N
measurements from the current measurements list.
Interface Access
================
In order to avoid the IMA measurements list being suddenly truncated by the
staging mechanism during a read, or having multiple concurrent staging, a
semaphore-like locking scheme has been implemented on all the measurements
list interfaces.
Multiple readers can access concurrently the original and staged
interfaces, and they can be in mutual exclusion with one writer. In order
to see the same state across all the measurement interfaces, the same
writer is allowed to open multiple interfaces for write or read/write.
If an illegal access occurs, the open to the measurements list interface is
denied.
Kexec
=====
In the event a kexec() system call occurs between staging and deleting, the
staged measurement records are marshalled before the current measurements
list, so that they are both available when the secondary kernel starts.
If measurement is suspended before requesting to delete staged or current
measurements, IMA returns an error to user space to let it know that
marshalling is already in progress, so that it does not save the
measurements twice.
IMA also disallows staging when suspending measurement, to avoid the
situation where neither measurements are carried over to the secondary
kernel, nor they are saved by user space to the storage.
Hash table
==========
By default, the template digest of staged measurement records are kept in
kernel memory (only template data are freed), to be able to detect
duplicate records independently of staging.
The new kernel option ``ima_flush_htable`` has been introduced to
explicitly request a complete deletion of the staged measurements, for
maximum kernel memory saving. If the option has been specified, duplicate
records are still avoided on records of the current measurements list,
but there can be duplicates between different groups of staged
measurements.
Flushing the hash table is supported only for the staging with prompt
flavor. For the staging and deleting flavor, it would have been necessary
to lock the hot path adding new measurements for the time needed to remove
each selected measurement individually.

View File

@@ -8,6 +8,7 @@ Security Documentation
credentials
snp-tdx-threat-model
IMA-templates
IMA-export-delete
keys/index
lsm
lsm-development

View File

@@ -12830,6 +12830,8 @@ R: Eric Snowberg <eric.snowberg@oracle.com>
L: linux-integrity@vger.kernel.org
S: Supported
T: git git://git.kernel.org/pub/scm/linux/kernel/git/zohar/linux-integrity.git
F: Documentation/security/IMA-export-delete.rst
F: Documentation/security/IMA-templates.rst
F: include/linux/secure_boot.h
F: security/integrity/
F: security/integrity/ima/

View File

@@ -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;
return asymmetric_verify(keyring, sig, siglen, hash.digest,
hash.hdr.length);
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);
}
out:
key_put(key);
return rc;
}

View File

@@ -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);

View File

@@ -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

View File

@@ -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)
{

View File

@@ -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) {

View File

@@ -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:

View File

@@ -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 = seq_release,
.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,
};
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;

View File

@@ -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;

View File

@@ -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 @@ static int ima_alloc_kexec_file_buf(size_t segment_size)
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;

View File

@@ -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;

View File

@@ -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 @@ int ima_add_template_entry(struct ima_template_entry *entry, int violation,
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;