From fca4a51b9aac712b3adc5b6b187cc31a8391bcf6 Mon Sep 17 00:00:00 2001
From: Alberto Planas <aplanas@suse.com>
Date: Fri, 22 Mar 2024 22:44:41 +0100
Subject: [PATCH] GRUB: predict cmdline, linux and initrd lines

Signed-off-by: Alberto Planas <aplanas@suse.com>
---
 src/eventlog.c | 195 ++++++++++++++++++++++++++++++++++++++-----------
 src/eventlog.h |  26 ++++---
 2 files changed, 169 insertions(+), 52 deletions(-)

diff --git a/src/eventlog.c b/src/eventlog.c
index db18f41..727f6a9 100644
--- a/src/eventlog.c
+++ b/src/eventlog.c
@@ -549,14 +549,66 @@ tpm_event_decode_uuid(const unsigned char *data)
 	return uuid;
 }
 
+/*
+ * For files residing on the EFI partition, grub usually formats these as
+ * (hdX,gptY)/EFI/BOOT/some.file
+ * Once it has determined the final root device, the device part will be
+ * omitted (eg for kernel and initrd).
+ */
+static bool
+__grub_file_parse(grub_file_t *grub_file, const char *value)
+{
+	if (value[0] == '/') {
+		grub_file->device = NULL;
+		grub_file->path = strdup(value);
+	} else if (value[0] == '(') {
+		char *copy = strdup(value);
+		char *path;
+
+		if ((path = strchr(copy, ')')) == NULL) {
+			free(copy);
+			return false;
+		}
+
+		*path++ = '\0';
+
+		grub_file->device = strdup(copy + 1);
+		grub_file->path = strdup(path);
+		free(copy);
+	} else {
+		return false;
+	}
+
+	return true;
+}
+
+static const char *
+__grub_file_join(grub_file_t grub_file)
+{
+	static char path[PATH_MAX];
+
+	if (grub_file.device == NULL)
+		snprintf(path, sizeof(path), "%s", grub_file.path);
+	else
+		snprintf(path, sizeof(path), "(%s)%s", grub_file.device, grub_file.path);
+
+	return path;
+}
+
+static void
+__grub_file_destroy(grub_file_t *grub_file)
+{
+	drop_string(&grub_file->device);
+	drop_string(&grub_file->path);
+}
+
 /*
  * Handle IPL events, which grub2 and sd-boot uses to hide its stuff in
  */
 static void
 __tpm_event_grub_file_destroy(tpm_parsed_event_t *parsed)
 {
-	drop_string(&parsed->grub_file.device);
-	drop_string(&parsed->grub_file.path);
+	__grub_file_destroy(&parsed->grub_file);
 }
 
 const char *
@@ -564,10 +616,7 @@ __tpm_event_grub_file_describe(const tpm_parsed_event_t *parsed)
 {
 	static char buffer[1024];
 
-	if (parsed->grub_file.device == NULL)
-		snprintf(buffer, sizeof(buffer), "grub2 file load from %s", parsed->grub_file.path);
-	else
-		snprintf(buffer, sizeof(buffer), "grub2 file load from (%s)%s", parsed->grub_file.device, parsed->grub_file.path);
+	snprintf(buffer, sizeof(buffer), "grub2 file load from %s", __grub_file_join(parsed->grub_file));
 	return buffer;
 }
 
