From 76db5a27a827c2c89e5120a3d486472da847863b Mon Sep 17 00:00:00 2001 From: Masami Hiramatsu Date: Sat, 11 Jan 2020 01:03:32 +0900 Subject: bootconfig: Add Extra Boot Config support Extra Boot Config (XBC) allows admin to pass a tree-structured boot configuration file when boot up the kernel. This extends the kernel command line in an efficient way. Boot config will contain some key-value commands, e.g. key.word = value1 another.key.word = value2 It can fold same keys with braces, also you can write array data. For example, key { word1 { setting1 = data setting2 } word2.array = "val1", "val2" } User can access these key-value pair and tree structure via SKC APIs. Link: http://lkml.kernel.org/r/157867221257.17873.1775090991929862549.stgit@devnote2 Signed-off-by: Masami Hiramatsu Signed-off-by: Steven Rostedt (VMware) --- init/Kconfig | 11 +++++++++++ 1 file changed, 11 insertions(+) (limited to 'init') diff --git a/init/Kconfig b/init/Kconfig index a34064a031a5..63450d3bbf12 100644 --- a/init/Kconfig +++ b/init/Kconfig @@ -1215,6 +1215,17 @@ source "usr/Kconfig" endif +config BOOT_CONFIG + bool "Boot config support" + select LIBXBC + default y + help + Extra boot config allows system admin to pass a config file as + complemental extension of kernel cmdline when booting. + The boot config file is usually attached at the end of initramfs. + + If unsure, say Y. + choice prompt "Compiler optimization level" default CC_OPTIMIZE_FOR_PERFORMANCE -- cgit From 7684b8582c24537dbe079a7d40e1d7e57ca56939 Mon Sep 17 00:00:00 2001 From: Masami Hiramatsu Date: Sat, 11 Jan 2020 01:03:44 +0900 Subject: bootconfig: Load boot config from the tail of initrd Load the extended boot config data from the tail of initrd image. If there is an SKC data there, it has [(u32)size][(u32)checksum] header (in really, this is a footer) at the end of initrd. If the checksum (simple sum of bytes) is match, this starts parsing it from there. Link: http://lkml.kernel.org/r/157867222435.17873.9936667353335606867.stgit@devnote2 Signed-off-by: Masami Hiramatsu Signed-off-by: Steven Rostedt (VMware) --- init/Kconfig | 1 + init/main.c | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+) (limited to 'init') diff --git a/init/Kconfig b/init/Kconfig index 63450d3bbf12..ffd240fb88c3 100644 --- a/init/Kconfig +++ b/init/Kconfig @@ -1217,6 +1217,7 @@ endif config BOOT_CONFIG bool "Boot config support" + depends on BLK_DEV_INITRD select LIBXBC default y help diff --git a/init/main.c b/init/main.c index 2cd736059416..59c418a57f92 100644 --- a/init/main.c +++ b/init/main.c @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -245,6 +246,58 @@ static int __init loglevel(char *str) early_param("loglevel", loglevel); +#ifdef CONFIG_BOOT_CONFIG +u32 boot_config_checksum(unsigned char *p, u32 size) +{ + u32 ret = 0; + + while (size--) + ret += *p++; + + return ret; +} + +static void __init setup_boot_config(void) +{ + u32 size, csum; + char *data, *copy; + u32 *hdr; + + if (!initrd_end) + return; + + hdr = (u32 *)(initrd_end - 8); + size = hdr[0]; + csum = hdr[1]; + + if (size >= XBC_DATA_MAX) + return; + + data = ((void *)hdr) - size; + if ((unsigned long)data < initrd_start) + return; + + if (boot_config_checksum((unsigned char *)data, size) != csum) + return; + + copy = memblock_alloc(size + 1, SMP_CACHE_BYTES); + if (!copy) { + pr_err("Failed to allocate memory for boot config\n"); + return; + } + + memcpy(copy, data, size); + copy[size] = '\0'; + + if (xbc_init(copy) < 0) + pr_err("Failed to parse boot config\n"); + else + pr_info("Load boot config: %d bytes\n", size); +} +#else +#define setup_boot_config() do { } while (0) +#endif + /* Change NUL term back to "=", to make "param" the whole string. */ static int __init repair_env_string(char *param, char *val, const char *unused, void *arg) @@ -595,6 +648,7 @@ asmlinkage __visible void __init start_kernel(void) pr_notice("%s", linux_banner); early_security_init(); setup_arch(&command_line); + setup_boot_config(); setup_command_line(command_line); setup_nr_cpu_ids(); setup_per_cpu_areas(); -- cgit From 0068c92a92707789b8711e40d584a2433481a29d Mon Sep 17 00:00:00 2001 From: Masami Hiramatsu Date: Sat, 11 Jan 2020 01:04:31 +0900 Subject: init/main.c: Alloc initcall_command_line in do_initcall() and free it Since initcall_command_line is used as a temporary buffer, it could be freed after usage. Allocate it in do_initcall() and free it after used. Link: http://lkml.kernel.org/r/157867227145.17873.17513760552008505454.stgit@devnote2 Signed-off-by: Masami Hiramatsu Signed-off-by: Steven Rostedt (VMware) --- init/main.c | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) (limited to 'init') diff --git a/init/main.c b/init/main.c index 59c418a57f92..0b4e0c8ccf16 100644 --- a/init/main.c +++ b/init/main.c @@ -137,8 +137,6 @@ char __initdata boot_command_line[COMMAND_LINE_SIZE]; char *saved_command_line; /* Command line for parameter parsing */ static char *static_command_line; -/* Command line for per-initcall parameter parsing */ -static char *initcall_command_line; static char *execute_command; static char *ramdisk_execute_command; @@ -433,10 +431,6 @@ static void __init setup_command_line(char *command_line) if (!saved_command_line) panic("%s: Failed to allocate %zu bytes\n", __func__, len); - initcall_command_line = memblock_alloc(len, SMP_CACHE_BYTES); - if (!initcall_command_line) - panic("%s: Failed to allocate %zu bytes\n", __func__, len); - static_command_line = memblock_alloc(len, SMP_CACHE_BYTES); if (!static_command_line) panic("%s: Failed to allocate %zu bytes\n", __func__, len); @@ -1044,13 +1038,12 @@ static const char *initcall_level_names[] __initdata = { "late", }; -static void __init do_initcall_level(int level) +static void __init do_initcall_level(int level, char *command_line) { initcall_entry_t *fn; - strcpy(initcall_command_line, saved_command_line); parse_args(initcall_level_names[level], - initcall_command_line, __start___param, + command_line, __start___param, __stop___param - __start___param, level, level, NULL, &repair_env_string); @@ -1063,9 +1056,20 @@ static void __init do_initcall_level(int level) static void __init do_initcalls(void) { int level; + size_t len = strlen(saved_command_line) + 1; + char *command_line; + + command_line = kzalloc(len, GFP_KERNEL); + if (!command_line) + panic("%s: Failed to allocate %zu bytes\n", __func__, len); + + for (level = 0; level < ARRAY_SIZE(initcall_levels) - 1; level++) { + /* Parser modifies command_line, restore it each time */ + strcpy(command_line, saved_command_line); + do_initcall_level(level, command_line); + } - for (level = 0; level < ARRAY_SIZE(initcall_levels) - 1; level++) - do_initcall_level(level); + kfree(command_line); } /* -- cgit From 51887d03aca101a24ab049179d1ab430464a24e6 Mon Sep 17 00:00:00 2001 From: Masami Hiramatsu Date: Sat, 11 Jan 2020 01:04:43 +0900 Subject: bootconfig: init: Allow admin to use bootconfig for kernel command line Since the current kernel command line is too short to describe many options which supported by kernel, allow user to use boot config to setup (add) the command line options. All kernel parameters under "kernel." keywords will be used for setting up extra kernel command line. For example, kernel { audit = on audit_backlog_limit = 256 } Note that you can not specify some early parameters (like console etc.) by this method, since it is loaded after early parameters parsed. Link: http://lkml.kernel.org/r/157867228333.17873.11962796367032622466.stgit@devnote2 Signed-off-by: Masami Hiramatsu Signed-off-by: Steven Rostedt (VMware) --- init/main.c | 106 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 101 insertions(+), 5 deletions(-) (limited to 'init') diff --git a/init/main.c b/init/main.c index 0b4e0c8ccf16..c0017d9d16e7 100644 --- a/init/main.c +++ b/init/main.c @@ -137,6 +137,8 @@ char __initdata boot_command_line[COMMAND_LINE_SIZE]; char *saved_command_line; /* Command line for parameter parsing */ static char *static_command_line; +/* Untouched extra command line */ +static char *extra_command_line; static char *execute_command; static char *ramdisk_execute_command; @@ -245,6 +247,83 @@ static int __init loglevel(char *str) early_param("loglevel", loglevel); #ifdef CONFIG_BOOT_CONFIG + +char xbc_namebuf[XBC_KEYLEN_MAX] __initdata; + +#define rest(dst, end) ((end) > (dst) ? (end) - (dst) : 0) + +static int __init xbc_snprint_cmdline(char *buf, size_t size, + struct xbc_node *root) +{ + struct xbc_node *knode, *vnode; + char *end = buf + size; + char c = '\"'; + const char *val; + int ret; + + xbc_node_for_each_key_value(root, knode, val) { + ret = xbc_node_compose_key_after(root, knode, + xbc_namebuf, XBC_KEYLEN_MAX); + if (ret < 0) + return ret; + + vnode = xbc_node_get_child(knode); + ret = snprintf(buf, rest(buf, end), "%s%c", xbc_namebuf, + vnode ? '=' : ' '); + if (ret < 0) + return ret; + buf += ret; + if (!vnode) + continue; + + c = '\"'; + xbc_array_for_each_value(vnode, val) { + ret = snprintf(buf, rest(buf, end), "%c%s", c, val); + if (ret < 0) + return ret; + buf += ret; + c = ','; + } + if (rest(buf, end) > 2) + strcpy(buf, "\" "); + buf += 2; + } + + return buf - (end - size); +} +#undef rest + +/* Make an extra command line under given key word */ +static char * __init xbc_make_cmdline(const char *key) +{ + struct xbc_node *root; + char *new_cmdline; + int ret, len = 0; + + root = xbc_find_node(key); + if (!root) + return NULL; + + /* Count required buffer size */ + len = xbc_snprint_cmdline(NULL, 0, root); + if (len <= 0) + return NULL; + + new_cmdline = memblock_alloc(len + 1, SMP_CACHE_BYTES); + if (!new_cmdline) { + pr_err("Failed to allocate memory for extra kernel cmdline.\n"); + return NULL; + } + + ret = xbc_snprint_cmdline(new_cmdline, len + 1, root); + if (ret < 0 || ret > len) { + pr_err("Failed to print extra kernel cmdline.\n"); + return NULL; + } + + return new_cmdline; +} + u32 boot_config_checksum(unsigned char *p, u32 size) { u32 ret = 0; @@ -289,8 +368,11 @@ static void __init setup_boot_config(void) if (xbc_init(copy) < 0) pr_err("Failed to parse boot config\n"); - else + else { pr_info("Load boot config: %d bytes\n", size); + /* keys starting with "kernel." are passed via cmdline */ + extra_command_line = xbc_make_cmdline("kernel"); + } } #else #define setup_boot_config() do { } while (0) @@ -425,7 +507,12 @@ static inline void smp_prepare_cpus(unsigned int maxcpus) { } */ static void __init setup_command_line(char *command_line) { - size_t len = strlen(boot_command_line) + 1; + size_t len, xlen = 0; + + if (extra_command_line) + xlen = strlen(extra_command_line); + + len = xlen + strlen(boot_command_line) + 1; saved_command_line = memblock_alloc(len, SMP_CACHE_BYTES); if (!saved_command_line) @@ -435,8 +522,17 @@ static void __init setup_command_line(char *command_line) if (!static_command_line) panic("%s: Failed to allocate %zu bytes\n", __func__, len); - strcpy(saved_command_line, boot_command_line); - strcpy(static_command_line, command_line); + if (xlen) { + /* + * We have to put extra_command_line before boot command + * lines because there could be dashes (separator of init + * command line) in the command lines. + */ + strcpy(saved_command_line, extra_command_line); + strcpy(static_command_line, extra_command_line); + } + strcpy(saved_command_line + xlen, boot_command_line); + strcpy(static_command_line + xlen, command_line); } /* @@ -652,7 +748,7 @@ asmlinkage __visible void __init start_kernel(void) build_all_zonelists(NULL); page_alloc_init(); - pr_notice("Kernel command line: %s\n", boot_command_line); + pr_notice("Kernel command line: %s\n", saved_command_line); /* parameters may set static keys */ jump_label_init(); parse_early_param(); -- cgit From 1319916209ce8f55a4f4f848e74500633e24bb99 Mon Sep 17 00:00:00 2001 From: Masami Hiramatsu Date: Sat, 11 Jan 2020 01:04:55 +0900 Subject: bootconfig: init: Allow admin to use bootconfig for init command line Since the current kernel command line is too short to describe long and many options for init (e.g. systemd command line options), this allows admin to use boot config for init command line. All init command line under "init." keywords will be passed to init. For example, init.systemd { unified_cgroup_hierarchy = 1 debug_shell default_timeout_start_sec = 60 } Link: http://lkml.kernel.org/r/157867229521.17873.654222294326542349.stgit@devnote2 Signed-off-by: Masami Hiramatsu Signed-off-by: Steven Rostedt (VMware) --- init/main.c | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) (limited to 'init') diff --git a/init/main.c b/init/main.c index c0017d9d16e7..dd7da62d99a5 100644 --- a/init/main.c +++ b/init/main.c @@ -139,6 +139,8 @@ char *saved_command_line; static char *static_command_line; /* Untouched extra command line */ static char *extra_command_line; +/* Extra init arguments */ +static char *extra_init_args; static char *execute_command; static char *ramdisk_execute_command; @@ -372,6 +374,8 @@ static void __init setup_boot_config(void) pr_info("Load boot config: %d bytes\n", size); /* keys starting with "kernel." are passed via cmdline */ extra_command_line = xbc_make_cmdline("kernel"); + /* Also, "init." keys are init arguments */ + extra_init_args = xbc_make_cmdline("init"); } } #else @@ -507,16 +511,18 @@ static inline void smp_prepare_cpus(unsigned int maxcpus) { } */ static void __init setup_command_line(char *command_line) { - size_t len, xlen = 0; + size_t len, xlen = 0, ilen = 0; if (extra_command_line) xlen = strlen(extra_command_line); + if (extra_init_args) + ilen = strlen(extra_init_args) + 4; /* for " -- " */ len = xlen + strlen(boot_command_line) + 1; - saved_command_line = memblock_alloc(len, SMP_CACHE_BYTES); + saved_command_line = memblock_alloc(len + ilen, SMP_CACHE_BYTES); if (!saved_command_line) - panic("%s: Failed to allocate %zu bytes\n", __func__, len); + panic("%s: Failed to allocate %zu bytes\n", __func__, len + ilen); static_command_line = memblock_alloc(len, SMP_CACHE_BYTES); if (!static_command_line) @@ -533,6 +539,22 @@ static void __init setup_command_line(char *command_line) } strcpy(saved_command_line + xlen, boot_command_line); strcpy(static_command_line + xlen, command_line); + + if (ilen) { + /* + * Append supplemental init boot args to saved_command_line + * so that user can check what command line options passed + * to init. + */ + len = strlen(saved_command_line); + if (!strstr(boot_command_line, " -- ")) { + strcpy(saved_command_line + len, " -- "); + len += 4; + } else + saved_command_line[len++] = ' '; + + strcpy(saved_command_line + len, extra_init_args); + } } /* @@ -759,6 +781,9 @@ asmlinkage __visible void __init start_kernel(void) if (!IS_ERR_OR_NULL(after_dashes)) parse_args("Setting init args", after_dashes, NULL, 0, -1, -1, NULL, set_init_arg); + if (extra_init_args) + parse_args("Setting extra init args", extra_init_args, + NULL, 0, -1, -1, NULL, set_init_arg); /* * These use large bootmem allocations and must precede -- cgit From 0947db01d9b9fd77a627e501fe563bdaf4c724a1 Mon Sep 17 00:00:00 2001 From: Masami Hiramatsu Date: Mon, 20 Jan 2020 12:23:00 +0900 Subject: bootconfig: Fix Kconfig help message for BOOT_CONFIG Fix Kconfig help message since the bootconfig file is only available to be appended to initramfs. And also add a reference to the documentation. Link: http://lkml.kernel.org/r/157949058031.25888.18399447161895787505.stgit@devnote2 Reported-by: Randy Dunlap Acked-by: Randy Dunlap Signed-off-by: Masami Hiramatsu Signed-off-by: Steven Rostedt (VMware) --- init/Kconfig | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'init') diff --git a/init/Kconfig b/init/Kconfig index ffd240fb88c3..9506299a53e3 100644 --- a/init/Kconfig +++ b/init/Kconfig @@ -1223,7 +1223,9 @@ config BOOT_CONFIG help Extra boot config allows system admin to pass a config file as complemental extension of kernel cmdline when booting. - The boot config file is usually attached at the end of initramfs. + The boot config file must be attached at the end of initramfs + with checksum and size. + See for details. If unsure, say Y. -- cgit From 7495e0926fdf302cb9e62a49f7c22198815624cd Mon Sep 17 00:00:00 2001 From: "Steven Rostedt (VMware)" Date: Tue, 4 Feb 2020 07:33:53 -0500 Subject: bootconfig: Only load bootconfig if "bootconfig" is on the kernel cmdline As the bootconfig is appended to the initrd it is not as easy to modify as the kernel command line. If there's some issue with the kernel, and the developer wants to boot a pristine kernel, it should not be needed to modify the initrd to remove the bootconfig for a single boot. As bootconfig is silently added (if the admin does not know where to look they may not know it's being loaded). It should be explicitly added to the kernel cmdline. The loading of the bootconfig is only done if "bootconfig" is on the kernel command line. This will let admins know that the kernel command line is extended. Note, after adding printk()s for when the size is too great or the checksum is wrong, exposed that the current method always looked for the boot config, and if this size and checksum matched, it would parse it (as if either is wrong a printk has been added to show this). It's better to only check this if the boot config is asked to be looked for. Link: https://lore.kernel.org/r/CAHk-=wjfjO+h6bQzrTf=YCZA53Y3EDyAs3Z4gEsT7icA3u_Psw@mail.gmail.com Acked-by: Masami Hiramatsu Suggested-by: Linus Torvalds Signed-off-by: Steven Rostedt (VMware) --- init/main.c | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) (limited to 'init') diff --git a/init/main.c b/init/main.c index dd7da62d99a5..f174a59d3903 100644 --- a/init/main.c +++ b/init/main.c @@ -336,28 +336,39 @@ u32 boot_config_checksum(unsigned char *p, u32 size) return ret; } -static void __init setup_boot_config(void) +static void __init setup_boot_config(const char *cmdline) { u32 size, csum; char *data, *copy; + const char *p; u32 *hdr; - if (!initrd_end) + p = strstr(cmdline, "bootconfig"); + if (!p || (p != cmdline && !isspace(*(p-1))) || + (p[10] && !isspace(p[10]))) return; + if (!initrd_end) + goto not_found; + hdr = (u32 *)(initrd_end - 8); size = hdr[0]; csum = hdr[1]; - if (size >= XBC_DATA_MAX) + if (size >= XBC_DATA_MAX) { + pr_err("bootconfig size %d greater than max size %d\n", + size, XBC_DATA_MAX); return; + } data = ((void *)hdr) - size; if ((unsigned long)data < initrd_start) - return; + goto not_found; - if (boot_config_checksum((unsigned char *)data, size) != csum) + if (boot_config_checksum((unsigned char *)data, size) != csum) { + pr_err("bootconfig checksum failed\n"); return; + } copy = memblock_alloc(size + 1, SMP_CACHE_BYTES); if (!copy) { @@ -377,9 +388,12 @@ static void __init setup_boot_config(void) /* Also, "init." keys are init arguments */ extra_init_args = xbc_make_cmdline("init"); } + return; +not_found: + pr_err("'bootconfig' found on command line, but no bootconfig found\n"); } #else -#define setup_boot_config() do { } while (0) +#define setup_boot_config(cmdline) do { } while (0) #endif /* Change NUL term back to "=", to make "param" the whole string. */ @@ -760,7 +774,7 @@ asmlinkage __visible void __init start_kernel(void) pr_notice("%s", linux_banner); early_security_init(); setup_arch(&command_line); - setup_boot_config(); + setup_boot_config(command_line); setup_command_line(command_line); setup_nr_cpu_ids(); setup_per_cpu_areas(); -- cgit From e241d14a82910ffa9060d81864760c93b7256195 Mon Sep 17 00:00:00 2001 From: Masami Hiramatsu Date: Wed, 5 Feb 2020 22:49:54 +0900 Subject: bootconfig: Use bootconfig instead of boot config Use "bootconfig" (1 word) instead of "boot config" (2 words) in the boot message. Link: http://lkml.kernel.org/r/158091059459.27924.14414336187441539879.stgit@devnote2 Signed-off-by: Masami Hiramatsu Signed-off-by: Steven Rostedt (VMware) --- init/main.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'init') diff --git a/init/main.c b/init/main.c index f174a59d3903..2de2f9f7aab9 100644 --- a/init/main.c +++ b/init/main.c @@ -372,7 +372,7 @@ static void __init setup_boot_config(const char *cmdline) copy = memblock_alloc(size + 1, SMP_CACHE_BYTES); if (!copy) { - pr_err("Failed to allocate memory for boot config\n"); + pr_err("Failed to allocate memory for bootconfig\n"); return; } @@ -380,9 +380,9 @@ static void __init setup_boot_config(const char *cmdline) copy[size] = '\0'; if (xbc_init(copy) < 0) - pr_err("Failed to parse boot config\n"); + pr_err("Failed to parse bootconfig\n"); else { - pr_info("Load boot config: %d bytes\n", size); + pr_info("Load bootconfig: %d bytes\n", size); /* keys starting with "kernel." are passed via cmdline */ extra_command_line = xbc_make_cmdline("kernel"); /* Also, "init." keys are init arguments */ -- cgit From a00574036c261421721fa770ccd21a1012e1fbbd Mon Sep 17 00:00:00 2001 From: Masami Hiramatsu Date: Wed, 5 Feb 2020 22:50:23 +0900 Subject: bootconfig: Show the number of nodes on boot message Show the number of bootconfig nodes on boot message. Link: http://lkml.kernel.org/r/158091062297.27924.9051634676068550285.stgit@devnote2 Signed-off-by: Masami Hiramatsu Signed-off-by: Steven Rostedt (VMware) --- init/main.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'init') diff --git a/init/main.c b/init/main.c index 2de2f9f7aab9..491f1cdb3105 100644 --- a/init/main.c +++ b/init/main.c @@ -342,6 +342,7 @@ static void __init setup_boot_config(const char *cmdline) char *data, *copy; const char *p; u32 *hdr; + int ret; p = strstr(cmdline, "bootconfig"); if (!p || (p != cmdline && !isspace(*(p-1))) || @@ -379,10 +380,11 @@ static void __init setup_boot_config(const char *cmdline) memcpy(copy, data, size); copy[size] = '\0'; - if (xbc_init(copy) < 0) + ret = xbc_init(copy); + if (ret < 0) pr_err("Failed to parse bootconfig\n"); else { - pr_info("Load bootconfig: %d bytes\n", size); + pr_info("Load bootconfig: %d bytes %d nodes\n", size, ret); /* keys starting with "kernel." are passed via cmdline */ extra_command_line = xbc_make_cmdline("kernel"); /* Also, "init." keys are init arguments */ -- cgit