* [PATCH v3 1/2] init: claim parameters consumed before the main kernel
2026-10-05 17:13 [PATCH v3 0/2] x86: stop handing boot-stage parameters to init sayo
@ 2026-10-05 17:13 ` sayo
2026-10-05 17:13 ` [PATCH v3 2/2] x86: claim the parameters consumed by the boot stub and decompressor sayo
1 sibling, 0 replies; 3+ messages in thread
From: sayo @ 2026-10-05 17:13 UTC (permalink / raw)
To: Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen,
H . Peter Anvin
Cc: x86, linux-kernel, m.younesbadr, nir
Some kernel parameters are consumed before the main kernel is running: the
boot stub, the decompressor and the EFI stub all read the command line
directly (cmdline_find_option*()) and act on options that the main kernel
never registers, because they describe things that are already decided by
then - the kernel's load address, 5-level paging setup, the memory
encryption mode, and so on.
Such parameters are not in any __setup()/early_param() table in the main
kernel, so parse_args() cannot know that they were eaten and
unknown_bootoption() classifies them as unknown: valueless ones end up in
init's argv, "key=value" ones end up in init's environment. On x86 that
list currently contains nokaslr, no5lvl, mem_encrypt= and edd=, all of them
documented parameter names in
Documentation/admin-guide/kernel-parameters.txt.
Inits that look at their arguments are affected: openrc-init takes the
runlevel from argv[1] and therefore reports "nokaslr is an invalid
runlevel", while a shell used as init treats the argument as a script name
and exits, which panics the kernel.
Adding a stub handler for each such parameter on each architecture does not
scale: arm64, loongarch and s390 each carry one for "nokaslr" alone. Let
architectures list the parameters their boot code consumed instead, and
have unknown_bootoption() drop those before they are classified: they are
then neither reported as unknown nor handed to init, while genuinely
unknown parameters keep reaching init exactly as before.
Signed-off-by: sayo <rg32@ciallo.tech>
---
include/linux/init.h | 12 ++++++++++++
init/main.c | 16 ++++++++++++++++
2 files changed, 28 insertions(+)
diff --git a/include/linux/init.h b/include/linux/init.h
index 6326c61e2332..90c77d6d8cb7 100644
--- a/include/linux/init.h
+++ b/include/linux/init.h
@@ -375,6 +375,18 @@ extern const struct obs_kernel_param __setup_start[], __setup_end[];
/* Relies on boot_command_line being set */
void __init parse_early_param(void);
void __init parse_early_options(char *cmdline);
+
+/*
+ * Parameters consumed before the main kernel started - by the boot stub, the
+ * decompressor or the EFI stub. They cannot be matched against the
+ * __setup()/early_param() tables in the main kernel, so architectures list
+ * them in an override of boot_param_consumed() to keep them from being
+ * reported as unknown and handed to init.
+ *
+ * @param is the parameter as it appeared on the command line, including
+ * "=value" for parameters that had one.
+ */
+bool boot_param_consumed(const char *param);
#endif /* __ASSEMBLY__ */
#else /* MODULE */
diff --git a/init/main.c b/init/main.c
index 31f2bf54976a..1230ac77029d 100644
--- a/init/main.c
+++ b/init/main.c
@@ -506,6 +506,18 @@ static int __init set_init_arg(char *param, char *val,
return 0;
}
+/*
+ * Did the boot stub, the decompressor or the EFI stub consume this parameter
+ * before the main kernel started? Such parameters are not in any parameter
+ * table here, so nothing else can tell that they were eaten - without this
+ * they are reported as unknown and handed to init. Architectures override
+ * this to list them; see also boot_param_consumed().
+ */
+bool __init __weak boot_param_consumed(const char *param)
+{
+ return false;
+}
+
/*
* Unknown boot options get handed to init, unless they look like
* unused parameters (modprobe will find them in /proc/cmdline).
@@ -527,6 +539,10 @@ static int __init unknown_bootoption(char *param, char *val,
repair_env_string(param, val);
+ /* Eaten by the boot stub/decompressor before the main kernel ran? */
+ if (boot_param_consumed(param))
+ return 0;
+
/* Handle bootloader identifier */
for (int i = 0; bootloader[i]; i++) {
if (strstarts(param, bootloader[i]))
^ permalink raw reply [flat|nested] 3+ messages in thread* [PATCH v3 2/2] x86: claim the parameters consumed by the boot stub and decompressor
2026-10-05 17:13 [PATCH v3 0/2] x86: stop handing boot-stage parameters to init sayo
2026-10-05 17:13 ` [PATCH v3 1/2] init: claim parameters consumed before the main kernel sayo
@ 2026-10-05 17:13 ` sayo
1 sibling, 0 replies; 3+ messages in thread
From: sayo @ 2026-10-05 17:13 UTC (permalink / raw)
To: Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen,
H . Peter Anvin
Cc: x86, linux-kernel, m.younesbadr, nir
List the parameters that arch/x86/boot/ and the decompressor consume before
the main kernel is running, so that unknown_bootoption() no longer forwards
them to init:
nokaslr arch/x86/boot/compressed/kaslr.c
no5lvl arch/x86/boot/compressed/pgtable_64.c
mem_encrypt= arch/x86/boot/compressed/misc.c
edd= arch/x86/boot/edd.c
forcepae 32-bit only: its __setup() is compiled out on x86-64
Booted with "nokaslr no5lvl edd=off forcepae mem_encrypt=off foo=bar --
extra1" (plus the usual console=), before:
Unknown kernel command line parameters "nokaslr no5lvl forcepae edd=off
mem_encrypt=off foo=bar", will be passed to user space.
argv: {"/init", "nokaslr", "no5lvl", "forcepae", "extra1"}
envp: {"HOME=/", "TERM=linux", "edd=off", "mem_encrypt=off", "foo=bar"}
and after:
Unknown kernel command line parameters "foo=bar", will be passed to
user space.
argv: {"/init", "extra1"}
envp: {"HOME=/", "TERM=linux", "foo=bar"}
which is also what makes the notice useful again: it now only names
parameters that really are unknown.
Signed-off-by: sayo <rg32@ciallo.tech>
---
arch/x86/kernel/setup.c | 40 ++++++++++++++++++++++++++++++++++++++++
1 file changed, 40 insertions(+)
diff --git a/arch/x86/kernel/setup.c b/arch/x86/kernel/setup.c
index cda6adb9f69c..34f7add84243 100644
--- a/arch/x86/kernel/setup.c
+++ b/arch/x86/kernel/setup.c
@@ -1325,3 +1325,43 @@ bool arch_cpu_is_hotpluggable(int cpu)
return cpu > 0;
}
#endif /* CONFIG_HOTPLUG_CPU */
+
+/*
+ * Parameters that the boot code consumes before the main kernel is running.
+ * They are deliberately not registered with early_param()/__setup() here:
+ * what they configure has already been decided by the time this code runs,
+ * and the decompressor (a separate program, arch/x86/boot/compressed/) does
+ * not contribute to any section in this image.
+ *
+ * Without being claimed, they are treated as unknown parameters and handed
+ * to init - valueless ones as arguments, "key=value" ones as environment
+ * variables. openrc-init, for instance, reads the runlevel from argv[1] and
+ * then complains that "nokaslr is an invalid runlevel"; a shell used as init
+ * treats the argument as a script name and exits, which panics the kernel.
+ *
+ * Keep this in sync with the cmdline_find_option*() calls in
+ * arch/x86/boot/ and drivers/firmware/efi/libstub/.
+ */
+static const char * const boot_consumed_params[] __initconst = {
+ "nokaslr", /* arch/x86/boot/compressed/kaslr.c */
+ "no5lvl", /* arch/x86/boot/compressed/pgtable_64.c */
+ "mem_encrypt", /* arch/x86/boot/compressed/misc.c */
+ "edd", /* arch/x86/boot/edd.c */
+ "forcepae", /* 32-bit only: __setup() is compiled out on x86-64 */
+};
+
+bool __init boot_param_consumed(const char *param)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(boot_consumed_params); i++) {
+ const char *name = boot_consumed_params[i];
+ size_t len = strlen(name);
+
+ if (!strncmp(param, name, len) &&
+ (param[len] == '\0' || param[len] == '='))
+ return true;
+ }
+
+ return false;
+}
^ permalink raw reply [flat|nested] 3+ messages in thread