From 4403669abfc3ba23c88b73539166133f3288cae4 Mon Sep 17 00:00:00 2001
From: Sergio Lopez <slp@redhat.com>
Date: Wed, 3 Aug 2022 12:35:12 +0200
Subject: [PATCH 14/15] Implement driver to retrieve secrets from cmdline

When CMDLINE_OVERRIDE is enabled, the contents originally present in
the location passed on the cmdline pointer of the zero page are
overwritten by the built-in cmdline.

This drivers allows to reuse that space to store an early boot
secret (such as an SEV secret injection) and serve it to userspace
through an entry in securityfs, following the model used by
efi_secret.c

Signed-off-by: Sergio Lopez <slp@redhat.com>
---
 arch/x86/kernel/setup.c                       |   7 +
 drivers/virt/Kconfig                          |   2 +
 drivers/virt/Makefile                         |   1 +
 drivers/virt/coco/cmdline_secret/Kconfig      |  13 ++
 drivers/virt/coco/cmdline_secret/Makefile     |   2 +
 .../virt/coco/cmdline_secret/cmdline_secret.c | 135 ++++++++++++++++++
 include/linux/init.h                          |   4 +
 init/main.c                                   |  13 ++
 8 files changed, 177 insertions(+)
 create mode 100644 drivers/virt/coco/cmdline_secret/Kconfig
 create mode 100644 drivers/virt/coco/cmdline_secret/Makefile
 create mode 100644 drivers/virt/coco/cmdline_secret/cmdline_secret.c

diff --git a/arch/x86/kernel/setup.c b/arch/x86/kernel/setup.c
index 16babff771bd..752f34387bff 100644
--- a/arch/x86/kernel/setup.c
+++ b/arch/x86/kernel/setup.c
@@ -882,7 +882,9 @@ void __init setup_arch(char **cmdline_p)
 	 */
 	__flush_tlb_all();
 #else
+#ifndef CONFIG_CMDLINE_SECRET
 	printk(KERN_INFO "Command line: %s\n", boot_command_line);
+#endif
 	boot_cpu_data.x86_phys_bits = MAX_PHYSMEM_BITS;
 #endif
 
@@ -967,6 +969,11 @@ void __init setup_arch(char **cmdline_p)
 
 #ifdef CONFIG_CMDLINE_BOOL
 #ifdef CONFIG_CMDLINE_OVERRIDE
