diff mbox series

initramfs: Protect the built-in initramfs from the external one

Message ID 20241203205318.238364-1-hristo@venev.name (mailing list archive)
State New
Headers show
Series initramfs: Protect the built-in initramfs from the external one | expand

Commit Message

Hristo Venev Dec. 3, 2024, 8:52 p.m. UTC
In a typical Secure Boot setup the kernel image is signed, but the
initramfs provided by the bootloader is not. This reduces the usefulness
of Secure Boot because an attacker can overwrite the initramfs without
detection.

With this change, when a built-in initramfs is used, the kernel can be
configured to extract the initramfs provided by the bootloader into a
subdirectory, ensuring it cannot overwrite the built-in one.

Userspace can implement a verification scheme. One simple approach is
to embed all executables in the built-in initramfs and use the external
one for the (signed) kernel modules necessary for the system to boot.

Signed-off-by: Hristo Venev <hristo@venev.name>
---
 init/initramfs.c | 38 +++++++++++++++++++++++++++++++++++++-
 usr/Kconfig      | 26 ++++++++++++++++++++++++++
 2 files changed, 63 insertions(+), 1 deletion(-)
diff mbox series

Patch

diff --git a/init/initramfs.c b/init/initramfs.c
index b2f7583bb1f5c..97eec8a6db07b 100644
--- a/init/initramfs.c
+++ b/init/initramfs.c
@@ -5,6 +5,7 @@ 
 #include <linux/slab.h>
 #include <linux/types.h>
 #include <linux/fcntl.h>
+#include <linux/fs_struct.h>
 #include <linux/delay.h>
 #include <linux/string.h>
 #include <linux/dirent.h>
@@ -355,6 +356,7 @@  static int __init maybe_link(void)
 
 static __initdata struct file *wfile;
 static __initdata loff_t wfile_pos;
+static bool skip_special __initdata;
 
 static int __init do_name(void)
 {
@@ -399,7 +401,7 @@  static int __init do_name(void)
 		dir_add(collected, mtime);
 	} else if (S_ISBLK(mode) || S_ISCHR(mode) ||
 		   S_ISFIFO(mode) || S_ISSOCK(mode)) {
-		if (maybe_link() == 0) {
+		if (!skip_special && maybe_link() == 0) {
 			init_mknod(collected, mode, rdev);
 			init_chown(collected, uid, gid, 0);
 			init_chmod(collected, mode);
@@ -705,6 +707,11 @@  static void __init populate_initrd_image(char *err)
 
 static void __init do_populate_rootfs(void *unused, async_cookie_t cookie)
 {
+#ifdef CONFIG_INITRAMFS_EXTERNAL_IS_SUBDIR
+	int r;
+	struct path orig_root, sub_root;
+#endif
+
 	/* Load the built in initramfs */
 	char *err = unpack_to_rootfs(__initramfs_start, __initramfs_size);
 	if (err)
@@ -718,6 +725,28 @@  static void __init do_populate_rootfs(void *unused, async_cookie_t cookie)
 	else
 		printk(KERN_INFO "Unpacking initramfs...\n");
 
+#ifdef CONFIG_INITRAMFS_EXTERNAL_IS_SUBDIR
+	/*
+	 * Switch the root so that the external initramfs is extracted there.
+	 * Use chroot so that paths under absolute symlinks resolve properly.
+	 */
+	get_fs_root(current->fs, &orig_root);
+
+	/*
+	 * Don't allow the creation of device nodes. Otherwise duplicate entries
+	 * may result in writes to devices.
+	 */
+	skip_special = true;
+
+	r = init_chdir(CONFIG_INITRAMFS_EXTERNAL_PATH);
+	if (r < 0)
+		panic_show_mem("Failed to open switch to external initramfs directory (%s): %d",
+			       CONFIG_INITRAMFS_EXTERNAL_PATH, r);
+	get_fs_pwd(current->fs, &sub_root);
+	set_fs_root(current->fs, &sub_root);
+	path_put(&sub_root);
+#endif
+
 	err = unpack_to_rootfs((char *)initrd_start, initrd_end - initrd_start);
 	if (err) {
 #ifdef CONFIG_BLK_DEV_RAM
@@ -727,6 +756,13 @@  static void __init do_populate_rootfs(void *unused, async_cookie_t cookie)
 #endif
 	}
 
+#ifdef CONFIG_INITRAMFS_EXTERNAL_IS_SUBDIR
+	/* Restore the original root now that the external initramfs is extracted. */
+	set_fs_root(current->fs, &orig_root);
+	set_fs_pwd(current->fs, &orig_root);
+	path_put(&orig_root);
+#endif
+
 done:
 	security_initramfs_populated();
 
diff --git a/usr/Kconfig b/usr/Kconfig
index 9279a2893ab0e..b781db0603903 100644
--- a/usr/Kconfig
+++ b/usr/Kconfig
@@ -32,6 +32,32 @@  config INITRAMFS_FORCE
 	  and is useful if you cannot or don't want to change the image
 	  your bootloader passes to the kernel.
 
+config INITRAMFS_EXTERNAL_PATH
+	string "External initramfs extraction path"
+	default "/"
+	depends on INITRAMFS_SOURCE!=""
+	depends on !INITRAMFS_FORCE
+	help
+	  This option causes the kernel to extract the initramfs image(s)
+	  provided by the bootloader into a subdirectory under the root
+	  directory. The subdirectory must exist in the built-in initramfs.
+
+	  This enables the built-in initramfs to check the integrity of the
+	  external one.
+
+	  If this option is used, any special nodes (device/fifo/socket) in the
+	  external initramfs are ignored. Symlinks, including ones pointing
+	  outside the subdirectory, are allowed.
+
+	  If your built-in initramfs is not capable of dealing with this, leave
+	  this option set to "/".
+
+config INITRAMFS_EXTERNAL_IS_SUBDIR
+	bool
+	default y
+	depends on INITRAMFS_EXTERNAL_PATH!=""
+	depends on INITRAMFS_EXTERNAL_PATH!="/"
+
 config INITRAMFS_ROOT_UID
 	int "User ID to map to 0 (user root)"
 	depends on INITRAMFS_SOURCE!=""