diff mbox series

[RFC,v2,7/7] ima: Support measurement of kexec initramfs components

Message ID 034c6e491e871e5902ca4d0af884adb07b37a39d.1659003817.git.noodles@fb.com (mailing list archive)
State New, archived
Headers show
Series [RFC,v2,1/7] initramfs: Move cpio handling routines into lib/ | expand

Commit Message

Jonathan McDowell July 28, 2022, 2:09 p.m. UTC
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@fb.com>
---
 security/integrity/ima/Kconfig    |  16 +++
 security/integrity/ima/ima_main.c | 191 ++++++++++++++++++++++++++++--
 2 files changed, 199 insertions(+), 8 deletions(-)
diff mbox series

Patch

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