Re: [PATCH v2] module: Extend module_blacklist parameter to built-in modules
From: sashiko-bot@kernel.org
Date: 2026-06-22 14:17:48
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider: - [Critical] Accessing the .initcall.modnames array during loadable module initialization reads freed .init memory, causing a Use-After-Free. - [High] Built-in module blacklisting silently bypasses or fails to compile under CONFIG_LTO_CLANG / CONFIG_CFI_CLANG due to function pointer mismatch. - [Low] Kernel build fails when CONFIG_MODULES is disabled due to missing module_is_blacklisted() definition. -- commit 5c29fcaa43d56c5e3cb5426f55d85a536519732f Author: Aaron Tomlin [off-list ref] module: Extend module_blacklist parameter to built-in modules This patch extends the module_blacklist parameter to intercept and prevent the initialization of built-in modules during system boot. It creates a .initcall.modnames section mapping initcalls to module names, using 32-bit relative offsets to preserve KASLR. Link: https://lore.kernel.org/lkml/20260510061301.41341-1-atomlin@atomlin.com/ (local)
quoted hunk ↗ jump to hunk
diff --git a/include/linux/init.h b/include/linux/init.h index 40331923b9f4a..212f64a07c739 100644 --- a/include/linux/init.h +++ b/include/linux/init.h
[ ... ]
quoted hunk ↗ jump to hunk
@@ -270,9 +280,26 @@ extern struct module __this_module; __initcall_stub(fn, __iid, id), \ __initcall_name(initcall, __iid, id), \ __initcall_section(__sec, __iid)) +#ifdef CONFIG_HAVE_ARCH_PREL32_RELOCATIONS +#define ___define_initcall(fn, id, __sec) \ + __unique_initcall(fn, id, __sec, __initcall_id(fn)); \ + asm(".pushsection \".initcall.modnames\", \"a\"\n" \ + ".balign 4\n" \ + ".long " #fn " - .\n" \
[Severity: High] When compiled with CONFIG_LTO_CLANG or CONFIG_CFI_CLANG, the kernel wraps initcalls in an __initcall_stub function, and stores the stub's address in the initcall array. Since ___define_initcall explicitly stores the original function's address here, will initcall_get_modname() fail to match the runtime CFI/LTO stub address passed by do_one_initcall()? Also, could using #fn in inline assembly for a static function renamed by LTO cause an undefined reference link error?
+ ".long __initcall_modstr_" #fn #id " - .\n" \ + ".popsection\n" \ + ".pushsection .init.rodata, \"a\"\n" \ + "__initcall_modstr_" #fn #id ": .string \"" KBUILD_MODNAME "\"\n" \ + ".popsection\n");
[ ... ]
quoted hunk ↗ jump to hunk
diff --git a/include/linux/module.h b/include/linux/module.h index d80c3ea574726..21668ef1430a5 100644 --- a/include/linux/module.h +++ b/include/linux/module.h@@ -754,6 +754,8 @@ void *dereference_module_function_descriptor(struct module *mod, void *ptr); int register_module_notifier(struct notifier_block *nb); int unregister_module_notifier(struct notifier_block *nb); +extern bool module_is_blacklisted(const char *module_name);
[Severity: Low] Does this cause a build failure when CONFIG_MODULES is disabled? The function module_is_blacklisted() is defined in kernel/module/main.c, but it is declared here unconditionally and called from do_one_initcall() in init/main.c. If CONFIG_MODULES is disabled, could the linker fail with an undefined reference?
+ extern void print_modules(void);
[ ... ]
quoted hunk ↗ jump to hunk
diff --git a/init/main.c b/init/main.c index b84818ad9685f..ce6d769b99e17 100644 --- a/init/main.c +++ b/init/main.c@@ -1365,12 +1365,39 @@ 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[]; + +static const char *initcall_get_modname(initcall_t fn) +{ + struct initcall_modname *p; + + for (p = __start_initcall_modnames; p < __stop_initcall_modnames; p++) { + if (initcall_from_entry(&p->initcall_fn) == fn) { +#ifdef CONFIG_HAVE_ARCH_PREL32_RELOCATIONS + return (const char *)offset_to_ptr(&p->modname_offset); +#else + return p->modname; +#endif + } + } + return NULL; +} + int __init_or_module do_one_initcall(initcall_t fn) { int count = preempt_count(); char msgbuf[64]; + const char *modname; int ret; + modname = initcall_get_modname(fn);
[Severity: Critical]
Is there a use-after-free risk here when loading dynamic modules?
The .initcall.modnames section is placed in INIT_DATA_SECTION, mapping it to
.init.data. Memory in the .init.* sections is freed by free_initmem() after
the system boots.
Since do_one_initcall() is marked __init_or_module, it is also called by
do_init_module() whenever a loadable module is initialized after boot:
do_init_module()
do_one_initcall()
initcall_get_modname()
Does this unconditionally iterate over the __start_initcall_modnames array
residing in the freed .init.data section?
+ if (modname && module_is_blacklisted(modname)) {
+ pr_info("Skipping initcall for blacklisted built-in module %s\n",
+ modname);
+ return 0;
+ }
+
if (initcall_blacklisted(fn))
return -EPERM;-- Sashiko AI review · https://sashiko.dev/#/patchset/20260622140259.2974-1-atomlin@atomlin.com?part=1