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 <[email protected]> > --- > 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

