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

Reply via email to