Linux Modules
 help / color / mirror / Atom feed
* [PATCH v10 0/2] module: Extend module_blacklist parameter to built-in modules
@ 2026-09-03 18:55 Aaron Tomlin
  2026-09-03 18:55 ` [PATCH v10 1/2] " Aaron Tomlin
                   ` (2 more replies)
  0 siblings, 3 replies; 16+ messages in thread
From: Aaron Tomlin @ 2026-09-03 18:55 UTC (permalink / raw)
  To: arnd, mcgrof, petr.pavlu, da.gomez, samitolvanen, peterz, ojeda
  Cc: akpm, mhiramat, boqun, atomlin, neelx, da.anzani, sean, chjohnst,
	steve, mproche, nick.lane, linux-arch, linux-modules,
	rust-for-linux, linux-kernel

Currently, the "module_blacklist=" command-line parameter only applies to
loadable modules. If a module is built-in, the parameter is silently
ignored. This patch series extends the blacklisting functionality to
built-in modules by intercepting their initialisation routines during early
boot.

Following review feedback, the implementation has been split into two
separate changes to decouple the introduction of the new feature from the
terminology renaming:

    1.  The first patch extends the "module_blacklist=" parameter to
        built-in modules using the original blacklist terminology. It
        introduces the ".initcall.modnames" section to map initcall
        function pointers to their associated KBUILD_MODNAME strings
        (restricted only to module_init() invocations to save memory and
        avoid matching core kernel subsystems). It also restricts the check
        to a boot-time __init wrapper to eliminate Use-After-Free (UAF) and
        Spectre v1 vulnerability risks when loading dynamic modules at
        runtime, and adds a fast-path check to eliminate lookup overhead
        when the parameter is not in use

    2.  The second patch renames the variables and helper functions to
        adopt the preferred "module_denylist=" and module_is_denylisted()
        terminology in the codebase. To preserve the existing user-space
        ABI, "module_blacklist=" is kept as a legacy alias pointing to the
        same module_denylist variable

