An initramfs can be made up of multiple components that are concatenated together e.g. an early uncompressed cpio archive containing early firmware followed by a gziped cpio archive containing the actual userspace initramfs. Add a Kconfig option to allow the IMA subsystem to measure these components separately rather than as a single blob, allowing for easier reasoning about system state when checking TPM PCR values or the IMA integrity log. Signed-off-by: Jonathan McDowell <noodles@xxxxxx> --- security/integrity/ima/Kconfig | 16 +++ security/integrity/ima/ima_main.c | 191 ++++++++++++++++++++++++++++-- 2 files changed, 199 insertions(+), 8 deletions(-) diff --git a/security/integrity/ima/Kconfig b/security/integrity/ima/Kconfig index 7249f16257c7..b75da44a32f2 100644 --- a/security/integrity/ima/Kconfig +++ b/security/integrity/ima/Kconfig @@ -41,6 +41,22 @@ config IMA_KEXEC Depending on the IMA policy, the measurement list can grow to be very large. +config IMA_MEASURE_INITRAMFS_COMPONENTS + bool "Enable measurement of individual kexec initramfs components" + depends on IMA + select CPIO + default n + help + initramfs images can be made up of multiple separate components, + e.g. an early uncompressed cpio archive containing early firmware + followed by a gziped cpio archive containing the actual userspace + initramfs. More complex systems might involve a firmware archive, + a userspace archive and then a kernel module archive, allowing for + only the piece that needs changed to vary between boots. + + This option tells IMA to measure each individual component of the + initramfs separately, rather than as a single blob. + config IMA_MEASURE_PCR_IDX int depends on IMA diff --git a/security/integrity/ima/ima_main.c b/security/integrity/ima/ima_main.c index 040b03ddc1c7..be7f446df4f2 100644 --- a/security/integrity/ima/ima_main.c +++ b/security/integrity/ima/ima_main.c @@ -26,6 +26,8 @@ #include <linux/ima.h> #include <linux/iversion.h> #include <linux/fs.h> +#include <linux/cpio.h> +#include <linux/decompress/generic.h> #include "ima.h" @@ -198,6 +200,169 @@ void ima_file_free(struct file *file) ima_check_last_writer(iint, inode, file); } +#ifdef CONFIG_IMA_MEASURE_INITRAMFS_COMPONENTS +static void initrd_error(char *x) +{ + pr_err("measure initrd: error from decompressor: %s\n", x); +} + +static long initrd_flush(void *buf, unsigned long size) +{ + return size; +} + +static int process_initrd_measurement(struct integrity_iint_cache *iint, + struct file *file, char *buf, + loff_t size, const char *pathname, + struct modsig *modsig, int pcr, + struct evm_ima_xattr_data *xattr_value, + int xattr_len, + struct ima_template_desc *template_desc) +{ + struct cpio_context cpio_ctx; + const char *compress_name; + enum hash_algo hash_algo; + decompress_fn decompress; + long consumed, written; + char *start, *cur; + char *component; + int buf_len; + bool in_cpio; + int rc = 0; + int part; + + /* + * We collect this once, over the whole buffer. + */ + if (modsig) + ima_collect_modsig(modsig, buf, size); + + hash_algo = ima_get_hash_algo(xattr_value, xattr_len); + + /* + * Pathname, compression name, 2 : separators, single digit part + * and a trailing NUL. + */ + buf_len = strlen(pathname) + 5 + 2 + 2; + component = kmalloc(buf_len, GFP_KERNEL); + if (!component) + return -ENOMEM; + + memset(&cpio_ctx, 0, sizeof(cpio_ctx)); + cpio_ctx.parse_only = true; + rc = cpio_start(&cpio_ctx); + if (rc) + goto out; + in_cpio = false; + start = buf; + cur = buf; + part = 0; + + while (rc == 0 && size) { + loff_t saved_offset = cpio_ctx.this_header; + + /* It's a CPIO archive, process it */ + if (*buf == '0' && !(cpio_ctx.this_header & 3)) { + in_cpio = true; + cpio_ctx.state = CPIO_START; + written = cpio_write_buffer(&cpio_ctx, buf, size); + + if (written < 0) { + pr_err("Failed to process archive: %ld\n", + written); + break; + } + + buf += written; + size -= written; + continue; + } + if (!*buf) { + buf++; + size--; + cpio_ctx.this_header++; + continue; + } + + if (in_cpio) { + iint->flags &= ~(IMA_COLLECTED); + iint->measured_pcrs &= ~(0x1 << pcr); + rc = ima_collect_measurement(iint, file, cur, + buf - cur, hash_algo, + NULL); + if (rc == -ENOMEM) + return rc; + + snprintf(component, buf_len, "%s:%s:%d", + pathname, "cpio", part); + + ima_store_measurement(iint, file, component, + xattr_value, xattr_len, NULL, pcr, + template_desc); + part++; + + in_cpio = false; + } + + decompress = decompress_method(buf, size, &compress_name); + if (decompress) { + rc = decompress(buf, size, NULL, initrd_flush, NULL, + &consumed, initrd_error); + if (rc) { + pr_err("Failed to decompress archive\n"); + break; + } + } else if (compress_name) { + pr_info("Compression method %s not configured.\n", compress_name); + break; + } + + iint->flags &= ~(IMA_COLLECTED); + iint->measured_pcrs &= ~(0x1 << pcr); + rc = ima_collect_measurement(iint, file, buf, + consumed, hash_algo, NULL); + if (rc == -ENOMEM) + goto out; + + snprintf(component, buf_len, "%s:%s:%d", pathname, + compress_name, part); + + ima_store_measurement(iint, file, component, + xattr_value, xattr_len, NULL, pcr, + template_desc); + part++; + + cpio_ctx.this_header = saved_offset + consumed; + buf += consumed; + size -= consumed; + cur = buf; + } + cpio_finish(&cpio_ctx); + + /* Measure anything that remains */ + if (size != 0) { + iint->flags &= ~(IMA_COLLECTED); + iint->measured_pcrs &= ~(0x1 << pcr); + rc = ima_collect_measurement(iint, file, buf, size, hash_algo, + NULL); + if (rc == -ENOMEM) + goto out; + + snprintf(component, buf_len, "%s:left:%d", + pathname, + part); + + ima_store_measurement(iint, file, component, + xattr_value, xattr_len, NULL, pcr, + template_desc); + } + +out: + kfree(component); + return rc; +} +#endif + static int process_measurement(struct file *file, const struct cred *cred, u32 secid, char *buf, loff_t size, int mask, enum ima_hooks func) @@ -334,17 +499,27 @@ static int process_measurement(struct file *file, const struct cred *cred, hash_algo = ima_get_hash_algo(xattr_value, xattr_len); - rc = ima_collect_measurement(iint, file, buf, size, hash_algo, modsig); - if (rc == -ENOMEM) - goto out_locked; - if (!pathbuf) /* ima_rdwr_violation possibly pre-fetched */ pathname = ima_d_path(&file->f_path, &pathbuf, filename); - if (action & IMA_MEASURE) - ima_store_measurement(iint, file, pathname, - xattr_value, xattr_len, modsig, pcr, - template_desc); + if (IS_ENABLED(CONFIG_IMA_MEASURE_INITRAMFS_COMPONENTS) && + (action & IMA_MEASURE) && func == KEXEC_INITRAMFS_CHECK) { + rc = process_initrd_measurement(iint, file, buf, size, + pathname, modsig, pcr, + xattr_value, xattr_len, + template_desc); + } else { + rc = ima_collect_measurement(iint, file, buf, size, hash_algo, + modsig); + if (rc == -ENOMEM) + goto out_locked; + + if (action & IMA_MEASURE) + ima_store_measurement(iint, file, pathname, + xattr_value, xattr_len, modsig, + pcr, template_desc); + } + if (rc == 0 && (action & IMA_APPRAISE_SUBMASK)) { rc = ima_check_blacklist(iint, modsig, pcr); if (rc != -EPERM) { -- 2.36.1