+#ifdef CONFIG_CMDLINE_SECRET
+	strscpy(early_secret_cmdline, boot_command_line, COMMAND_LINE_SIZE);
+	memzero_explicit(boot_command_line, COMMAND_LINE_SIZE);
+	clflush_cache_range(boot_command_line, COMMAND_LINE_SIZE);
+#endif
 	strscpy(boot_command_line, builtin_cmdline, COMMAND_LINE_SIZE);
 #else
 	if (builtin_cmdline[0]) {
diff --git a/drivers/virt/Kconfig b/drivers/virt/Kconfig
index f79ab13a5c28..4a62feec1a0e 100644
--- a/drivers/virt/Kconfig
+++ b/drivers/virt/Kconfig
@@ -50,6 +50,8 @@ source "drivers/virt/acrn/Kconfig"
 
 source "drivers/virt/coco/efi_secret/Kconfig"
 
+source "drivers/virt/coco/cmdline_secret/Kconfig"
+
 source "drivers/virt/coco/sev-guest/Kconfig"
 
 source "drivers/virt/coco/tdx-guest/Kconfig"
diff --git a/drivers/virt/Makefile b/drivers/virt/Makefile
index e9aa6fc96fab..9174d4f9286e 100644
--- a/drivers/virt/Makefile
+++ b/drivers/virt/Makefile
@@ -10,5 +10,6 @@ obj-y				+= vboxguest/
 obj-$(CONFIG_NITRO_ENCLAVES)	+= nitro_enclaves/
 obj-$(CONFIG_ACRN_HSM)		+= acrn/
 obj-$(CONFIG_EFI_SECRET)	+= coco/efi_secret/
+obj-$(CONFIG_CMDLINE_SECRET)	+= coco/cmdline_secret/
 obj-$(CONFIG_SEV_GUEST)		+= coco/sev-guest/
 obj-$(CONFIG_INTEL_TDX_GUEST)	+= coco/tdx-guest/
diff --git a/drivers/virt/coco/cmdline_secret/Kconfig b/drivers/virt/coco/cmdline_secret/Kconfig
new file mode 100644
index 000000000000..bb601280d50d
--- /dev/null
+++ b/drivers/virt/coco/cmdline_secret/Kconfig
@@ -0,0 +1,13 @@
+# SPDX-License-Identifier: GPL-2.0-only
+config CMDLINE_SECRET
+	tristate "CMDLINE secret area securityfs support"
+	depends on CMDLINE_OVERRIDE && X86_64
+	select SECURITYFS
+	help
+	  This is a driver for accessing the CMDLINE secret area via securityfs.
+	  The driver exposes the secrets as files in <securityfs>/secrets/coco.
+	  Files can be read and deleted (deleting a file wipes the secret from
+	  memory).
+
+	  To compile this driver as a module, choose M here.
+	  The module will be called cmdline_secret.
diff --git a/drivers/virt/coco/cmdline_secret/Makefile b/drivers/virt/coco/cmdline_secret/Makefile
new file mode 100644
index 000000000000..327a29d0b5bd
--- /dev/null
+++ b/drivers/virt/coco/cmdline_secret/Makefile
@@ -0,0 +1,2 @@
+# SPDX-License-Identifier: GPL-2.0-only
+obj-$(CONFIG_CMDLINE_SECRET) += cmdline_secret.o
diff --git a/drivers/virt/coco/cmdline_secret/cmdline_secret.c b/drivers/virt/coco/cmdline_secret/cmdline_secret.c
new file mode 100644
index 000000000000..e53edce18768
--- /dev/null
+++ b/drivers/virt/coco/cmdline_secret/cmdline_secret.c
@@ -0,0 +1,135 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * cmdline_secret module
+ *
+ * Based on efi_secrets.c
+ *
+ * Copyright (C) 2022 Red Hat Inc.
+ * Author: Sergio Lopez <slp@redhat.com>
+ */
+
+#include <linux/seq_file.h>
+#include <linux/fs.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/security.h>
+#include <asm/cacheflush.h>
+
+#define MAX_CMDLINE_SIZE 2048
+
+struct cmdline_secret {
+	struct dentry *secrets_dir;
+	struct dentry *fs_dir;
+	u64 secret_cmdline_len;
+};
+
+static struct cmdline_secret the_cmdline_secret;
+
+static inline struct cmdline_secret *cmdline_secret_get(void)
+{
+	return &the_cmdline_secret;
+}
+
+static int cmdline_secret_bin_file_show(struct seq_file *file, void *data)
+{
+	struct cmdline_secret *s = cmdline_secret_get();
+
+	seq_write(file, secret_cmdline, s->secret_cmdline_len);
+
+	return 0;
+}
+
+DEFINE_SHOW_ATTRIBUTE(cmdline_secret_bin_file);
+
+static int cmdline_secret_unlink(struct inode *dir, struct dentry *dentry)
+{
+	struct cmdline_secret *s = cmdline_secret_get();
+
+	memzero_explicit(secret_cmdline, s->secret_cmdline_len);
+#ifdef CONFIG_X86
+	clflush_cache_range(secret_cmdline, s->secret_cmdline_len);
+#endif
+
+	/*
+	 * securityfs_remove tries to lock the directory's inode, but we reach
+	 * the unlink callback when it's already locked
+	 */
+	inode_unlock(dir);
+	securityfs_remove(dentry);
+	inode_lock(dir);
+
+	return 0;
+}
+
+static const struct inode_operations efi_secret_dir_inode_operations = {
+	.lookup = simple_lookup,
+	.unlink = cmdline_secret_unlink,
+};
+
+static int __init cmdline_secret_init(void)
+{
+	struct cmdline_secret *s = cmdline_secret_get();
+	struct dentry *dent;
+	int ret;
+
+	s->secrets_dir = NULL;
+	s->fs_dir = NULL;
+
+	dent = securityfs_create_dir("secrets", NULL);
+	if (IS_ERR(dent)) {
+		printk
+		    ("Error creating secrets securityfs directory entry err=%ld\n",
+		     PTR_ERR(dent));
+		return PTR_ERR(dent);
+	}
+	s->secrets_dir = dent;
+
+	dent = securityfs_create_dir("coco", s->secrets_dir);
+	if (IS_ERR(dent)) {
+		printk
+		    ("Error creating coco securityfs directory entry err=%ld\n",
+		     PTR_ERR(dent));
+		ret = PTR_ERR(dent);
+		goto cleanup_dir;
+	}
+	d_inode(dent)->i_op = &efi_secret_dir_inode_operations;
+	s->fs_dir = dent;
+
+	dent = securityfs_create_file("cmdline", 0440, s->fs_dir, NULL,
+				      &cmdline_secret_bin_file_fops);
+	if (IS_ERR(dent)) {
+		printk("Error creating efi_secret securityfs entry\n");
+		ret = PTR_ERR(dent);
+		goto cleanup_all;
+	}
+
+	s->secret_cmdline_len = strnlen(secret_cmdline, MAX_CMDLINE_SIZE);
+
+	return 0;
+
+cleanup_all:
+	securityfs_remove(s->fs_dir);
+	s->fs_dir = NULL;
+cleanup_dir:
+	securityfs_remove(s->secrets_dir);
+	s->secrets_dir = NULL;
+
+	return ret;
+}
+
+static void __exit cmdline_secret_exit(void)
+{
+	struct cmdline_secret *s = cmdline_secret_get();
+
+	securityfs_remove(s->fs_dir);
+	s->fs_dir = NULL;
+
+	securityfs_remove(s->secrets_dir);
+	s->secrets_dir = NULL;
+}
+
+MODULE_DESCRIPTION("Confidential computing CMDLINE secret area access");
+MODULE_AUTHOR("Red Hat");
+MODULE_LICENSE("GPL");
+module_init(cmdline_secret_init);
+module_exit(cmdline_secret_exit);
diff --git a/include/linux/init.h b/include/linux/init.h
index c5fe6d26f5b1..a30efcccd958 100644
--- a/include/linux/init.h
+++ b/include/linux/init.h
@@ -146,6 +146,10 @@ extern int do_one_initcall(initcall_t fn);
 extern char __initdata boot_command_line[];
 extern char *saved_command_line;
 extern unsigned int saved_command_line_len;
+#if defined(CONFIG_CMDLINE_SECRET)
+extern char __initdata early_secret_cmdline[];
+extern char *secret_cmdline;
+#endif
 extern unsigned int reset_devices;
 
 /* used by init/main.c */
diff --git a/init/main.c b/init/main.c
index af50044deed5..750c7e23806a 100644
--- a/init/main.c
+++ b/init/main.c
@@ -153,6 +153,11 @@ static char *extra_command_line;
 /* Extra init arguments */
 static char *extra_init_args;
 
+#if defined(CONFIG_CMDLINE_SECRET)
+char __initdata early_secret_cmdline[COMMAND_LINE_SIZE];
+char *secret_cmdline;
+#endif
+
 #ifdef CONFIG_BOOT_CONFIG
 /* Is bootconfig on command line? */
 static bool bootconfig_found;
@@ -673,6 +678,14 @@ static void __init setup_command_line(char *command_line)
 	}
 
 	saved_command_line_len = strlen(saved_command_line);
+#ifdef CONFIG_CMDLINE_SECRET
+	secret_cmdline = memblock_alloc(COMMAND_LINE_SIZE, SMP_CACHE_BYTES);
+	strscpy(secret_cmdline, early_secret_cmdline, COMMAND_LINE_SIZE);
+	memzero_explicit(early_secret_cmdline, COMMAND_LINE_SIZE);
+#ifdef CONFIG_X86
+	clflush_cache_range(early_secret_cmdline, COMMAND_LINE_SIZE);
+#endif
+#endif
 }
 
 /*
-- 
2.40.0

