Bootconfig updates for v7.4:

- bootconfig: Parser robustness and validation improvements
  . Skip printing early params to cmdline from bootconfig: Suppress
    early_param keys from /proc/cmdline and warn the user, since bootconfig
    does not invoke early_param handlers and silently ignoring them is
    misleading.
  . Reject unexpected data after null character: Detect and reject non-null
    data following an intermediate null character in the bootconfig buffer,
    preventing silently ignored configuration after an early termination.
 
 - bootconfig: Code cleanups
  . Merge _xbc_exit() into xbc_exit(): Since memblock_free() now handles
    late freeing and NULL addresses, the separate _xbc_exit() helper and
    its early argument are unnecessary. Inline it into xbc_exit().
  . Remove redundant assignment in xbc_parse_array(): The node->child = 0
    assignment after the loop duplicates what xbc_init_node() already did.
  . Skip internal tree sanity checks in kernel: Move the node index bounds
    loop from xbc_verify_tree() into a userspace-only xbc_sanity_check_tree()
    to reduce boot-time overhead, since out-of-bounds indices cannot be
    produced by malformed input.
  . Move BOOTCONFIG_FOOTER_SIZE to include/linux/bootconfig.h: Share the
    footer size constant between kernel and user-space tools, replacing the
    hardcoded (BOOTCONFIG_MAGIC_LEN + 8) expression in init/main.c.
 
 - tools/bootconfig: Tooling improvements
  . Remove unused PAGE_SIZE macro from the bootconfig tool.
  . Consolidate xbc_init() to error message wrapper: Use
    init_xbc_with_error() for all bootconfig initialization paths in the
    tool for simpler and more consistent error handling.
 -----BEGIN PGP SIGNATURE-----
 
 iQFPBAABCgA5FiEEh7BulGwFlgAOi5DV2/sHvwUrPxsFAmrX+EEbHG1hc2FtaS5o
 aXJhbWF0c3VAZ21haWwuY29tAAoJENv7B78FKz8bSdEIAKQhVr/CRW7/50NFdHB7
 XnyJFjGgAmY0DYEhlfv0v2S0YjDcicrTzrV1qMeNF/MwZ/2rMEOZhAnVUVlM3eIi
 28k/mEUixMmZGBeLtUUmdrbF8OkvptdmwtjWYdXF2PUDqRlxfvUV/vWqvVuL5EAu
 xcDykilReFxmHLUWTJIn8EhfcXP96Lm+wOMhbLI97cAVaP/swtegqHEefIBPvSj4
 YIkZtetAWJQIJKGeq97gQHA7SAlIdZEQW5YgUqr7eKAdK8ffIOk0+nr7bmNIER+m
 fBe6DHO7Ho3B4EzH/u5MiQBDmYcZ0Akp7meP3h6VXvEpfuCeuqhzsO8+szzkmjbe
 4UQ=
 =hhPo
 -----END PGP SIGNATURE-----

Merge tag 'bootconfig-v7.4' of git://git.kernel.org/pub/scm/linux/kernel/git/trace/linux-trace

Pull bootconfig updates from Masami Hiramatsu:
 "Parser robustness and validation improvements:

   - Skip printing early params to cmdline from bootconfig

     Suppress early_param keys from /proc/cmdline and warn the user,
     since bootconfig does not invoke early_param handlers and silently
     ignoring them is misleading

   - Reject unexpected data after null character

     Detect and reject non-null data following an intermediate null
     character in the bootconfig buffer, preventing silently ignored
     configuration after an early termination

  Code cleanups:

   - Merge _xbc_exit() into xbc_exit()

     Since memblock_free() now handles late freeing and NULL addresses,
     the separate _xbc_exit() helper and its early argument are
     unnecessary. Inline it into xbc_exit()

   - Remove redundant assignment in xbc_parse_array()

     The node->child = 0 assignment after the loop duplicates what
     xbc_init_node() already did

   - Skip internal tree sanity checks in kernel

     Move the node index bounds loop from xbc_verify_tree() into a
     userspace-only xbc_sanity_check_tree() to reduce boot-time
     overhead, since out-of-bounds indices cannot be produced by
     malformed input

   - Move BOOTCONFIG_FOOTER_SIZE to include/linux/bootconfig.h

     Share the footer size constant between kernel and user-space tools,
     replacing the hardcoded (BOOTCONFIG_MAGIC_LEN + 8) expression in
     init/main.c

  Tooling improvements:

   - Remove unused PAGE_SIZE macro from the bootconfig tool

   - Consolidate xbc_init() to error message wrapper

     Use init_xbc_with_error() for all bootconfig initialization paths
     in the tool for simpler and more consistent error handling"

* tag 'bootconfig-v7.4' of git://git.kernel.org/pub/scm/linux/kernel/git/trace/linux-trace:
  bootconfig: Move BOOTCONFIG_FOOTER_SIZE to include/linux/bootconfig.h
  bootconfig: Skip internal tree sanity checks in kernel
  tools/bootconfig: Consolidate xbc_init() to error message wrapper
  bootconfig: Reject unexpected data after null character
  bootconfig: Skip printing early params to cmdline from bootconfig
  tools/bootconfig: remove unused PAGE_SIZE macro
  bootconfig: remove redundant assignment in xbc_parse_array()
  bootconfig: merge _xbc_exit() into xbc_exit()
This commit is contained in:
Linus Torvalds 2026-10-23 12:20:54 -10:00
commit 90b754d401
5 changed files with 193 additions and 109 deletions

View File

@ -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

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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);

View File

@ -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