diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt index 00e8c4fa93b8..5602e42ebf97 100644 --- a/Documentation/admin-guide/kernel-parameters.txt +++ b/Documentation/admin-guide/kernel-parameters.txt @@ -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 | ... } diff --git a/Documentation/security/IMA-export-delete.rst b/Documentation/security/IMA-export-delete.rst new file mode 100644 index 000000000000..1600ead03b03 --- /dev/null +++ b/Documentation/security/IMA-export-delete.rst @@ -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 ``: the user reads the current measurements + list and determines what the value N for staging should be; + 2. ``echo N > ``: 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. diff --git a/Documentation/security/index.rst b/Documentation/security/index.rst index 3e0a7114a862..00650dcf38cb 100644 --- a/Documentation/security/index.rst +++ b/Documentation/security/index.rst @@ -8,6 +8,7 @@ Security Documentation credentials snp-tdx-threat-model IMA-templates + IMA-export-delete keys/index lsm lsm-development diff --git a/MAINTAINERS b/MAINTAINERS index 63c2cb1740c8..d95d3ef77773 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -12830,6 +12830,8 @@ R: Eric Snowberg 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/ 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; - 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; } 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 = 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; 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 @@ 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; 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 @@ 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;