Changes since v9:

 - Enforced natural structure alignment on struct initcall_modname in
   include/linux/init.h via __aligned(__alignof__(struct initcall_modname))
   to prevent compiler over-alignment and inter-element linker padding
   (Petr Pavlu)

 - Removed STRUCT_ALIGN() before BOUNDED_SECTION_BY(.initcall.modnames,
   _initcall_modnames) to eliminate unnecessary alignment (Petr Pavlu)

 - Added <linux/init.h> to rust/bindings/bindings_helper.h and updated
   rust/macros/module.rs to use the generated
   ::kernel::bindings::initcall_modname struct rather than a locally
   defined type (Gary Guo and Petr Pavlu)

 - Placed the Rust module name string explicitly in .init.rodata within
   rust/macros/module.rs (#[link_section = ".init.rodata"]) so that the
   string memory is reclaimed alongside the initcall table after boot,
   matching the C implementation (Petr Pavlu)

 - Link to v9: https://lore.kernel.org/lkml/20260807012601.360452-1-atomlin@atomlin.com/

Changes since v8:

 - Extended Rust procedural macro support in rust/macros/module.rs to
   generate .initcall.modnames metadata for built-in Rust modules,
   maintaining feature parity with C built-in modules when evaluating
   "module_blacklist=" and "module_denylist=" (Petr Pavlu)

 - Merged the intermediate ___define_initcall_modname macro directly into
   __define_initcall_modname in include/linux/init.h to clean up macro
   expansion (Petr Pavlu)

 - Reverted the parameter name in module_init(x) back to 'x' in
   include/linux/module.h to remain consistent with surrounding comments
   (Petr Pavlu)

 - Cleaned up whitespace formatting in kernel/module/main.c (Petr Pavlu)

 - Link to v8: https://lore.kernel.org/lkml/20260724024744.616286-1-atomlin@atomlin.com/

Changes since v7:

 - Fixed a double evaluation of __initcall_id(fn) in the built-in module
   initcall macro expansion

 - Link to v7: https://lore.kernel.org/lkml/20260724014345.589326-1-atomlin@atomlin.com/

Changes since v6:

 - Grouped the __initcall_fn_ptr() macro definition inside the existing
   CONFIG_HAVE_ARCH_PREL32_RELOCATIONS block in include/linux/init.h
   (Petr Pavlu)

 - Localised the built-in module initcall level and section naming strictly
   to include/linux/init.h by introducing the macros
   __define_initcall_modname() and __builtin_module_initcall(), keeping
   include/linux/module.h clean (Petr Pavlu)

 - Removed the unnecessary dereference_function_descriptor() lookup wrapper
   in get_builtin_modname() in favour of a direct pointer comparison
   (Petr Pavlu)

 - Cleaned up whitespace formatting in kernel/module/main.c (Petr Pavlu)

 - Link to v6: https://lore.kernel.org/lkml/20260718190121.378314-1-atomlin@atomlin.com/

Changes since v5:

 - Resolved a modpost cross-section mismatch warning by introducing
   do_one_initcall_builtin() as a strict __init wrapper function, rather
   than performing the built-in module checks inside the __init_or_module
   do_one_initcall() function

 - Addressed a UAF race condition with concurrent dynamic module loading by
   strictly bounding the blacklist evaluation to early boot via the new
   __init wrapper, removing temporal check

 - Mitigated a potential Spectre v1 speculative execution vulnerability by
   ensuring get_builtin_modname() is exclusively called by __init code,
   preventing unprivileged runtime module loading from speculatively
   jumping into reclaimed ".init.text" instructions

 - Updated Documentation/admin-guide/kernel-parameters.txt to explicitly
   mark "module_blacklist=" as deprecated and document "module_denylist="

 - Link to v5: https://lore.kernel.org/lkml/20260718051350.344772-1-atomlin@atomlin.com/

Changes since v4:

 - Split the monolithic patch into two distinct commits. One to extend the
   functionality to built-in modules, and a second to safely transition the
   internal terminology to "denylist" (Arnd Bergmann)

 - Preserved "module_blacklist=" as a legacy core_param alias in the second
   commit to ensure backwards compatibility with existing userspace
   configurations

 - Restricted the population of the ".initcall.modnames" section strictly
   to module_init() rather than all ___define_initcall() invocations. This
   prevents non-module core initcalls from being redundantly mapped, saving
   memory and avoiding false-positive matches (Petr Pavlu)

 - Introduced a fast-path evaluation to check if the blacklist/denylist is
   actually populated before invoking get_builtin_modname(), avoiding
   unnecessary lookups during boot (Petr Pavlu)

 - Link to v4: https://lore.kernel.org/lkml/20260708020007.55728-1-atomlin@atomlin.com/

Changes since v3:

 - Renamed the external function prototype and internal helper to
   module_is_denylisted(), while updating the backing variable in
   main.c to module_denylist. To preserve user-space compatibility
   while adopting modern terminology, separate core_param entries have
   been introduced, allowing both the preferred module_denylist=
   parameter and the legacy module_blacklist= parameter to resolve to
   the same underlying variable (Andrew Morton)

 - I introduced the __initcall_fn_ptr() macro helper to dynamically
   resolve the initcall pointer configuration:
    - For architectures with relative 32-bit relocations
      (CONFIG_HAVE_ARCH_PREL32_RELOCATIONS=y), it resolves to the
      relocation stub pointer  __initcall_stub(fn, __iid, id)
    - For architectures without PREL32 relocations, it resolves
      directly to the function pointer fn

 - Decoupled the module_denylist parameter parsing and the
   module_is_denylisted() function from CONFIG_MODULES, moving the
   logic to init/main.c. This ensures the denylist works for built-in
   modules even on monolithic kernels built without loadable module
   support (CONFIG_MODULES=n)

 - Removed the conditional stub implementation of
   module_is_denylisted() in module.h and replaced it with a single,
   unconditional declaration outside of the #ifdef CONFIG_MODULES block.
   This prevents compiler warnings about missing prototypes and ensures
   visibility under a monolithic configuration

 - Replaced the initmem_freed state variable and its synchronisation
   logic in kernel_init() with race-free spatial boundary checks using
   is_kernel_text() and is_kernel_inittext() in initcall_get_modname()

 - Aligned the .initcall_modnames table with relocations by assigning
   .initcall_fn using the __initcall_stub() helper in
   ___define_initcall(). This ensures the lookup matches the actual stub
   pointer passed to do_one_initcall() when
   CONFIG_HAVE_ARCH_PREL32_RELOCATIONS is enabled. Passed the preprocessor
   __iid argument to ____define_initcall_modname once to avoid double
   evaluation of __COUNTER__ (which caused build failures with LTO)

 - Updated initcall_get_modname() in main.c to resolve the function
   pointer fn using dereference_function_descriptor(fn) prior to
   checking the .text and .init.text boundaries, and dereference both
   fn and p->initcall_fn in the comparison loop to support descriptor-based
   architectures (e.g., PPC64)

 - Link to v3: https://lore.kernel.org/lkml/20260706050337.7613-1-atomlin@atomlin.com/

Changes since v2:

 - Avoided relative 32-bit offsets (PREL32) with inline assembly, opting
   instead for standard C structures with absolute pointers. This fixes LTO
   and CFI compatibility issues (e.g., under Clang) where raw inline assembly
   fails to track compiler-generated symbols and CFI stubs

 - Placed module name strings into the ".init.rodata" section via a dedicated
   static array to ensure they are freed from memory after boot

 - Avoided Use-After-Free (UAF) bugs post-boot when loading dynamic modules:
   - Added an 'initmem_freed' flag, marked as '__ro_after_init', set after
     free_initmem() to skip table lookups for dynamically loaded modules
   - Added a blacklist check in do_init_module() for dynamic modules

 - Simplified the linker script using the BOUNDED_SECTION_PRE_LABEL() macro
   to define the ".initcall.modnames" section boundary

 - Added a dummy/stub implementation of module_is_blacklisted() when
   CONFIG_MODULES is disabled to avoid build errors

 - Link to v2: https://lore.kernel.org/lkml/20260622140259.2974-1-atomlin@atomlin.com/

Changes since v1:

 - Pivoted entirely from exposing built-in initcalls and their blacklist
   status via a debugfs interface to directly extending the existing
   "module_blacklist=" and new "module_blacklist=" to intercept built-in
   modules at boot (Petr Pavlu)

 - Implemented 32-bit relative offsets (CONFIG_HAVE_ARCH_PREL32_RELOCATIONS)
   to store the mappings, preventing binary bloat and preserving KASLR
   efficacy

 - Link to v1: https://lore.kernel.org/lkml/20260510061301.41341-1-atomlin@atomlin.com/

Aaron Tomlin (2):
  module: Extend module_blacklist parameter to built-in modules
  module: Rename module_blacklist to module_denylist

 .../admin-guide/kernel-parameters.txt         |  6 +-
 include/asm-generic/vmlinux.lds.h             |  3 +-
 include/linux/init.h                          | 25 ++++++++-
 include/linux/module.h                        |  4 +-
 init/main.c                                   | 55 ++++++++++++++++++-
 kernel/module/main.c                          | 25 +--------
 rust/bindings/bindings_helper.h               |  1 +
 rust/macros/module.rs                         | 17 ++++++
 8 files changed, 107 insertions(+), 29 deletions(-)

-- 
2.55.0


^ permalink raw reply	[flat|nested] 16+ messages in thread

* [PATCH v10 1/2] module: Extend module_blacklist parameter to built-in modules
  2026-09-03 18:55 [PATCH v10 0/2] module: Extend module_blacklist parameter to built-in modules Aaron Tomlin
@ 2026-09-03 18:55 ` Aaron Tomlin
  2026-09-03 19:13   ` sashiko-bot
  2026-09-03 20:23   ` Aaron Tomlin
  2026-09-03 18:55 ` [PATCH v10 2/2] module: Rename module_blacklist to module_denylist Aaron Tomlin
  2026-09-03 20:29 ` [PATCH v10 0/2] module: Extend module_blacklist parameter to built-in modules Andrew Morton
  2 siblings, 2 replies; 16+ messages in thread
From: Aaron Tomlin @ 2026-09-03 18:55 UTC (permalink / raw)
  To: arnd, mcgrof, petr.pavlu, da.gomez, samitolvanen, peterz, ojeda
  Cc: akpm, mhiramat, boqun, atomlin, neelx, da.anzani, sean, chjohnst,
	steve, mproche, nick.lane, linux-arch, linux-modules,
	rust-for-linux, linux-kernel

Currently, the "module_blacklist=" command-line parameter only applies
to loadable modules. If a module is built-in, the parameter is silently
ignored. This patch extends the blacklisting functionality to built-in
modules by intercepting their initialisation routines during early boot.

To achieve this, we introduce a new ".initcall.modnames" memory section.
For each built-in module, we use a standard C structure (i.e., struct
initcall_modname) to map its initcall function pointer to its associated
KBUILD_MODNAME string. This mapping is restricted only to files implementing
built-in modules via module_init() to avoid mapping core kernel subsystems
and save memory.

During boot, built-in initcalls are executed sequentially via
do_initcall_level() and do_pre_smp_initcalls(). We introduce a new
wrapper function, do_one_initcall_builtin(), to cross-reference the
initcall function pointer against the ".initcall.modnames" table. If
a match is found and the module is present in the blacklist, the
initcall is skipped.

To make the blacklist functional on monolithic kernels, the command-line
parameter parsing and the module_is_blacklisted() lookup function are
decoupled from the loadable module subsystem and moved to init/main.c.
This enables "module_blacklist=" to intercept built-in modules even on
kernels built with CONFIG_MODULES=n.

Signed-off-by: Aaron Tomlin <atomlin@atomlin.com>
---
 include/asm-generic/vmlinux.lds.h |  3 +-
 include/linux/init.h              | 25 +++++++++++++-
 include/linux/module.h            |  4 ++-
 init/main.c                       | 54 +++++++++++++++++++++++++++++--
 kernel/module/main.c              | 23 +------------
 rust/bindings/bindings_helper.h   |  1 +
 rust/macros/module.rs             | 17 ++++++++++
 7 files changed, 100 insertions(+), 27 deletions(-)

diff --git a/include/asm-generic/vmlinux.lds.h b/include/asm-generic/vmlinux.lds.h
index b2988aa12f66..7490278b7a2d 100644
--- a/include/asm-generic/vmlinux.lds.h
+++ b/include/asm-generic/vmlinux.lds.h
@@ -734,7 +734,8 @@
 	EARLYCON_TABLE()						\
 	LSM_TABLE()							\
 	EARLY_LSM_TABLE()						\
-	KUNIT_INIT_TABLE()
+	KUNIT_INIT_TABLE()						\
+	BOUNDED_SECTION_BY(.initcall.modnames, _initcall_modnames)
 
 #define INIT_TEXT							\
 	*(.init.text .init.text.*)					\
diff --git a/include/linux/init.h b/include/linux/init.h
index 6326c61e2332..833b837ce11d 100644
--- a/include/linux/init.h
+++ b/include/linux/init.h
@@ -252,6 +252,7 @@ extern struct module __this_module;
 #endif
 
 #ifdef CONFIG_HAVE_ARCH_PREL32_RELOCATIONS
+#define __initcall_fn_ptr(fn, __iid, id)	__initcall_stub(fn, __iid, id)
 #define ____define_initcall(fn, __stub, __name, __sec)		\
 	__define_initcall_stub(__stub, fn)			\
 	asm(".section	\"" __sec "\", \"a\"		\n"	\
@@ -260,6 +261,7 @@ extern struct module __this_module;
 	    ".previous					\n");	\
 	static_assert(__same_type(initcall_t, &fn));
 #else
+#define __initcall_fn_ptr(fn, __iid, id)	fn
 #define ____define_initcall(fn, __unused, __name, __sec)	\
 	static initcall_t __name __used 			\
 		__attribute__((__section__(__sec))) = fn;
@@ -271,7 +273,28 @@ extern struct module __this_module;
 		__initcall_name(initcall, __iid, id),		\
 		__initcall_section(__sec, __iid))
 
-#define ___define_initcall(fn, id, __sec)			\
+struct initcall_modname {
+	initcall_t initcall_fn;
+	const char *modname;
+};
+
+#define ____define_initcall_modname(fn, id, __sec, __iid)		\
+	__unique_initcall(fn, id, __sec, __iid)				\
+	static const char __initstr_##fn[] __used __aligned(1)		\
+		__section(".init.rodata") = KBUILD_MODNAME;		\
+	static const struct initcall_modname __modname_##fn __used	\
+		__section(".initcall.modnames")				\
+		__aligned(__alignof__(struct initcall_modname)) = {	\
+			.initcall_fn = __initcall_fn_ptr(fn, __iid, id),\
+			.modname = __initstr_##fn			\
+		};
+
+#define __define_initcall_modname(fn, id)				\
+	____define_initcall_modname(fn, id, .initcall##id, __initcall_id(fn))
+
+#define __builtin_module_initcall(fn)	__define_initcall_modname(fn, 6)
+
+#define ___define_initcall(fn, id, __sec)				\
 	__unique_initcall(fn, id, __sec, __initcall_id(fn))
 
 #define __define_initcall(fn, id) ___define_initcall(fn, id, .initcall##id)
diff --git a/include/linux/module.h b/include/linux/module.h
index 96cc98568eea..bcc54edbde7d 100644
--- a/include/linux/module.h
+++ b/include/linux/module.h
@@ -86,7 +86,7 @@ extern void cleanup_module(void);
  * builtin) or at module insertion time (if a module).  There can only
  * be one per module.
  */
-#define module_init(x)	__initcall(x);
+#define module_init(x)	__builtin_module_initcall(x);
 
 /**
  * module_exit() - driver exit entry point
@@ -879,6 +879,8 @@ static inline void module_for_each_mod(int(*func)(struct module *mod, void *data
 }
 #endif /* CONFIG_MODULES */
 
+bool module_is_blacklisted(const char *module_name);
+
 #ifdef CONFIG_SYSFS
 extern struct kset *module_kset;
 extern const struct kobj_type module_ktype;
diff --git a/init/main.c b/init/main.c
index 2613d3f9b3ce..0ee3b23bcd2b 100644
--- a/init/main.c
+++ b/init/main.c
@@ -1344,6 +1344,56 @@ static inline void do_trace_initcall_level(const char *level)
 }
 #endif /* !TRACEPOINTS_ENABLED */
 
+extern struct initcall_modname __start_initcall_modnames[];
+extern struct initcall_modname __stop_initcall_modnames[];
+
+/* module_blacklist is a comma-separated list of module names */
+static char *module_blacklist;
+bool __init_or_module module_is_blacklisted(const char *module_name)
+{
+	const char *p;
+	size_t len;
+
+	if (!module_blacklist)
+		return false;
+
+	for (p = module_blacklist; *p; p += len) {
+		len = strcspn(p, ",");
+		if (strlen(module_name) == len && !memcmp(module_name, p, len))
+			return true;
+		if (p[len] == ',')
+			len++;
+	}
+	return false;
+}
+core_param(module_blacklist, module_blacklist, charp, 0400);
+
+static const char *__init get_builtin_modname(initcall_t fn)
+{
+	struct initcall_modname *p;
+
+	for (p = __start_initcall_modnames; p < __stop_initcall_modnames; p++) {
+		if (p->initcall_fn == fn)
+			return p->modname;
+	}
+	return NULL;
+}
+
+static void __init do_one_initcall_builtin(initcall_t fn)
+{
+	const char *modname;
+
+	if (module_blacklist) {
+		modname = get_builtin_modname(fn);
+		if (modname && module_is_blacklisted(modname)) {
+			pr_info("Skipping initcall for blacklisted built-in module %s\n",
+				modname);
+			return;
+		}
+	}
+	do_one_initcall(fn);
+}
+
 int __init_or_module do_one_initcall(initcall_t fn)
 {
 	int count = preempt_count();
@@ -1416,7 +1466,7 @@ static void __init do_initcall_level(int level, char *command_line)
 
 	do_trace_initcall_level(initcall_level_names[level]);
 	for (fn = initcall_levels[level]; fn < initcall_levels[level+1]; fn++)
-		do_one_initcall(initcall_from_entry(fn));
+		do_one_initcall_builtin(initcall_from_entry(fn));
 }
 
 static void __init do_initcalls(void)
@@ -1461,7 +1511,7 @@ static void __init do_pre_smp_initcalls(void)
 
 	do_trace_initcall_level("early");
 	for (fn = __initcall_start; fn < __initcall0_start; fn++)
-		do_one_initcall(initcall_from_entry(fn));
+		do_one_initcall_builtin(initcall_from_entry(fn));
 }
 
 static int run_init_process(const char *init_filename)
diff --git a/kernel/module/main.c b/kernel/module/main.c
index d0e1e0bd2ad0..a9fd6aaedc69 100644
--- a/kernel/module/main.c
+++ b/kernel/module/main.c
@@ -2930,27 +2930,6 @@ int __weak module_frob_arch_sections(Elf_Ehdr *hdr,
 	return 0;
 }
 
-/* module_blacklist is a comma-separated list of module names */
-static char *module_blacklist;
-static bool blacklisted(const char *module_name)
-{
-	const char *p;
-	size_t len;
-
-	if (!module_blacklist)
-		return false;
-
-	for (p = module_blacklist; *p; p += len) {
-		len = strcspn(p, ",");
-		if (strlen(module_name) == len && !memcmp(module_name, p, len))
-			return true;
-		if (p[len] == ',')
-			len++;
-	}
-	return false;
-}
-core_param(module_blacklist, module_blacklist, charp, 0400);
-
 static struct module *layout_and_allocate(struct load_info *info, int flags)
 {
 	struct module *mod;
@@ -3402,7 +3381,7 @@ static int early_mod_check(struct load_info *info, int flags)
 	 * Now that we know we have the correct module name, check
 	 * if it's blacklisted.
 	 */
-	if (blacklisted(info->name)) {
+	if (module_is_blacklisted(info->name)) {
 		pr_err("Module %s is blacklisted\n", info->name);
 		return -EPERM;
 	}
diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h
index 4b31aa7f432f..1075b26e53ac 100644
--- a/rust/bindings/bindings_helper.h
+++ b/rust/bindings/bindings_helper.h
@@ -63,6 +63,7 @@
 #include <linux/fwctl.h>
 #include <linux/fs.h>
 #include <linux/i2c.h>
+#include <linux/init.h>
 #include <linux/interrupt.h>
 #include <linux/io-pgtable.h>
 #include <linux/ioport.h>
diff --git a/rust/macros/module.rs b/rust/macros/module.rs
index bc7027f8dbb2..a96157598197 100644
--- a/rust/macros/module.rs
+++ b/rust/macros/module.rs
@@ -480,6 +480,8 @@ pub(crate) fn module(info: ModuleInfo) -> Result<TokenStream> {
     let ident_init = format_ident!("__{ident}_init");
     let ident_exit = format_ident!("__{ident}_exit");
     let ident_initcall = format_ident!("__{ident}_initcall");
+    let ident_modname = format_ident!("__{ident}_modname");
+    let ident_modname_str = format_ident!("__{ident}_modname_str");
     let initcall_section = ".initcall6.init";
 
     let global_asm = format!(
@@ -491,6 +493,7 @@ pub(crate) fn module(info: ModuleInfo) -> Result<TokenStream> {
     );
 
     let name_cstr = CString::new(name.value()).expect("name contains NUL-terminator");
+    let name_len = name_cstr.to_bytes_with_nul().len();
 
     Ok(quote! {
         /// The module name.
@@ -591,6 +594,20 @@ pub extern "C" fn cleanup_module() {
                 #[cfg(CONFIG_HAVE_ARCH_PREL32_RELOCATIONS)]
                 ::core::arch::global_asm!(#global_asm);
 
+                #[cfg(not(MODULE))]
+                #[used(compiler)]
+                #[link_section = ".init.rodata"]
+                static #ident_modname_str: [u8; #name_len] = *#name_cstr.to_bytes_with_nul();
+
+                #[cfg(not(MODULE))]
+                #[used(compiler)]
+                #[link_section = ".initcall.modnames"]
+                static #ident_modname: ::kernel::bindings::initcall_modname =
+                    ::kernel::bindings::initcall_modname {
+                        initcall_fn: Some(#ident_init),
+                        modname: #ident_modname_str.as_ptr().cast(),
+                    };
+
                 #[cfg(not(MODULE))]
                 #[no_mangle]
                 pub extern "C" fn #ident_init() -> ::kernel::ffi::c_int {
-- 
2.55.0


^ permalink raw reply related	[flat|nested] 16+ messages in thread

* [PATCH v10 2/2] module: Rename module_blacklist to module_denylist
  2026-09-03 18:55 [PATCH v10 0/2] module: Extend module_blacklist parameter to built-in modules Aaron Tomlin
  2026-09-03 18:55 ` [PATCH v10 1/2] " Aaron Tomlin
@ 2026-09-03 18:55 ` Aaron Tomlin
  2026-09-03 19:07   ` sashiko-bot
  2026-09-03 20:29 ` [PATCH v10 0/2] module: Extend module_blacklist parameter to built-in modules Andrew Morton
  2 siblings, 1 reply; 16+ messages in thread
From: Aaron Tomlin @ 2026-09-03 18:55 UTC (permalink / raw)
  To: arnd, mcgrof, petr.pavlu, da.gomez, samitolvanen, peterz, ojeda
  Cc: akpm, mhiramat, boqun, atomlin, neelx, da.anzani, sean, chjohnst,
	steve, mproche, nick.lane, linux-arch, linux-modules,
	rust-for-linux, linux-kernel

To preserve the existing user-space ABI, "module_blacklist=" is kept
as a legacy alias pointing to the same module_denylist variable.

This patch addresses the documentation by marking "module_blacklist="
as deprecated in admin-guide/kernel-parameters.txt, and documents
the new "module_denylist=" parameter. All internal symbols, such as
module_is_blacklisted(), have been renamed to use "denylist" and all
log messages now use "denylisted".

Signed-off-by: Aaron Tomlin <atomlin@atomlin.com>
---
 .../admin-guide/kernel-parameters.txt         |  6 +++++-
 include/linux/module.h                        |  2 +-
 init/main.c                                   | 19 ++++++++++---------
 kernel/module/main.c                          |  4 ++--
 4 files changed, 18 insertions(+), 13 deletions(-)

diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt
index 68647ff4bdd2..aae1da11a592 100644
--- a/Documentation/admin-guide/kernel-parameters.txt
+++ b/Documentation/admin-guide/kernel-parameters.txt
@@ -4232,7 +4232,11 @@ Kernel parameters
 			Note that if CONFIG_MODULE_SIG_FORCE is set, that
 			is always true, so this option does nothing.
 
-	module_blacklist=  [KNL] Do not load a comma-separated list of
+	module_blacklist=  [KNL] (deprecated)
+			This parameter has been renamed to module_denylist=.
+			Please use module_denylist= instead.
+
+	module_denylist=  [KNL] Do not load a comma-separated list of
 			modules.  Useful for debugging problem modules.
 
 	mousedev.tap_time=
diff --git a/include/linux/module.h b/include/linux/module.h
index bcc54edbde7d..39386a18285b 100644
--- a/include/linux/module.h
+++ b/include/linux/module.h
@@ -879,7 +879,7 @@ static inline void module_for_each_mod(int(*func)(struct module *mod, void *data
 }
 #endif /* CONFIG_MODULES */
 
-bool module_is_blacklisted(const char *module_name);
+bool module_is_denylisted(const char *module_name);
 
 #ifdef CONFIG_SYSFS
 extern struct kset *module_kset;
diff --git a/init/main.c b/init/main.c
index 0ee3b23bcd2b..aedfe858ae9b 100644
--- a/init/main.c
+++ b/init/main.c
@@ -1347,17 +1347,17 @@ static inline void do_trace_initcall_level(const char *level)
 extern struct initcall_modname __start_initcall_modnames[];
 extern struct initcall_modname __stop_initcall_modnames[];
 
-/* module_blacklist is a comma-separated list of module names */
-static char *module_blacklist;
-bool __init_or_module module_is_blacklisted(const char *module_name)
+/* module_denylist is a comma-separated list of module names */
+static char *module_denylist;
+bool __init_or_module module_is_denylisted(const char *module_name)
 {
 	const char *p;
 	size_t len;
 
-	if (!module_blacklist)
+	if (!module_denylist)
 		return false;
 
-	for (p = module_blacklist; *p; p += len) {
+	for (p = module_denylist; *p; p += len) {
 		len = strcspn(p, ",");
 		if (strlen(module_name) == len && !memcmp(module_name, p, len))
 			return true;
@@ -1366,7 +1366,8 @@ bool __init_or_module module_is_blacklisted(const char *module_name)
 	}
 	return false;
 }
-core_param(module_blacklist, module_blacklist, charp, 0400);
+core_param(module_denylist, module_denylist, charp, 0400);
+core_param(module_blacklist, module_denylist, charp, 0400);
 
 static const char *__init get_builtin_modname(initcall_t fn)
 {
@@ -1383,10 +1384,10 @@ static void __init do_one_initcall_builtin(initcall_t fn)
 {
 	const char *modname;
 
-	if (module_blacklist) {
+	if (module_denylist) {
 		modname = get_builtin_modname(fn);
-		if (modname && module_is_blacklisted(modname)) {
-			pr_info("Skipping initcall for blacklisted built-in module %s\n",
+		if (modname && module_is_denylisted(modname)) {
+			pr_info("Skipping initcall for denylisted built-in module %s\n",
 				modname);
 			return;
 		}
diff --git a/kernel/module/main.c b/kernel/module/main.c
index a9fd6aaedc69..1a58313649d9 100644
--- a/kernel/module/main.c
+++ b/kernel/module/main.c
@@ -3381,8 +3381,8 @@ static int early_mod_check(struct load_info *info, int flags)
 	 * Now that we know we have the correct module name, check
 	 * if it's blacklisted.
 	 */
-	if (module_is_blacklisted(info->name)) {
-		pr_err("Module %s is blacklisted\n", info->name);
+	if (module_is_denylisted(info->name)) {
+		pr_err("Module %s is denylisted\n", info->name);
 		return -EPERM;
 	}
 
-- 
2.55.0


^ permalink raw reply related	[flat|nested] 16+ messages in thread

* Re: [PATCH v10 2/2] module: Rename module_blacklist to module_denylist
  2026-09-03 18:55 ` [PATCH v10 2/2] module: Rename module_blacklist to module_denylist Aaron Tomlin
@ 2026-09-03 19:07   ` sashiko-bot
  2026-09-04 15:13     ` Aaron Tomlin
  0 siblings, 1 reply; 16+ messages in thread
From: sashiko-bot @ 2026-09-03 19:07 UTC (permalink / raw)
  To: Aaron Tomlin; +Cc: ojeda, linux-modules

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:

Pre-existing issues:
- [High] module_is_denylisted() uses exact memory comparison (memcmp) instead of treating dashes and underscores interchangeably, breaking denylisting for module names with dashes.
--

commit 2df01653b8cde06423a15fc161d3df5e8eddbdba
Author: Aaron Tomlin <atomlin@atomlin.com>

module: Rename module_blacklist to module_denylist

This patch renames the module_blacklist boot parameter and internal
variables to module_denylist, keeping the old parameter as a deprecated
legacy alias. Internal symbols and log messages are also updated to
reflect the new nomenclature.

> diff --git a/init/main.c b/init/main.c
> --- a/init/main.c
> +++ b/init/main.c
> @@ -1347,17 +1347,17 @@ static inline void do_trace_initcall_level(const char *level)
[ ... ]
> -bool __init_or_module module_is_blacklisted(const char *module_name)
> +bool __init_or_module module_is_denylisted(const char *module_name)
>  {
>  	const char *p;
>  	size_t len;
>  
> -	if (!module_blacklist)
> +	if (!module_denylist)
>  		return false;
>  
> -	for (p = module_blacklist; *p; p += len) {
> +	for (p = module_denylist; *p; p += len) {
>  		len = strcspn(p, ",");
>  		if (strlen(module_name) == len && !memcmp(module_name, p, len))
>  			return true;

[Severity: High]
This isn't a bug introduced by this patch, but it appears the renamed
module_is_denylisted() function retains a pre-existing logic issue.

Does the exact memory comparison here break denylisting for module names
containing hyphens?

Since the kernel build system converts module names to use underscores
internally (e.g. "my_module"), if an administrator passes a module name
with a hyphen on the command line (like "module_denylist=my-module"),
the strict memcmp() will fail to match the hyphen to the underscore.

This would allow an explicitly denylisted module to load, which could
defeat the administrative mitigation for buggy or malicious modules.
Is there a string comparison utility we should use here instead to treat
hyphens and underscores interchangeably?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260903185557.183224-1-atomlin@atomlin.com?part=2

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH v10 1/2] module: Extend module_blacklist parameter to built-in modules
  2026-09-03 18:55 ` [PATCH v10 1/2] " Aaron Tomlin
@ 2026-09-03 19:13   ` sashiko-bot
  2026-09-04 14:34     ` Aaron Tomlin
  2026-09-03 20:23   ` Aaron Tomlin
  1 sibling, 1 reply; 16+ messages in thread
From: sashiko-bot @ 2026-09-03 19:13 UTC (permalink / raw)
  To: Aaron Tomlin; +Cc: ojeda, linux-modules

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] Missing alignment for `.initcall.modnames` section causes unaligned pointer reads and potential kernel crashes during early boot.
- [Low] The Rust `module!` macro generates invalid code by dereferencing a dynamically sized slice `[u8]` into a fixed-size array `[u8; N]`.
--

commit 828a4fa4f29a0a3c5aba9065f4ba35eb845dddef
Author: Aaron Tomlin <atomlin@atomlin.com>

module: Extend module_blacklist parameter to built-in modules

This commit extends the module_blacklist command-line parameter to apply to
built-in modules. It achieves this by creating a new .initcall.modnames
section to map initcalls to module names, intercepting blacklisted builtin
initcalls during early boot.

> diff --git a/include/asm-generic/vmlinux.lds.h b/include/asm-generic/vmlinux.lds.h
> index b2988aa12f664..7490278b7a2df 100644
> --- a/include/asm-generic/vmlinux.lds.h
> +++ b/include/asm-generic/vmlinux.lds.h
> @@ -734,7 +734,8 @@
>  	EARLYCON_TABLE()						\
>  	LSM_TABLE()							\
>  	EARLY_LSM_TABLE()						\
> -	KUNIT_INIT_TABLE()
> +	KUNIT_INIT_TABLE()						\
> +	BOUNDED_SECTION_BY(.initcall.modnames, _initcall_modnames)

[Severity: High]
Does this section declaration need an alignment constraint? Without a preceding
. = ALIGN(8); or similar, the __start_initcall_modnames label might capture
the unaligned location counter before padding is inserted by the linker.

When iterating over this section during early boot in do_initcall_level(),
could the kernel read padding bytes as garbage pointers, breaking the
builtin module blacklist feature or causing an unaligned memory access panic?

[ ... ]

> diff --git a/rust/macros/module.rs b/rust/macros/module.rs
> index bc7027f8dbb2c..a96157598197f 100644
> --- a/rust/macros/module.rs
> +++ b/rust/macros/module.rs
[ ... ]
> @@ -591,6 +594,20 @@ pub extern "C" fn cleanup_module() {
>                  #[cfg(CONFIG_HAVE_ARCH_PREL32_RELOCATIONS)]
>                  ::core::arch::global_asm!(#global_asm);
>  
> +                #[cfg(not(MODULE))]
> +                #[used(compiler)]
> +                #[link_section = ".init.rodata"]
> +                static #ident_modname_str: [u8; #name_len] = *#name_cstr.to_bytes_with_nul();

[Severity: Low]
Will this cause a compilation failure for built-in Rust modules?

It attempts to initialize a fixed-size array by dereferencing a dynamically
sized slice returned by to_bytes_with_nul(). This could break the build when
CONFIG_MODULES=n or when a Rust module is configured as built-in.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260903185557.183224-1-atomlin@atomlin.com?part=1

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH v10 1/2] module: Extend module_blacklist parameter to built-in modules
  2026-09-03 18:55 ` [PATCH v10 1/2] " Aaron Tomlin
  2026-09-03 19:13   ` sashiko-bot
@ 2026-09-03 20:23   ` Aaron Tomlin
  1 sibling, 0 replies; 16+ messages in thread
From: Aaron Tomlin @ 2026-09-03 20:23 UTC (permalink / raw)
  To: arnd, mcgrof, petr.pavlu, da.gomez, samitolvanen, peterz, ojeda
  Cc: akpm, mhiramat, boqun, neelx, da.anzani, sean, chjohnst, steve,
	mproche, nick.lane, linux-arch, linux-modules, rust-for-linux,
	linux-kernel

On Thu, Sep 03, 2026 at 02:55:56PM -0400, Aaron Tomlin wrote:
> Currently, the "module_blacklist=" command-line parameter only applies
> to loadable modules. If a module is built-in, the parameter is silently
> ignored. This patch extends the blacklisting functionality to built-in
> modules by intercepting their initialisation routines during early boot.
> 
> To achieve this, we introduce a new ".initcall.modnames" memory section.
> For each built-in module, we use a standard C structure (i.e., struct
> initcall_modname) to map its initcall function pointer to its associated
> KBUILD_MODNAME string. This mapping is restricted only to files implementing
> built-in modules via module_init() to avoid mapping core kernel subsystems
> and save memory.
> 
> During boot, built-in initcalls are executed sequentially via
> do_initcall_level() and do_pre_smp_initcalls(). We introduce a new
> wrapper function, do_one_initcall_builtin(), to cross-reference the
> initcall function pointer against the ".initcall.modnames" table. If
> a match is found and the module is present in the blacklist, the
> initcall is skipped.
> 
> To make the blacklist functional on monolithic kernels, the command-line
> parameter parsing and the module_is_blacklisted() lookup function are
> decoupled from the loadable module subsystem and moved to init/main.c.
> This enables "module_blacklist=" to intercept built-in modules even on
> kernels built with CONFIG_MODULES=n.
> 
> Signed-off-by: Aaron Tomlin <atomlin@atomlin.com>
> ---
>  include/asm-generic/vmlinux.lds.h |  3 +-
>  include/linux/init.h              | 25 +++++++++++++-
>  include/linux/module.h            |  4 ++-
>  init/main.c                       | 54 +++++++++++++++++++++++++++++--
>  kernel/module/main.c              | 23 +------------
>  rust/bindings/bindings_helper.h   |  1 +
>  rust/macros/module.rs             | 17 ++++++++++
>  7 files changed, 100 insertions(+), 27 deletions(-)
> 
> diff --git a/include/asm-generic/vmlinux.lds.h b/include/asm-generic/vmlinux.lds.h
> index b2988aa12f66..7490278b7a2d 100644
> --- a/include/asm-generic/vmlinux.lds.h
> +++ b/include/asm-generic/vmlinux.lds.h
> @@ -734,7 +734,8 @@
>  	EARLYCON_TABLE()						\
>  	LSM_TABLE()							\
>  	EARLY_LSM_TABLE()						\
> -	KUNIT_INIT_TABLE()
> +	KUNIT_INIT_TABLE()						\
> +	BOUNDED_SECTION_BY(.initcall.modnames, _initcall_modnames)
>  
>  #define INIT_TEXT							\
>  	*(.init.text .init.text.*)					\
> diff --git a/include/linux/init.h b/include/linux/init.h
> index 6326c61e2332..833b837ce11d 100644
> --- a/include/linux/init.h
> +++ b/include/linux/init.h
> @@ -252,6 +252,7 @@ extern struct module __this_module;
>  #endif
>  
>  #ifdef CONFIG_HAVE_ARCH_PREL32_RELOCATIONS
> +#define __initcall_fn_ptr(fn, __iid, id)	__initcall_stub(fn, __iid, id)
>  #define ____define_initcall(fn, __stub, __name, __sec)		\
>  	__define_initcall_stub(__stub, fn)			\
>  	asm(".section	\"" __sec "\", \"a\"		\n"	\
> @@ -260,6 +261,7 @@ extern struct module __this_module;
>  	    ".previous					\n");	\
>  	static_assert(__same_type(initcall_t, &fn));
>  #else
> +#define __initcall_fn_ptr(fn, __iid, id)	fn
>  #define ____define_initcall(fn, __unused, __name, __sec)	\
>  	static initcall_t __name __used 			\
>  		__attribute__((__section__(__sec))) = fn;
> @@ -271,7 +273,28 @@ extern struct module __this_module;
>  		__initcall_name(initcall, __iid, id),		\
>  		__initcall_section(__sec, __iid))
>  
> -#define ___define_initcall(fn, id, __sec)			\
> +struct initcall_modname {
> +	initcall_t initcall_fn;
> +	const char *modname;
> +};
> +
> +#define ____define_initcall_modname(fn, id, __sec, __iid)		\
> +	__unique_initcall(fn, id, __sec, __iid)				\
> +	static const char __initstr_##fn[] __used __aligned(1)		\
> +		__section(".init.rodata") = KBUILD_MODNAME;		\
> +	static const struct initcall_modname __modname_##fn __used	\
> +		__section(".initcall.modnames")				\
> +		__aligned(__alignof__(struct initcall_modname)) = {	\
> +			.initcall_fn = __initcall_fn_ptr(fn, __iid, id),\
> +			.modname = __initstr_##fn			\
> +		};
> +
> +#define __define_initcall_modname(fn, id)				\
> +	____define_initcall_modname(fn, id, .initcall##id, __initcall_id(fn))
> +
> +#define __builtin_module_initcall(fn)	__define_initcall_modname(fn, 6)
> +
> +#define ___define_initcall(fn, id, __sec)				\
>  	__unique_initcall(fn, id, __sec, __initcall_id(fn))
>  
>  #define __define_initcall(fn, id) ___define_initcall(fn, id, .initcall##id)
> diff --git a/include/linux/module.h b/include/linux/module.h
> index 96cc98568eea..bcc54edbde7d 100644
> --- a/include/linux/module.h
> +++ b/include/linux/module.h
> @@ -86,7 +86,7 @@ extern void cleanup_module(void);
>   * builtin) or at module insertion time (if a module).  There can only
>   * be one per module.
>   */
> -#define module_init(x)	__initcall(x);
> +#define module_init(x)	__builtin_module_initcall(x);
>  
>  /**
>   * module_exit() - driver exit entry point
> @@ -879,6 +879,8 @@ static inline void module_for_each_mod(int(*func)(struct module *mod, void *data
>  }
>  #endif /* CONFIG_MODULES */
>  
> +bool module_is_blacklisted(const char *module_name);
> +
>  #ifdef CONFIG_SYSFS
>  extern struct kset *module_kset;
>  extern const struct kobj_type module_ktype;
> diff --git a/init/main.c b/init/main.c
> index 2613d3f9b3ce..0ee3b23bcd2b 100644
> --- a/init/main.c
> +++ b/init/main.c
> @@ -1344,6 +1344,56 @@ static inline void do_trace_initcall_level(const char *level)
>  }
>  #endif /* !TRACEPOINTS_ENABLED */
>  
> +extern struct initcall_modname __start_initcall_modnames[];
> +extern struct initcall_modname __stop_initcall_modnames[];
> +
> +/* module_blacklist is a comma-separated list of module names */
> +static char *module_blacklist;
> +bool __init_or_module module_is_blacklisted(const char *module_name)
> +{
> +	const char *p;
> +	size_t len;
> +
> +	if (!module_blacklist)
> +		return false;
> +
> +	for (p = module_blacklist; *p; p += len) {
> +		len = strcspn(p, ",");
> +		if (strlen(module_name) == len && !memcmp(module_name, p, len))
> +			return true;
> +		if (p[len] == ',')
> +			len++;
> +	}
> +	return false;
> +}
> +core_param(module_blacklist, module_blacklist, charp, 0400);
> +
> +static const char *__init get_builtin_modname(initcall_t fn)
> +{
> +	struct initcall_modname *p;
> +
> +	for (p = __start_initcall_modnames; p < __stop_initcall_modnames; p++) {
> +		if (p->initcall_fn == fn)
> +			return p->modname;
> +	}
> +	return NULL;
> +}
> +
> +static void __init do_one_initcall_builtin(initcall_t fn)
> +{
> +	const char *modname;
> +
> +	if (module_blacklist) {
> +		modname = get_builtin_modname(fn);
> +		if (modname && module_is_blacklisted(modname)) {
> +			pr_info("Skipping initcall for blacklisted built-in module %s\n",
> +				modname);
> +			return;
> +		}
> +	}
> +	do_one_initcall(fn);
> +}
> +
>  int __init_or_module do_one_initcall(initcall_t fn)
>  {
>  	int count = preempt_count();
> @@ -1416,7 +1466,7 @@ static void __init do_initcall_level(int level, char *command_line)
>  
>  	do_trace_initcall_level(initcall_level_names[level]);
>  	for (fn = initcall_levels[level]; fn < initcall_levels[level+1]; fn++)
> -		do_one_initcall(initcall_from_entry(fn));
> +		do_one_initcall_builtin(initcall_from_entry(fn));
>  }
>  
>  static void __init do_initcalls(void)
> @@ -1461,7 +1511,7 @@ static void __init do_pre_smp_initcalls(void)
>  
>  	do_trace_initcall_level("early");
>  	for (fn = __initcall_start; fn < __initcall0_start; fn++)
> -		do_one_initcall(initcall_from_entry(fn));
> +		do_one_initcall_builtin(initcall_from_entry(fn));
>  }
>  
>  static int run_init_process(const char *init_filename)
> diff --git a/kernel/module/main.c b/kernel/module/main.c
> index d0e1e0bd2ad0..a9fd6aaedc69 100644
> --- a/kernel/module/main.c
> +++ b/kernel/module/main.c
> @@ -2930,27 +2930,6 @@ int __weak module_frob_arch_sections(Elf_Ehdr *hdr,
>  	return 0;
>  }
>  
> -/* module_blacklist is a comma-separated list of module names */
> -static char *module_blacklist;
> -static bool blacklisted(const char *module_name)
> -{
> -	const char *p;
> -	size_t len;
> -
> -	if (!module_blacklist)
> -		return false;
> -
> -	for (p = module_blacklist; *p; p += len) {
> -		len = strcspn(p, ",");
> -		if (strlen(module_name) == len && !memcmp(module_name, p, len))
> -			return true;
> -		if (p[len] == ',')
> -			len++;
> -	}
> -	return false;
> -}
> -core_param(module_blacklist, module_blacklist, charp, 0400);
> -
>  static struct module *layout_and_allocate(struct load_info *info, int flags)
>  {
>  	struct module *mod;
> @@ -3402,7 +3381,7 @@ static int early_mod_check(struct load_info *info, int flags)
>  	 * Now that we know we have the correct module name, check
>  	 * if it's blacklisted.
>  	 */
> -	if (blacklisted(info->name)) {
> +	if (module_is_blacklisted(info->name)) {
>  		pr_err("Module %s is blacklisted\n", info->name);
>  		return -EPERM;
>  	}
> diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h
> index 4b31aa7f432f..1075b26e53ac 100644
> --- a/rust/bindings/bindings_helper.h
> +++ b/rust/bindings/bindings_helper.h
> @@ -63,6 +63,7 @@
>  #include <linux/fwctl.h>
>  #include <linux/fs.h>
>  #include <linux/i2c.h>
> +#include <linux/init.h>
>  #include <linux/interrupt.h>
>  #include <linux/io-pgtable.h>
>  #include <linux/ioport.h>
> diff --git a/rust/macros/module.rs b/rust/macros/module.rs
> index bc7027f8dbb2..a96157598197 100644
> --- a/rust/macros/module.rs
> +++ b/rust/macros/module.rs
> @@ -480,6 +480,8 @@ pub(crate) fn module(info: ModuleInfo) -> Result<TokenStream> {
>      let ident_init = format_ident!("__{ident}_init");
>      let ident_exit = format_ident!("__{ident}_exit");
>      let ident_initcall = format_ident!("__{ident}_initcall");
> +    let ident_modname = format_ident!("__{ident}_modname");
> +    let ident_modname_str = format_ident!("__{ident}_modname_str");
>      let initcall_section = ".initcall6.init";
>  
>      let global_asm = format!(
> @@ -491,6 +493,7 @@ pub(crate) fn module(info: ModuleInfo) -> Result<TokenStream> {
>      );
>  
>      let name_cstr = CString::new(name.value()).expect("name contains NUL-terminator");
> +    let name_len = name_cstr.to_bytes_with_nul().len();
>  
>      Ok(quote! {
>          /// The module name.
> @@ -591,6 +594,20 @@ pub extern "C" fn cleanup_module() {
>                  #[cfg(CONFIG_HAVE_ARCH_PREL32_RELOCATIONS)]
>                  ::core::arch::global_asm!(#global_asm);
>  
> +                #[cfg(not(MODULE))]
> +                #[used(compiler)]
> +                #[link_section = ".init.rodata"]
> +                static #ident_modname_str: [u8; #name_len] = *#name_cstr.to_bytes_with_nul();
> +
> +                #[cfg(not(MODULE))]
> +                #[used(compiler)]
> +                #[link_section = ".initcall.modnames"]
> +                static #ident_modname: ::kernel::bindings::initcall_modname =
> +                    ::kernel::bindings::initcall_modname {
> +                        initcall_fn: Some(#ident_init),
> +                        modname: #ident_modname_str.as_ptr().cast(),
> +                    };
> +
>                  #[cfg(not(MODULE))]
>                  #[no_mangle]
>                  pub extern "C" fn #ident_init() -> ::kernel::ffi::c_int {
> -- 
> 2.55.0
> 

Hi Petr,

Please ignore.

I forgot to restore ". = ALIGN(8)" to ensures that the location counter
within .init.data is aligned to an 8-byte boundary immediately before
__start_initcall_modnames is captured.

I will send another iteration.


Kind regards,
-- 
Aaron Tomlin

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH v10 0/2] module: Extend module_blacklist parameter to built-in modules
  2026-09-03 18:55 [PATCH v10 0/2] module: Extend module_blacklist parameter to built-in modules Aaron Tomlin
  2026-09-03 18:55 ` [PATCH v10 1/2] " Aaron Tomlin
  2026-09-03 18:55 ` [PATCH v10 2/2] module: Rename module_blacklist to module_denylist Aaron Tomlin
@ 2026-09-03 20:29 ` Andrew Morton
  2026-09-04 14:59   ` Aaron Tomlin
  2 siblings, 1 reply; 16+ messages in thread
From: Andrew Morton @ 2026-09-03 20:29 UTC (permalink / raw)
  To: Aaron Tomlin
  Cc: arnd, mcgrof, petr.pavlu, da.gomez, samitolvanen, peterz, ojeda,
	mhiramat, boqun, neelx, da.anzani, sean, chjohnst, steve, mproche,
	nick.lane, linux-arch, linux-modules, rust-for-linux,
	linux-kernel

On Thu,  3 Sep 2026 14:55:55 -0400 Aaron Tomlin <atomlin@atomlin.com> wrote:

> Currently, the "module_blacklist=" command-line parameter only applies to
> loadable modules. If a module is built-in, the parameter is silently
> ignored. This patch series extends the blacklisting functionality to
> built-in modules by intercepting their initialisation routines during early
> boot.

Why?  What are the use-cases and what is the value of this change
to our users?

Important, so please don't skimp on the details.

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH v10 1/2] module: Extend module_blacklist parameter to built-in modules
  2026-09-03 19:13   ` sashiko-bot
@ 2026-09-04 14:34     ` Aaron Tomlin
  2026-09-06 13:39       ` Gary Guo
  0 siblings, 1 reply; 16+ messages in thread
From: Aaron Tomlin @ 2026-09-04 14:34 UTC (permalink / raw)
  To: sashiko-reviews, petr.pavlu, gary; +Cc: ojeda, linux-modules

On Thu, Sep 03, 2026 at 07:13:02PM +0000, sashiko-bot@kernel.org wrote:
> commit 828a4fa4f29a0a3c5aba9065f4ba35eb845dddef
> Author: Aaron Tomlin <atomlin@atomlin.com>
> 
> module: Extend module_blacklist parameter to built-in modules
> 
> This commit extends the module_blacklist command-line parameter to apply to
> built-in modules. It achieves this by creating a new .initcall.modnames
> section to map initcalls to module names, intercepting blacklisted builtin
> initcalls during early boot.
> 
> > diff --git a/include/asm-generic/vmlinux.lds.h b/include/asm-generic/vmlinux.lds.h
> > index b2988aa12f664..7490278b7a2df 100644
> > --- a/include/asm-generic/vmlinux.lds.h
> > +++ b/include/asm-generic/vmlinux.lds.h
> > @@ -734,7 +734,8 @@
> >  	EARLYCON_TABLE()						\
> >  	LSM_TABLE()							\
> >  	EARLY_LSM_TABLE()						\
> > -	KUNIT_INIT_TABLE()
> > +	KUNIT_INIT_TABLE()						\
> > +	BOUNDED_SECTION_BY(.initcall.modnames, _initcall_modnames)
> 
> [Severity: High]
> Does this section declaration need an alignment constraint? Without a preceding
> . = ALIGN(8); or similar, the __start_initcall_modnames label might capture
> the unaligned location counter before padding is inserted by the linker.
> 
> When iterating over this section during early boot in do_initcall_level(),
> could the kernel read padding bytes as garbage pointers, breaking the
> builtin module blacklist feature or causing an unaligned memory access panic?

Hi Petr,

I forgot to restore ". = ALIGN(8)" in this iteration.

The following ensures the location counter . within .init.data is aligned
to an 8-byte boundary before __start_initcall_modnames = . is captured,
preventing any linker padding from preceding the first element of the
table.

Are you okay with the following?

    --- a/include/asm-generic/vmlinux.lds.h
    +++ b/include/asm-generic/vmlinux.lds.h
    @@ -735,6 +735,7 @@
         LSM_TABLE()                            \
         EARLY_LSM_TABLE()                        \
         KUNIT_INIT_TABLE()                        \
    +    . = ALIGN(8);                            \
         BOUNDED_SECTION_BY(.initcall.modnames, _initcall_modnames)

     #define INIT_TEXT                            \


> > diff --git a/rust/macros/module.rs b/rust/macros/module.rs
> > index bc7027f8dbb2c..a96157598197f 100644
> > --- a/rust/macros/module.rs
> > +++ b/rust/macros/module.rs
> [ ... ]
> > @@ -591,6 +594,20 @@ pub extern "C" fn cleanup_module() {
> >                  #[cfg(CONFIG_HAVE_ARCH_PREL32_RELOCATIONS)]
> >                  ::core::arch::global_asm!(#global_asm);
> >  
> > +                #[cfg(not(MODULE))]
> > +                #[used(compiler)]
> > +                #[link_section = ".init.rodata"]
> > +                static #ident_modname_str: [u8; #name_len] = *#name_cstr.to_bytes_with_nul();
> 
> [Severity: Low]
> Will this cause a compilation failure for built-in Rust modules?
> 
> It attempts to initialize a fixed-size array by dereferencing a dynamically
> sized slice returned by to_bytes_with_nul(). This could break the build when
> CONFIG_MODULES=n or when a Rust module is configured as built-in.

Hi Gary,

Calling .to_bytes_with_nul() on &CStr returns a slice reference (&[u8]), so
dereferencing it yields an unsized slice ([u8]).

To resolve this, we can follow the pattern already used in
rust/macros/module.rs for emitting '.modinfo' entries construct a byte
string literal at macro-expansion time using Literal::byte_string(). In
Rust, a byte string literal (b"...\0") has type &'static [u8; N], which can
be cleanly dereferenced with '*' into a fixed-size array [u8; N].

Would the following be appropriate?

diff --git a/rust/macros/module.rs b/rust/macros/module.rs
index a96157598197..67e3df8d4389 100644
--- a/rust/macros/module.rs
+++ b/rust/macros/module.rs
@@ -493,7 +493,9 @@ pub(crate) fn module(info: ModuleInfo) -> Result<TokenStream> {
     );

     let name_cstr = CString::new(name.value()).expect("name contains NUL-terminator");
-    let name_len = name_cstr.to_bytes_with_nul().len();
+    let name_bytes = name_cstr.to_bytes_with_nul();
+    let name_len = name_bytes.len();
+    let name_byte_literal = Literal::byte_string(name_bytes);

     Ok(quote! {
         /// The module name.
@@ -596,7 +598,7 @@ pub extern "C" fn cleanup_module() {
                 #[cfg(not(MODULE))]
                 #[used(compiler)]
                 #[link_section = ".init.rodata"]
-                static #ident_modname_str: [u8; #name_len] = *#name_cstr.to_bytes_with_nul();
+                static #ident_modname_str: [u8; #name_len] = *#name_byte_literal;

                 #[cfg(not(MODULE))]
                 #[used(compiler)]


Kind regards,
-- 
Aaron Tomlin

^ permalink raw reply related	[flat|nested] 16+ messages in thread

* Re: [PATCH v10 0/2] module: Extend module_blacklist parameter to built-in modules
  2026-09-03 20:29 ` [PATCH v10 0/2] module: Extend module_blacklist parameter to built-in modules Andrew Morton
@ 2026-09-04 14:59   ` Aaron Tomlin
  2026-09-06  0:28     ` Andrew Morton
  0 siblings, 1 reply; 16+ messages in thread
From: Aaron Tomlin @ 2026-09-04 14:59 UTC (permalink / raw)
  To: Andrew Morton
  Cc: arnd, mcgrof, petr.pavlu, da.gomez, samitolvanen, peterz, ojeda,
	mhiramat, boqun, neelx, da.anzani, sean, chjohnst, steve, mproche,
	nick.lane, rishil1999, linux-arch, linux-modules, rust-for-linux,
	linux-kernel

On Thu, Sep 03, 2026 at 01:29:24PM -0700, Andrew Morton wrote:
> On Thu,  3 Sep 2026 14:55:55 -0400 Aaron Tomlin <atomlin@atomlin.com> wrote:
> 
> > Currently, the "module_blacklist=" command-line parameter only applies to
> > loadable modules. If a module is built-in, the parameter is silently
> > ignored. This patch series extends the blacklisting functionality to
> > built-in modules by intercepting their initialisation routines during early
> > boot.
> 
> Why?  What are the use-cases and what is the value of this change
> to our users?
> 
> Important, so please don't skimp on the details.

Hi Andrew,

Thanks for asking.

Here is the rationale, concrete use cases, and the value this hopefully
brings to users and administrators.

1.  The Core Problem and User Experience Gap
============================================

Today, module_blacklist= works strictly on loadable modules. When a user or
system administrator encounters a driver bug, hang during device probe, or
hardware fault during boot, the natural and widely documented remedy is to
pass module_blacklist=[driver] via the bootloader (GRUB, systemd-boot,
etc.).

However, if that driver is built into the kernel, the parameter is silently
ignored. The kernel proceeds to run the driver's initialisation routine
anyway, leading to the same panic, hang, or hardware misbehaviour.

From the user's standpoint, whether a driver was packaged by their
distribution or built by their provider as =m or =y is an internal
implementation detail. Having module_blacklist= silently fail solely based
on compilation configuration violates the principle of least surprise and
complicates system recovery.

2.  Why initcall_blacklist= is not an adequate substitute
=========================================================

The kernel does provide initcall_blacklist=, but it is impractical for
general users, sysadmins, and automated fleet management tools for several
reasons:

    Obscure symbol names

        - initcall_blacklist= requires the exact function name of the
          initcall (e.g., snb_pci_uarch_init and e1000_init_module). Users
          typically know the module name, not the internal function name.

    Internal instability

        - Initcall function names are internal kernel implementation
          details. They change across kernel releases, refactors, or macro
          rewrites, making it impossible to write stable bootloader
          configurations or recovery documentation across multiple kernel
          versions.

    Mangled names (Rust)

        - For modern drivers written in Rust, the initcall symbol names are
          compiler-mangled symbols (e.g. "_RNvX"), making
          initcall_blacklist= practically impossible for a human user to
          specify manually at a boot prompt.

module_blacklist= (or module_denylist=) resolves this by allowing users to
specify the canonical, user-facing module name (KBUILD_MODNAME) that they
already know.

3.  Concrete use cases
======================

    A.  Disaster recovery and triage on production systems

        When a kernel update introduces a regression in a built-in driver
        (e.g., a storage controller), administrators need a way to bypass
        that driver at boot time to get the system into a usable emergency
        shell or collect diagnostic logs, without having to rebuild the
        kernel on another machine.

    B.  Monolithic/Hardened environments (CONFIG_MODULES=n)

        In security-sensitive environments, kernels are frequently compiled
        without loadable module support (CONFIG_MODULES=n) to eliminate
        module loading attack vectors. On these systems, all drivers are
        built-in. If a hardware erratum or firmware bug triggers a hang in
        a built-in driver, administrators previously had no
        module-name-based mechanism to disable the offending driver.

    C. Hardware Errata and Conflicting Devices
       On systems with buggy firmware or conflicting device IDs where two drivers
       attempt to bind to the same hardware, users can prevent the conflicting
       built-in driver from initializing without patching and recompiling the
       entire kernel image.

4. Implementation and Overhead Considerations
=============================================

We took great care to ensure this change introduces virtually zero runtime
overhead:

    Scoped to module_init():
   
        - Only built-in drivers that explicitly use module_init() are
          tracked. Core kernel subsystems using core_initcall(),
          subsys_initcall(), etc. are unaffected.

    Zero Resident Memory:
   
        - The metadata table (.initcall.modnames) and the module name
          strings (.init.rodata) are placed entirely in init sections and
          are completely freed from memory after boot via free_initmem().

    Fast Path:
   
        - During boot, if neither module_blacklist= nor module_denylist=
          was supplied on the kernel command line, the lookup is bypassed
          entirely.


In summary, this patch brings parity between modular and built-in drivers,
removes a pain point in boot-time disaster recovery, and provides users
with a predictable, consistent interface.

Thanks,
--
Aaron Tomlin

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH v10 2/2] module: Rename module_blacklist to module_denylist
  2026-09-03 19:07   ` sashiko-bot
@ 2026-09-04 15:13     ` Aaron Tomlin
  0 siblings, 0 replies; 16+ messages in thread
From: Aaron Tomlin @ 2026-09-04 15:13 UTC (permalink / raw)
  To: sashiko-reviews, petr.pavlu; +Cc: ojeda, linux-modules

On Thu, Sep 03, 2026 at 07:07:35PM +0000, sashiko-bot@kernel.org wrote:
> commit 2df01653b8cde06423a15fc161d3df5e8eddbdba
> Author: Aaron Tomlin <atomlin@atomlin.com>
> 
> module: Rename module_blacklist to module_denylist
> 
> This patch renames the module_blacklist boot parameter and internal
> variables to module_denylist, keeping the old parameter as a deprecated
> legacy alias. Internal symbols and log messages are also updated to
> reflect the new nomenclature.
> 
> > diff --git a/init/main.c b/init/main.c
> > --- a/init/main.c
> > +++ b/init/main.c
> > @@ -1347,17 +1347,17 @@ static inline void do_trace_initcall_level(const char *level)
> [ ... ]
> > -bool __init_or_module module_is_blacklisted(const char *module_name)
> > +bool __init_or_module module_is_denylisted(const char *module_name)
> >  {
> >  	const char *p;
> >  	size_t len;
> >  
> > -	if (!module_blacklist)
> > +	if (!module_denylist)
> >  		return false;
> >  
> > -	for (p = module_blacklist; *p; p += len) {
> > +	for (p = module_denylist; *p; p += len) {
> >  		len = strcspn(p, ",");
> >  		if (strlen(module_name) == len && !memcmp(module_name, p, len))
> >  			return true;
> 
> [Severity: High]
> This isn't a bug introduced by this patch, but it appears the renamed
> module_is_denylisted() function retains a pre-existing logic issue.
> 
> Does the exact memory comparison here break denylisting for module names
> containing hyphens?
> 
> Since the kernel build system converts module names to use underscores
> internally (e.g. "my_module"), if an administrator passes a module name
> with a hyphen on the command line (like "module_denylist=my-module"),
> the strict memcmp() will fail to match the hyphen to the underscore.
> 
> This would allow an explicitly denylisted module to load, which could
> defeat the administrative mitigation for buggy or malicious modules.
> Is there a string comparison utility we should use here instead to treat
> hyphens and underscores interchangeably?

Since the build system converts module names to underscores internally,
passing hyphenated names on the command line
(e.g., "module_blacklist=my-module") currently fails to match due to the
strict memcmp(), allowing the module to load.

The kernel already provides parameqn() in <linux/moduleparam.h>, which
treats '-' and '_' interchangeably. Using parameqn(module_name, p, len)
resolves this directly.

Since this issue dates back to commit be7de5f91fdc ("modules: Add kernel
parameter to blacklist modules") in Linux 4.8, I will split this out into a
separate prerequisite patch with a Fixes: tag and CC stable, placing it as
patch 1/3 in the series.

Kind regards,
-- 
Aaron Tomlin

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH v10 0/2] module: Extend module_blacklist parameter to built-in modules
  2026-09-04 14:59   ` Aaron Tomlin
@ 2026-09-06  0:28     ` Andrew Morton
  2026-09-06  9:56       ` Arnd Bergmann
  2026-09-06 13:15       ` Aaron Tomlin
  0 siblings, 2 replies; 16+ messages in thread
From: Andrew Morton @ 2026-09-06  0:28 UTC (permalink / raw)
  To: Aaron Tomlin
  Cc: arnd, mcgrof, petr.pavlu, da.gomez, samitolvanen, peterz, ojeda,
	mhiramat, boqun, neelx, da.anzani, sean, chjohnst, steve, mproche,
	nick.lane, rishil1999, linux-arch, linux-modules, rust-for-linux,
	linux-kernel

On Fri, 4 Sep 2026 10:59:38 -0400 Aaron Tomlin <atomlin@atomlin.com> wrote:

> On Thu, Sep 03, 2026 at 01:29:24PM -0700, Andrew Morton wrote:
> > On Thu,  3 Sep 2026 14:55:55 -0400 Aaron Tomlin <atomlin@atomlin.com> wrote:
> > 
> > > Currently, the "module_blacklist=" command-line parameter only applies to
> > > loadable modules. If a module is built-in, the parameter is silently
> > > ignored. This patch series extends the blacklisting functionality to
> > > built-in modules by intercepting their initialisation routines during early
> > > boot.
> > 
> > Why?  What are the use-cases and what is the value of this change
> > to our users?
> > 
> > Important, so please don't skimp on the details.
> 
> Hi Andrew,
> 
> Thanks for asking.
> 
> Here is the rationale, concrete use cases, and the value this hopefully
> brings to users and administrators.
>
> ...
>
> In summary, this patch brings parity between modular and built-in drivers,
> removes a pain point in boot-time disaster recovery, and provides users
> with a predictable, consistent interface.

Really helpful, thanks.  Please add this to the [0/N] and maintain it.

I don't really know who are the potential audience for this change, nor
how to attract their attention.  Greg might have some insights but he
wasn't cc'ed.

Let's leave it a week to see if there's feedback then resend with these
adjustments?


^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH v10 0/2] module: Extend module_blacklist parameter to built-in modules
  2026-09-06  0:28     ` Andrew Morton
@ 2026-09-06  9:56       ` Arnd Bergmann
  2026-09-06 12:33         ` Gary Guo
  2026-09-06 14:08         ` Aaron Tomlin
  2026-09-06 13:15       ` Aaron Tomlin
  1 sibling, 2 replies; 16+ messages in thread
From: Arnd Bergmann @ 2026-09-06  9:56 UTC (permalink / raw)
  To: Andrew Morton, Aaron Tomlin
  Cc: Luis Chamberlain, Petr Pavlu, da.gomez, Sami Tolvanen,
	Peter Zijlstra, Miguel Ojeda, Masami Hiramatsu, Boqun Feng, neelx,
	da.anzani, sean, chjohnst, steve, mproche, nick.lane, rishil1999,
	Linux-Arch, linux-modules, rust-for-linux, linux-kernel

On Sun, Sep 6, 2026, at 02:28, Andrew Morton wrote:
> On Fri, 4 Sep 2026 10:59:38 -0400 Aaron Tomlin <atomlin@atomlin.com> wrote:
>>
>> In summary, this patch brings parity between modular and built-in drivers,
>> removes a pain point in boot-time disaster recovery, and provides users
>> with a predictable, consistent interface.
>
> Really helpful, thanks.  Please add this to the [0/N] and maintain it.
>
> I don't really know who are the potential audience for this change, nor
> how to attract their attention.  Greg might have some insights but he
> wasn't cc'ed.
>
> Let's leave it a week to see if there's feedback then resend with these
> adjustments?

FWIW, I previously asked the same question on this series
and still don't find the explanation lacking. Obviously,
consistency is good, but the added complexity doesn't feel
worth it here, given that this still has most of the same
problems as the existing "initcall_blacklist=" option [1].

I think patch 2/2 would be fine on its own, but for 1/2
I have yet to see a single example of a real-world problem
that could have been solved by this but not using
initcall_blacklist. In distro kernels, almost everything
is already a loadable module, while users with custom
kernels could easily turn off the drivers they don't want
or disable the initcall by name.

      Arnd

[1] https://lore.kernel.org/all/78ec1da5-11ae-4c35-a08e-cc88a5d083f4@app.fastmail.com/

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH v10 0/2] module: Extend module_blacklist parameter to built-in modules
  2026-09-06  9:56       ` Arnd Bergmann
@ 2026-09-06 12:33         ` Gary Guo
  2026-09-06 14:08         ` Aaron Tomlin
  1 sibling, 0 replies; 16+ messages in thread
From: Gary Guo @ 2026-09-06 12:33 UTC (permalink / raw)
  To: Arnd Bergmann, Andrew Morton, Aaron Tomlin
  Cc: Luis Chamberlain, Petr Pavlu, da.gomez, Sami Tolvanen,
	Peter Zijlstra, Miguel Ojeda, Masami Hiramatsu, Boqun Feng, neelx,
	da.anzani, sean, chjohnst, steve, mproche, nick.lane, rishil1999,
	Linux-Arch, linux-modules, rust-for-linux, linux-kernel

On Sun Sep 6, 2026 at 10:56 AM BST, Arnd Bergmann wrote:
> On Sun, Sep 6, 2026, at 02:28, Andrew Morton wrote:
>> On Fri, 4 Sep 2026 10:59:38 -0400 Aaron Tomlin <atomlin@atomlin.com> wrote:
>>>
>>> In summary, this patch brings parity between modular and built-in drivers,
>>> removes a pain point in boot-time disaster recovery, and provides users
>>> with a predictable, consistent interface.
>>
>> Really helpful, thanks.  Please add this to the [0/N] and maintain it.
>>
>> I don't really know who are the potential audience for this change, nor
>> how to attract their attention.  Greg might have some insights but he
>> wasn't cc'ed.
>>
>> Let's leave it a week to see if there's feedback then resend with these
>> adjustments?
>
> FWIW, I previously asked the same question on this series
> and still don't find the explanation lacking. Obviously,
> consistency is good, but the added complexity doesn't feel
> worth it here, given that this still has most of the same
> problems as the existing "initcall_blacklist=" option [1].

Yeah, I like the better consistency, which is why I propose unifying handling
for both built-in modules and loadable ones in
https://lore.kernel.org/all/DKNWEO3BOUOL.9COZBXZIGS6A@garyguo.net/.

IMO if we can unify them, then we get something that is both consistent and
*less* complexity.

The current solution makes thing more complex with only apparent consistency
(i.e. user observe the consistency from kernel parameter, but the internal
handling are two separate mechanisms), which should require a very strong reason
to pursue.

Best,
Gary

>
> I think patch 2/2 would be fine on its own, but for 1/2
> I have yet to see a single example of a real-world problem
> that could have been solved by this but not using
> initcall_blacklist. In distro kernels, almost everything
> is already a loadable module, while users with custom
> kernels could easily turn off the drivers they don't want
> or disable the initcall by name.
>
>       Arnd
>
> [1] https://lore.kernel.org/all/78ec1da5-11ae-4c35-a08e-cc88a5d083f4@app.fastmail.com/



^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH v10 0/2] module: Extend module_blacklist parameter to built-in modules
  2026-09-06  0:28     ` Andrew Morton
  2026-09-06  9:56       ` Arnd Bergmann
@ 2026-09-06 13:15       ` Aaron Tomlin
  1 sibling, 0 replies; 16+ messages in thread
From: Aaron Tomlin @ 2026-09-06 13:15 UTC (permalink / raw)
  To: Andrew Morton
  Cc: arnd, mcgrof, petr.pavlu, da.gomez, samitolvanen, peterz, ojeda,
	mhiramat, boqun, neelx, da.anzani, sean, chjohnst, steve, mproche,
	nick.lane, rishil1999, linux-arch, linux-modules, rust-for-linux,
	linux-kernel

On Sat, Sep 05, 2026 at 05:28:17PM -0700, Andrew Morton wrote:
> On Fri, 4 Sep 2026 10:59:38 -0400 Aaron Tomlin <atomlin@atomlin.com> wrote:
> 
> > On Thu, Sep 03, 2026 at 01:29:24PM -0700, Andrew Morton wrote:
> > > On Thu,  3 Sep 2026 14:55:55 -0400 Aaron Tomlin <atomlin@atomlin.com> wrote:
> > > 
> > > > Currently, the "module_blacklist=" command-line parameter only applies to
> > > > loadable modules. If a module is built-in, the parameter is silently
> > > > ignored. This patch series extends the blacklisting functionality to
> > > > built-in modules by intercepting their initialisation routines during early
> > > > boot.
> > > 
> > > Why?  What are the use-cases and what is the value of this change
> > > to our users?
> > > 
> > > Important, so please don't skimp on the details.
> > 
> > Hi Andrew,
> > 
> > Thanks for asking.
> > 
> > Here is the rationale, concrete use cases, and the value this hopefully
> > brings to users and administrators.
> >
> > ...
> >
> > In summary, this patch brings parity between modular and built-in drivers,
> > removes a pain point in boot-time disaster recovery, and provides users
> > with a predictable, consistent interface.
> 
> Really helpful, thanks.  Please add this to the [0/N] and maintain it.
> 
> I don't really know who are the potential audience for this change, nor
> how to attract their attention.  Greg might have some insights but he
> wasn't cc'ed.
> 
> Let's leave it a week to see if there's feedback then resend with these
> adjustments?
> 

Hi Andrew,

Sounds like a great plan.

I will incorporate the rationale, use cases, and value proposition into the
cover letter and keep it maintained across future revisions.

I will also make sure Greg Kroah-Hartman is CC'd on the next iteration.

Waiting a week gives us good time to collect any further input.
When resending, I will also incorporate a few other refinements raised
during review i.e., a prerequisite fix to treat hyphens and underscores
interchangeably via parameqn() and as well as a Rust specific issue and
linker alignment cleanups [1]).

Petr and Gary, when you have a moment, please let me know your thoughts on [1].

[1]: https://lore.kernel.org/sashiko-reviews/xuj6okhlofl4cboopwjzqe3jyfmaxgvdpf5a3ek7bubew4vcbk@nkfqgoe53533/

Kind regards,
-- 
Aaron Tomlin

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH v10 1/2] module: Extend module_blacklist parameter to built-in modules
  2026-09-04 14:34     ` Aaron Tomlin
@ 2026-09-06 13:39       ` Gary Guo
  0 siblings, 0 replies; 16+ messages in thread
From: Gary Guo @ 2026-09-06 13:39 UTC (permalink / raw)
  To: Aaron Tomlin, sashiko-reviews, petr.pavlu, gary; +Cc: ojeda, linux-modules

On Fri Sep 4, 2026 at 3:34 PM BST, Aaron Tomlin wrote:
> On Thu, Sep 03, 2026 at 07:13:02PM +0000, sashiko-bot@kernel.org wrote:
>> commit 828a4fa4f29a0a3c5aba9065f4ba35eb845dddef
>> Author: Aaron Tomlin <atomlin@atomlin.com>
>> 
>> module: Extend module_blacklist parameter to built-in modules
>> 
>> This commit extends the module_blacklist command-line parameter to apply to
>> built-in modules. It achieves this by creating a new .initcall.modnames
>> section to map initcalls to module names, intercepting blacklisted builtin
>> initcalls during early boot.
>> 
>> > diff --git a/include/asm-generic/vmlinux.lds.h b/include/asm-generic/vmlinux.lds.h
>> > index b2988aa12f664..7490278b7a2df 100644
>> > --- a/include/asm-generic/vmlinux.lds.h
>> > +++ b/include/asm-generic/vmlinux.lds.h
>> > @@ -734,7 +734,8 @@
>> >  	EARLYCON_TABLE()						\
>> >  	LSM_TABLE()							\
>> >  	EARLY_LSM_TABLE()						\
>> > -	KUNIT_INIT_TABLE()
>> > +	KUNIT_INIT_TABLE()						\
>> > +	BOUNDED_SECTION_BY(.initcall.modnames, _initcall_modnames)
>> 
>> [Severity: High]
>> Does this section declaration need an alignment constraint? Without a preceding
>> . = ALIGN(8); or similar, the __start_initcall_modnames label might capture
>> the unaligned location counter before padding is inserted by the linker.
>> 
>> When iterating over this section during early boot in do_initcall_level(),
>> could the kernel read padding bytes as garbage pointers, breaking the
>> builtin module blacklist feature or causing an unaligned memory access panic?
>
> Hi Petr,
>
> I forgot to restore ". = ALIGN(8)" in this iteration.
>
> The following ensures the location counter . within .init.data is aligned
> to an 8-byte boundary before __start_initcall_modnames = . is captured,
> preventing any linker padding from preceding the first element of the
> table.

Have you observed cases where this as an actual problem?

I suppose this could be fixed in the BOUNDED_SECTION_BY instead by doing

    . = ALIGN(ALIGNOF(NEXT_SECTION));

>
> Are you okay with the following?
>
>     --- a/include/asm-generic/vmlinux.lds.h
>     +++ b/include/asm-generic/vmlinux.lds.h
>     @@ -735,6 +735,7 @@
>          LSM_TABLE()                            \
>          EARLY_LSM_TABLE()                        \
>          KUNIT_INIT_TABLE()                        \
>     +    . = ALIGN(8);                            \
>          BOUNDED_SECTION_BY(.initcall.modnames, _initcall_modnames)
>
>      #define INIT_TEXT                            \
>
>
>> > diff --git a/rust/macros/module.rs b/rust/macros/module.rs
>> > index bc7027f8dbb2c..a96157598197f 100644
>> > --- a/rust/macros/module.rs
>> > +++ b/rust/macros/module.rs
>> [ ... ]
>> > @@ -591,6 +594,20 @@ pub extern "C" fn cleanup_module() {
>> >                  #[cfg(CONFIG_HAVE_ARCH_PREL32_RELOCATIONS)]
>> >                  ::core::arch::global_asm!(#global_asm);
>> >  
>> > +                #[cfg(not(MODULE))]
>> > +                #[used(compiler)]
>> > +                #[link_section = ".init.rodata"]
>> > +                static #ident_modname_str: [u8; #name_len] = *#name_cstr.to_bytes_with_nul();
>> 
>> [Severity: Low]
>> Will this cause a compilation failure for built-in Rust modules?
>> 
>> It attempts to initialize a fixed-size array by dereferencing a dynamically
>> sized slice returned by to_bytes_with_nul(). This could break the build when
>> CONFIG_MODULES=n or when a Rust module is configured as built-in.
>
> Hi Gary,
>
> Calling .to_bytes_with_nul() on &CStr returns a slice reference (&[u8]), so
> dereferencing it yields an unsized slice ([u8]).
>
> To resolve this, we can follow the pattern already used in
> rust/macros/module.rs for emitting '.modinfo' entries construct a byte
> string literal at macro-expansion time using Literal::byte_string(). In
> Rust, a byte string literal (b"...\0") has type &'static [u8; N], which can
> be cleanly dereferenced with '*' into a fixed-size array [u8; N].
>
> Would the following be appropriate?

Looks reasonable.

Best,
Gary

>
> diff --git a/rust/macros/module.rs b/rust/macros/module.rs
> index a96157598197..67e3df8d4389 100644
> --- a/rust/macros/module.rs
> +++ b/rust/macros/module.rs
> @@ -493,7 +493,9 @@ pub(crate) fn module(info: ModuleInfo) -> Result<TokenStream> {
>      );
>
>      let name_cstr = CString::new(name.value()).expect("name contains NUL-terminator");
> -    let name_len = name_cstr.to_bytes_with_nul().len();
> +    let name_bytes = name_cstr.to_bytes_with_nul();
> +    let name_len = name_bytes.len();
> +    let name_byte_literal = Literal::byte_string(name_bytes);
>
>      Ok(quote! {
>          /// The module name.
> @@ -596,7 +598,7 @@ pub extern "C" fn cleanup_module() {
>                  #[cfg(not(MODULE))]
>                  #[used(compiler)]
>                  #[link_section = ".init.rodata"]
> -                static #ident_modname_str: [u8; #name_len] = *#name_cstr.to_bytes_with_nul();
> +                static #ident_modname_str: [u8; #name_len] = *#name_byte_literal;
>
>                  #[cfg(not(MODULE))]
>                  #[used(compiler)]
>
>
> Kind regards,



^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH v10 0/2] module: Extend module_blacklist parameter to built-in modules
  2026-09-06  9:56       ` Arnd Bergmann
  2026-09-06 12:33         ` Gary Guo
@ 2026-09-06 14:08         ` Aaron Tomlin
  1 sibling, 0 replies; 16+ messages in thread
From: Aaron Tomlin @ 2026-09-06 14:08 UTC (permalink / raw)
  To: Arnd Bergmann
  Cc: Andrew Morton, Luis Chamberlain, Petr Pavlu, da.gomez,
	Sami Tolvanen, Peter Zijlstra, Miguel Ojeda, Masami Hiramatsu,
	Boqun Feng, neelx, da.anzani, sean, chjohnst, steve, mproche,
	nick.lane, rishil1999, Linux-Arch, linux-modules, rust-for-linux,
	linux-kernel

On Sun, Sep 06, 2026 at 11:56:04AM +0200, Arnd Bergmann wrote:
> On Sun, Sep 6, 2026, at 02:28, Andrew Morton wrote:
> > On Fri, 4 Sep 2026 10:59:38 -0400 Aaron Tomlin <atomlin@atomlin.com> wrote:
> >>
> >> In summary, this patch brings parity between modular and built-in drivers,
> >> removes a pain point in boot-time disaster recovery, and provides users
> >> with a predictable, consistent interface.
> >
> > Really helpful, thanks.  Please add this to the [0/N] and maintain it.
> >
> > I don't really know who are the potential audience for this change, nor
> > how to attract their attention.  Greg might have some insights but he
> > wasn't cc'ed.
> >
> > Let's leave it a week to see if there's feedback then resend with these
> > adjustments?
> 
> FWIW, I previously asked the same question on this series
> and still don't find the explanation lacking. Obviously,
> consistency is good, but the added complexity doesn't feel
> worth it here, given that this still has most of the same
> problems as the existing "initcall_blacklist=" option [1].
> 
> I think patch 2/2 would be fine on its own, but for 1/2
> I have yet to see a single example of a real-world problem
> that could have been solved by this but not using
> initcall_blacklist. In distro kernels, almost everything
> is already a loadable module, while users with custom
> kernels could easily turn off the drivers they don't want
> or disable the initcall by name.
> 
>       Arnd
> 
> [1] https://lore.kernel.org/all/78ec1da5-11ae-4c35-a08e-cc88a5d083f4@app.fastmail.com/

Hi Arnd,

I understand your skepticism.

Let me provide more concrete context on the operational realities and why
initcall_blacklist= does not solve this for users.

First, regarding dependencies and undefined behavior, module_blacklist= is
indeed not intended as a day-to-day configuration knob, but as a boot-time
emergency recovery and triage tool. In terms of dependencies, built-in
drivers face the exact same behavior as modular drivers: if a blacklisted
driver provides resources to other drivers, those dependents fail their
probe or defer cleanly via -EPROBE_DEFER. The Linux driver model handles
missing devices without crashing the kernel. Moreover, this mechanism is
strictly restricted to drivers using module_init(), leaving core kernel
subsystems (e.g., sched and memory) completely untouched.

Second, regarding why initcall_blacklist= is impractical for users:

    1.  Obscure and unstable symbols

        initcall_blacklist= requires the exact internal C function name of
        the initcall (e.g., crypto_cmac_module_init). Typically,
        administrators and users know the module name, not internal C
        symbols. Furthermore, these function names are not stable and
        frequently change across kernel changes.

    2.  Mangled Names in Rust

        For drivers written in Rust, initcall symbols are compiler-mangled,
        which makes initcall_blacklist= virtually impossible for a user to
        specify at a boot prompt.

Consider a hardened appliance built with CONFIG_MODULES=n.
Should a kernel update introduce a panic in a built-in driver, the
administrator currently has no means of circumventing that driver using its
documented module name. They are consequently compelled either to
cross-compile a bespoke kernel on an auxiliary machine or to recover the
system via external media. Extending module_blacklist= enables them to
suppress the offending driver directly at the bootloader prompt, thereby
reaching an emergency shell to gather logs and triage the fault.

Andrew Morton suggested incorporating this detailed rationale into the
cover letter and looping in Greg Kroah-Hartman for driver-core perspective,
which I plan to do for the next revision.

I hope this helps.

Kind regards,
-- 
Aaron Tomlin

^ permalink raw reply	[flat|nested] 16+ messages in thread

end of thread, other threads:[~2026-09-06 14:09 UTC | newest]

Thread overview: 16+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-03 18:55 [PATCH v10 0/2] module: Extend module_blacklist parameter to built-in modules Aaron Tomlin
2026-09-03 18:55 ` [PATCH v10 1/2] " Aaron Tomlin
2026-09-03 19:13   ` sashiko-bot
2026-09-04 14:34     ` Aaron Tomlin
2026-09-06 13:39       ` Gary Guo
2026-09-03 20:23   ` Aaron Tomlin
2026-09-03 18:55 ` [PATCH v10 2/2] module: Rename module_blacklist to module_denylist Aaron Tomlin
2026-09-03 19:07   ` sashiko-bot
2026-09-04 15:13     ` Aaron Tomlin
2026-09-03 20:29 ` [PATCH v10 0/2] module: Extend module_blacklist parameter to built-in modules Andrew Morton
2026-09-04 14:59   ` Aaron Tomlin
2026-09-06  0:28     ` Andrew Morton
2026-09-06  9:56       ` Arnd Bergmann
2026-09-06 12:33         ` Gary Guo
2026-09-06 14:08         ` Aaron Tomlin
2026-09-06 13:15       ` Aaron Tomlin

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox