diff --git a/include/linux/bootconfig.h b/include/linux/bootconfig.h index deda507500da..260a6f46042e 100644 --- a/include/linux/bootconfig.h +++ b/include/linux/bootconfig.h @@ -27,6 +27,10 @@ bool __init cmdline_has_extra_options(void); #define BOOTCONFIG_ALIGN (1 << BOOTCONFIG_ALIGN_SHIFT) #define BOOTCONFIG_ALIGN_MASK (BOOTCONFIG_ALIGN - 1) +/* Bootconfig footer is [size][csum][BOOTCONFIG_MAGIC]. */ +#define BOOTCONFIG_FOOTER_SIZE \ + (sizeof(uint32_t) * 2 + BOOTCONFIG_MAGIC_LEN) + /** * xbc_calc_checksum() - Calculate checksum of bootconfig * @data: Bootconfig data. @@ -266,7 +270,15 @@ int __init xbc_node_compose_key_after(struct xbc_node *root, struct xbc_node *node, char *buf, size_t size); /* Render key/value pairs under @root as a flat cmdline string */ -int __init xbc_snprint_cmdline(char *buf, size_t size, struct xbc_node *root); +typedef bool (*xbc_cmdline_filter_fn)(struct xbc_node *node, const char *key, void *data); + +int __init xbc_snprint_cmdline_filter(char *buf, size_t size, struct xbc_node *root, + xbc_cmdline_filter_fn filter, void *data); + +static inline int __init xbc_snprint_cmdline(char *buf, size_t size, struct xbc_node *root) +{ + return xbc_snprint_cmdline_filter(buf, size, root, NULL, NULL); +} /** * xbc_node_compose_key() - Compose full key string of the XBC node @@ -291,12 +303,7 @@ int __init xbc_init(const char *buf, size_t size, const char **emsg, int *epos); int __init xbc_get_info(int *node_size, size_t *data_size); /* XBC cleanup data structures */ -void __init _xbc_exit(bool early); - -static __always_inline void xbc_exit(void) -{ - _xbc_exit(false); -} +void __init xbc_exit(void); /* XBC embedded bootconfig data in kernel */ #ifdef CONFIG_BOOT_CONFIG_EMBED diff --git a/init/main.c b/init/main.c index 31f2bf54976a..27f4cb392349 100644 --- a/init/main.c +++ b/init/main.c @@ -278,7 +278,7 @@ static void * __init get_boot_config_from_initrd(size_t *_size) int i; if (!initrd_end || initrd_end < initrd_start || - initrd_end - initrd_start < BOOTCONFIG_MAGIC_LEN + 8) + initrd_end - initrd_start < BOOTCONFIG_FOOTER_SIZE) return NULL; data = (char *)initrd_end - BOOTCONFIG_MAGIC_LEN; @@ -336,9 +336,64 @@ static void * __init get_boot_config_from_initrd(size_t *_size) #ifdef CONFIG_BOOT_CONFIG -/* Make an extra command line under given key word */ -static char * __init xbc_make_cmdline(const char *key) +/* Check if a parameter name matches an obs_kernel_param entry */ +static bool __init match_obs_param(const char *param, const char *pattern) { + size_t n = strlen(pattern); + + if (n > 0 && pattern[n - 1] == '=') + return parameqn(param, pattern, n - 1) && param[n - 1] == '\0'; + + return parameq(param, pattern); +} + +/* Return true if the given param is only defined by early_param(). */ +static bool __init is_early_only_param(const char *param) +{ + const struct obs_kernel_param *p; + const struct kernel_param *kp; + bool has_early = false; + + /* If handled by a regular module_param, it is not early-only */ + for (kp = __start___param; kp < __stop___param; kp++) { + if (parameq(param, kp->name)) + return false; + } + + /* Check __setup / early_param table */ + for (p = __setup_start; p < __setup_end; p++) { + if (match_obs_param(param, p->str)) { + if (!p->early) + return false; + has_early = true; + } + } + return has_early; +} + +struct xbc_filter_data { + bool warn; +}; + +static bool __init xbc_kernel_param_filter(struct xbc_node *node, + const char *key, void *data) +{ + struct xbc_filter_data *fdata = data; + + if (is_early_only_param(key)) { + if (fdata && fdata->warn) + pr_warn("bootconfig: early_param 'kernel.%s' cannot be applied from bootconfig, skipping\n", + key); + return false; + } + return true; +} + +/* Make an extra command line under given key word */ +static char * __init xbc_make_cmdline(const char *key, bool is_kernel) +{ + struct xbc_filter_data fdata = { .warn = true }; + xbc_cmdline_filter_fn filter = is_kernel ? xbc_kernel_param_filter : NULL; struct xbc_node *root; char *new_cmdline; int ret, len = 0; @@ -347,8 +402,12 @@ static char * __init xbc_make_cmdline(const char *key) if (!root) return NULL; - /* Count required buffer size */ - len = xbc_snprint_cmdline(NULL, 0, root); + /* + * Pass 1: Count required buffer size. Emit warnings for skipped early + * params in this pass so they are logged even if all parameters under + * @key are skipped and len becomes 0. + */ + len = xbc_snprint_cmdline_filter(NULL, 0, root, filter, &fdata); if (len <= 0) return NULL; @@ -358,7 +417,9 @@ static char * __init xbc_make_cmdline(const char *key) return NULL; } - ret = xbc_snprint_cmdline(new_cmdline, len + 1, root); + /* Pass 2: Render into buffer with warnings suppressed */ + fdata.warn = false; + ret = xbc_snprint_cmdline_filter(new_cmdline, len + 1, root, filter, &fdata); if (ret < 0 || ret > len) { pr_err("Failed to print extra kernel cmdline.\n"); memblock_free(new_cmdline, len + 1); @@ -432,9 +493,9 @@ static void __init setup_boot_config(void) * before this series. */ if (!from_embedded || !xbc_embedded_cmdline_applied()) - extra_command_line = xbc_make_cmdline("kernel"); + extra_command_line = xbc_make_cmdline("kernel", true); /* Also, "init." keys are init arguments */ - extra_init_args = xbc_make_cmdline("init"); + extra_init_args = xbc_make_cmdline("init", false); } return; } diff --git a/lib/bootconfig.c b/lib/bootconfig.c index 89c88e359179..819685d6c68d 100644 --- a/lib/bootconfig.c +++ b/lib/bootconfig.c @@ -187,12 +187,9 @@ static inline void * __init xbc_alloc_mem(size_t size) return memblock_alloc(size, SMP_CACHE_BYTES); } -static inline void __init xbc_free_mem(void *addr, size_t size, bool early) +static inline void __init xbc_free_mem(void *addr, size_t size) { - if (early) - memblock_free(addr, size); - else if (addr) - memblock_free(addr, size); + memblock_free(addr, size); } #else /* !__KERNEL__ */ @@ -202,7 +199,7 @@ static inline void *xbc_alloc_mem(size_t size) return calloc(1, size); } -static inline void xbc_free_mem(void *addr, size_t size, bool early) +static inline void xbc_free_mem(void *addr, size_t size) { free(addr); } @@ -539,18 +536,21 @@ static char xbc_namebuf[XBC_KEYLEN_MAX] __initdata; #define rest(dst, end) ((end) > (dst) ? (end) - (dst) : 0) /** - * xbc_snprint_cmdline() - Render bootconfig keys under @root as a cmdline string + * xbc_snprint_cmdline_filter() - Render bootconfig keys as cmdline string * @buf: Destination buffer (may be NULL when @size is 0 to query the length) * @size: Size of @buf in bytes * @root: Subtree root whose key=value pairs should be rendered + * @filter: Filter callback (returns true to include, false to skip; may be NULL) + * @data: Private context passed to @filter * * Walk all key/value pairs under @root and emit them as a space-separated * cmdline string into @buf. Values containing whitespace are quoted with - * double quotes. Returns the number of bytes that would be written if @buf - * were large enough (matching snprintf semantics), or a negative errno on - * failure. + * double quotes. Keys for which @filter returns false are omitted. + * Returns the number of bytes that would be written if @buf were large enough + * (matching snprintf semantics), or a negative errno on failure. */ -int __init xbc_snprint_cmdline(char *buf, size_t size, struct xbc_node *root) +int __init xbc_snprint_cmdline_filter(char *buf, size_t size, struct xbc_node *root, + xbc_cmdline_filter_fn filter, void *data) { struct xbc_node *knode, *vnode; const char *val, *q; @@ -582,6 +582,9 @@ int __init xbc_snprint_cmdline(char *buf, size_t size, struct xbc_node *root) if (ret < 0) return ret; + if (filter && !filter(knode, xbc_namebuf, data)) + continue; + vnode = xbc_node_get_child(knode); if (!vnode) { ret = snprintf(buf ? buf + len : NULL, rest(len, size), @@ -836,7 +839,6 @@ static int __init xbc_parse_array(char **__v) return -ENOMEM; *__v = next; } while (c == ','); - node->child = 0; return c; } @@ -1003,9 +1005,30 @@ static int __init xbc_close_brace(char **k, char *n) return __xbc_close_brace(n - 1); } +#ifndef __KERNEL__ +/* Sanity check for regression: node indices must be within bounds */ +static int __init xbc_sanity_check_tree(void) +{ + int i; + + for (i = 0; i < xbc_node_num; i++) { + if (xbc_nodes[i].next >= xbc_node_num) { + return xbc_parse_error("No closing brace", + xbc_node_get_data(xbc_nodes + i)); + } + if (xbc_nodes[i].child >= xbc_node_num) { + return xbc_parse_error("Broken child node", + xbc_node_get_data(xbc_nodes + i)); + } + } + + return 0; +} +#endif + static int __init xbc_verify_tree(void) { - int i, depth; + int depth; size_t len, wlen; struct xbc_node *n, *m; @@ -1022,17 +1045,6 @@ static int __init xbc_verify_tree(void) return -ENOENT; } - for (i = 0; i < xbc_node_num; i++) { - if (xbc_nodes[i].next >= xbc_node_num) { - return xbc_parse_error("No closing brace", - xbc_node_get_data(xbc_nodes + i)); - } - if (xbc_nodes[i].child >= xbc_node_num) { - return xbc_parse_error("Broken child node", - xbc_node_get_data(xbc_nodes + i)); - } - } - /* Key tree limitation check */ n = &xbc_nodes[0]; depth = 1; @@ -1119,24 +1131,30 @@ static int __init xbc_parse_tree(void) } } while (!ret); + if (!ret) { + while (p < xbc_data + xbc_data_size - 1 && *p == '\0') + p++; + if (p < xbc_data + xbc_data_size - 1) + ret = xbc_parse_error("Unexpected data after null character", p); + } + return ret; } /** - * _xbc_exit() - Clean up all parsed bootconfig - * @early: Set true if this is called before budy system is initialized. + * xbc_exit() - Clean up all parsed bootconfig * * This clears all data structures of parsed bootconfig on memory. * If you need to reuse xbc_init() with new boot config, you can * use this. */ -void __init _xbc_exit(bool early) +void __init xbc_exit(void) { - xbc_free_mem(xbc_data, xbc_data_size, early); + xbc_free_mem(xbc_data, xbc_data_size); xbc_data = NULL; xbc_data_size = 0; xbc_node_num = 0; - xbc_free_mem(xbc_nodes, sizeof(struct xbc_node) * XBC_NODE_MAX, early); + xbc_free_mem(xbc_nodes, sizeof(struct xbc_node) * XBC_NODE_MAX); xbc_nodes = NULL; brace_index = 0; } @@ -1189,20 +1207,24 @@ int __init xbc_init(const char *data, size_t size, const char **emsg, int *epos) if (!xbc_nodes) { if (emsg) *emsg = "Failed to allocate bootconfig nodes"; - _xbc_exit(true); + xbc_exit(); return -ENOMEM; } ret = xbc_parse_tree(); if (!ret) ret = xbc_verify_tree(); +#ifndef __KERNEL__ + if (!ret) + ret = xbc_sanity_check_tree(); +#endif if (ret < 0) { if (epos) *epos = xbc_err_pos; if (emsg) *emsg = xbc_err_msg; - _xbc_exit(true); + xbc_exit(); } else { ret = xbc_node_num; } diff --git a/tools/bootconfig/main.c b/tools/bootconfig/main.c index df0534035857..6f476e3c06b8 100644 --- a/tools/bootconfig/main.c +++ b/tools/bootconfig/main.c @@ -17,9 +17,38 @@ #define pr_err(fmt, ...) fprintf(stderr, fmt, ##__VA_ARGS__) -/* Bootconfig footer is [size][csum][BOOTCONFIG_MAGIC]. */ -#define BOOTCONFIG_FOOTER_SIZE \ - (sizeof(uint32_t) * 2 + BOOTCONFIG_MAGIC_LEN) +static void show_xbc_error(const char *data, const char *msg, int pos) +{ + int lin = 1, col, i; + + if (pos < 0) { + pr_err("Error: %s.\n", msg); + return; + } + + /* Note that pos starts from 0 but lin and col should start from 1. */ + col = pos + 1; + for (i = 0; i < pos; i++) { + if (data[i] == '\n') { + lin++; + col = pos - i; + } + } + pr_err("Parse Error: %s at %d:%d\n", msg, lin, col); + +} + +static int init_xbc_with_error(char *buf, int len) +{ + const char *msg; + int ret, pos; + + ret = xbc_init(buf, len, &msg, &pos); + if (ret < 0) + show_xbc_error(buf, msg, pos); + + return ret; +} static int xbc_show_value(struct xbc_node *node, bool semicolon) { @@ -134,8 +163,6 @@ static void xbc_show_list(void) } } -#define PAGE_SIZE 4096 - static int load_xbc_fd(int fd, char **buf, int size) { int ret; @@ -197,7 +224,6 @@ static int load_xbc_from_initrd(int fd, char **buf) int ret; uint32_t size = 0, csum = 0, rcsum; char magic[BOOTCONFIG_MAGIC_LEN]; - const char *msg; ret = fstat(fd, &stat); if (ret < 0) @@ -249,52 +275,9 @@ static int load_xbc_from_initrd(int fd, char **buf) return -EINVAL; } - ret = xbc_init(*buf, size, &msg, NULL); - /* Wrong data */ - if (ret < 0) { - pr_err("parse error: %s.\n", msg); - return ret; - } + ret = init_xbc_with_error(*buf, size); - return size; -} - -static void show_xbc_error(const char *data, const char *msg, int pos) -{ - int lin = 1, col, i; - - if (pos < 0) { - pr_err("Error: %s.\n", msg); - return; - } - - /* Note that pos starts from 0 but lin and col should start from 1. */ - col = pos + 1; - for (i = 0; i < pos; i++) { - if (data[i] == '\n') { - lin++; - col = pos - i; - } - } - pr_err("Parse Error: %s at %d:%d\n", msg, lin, col); - -} - -static int init_xbc_with_error(char *buf, int len) -{ - char *copy = strdup(buf); - const char *msg; - int ret, pos; - - if (!copy) - return -ENOMEM; - - ret = xbc_init(buf, len, &msg, &pos); - if (ret < 0) - show_xbc_error(copy, msg, pos); - free(copy); - - return ret; + return ret < 0 ? ret : size; } static int show_xbc_kernel_cmdline(void) @@ -423,9 +406,8 @@ static int apply_xbc(const char *path, const char *xbc_path) char *buf, *data; size_t total_size; struct stat stat; - const char *msg; uint32_t size, csum; - int pos, pad; + int pad; int ret, fd; ret = load_xbc_file(xbc_path, &buf); @@ -433,9 +415,18 @@ static int apply_xbc(const char *path, const char *xbc_path) pr_err("Failed to load %s : %d\n", xbc_path, ret); return ret; } - size = strlen(buf) + 1; + size = ret; + if (size == 0 || buf[size - 1] != '\0') + size++; csum = xbc_calc_checksum(buf, size); + /* Verify the data format */ + ret = init_xbc_with_error(buf, size); + if (ret < 0) { + free(buf); + return ret; + } + /* Backup the bootconfig data */ data = calloc(size + BOOTCONFIG_ALIGN + BOOTCONFIG_FOOTER_SIZE, 1); if (!data) { @@ -444,15 +435,6 @@ static int apply_xbc(const char *path, const char *xbc_path) } memcpy(data, buf, size); - /* Check the data format */ - ret = xbc_init(buf, size, &msg, &pos); - if (ret < 0) { - show_xbc_error(data, msg, pos); - free(data); - free(buf); - - return ret; - } printf("Apply %s to %s\n", xbc_path, path); xbc_get_info(&ret, NULL); printf("\tNumber of nodes: %d\n", ret); diff --git a/tools/bootconfig/test-bootconfig.sh b/tools/bootconfig/test-bootconfig.sh index fc69f815ce4a..530ce7e28d63 100755 --- a/tools/bootconfig/test-bootconfig.sh +++ b/tools/bootconfig/test-bootconfig.sh @@ -180,6 +180,18 @@ EOF $BOOTCONF -a $TEMPCONF $INITRD 2> $OUTFILE xpass grep -q "1:1" $OUTFILE +echo "Intermediate null character test" +printf "key = value\n\0extra = data\n" > $TEMPCONF +xfail $BOOTCONF -a $TEMPCONF $INITRD +$BOOTCONF -a $TEMPCONF $INITRD 2> $OUTFILE +xpass grep -q "Unexpected" $OUTFILE + +echo "Trailing null character test" +printf "key = value\n\0" > $TEMPCONF +xpass $BOOTCONF -a $TEMPCONF $INITRD +$BOOTCONF $INITRD > $OUTFILE +xpass grep -q "value" $OUTFILE + echo "=== expected failure cases ===" for i in samples/bad-* ; do xfail $BOOTCONF -a $i $INITRD