@@ -575,7 +624,7 @@ __tpm_event_grub_file_describe(const tpm_parsed_event_t *parsed)
 static const tpm_evdigest_t *
 __tpm_event_grub_file_rehash(const tpm_event_t *ev, const tpm_parsed_event_t *parsed, tpm_event_log_rehash_ctx_t *ctx)
 {
-	const struct grub_file_event *evspec = &parsed->grub_file;
+	const grub_file_event *evspec = &parsed->grub_file;
 	const tpm_evdigest_t *md = NULL;
 
 	debug("  re-hashing %s\n", __tpm_event_grub_file_describe(parsed));
@@ -606,35 +655,11 @@ __tpm_event_grub_file_rehash(const tpm_event_t *ev, const tpm_parsed_event_t *pa
 	return md;
 }
 
-/*
- * For files residing on the EFI partition, grub usually formats these as
- * (hdX,gptY)/EFI/BOOT/some.file
- * Once it has determined the final root device, the device part will be
- * omitted (eg for kernel and initrd).
- */
 static bool
 __tpm_event_grub_file_event_parse(tpm_event_t *ev, tpm_parsed_event_t *parsed, const char *value)
 {
-	if (value[0] == '/') {
-		parsed->grub_file.device = NULL;
-		parsed->grub_file.path = strdup(value);
-	} else if (value[0] == '(') {
-		char *copy = strdup(value);
-		char *path;
-
-		if ((path = strchr(copy, ')')) == NULL) {
-			free(copy);
-			return false;
-		}
-
-		*path++ = '\0';
-
-		parsed->grub_file.device = strdup(copy + 1);
-		parsed->grub_file.path = strdup(path);
-		free(copy);
-	} else {
+	if (!__grub_file_parse(&parsed->grub_file, value))
 		return false;
-	}
 
 	parsed->event_subtype = GRUB_EVENT_FILE;
 	parsed->destroy = __tpm_event_grub_file_destroy;
@@ -658,21 +683,87 @@ static const char *
 __tpm_event_grub_command_describe(const tpm_parsed_event_t *parsed)
 {
 	static char buffer[128];
+	static char *topic = NULL;
+
+	switch (parsed->event_subtype) {
+	case GRUB_EVENT_COMMAND:
+		topic = "grub2 command";
+		break;
+	case GRUB_EVENT_COMMAND_LINUX:
+		topic = "grub2 linux command";
+		break;
+	case GRUB_EVENT_COMMAND_INITRD:
+		topic = "grub2 initrd command";
+		break;
+	case GRUB_EVENT_KERNEL_CMDLINE:
+		topic = "grub2 kernel cmdline";
+		break;
+	}
+
+	snprintf(buffer, sizeof(buffer), "%s \"%s\"", topic, parsed->grub_command.string);
 
-	if (parsed->event_subtype == GRUB_EVENT_COMMAND)
-		snprintf(buffer, sizeof(buffer), "grub2 command \"%s\"", parsed->grub_command.string);
-	else
-		snprintf(buffer, sizeof(buffer), "grub2 kernel cmdline \"%s\"", parsed->grub_command.string);
 	return buffer;
 }
 
 static const tpm_evdigest_t *
 __tpm_event_grub_command_rehash(const tpm_event_t *ev, const tpm_parsed_event_t *parsed, tpm_event_log_rehash_ctx_t *ctx)
 {
-	if (parsed->grub_command.string == NULL)
-		return NULL;
+	char *str = NULL;
+	size_t sz = 0;
+	const tpm_evdigest_t *digest = NULL;
+	grub_file_t file;
+
+	switch (parsed->event_subtype) {
+	case GRUB_EVENT_COMMAND:
+		str = strdup(parsed->grub_command.string);
+		break;
+	case GRUB_EVENT_COMMAND_LINUX:
+		if (ctx->boot_entry && parsed->grub_command.file.path) {
+			file = (grub_file_t) {
+				.device = parsed->grub_command.file.device,
+				.path = ctx->boot_entry->image_path,
+			};
+			sz = snprintf(NULL, 0, "linux %s %s", __grub_file_join(file), ctx->boot_entry->options);
+			str = malloc(sz + 1);
+			snprintf(str, sz + 1, "linux %s %s", __grub_file_join(file), ctx->boot_entry->options);
+			debug("Hashed linux command: %s\n", str);
+		} else
+			str = strdup(parsed->grub_command.string);
+		break;
+	case GRUB_EVENT_COMMAND_INITRD:
+		if (ctx->boot_entry && parsed->grub_command.file.path) {
+			file = (grub_file_t) {
+				.device = parsed->grub_command.file.device,
+				.path = ctx->boot_entry->initrd_path,
+			};
+			sz = snprintf(NULL, 0, "initrd %s", __grub_file_join(file));
+			str = malloc(sz + 1);
+			snprintf(str, sz + 1, "initrd %s", __grub_file_join(file));
+			debug("Hashed initrd command: %s\n", str);
+		} else
+			str = strdup(parsed->grub_command.string);
+		break;
+	case GRUB_EVENT_KERNEL_CMDLINE:
+		if (ctx->boot_entry && parsed->grub_command.file.path) {
+			file = (grub_file_t) {
+				.device = parsed->grub_command.file.device,
+				.path = ctx->boot_entry->image_path,
+			};
+			sz = snprintf(NULL, 0, "%s %s", __grub_file_join(file), ctx->boot_entry->options);
+			str = malloc(sz + 1);
+			snprintf(str, sz + 1, "%s %s", __grub_file_join(file), ctx->boot_entry->options);
+			debug("Hashed kernel cmdline: %s\n", str);
+		} else
+			str = strdup(parsed->grub_command.string);
+		break;
+	}
+
+	if (str) {
+		digest = digest_compute(ctx->algo, str, strlen(str));
+		free(str);
+	}
 
-	return digest_compute(ctx->algo, parsed->grub_command.string, strlen(parsed->grub_command.string));
+	return digest;
 }
 
 /*
@@ -703,15 +794,29 @@ __tpm_event_grub_command_event_parse(tpm_event_t *ev, tpm_parsed_event_t *parsed
 	keyword = copy;
 	arg = copy + wordlen;
 
+	if (!strcmp(keyword, "grub_cmd") && !strncmp(arg, "linux", strlen("linux"))) {
+		for (wordlen = 0; (cc = arg[wordlen]) && (cc != ' '); ++wordlen)
+			;
+		if (arg[wordlen] == ' ' && !__grub_file_parse(&parsed->grub_command.file, arg + wordlen + 1))
+			goto failed;
+		parsed->event_subtype = GRUB_EVENT_COMMAND_LINUX;
+	} else
+	if (!strcmp(keyword, "grub_cmd") && !strncmp(arg, "initrd", strlen("initrd"))) {
+		for (wordlen = 0; (cc = arg[wordlen]) && (cc != ' '); ++wordlen)
+			;
+		if (arg[wordlen] == ' ' && !__grub_file_parse(&parsed->grub_command.file, arg + wordlen + 1))
+			goto failed;
+		parsed->event_subtype = GRUB_EVENT_COMMAND_INITRD;
+	} else
 	if (!strcmp(keyword, "grub_cmd")) {
 		parsed->event_subtype = GRUB_EVENT_COMMAND;
 	} else
 	if (!strcmp(keyword, "kernel_cmdline")) {
+		if (!__grub_file_parse(&parsed->grub_command.file, arg))
+			goto failed;
 		parsed->event_subtype = GRUB_EVENT_KERNEL_CMDLINE;
-	} else {
-		free(copy);
-		return false;
-	}
+	} else
+		goto failed;
 
 	parsed->grub_command.string = strdup(arg);
 	for (argc = 0, s = strtok(arg, " \t"); s && argc < GRUB_COMMAND_ARGV_MAX - 1; s = strtok(NULL, " \t")) {
@@ -725,6 +830,10 @@ __tpm_event_grub_command_event_parse(tpm_event_t *ev, tpm_parsed_event_t *parsed
 
 	free(copy);
 	return true;
+
+failed:
+	free(copy);
+	return false;
 }
 
 static void
diff --git a/src/eventlog.h b/src/eventlog.h
index d142744..6a8c3a4 100644
--- a/src/eventlog.h
+++ b/src/eventlog.h
@@ -89,10 +89,12 @@ enum {
 enum {
 	/* IPL subtypes for grub */
 	GRUB_EVENT_COMMAND		     = 0x0001,
-	GRUB_EVENT_FILE			     = 0x0002,
-	GRUB_EVENT_KERNEL_CMDLINE	     = 0x0003,
-	SHIM_EVENT_VARIABLE		     = 0x0004,
-	SYSTEMD_EVENT_VARIABLE		     = 0x0005,
+	GRUB_EVENT_COMMAND_LINUX	     = 0x0002,
+	GRUB_EVENT_COMMAND_INITRD	     = 0x0003,
+	GRUB_EVENT_FILE			     = 0x0004,
+	GRUB_EVENT_KERNEL_CMDLINE	     = 0x0005,
+	SHIM_EVENT_VARIABLE		     = 0x0006,
+	SYSTEMD_EVENT_VARIABLE		     = 0x0007,
 };
 
 enum {
@@ -208,6 +210,13 @@ typedef struct tpm_event_log_rehash_ctx {
 
 #define GRUB_COMMAND_ARGV_MAX	32
 
+typedef struct grub_file {
+	char *			device;
+	char *			path;
+} grub_file_t;
+
+typedef grub_file_t		grub_file_event;
+
 /*
  * Parsed event types
  */
@@ -247,13 +256,12 @@ typedef struct tpm_parsed_event {
 		/* for GRUB_COMMAND, GRUB_KERNEL_CMDLINE */
 		struct grub_command_event {
 			char *		string;
-			char *		argv[GRUB_COMMAND_ARGV_MAX];
+			char *          argv[GRUB_COMMAND_ARGV_MAX];
+			grub_file_t	file;
 		} grub_command;
 
-		struct grub_file_event {
-			char *		device;
-			char *		path;
-		} grub_file;
+		/* for GRUB_FILE */
+		grub_file_event		grub_file;
 
 		struct shim_event {
 			char *		string;