From b51593c4cd739dff7fc40bbed368572d98b19ae8 Mon Sep 17 00:00:00 2001 From: Vivek Goyal Date: Fri, 17 Sep 2021 09:13:23 -0400 Subject: init/do_mounts.c: Harden split_fs_names() against buffer overflow split_fs_names() currently takes comma separate list of filesystems and converts it into individual filesystem strings. Pleaces these strings in the input buffer passed by caller and returns number of strings. If caller manages to pass input string bigger than buffer, then we can write beyond the buffer. Or if string just fits buffer, we will still write beyond the buffer as we append a '\0' byte at the end. Pass size of input buffer to split_fs_names() and put enough checks in place so such buffer overrun possibilities do not occur. This patch does few things. - Add a parameter "size" to split_fs_names(). This specifies size of input buffer. - Use strlcpy() (instead of strcpy()) so that we can't go beyond buffer size. If input string "names" is larger than passed in buffer, input string will be truncated to fit in buffer. - Stop appending extra '\0' character at the end and avoid one possibility of going beyond the input buffer size. - Do not use extra loop to count number of strings. - Previously if one passed "rootfstype=foo,,bar", split_fs_names() will return only 1 string "foo" (and "bar" will be truncated due to extra ,). After this patch, now split_fs_names() will return 3 strings ("foo", zero-sized-string, and "bar"). Callers of split_fs_names() have been modified to check for zero sized string and skip to next one. Reported-by: xu xin Signed-off-by: Vivek Goyal Reviewed-by: Jan Kara Signed-off-by: Al Viro --- init/do_mounts.c | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/init/do_mounts.c b/init/do_mounts.c index 2ed30ff6c906..9207bde9ca3f 100644 --- a/init/do_mounts.c +++ b/init/do_mounts.c @@ -338,20 +338,19 @@ __setup("rootflags=", root_data_setup); __setup("rootfstype=", fs_names_setup); __setup("rootdelay=", root_delay_setup); -static int __init split_fs_names(char *page, char *names) +/* This can return zero length strings. Caller should check */ +static int __init split_fs_names(char *page, size_t size, char *names) { - int count = 0; + int count = 1; char *p = page; - strcpy(p, root_fs_names); + strlcpy(p, root_fs_names, size); while (*p++) { - if (p[-1] == ',') + if (p[-1] == ',') { p[-1] = '\0'; + count++; + } } - *p = '\0'; - - for (p = page; *p; p += strlen(p)+1) - count++; return count; } @@ -404,12 +403,16 @@ void __init mount_block_root(char *name, int flags) scnprintf(b, BDEVNAME_SIZE, "unknown-block(%u,%u)", MAJOR(ROOT_DEV), MINOR(ROOT_DEV)); if (root_fs_names) - num_fs = split_fs_names(fs_names, root_fs_names); + num_fs = split_fs_names(fs_names, PAGE_SIZE, root_fs_names); else num_fs = list_bdev_fs_names(fs_names, PAGE_SIZE); retry: for (i = 0, p = fs_names; i < num_fs; i++, p += strlen(p)+1) { - int err = do_mount_root(name, p, flags, root_mount_data); + int err; + + if (!*p) + continue; + err = do_mount_root(name, p, flags, root_mount_data); switch (err) { case 0: goto out; @@ -543,10 +546,12 @@ static int __init mount_nodev_root(void) fs_names = (void *)__get_free_page(GFP_KERNEL); if (!fs_names) return -EINVAL; - num_fs = split_fs_names(fs_names, root_fs_names); + num_fs = split_fs_names(fs_names, PAGE_SIZE, root_fs_names); for (i = 0, fstype = fs_names; i < num_fs; i++, fstype += strlen(fstype) + 1) { + if (!*fstype) + continue; if (!fs_is_nodev(fstype)) continue; err = do_mount_root(root_device_name, fstype, root_mountflags, -- cgit v1.2.3 From 40c8ee67cfc49d00a13ccbf542e307b6b5421ad3 Mon Sep 17 00:00:00 2001 From: Leon Romanovsky Date: Tue, 14 Sep 2021 12:12:10 +0300 Subject: init: don't panic if mount_nodev_root failed Attempt to mount 9p file system as root gives the following kernel panic: 9pnet_virtio: no channels available for device root Kernel panic - not syncing: VFS: Unable to mount root "root" (9p), err=-2 CPU: 2 PID: 1 Comm: swapper/0 Not tainted 5.15.0-rc1+ #127 Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS rel-1.13.0-0-gf21b5a4aeb02-prebuilt.qemu.org 04/01/2014 Call Trace: dump_stack_lvl+0x45/0x59 panic+0x1e2/0x44b ? __warn_printk+0xf3/0xf3 ? free_unref_page+0x2d4/0x4a0 ? trace_hardirqs_on+0x32/0x120 ? free_unref_page+0x2d4/0x4a0 mount_root+0x189/0x1e0 prepare_namespace+0x136/0x165 kernel_init_freeable+0x3b8/0x3cb ? rest_init+0x2e0/0x2e0 kernel_init+0x19/0x130 ret_from_fork+0x1f/0x30 Kernel Offset: disabled ---[ end Kernel panic - not syncing: VFS: Unable to mount root "root" (9p), err=-2 ]--- QEMU command line: "qemu-system-x86_64 -append root=/dev/root rw rootfstype=9p rootflags=trans=virtio ..." This error is because root_device_name is truncated in prepare_namespace() from being "/dev/root" to be "root" prior to call to mount_nodev_root(). As a solution, don't treat errors in mount_nodev_root() as errors that require panics and allow failback to the mount flow that existed before patch citied in Fixes tag. Fixes: f9259be6a9e7 ("init: allow mounting arbitrary non-blockdevice filesystems as root") Signed-off-by: Leon Romanovsky Reviewed-by: Christoph Hellwig Signed-off-by: Al Viro --- init/do_mounts.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/init/do_mounts.c b/init/do_mounts.c index 9207bde9ca3f..762b534978d9 100644 --- a/init/do_mounts.c +++ b/init/do_mounts.c @@ -558,9 +558,6 @@ static int __init mount_nodev_root(void) root_mount_data); if (!err) break; - if (err != -EACCES && err != -EINVAL) - panic("VFS: Unable to mount root \"%s\" (%s), err=%d\n", - root_device_name, fstype, err); } free_page((unsigned long)fs_names); -- cgit v1.2.3