* [PATCH v6 0/2] module: Extend blacklist parameter to support built-in modules
@ 2026-07-18 19:01 Aaron Tomlin
2026-07-18 19:01 ` [PATCH v6 1/2] module: Extend module_blacklist parameter to " Aaron Tomlin
2026-07-18 19:01 ` [PATCH v6 2/2] module: Rename module_blacklist to module_denylist Aaron Tomlin
0 siblings, 2 replies; 4+ messages in thread
From: Aaron Tomlin @ 2026-07-18 19:01 UTC (permalink / raw)
To: arnd, mcgrof, petr.pavlu, da.gomez, samitolvanen, peterz
Cc: akpm, mhiramat, atomlin, neelx, da.anzani, sean, chjohnst, steve,
mproche, nick.lane, linux-arch, linux-modules, 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 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="
- Linked 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)
- Linked 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)
- Linked 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
- Linked 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
- Linked 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 | 4 +-
include/linux/init.h | 23 +++++++-
include/linux/module.h | 5 +-
init/main.c | 56 ++++++++++++++++++-
kernel/module/main.c | 23 +-------
6 files changed, 90 insertions(+), 27 deletions(-)
--
2.54.0
^ permalink raw reply [flat|nested] 4+ messages in thread
* [PATCH v6 1/2] module: Extend module_blacklist parameter to built-in modules
2026-07-18 19:01 [PATCH v6 0/2] module: Extend blacklist parameter to support built-in modules Aaron Tomlin
@ 2026-07-18 19:01 ` Aaron Tomlin
2026-07-18 19:01 ` [PATCH v6 2/2] module: Rename module_blacklist to module_denylist Aaron Tomlin
1 sibling, 0 replies; 4+ messages in thread
From: Aaron Tomlin @ 2026-07-18 19:01 UTC (permalink / raw)
To: arnd, mcgrof, petr.pavlu, da.gomez, samitolvanen, peterz
Cc: akpm, mhiramat, atomlin, neelx, da.anzani, sean, chjohnst, steve,
mproche, nick.lane, linux-arch, linux-modules, 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.
Design Considerations and Trade-offs:
1. LTO and CFI Compatibility vs. PREL32
Previous iterations of this patch attempted to use top-level
inline assembly to generate 32-bit relative offsets (PREL32) to
save memory. However, raw inline assembly operates blindly
outside of the C compiler's visibility. When compiled with
CONFIG_LTO_CLANG or CONFIG_CFI_CLANG, the compiler applies
symbol renaming and generates Control Flow Integrity stubs.
The raw assembly string-matching fails to track these changes,
resulting in undefined references or runtime address mismatches.
To resolve this, we strictly use standard C structures to hold
the function pointers. This natively allows the compiler to
resolve LTO renaming and map CFI stubs correctly. We trade the
minor spatial optimisation of PREL32 (using absolute 64-bit
pointers instead) to guarantee architectural safety under modern
compiler protections. Because this metadata is placed in an
".init" section and freed entirely after boot, the temporary
memory overhead is negligible.
2. Architectural Safety and Elimination of Runtime Vulnerabilities:
By embedding the boot-time blacklist check inside the
do_one_initcall_builtin() __init wrapper function, we ensure
the metadata lookup logic is exclusively invoked during early
boot. This approach provides strict structural guarantees:
- It inherently eliminates Use-After-Free (UAF) and race conditions
since loadable modules (which execute post-boot and invoke
do_one_initcall() directly) bypass this __init wrapper entirely.
- It prevents modpost section mismatch warnings since the __init
metadata is strictly accessed by other __init functions.
- It mitigates Spectre v1 speculative execution vulnerabilities
by guaranteeing the unprivileged runtime module loading path
cannot speculatively branch into reclaimed .init.text instructions.
Signed-off-by: Aaron Tomlin <atomlin@atomlin.com>
---
include/asm-generic/vmlinux.lds.h | 4 ++-
include/linux/init.h | 23 ++++++++++++-
include/linux/module.h | 5 ++-
init/main.c | 55 +++++++++++++++++++++++++++++--
kernel/module/main.c | 21 +-----------
5 files changed, 83 insertions(+), 25 deletions(-)
diff --git a/include/asm-generic/vmlinux.lds.h b/include/asm-generic/vmlinux.lds.h
index 5659f4b5a125..799d912dcbc0 100644
--- a/include/asm-generic/vmlinux.lds.h
+++ b/include/asm-generic/vmlinux.lds.h
@@ -734,7 +734,9 @@
EARLYCON_TABLE() \
LSM_TABLE() \
EARLY_LSM_TABLE() \
- KUNIT_INIT_TABLE()
+ KUNIT_INIT_TABLE() \
+ STRUCT_ALIGN(); \
+ 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 40331923b9f4..1cf163715264 100644
--- a/include/linux/init.h
+++ b/include/linux/init.h
@@ -271,7 +271,28 @@ extern struct module __this_module;
__initcall_name(initcall, __iid, id), \
__initcall_section(__sec, __iid))
-#define ___define_initcall(fn, id, __sec) \
+#ifdef CONFIG_HAVE_ARCH_PREL32_RELOCATIONS
+#define __initcall_fn_ptr(fn, __iid, id) __initcall_stub(fn, __iid, id)
+#else
+#define __initcall_fn_ptr(fn, __iid, id) fn
+#endif
+
+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") = { \
+ .initcall_fn = __initcall_fn_ptr(fn, __iid, id), \
+ .modname = __initstr_##fn \
+ };
+
+#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 7566815fabbe..fc1525e8f63c 100644
--- a/include/linux/module.h
+++ b/include/linux/module.h
@@ -86,7 +86,8 @@ 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(initfn) \
+ ____define_initcall_modname(initfn, 6, .initcall6, __initcall_id(initfn))
/**
* module_exit() - driver exit entry point
@@ -883,6 +884,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 e363232b428b..ee6d7db212d5 100644
--- a/init/main.c
+++ b/init/main.c
@@ -1334,6 +1334,57 @@ 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 (dereference_function_descriptor(p->initcall_fn) ==
+ dereference_function_descriptor(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();
@@ -1406,7 +1457,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)
@@ -1451,7 +1502,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 46dd8d25a605..a8021039d44b 100644
--- a/kernel/module/main.c
+++ b/kernel/module/main.c
@@ -2919,26 +2919,7 @@ 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)
{
@@ -3391,7 +3372,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;
}
--
2.54.0
^ permalink raw reply related [flat|nested] 4+ messages in thread
* [PATCH v6 2/2] module: Rename module_blacklist to module_denylist
2026-07-18 19:01 [PATCH v6 0/2] module: Extend blacklist parameter to support built-in modules Aaron Tomlin
2026-07-18 19:01 ` [PATCH v6 1/2] module: Extend module_blacklist parameter to " Aaron Tomlin
@ 2026-07-18 19:01 ` Aaron Tomlin
2026-07-18 19:11 ` sashiko-bot
1 sibling, 1 reply; 4+ messages in thread
From: Aaron Tomlin @ 2026-07-18 19:01 UTC (permalink / raw)
To: arnd, mcgrof, petr.pavlu, da.gomez, samitolvanen, peterz
Cc: akpm, mhiramat, atomlin, neelx, da.anzani, sean, chjohnst, steve,
mproche, nick.lane, linux-arch, linux-modules, 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 a68003c3599c..211aa16c53ef 100644
--- a/Documentation/admin-guide/kernel-parameters.txt
+++ b/Documentation/admin-guide/kernel-parameters.txt
@@ -4179,7 +4179,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 fc1525e8f63c..e83ce13e6db5 100644
--- a/include/linux/module.h
+++ b/include/linux/module.h
@@ -884,7 +884,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 ee6d7db212d5..de1fa1b21e82 100644
--- a/init/main.c
+++ b/init/main.c
@@ -1337,17 +1337,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;
@@ -1356,7 +1356,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)
{
@@ -1374,10 +1375,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 a8021039d44b..bf6aa98d8d36 100644
--- a/kernel/module/main.c
+++ b/kernel/module/main.c
@@ -3372,8 +3372,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.54.0
^ permalink raw reply related [flat|nested] 4+ messages in thread
* Re: [PATCH v6 2/2] module: Rename module_blacklist to module_denylist
2026-07-18 19:01 ` [PATCH v6 2/2] module: Rename module_blacklist to module_denylist Aaron Tomlin
@ 2026-07-18 19:11 ` sashiko-bot
0 siblings, 0 replies; 4+ messages in thread
From: sashiko-bot @ 2026-07-18 19:11 UTC (permalink / raw)
To: Aaron Tomlin; +Cc: linux-modules
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
Pre-existing issues:
- [High] The module denylist/blacklist fails to block modules if the command-line parameter uses a hyphen while the internal module name uses an underscore.
--
--- Patch [2]: [PATCH v6 2/2] module: Rename module_blacklist to module_denylist ---
commit e8f656a2a563ecf3826a9353c13ab68af8069728
Author: Aaron Tomlin <atomlin@atomlin.com>
module: Rename module_blacklist to module_denylist
This patch renames the module_blacklist parameter to module_denylist while
keeping the old parameter as a legacy alias. It also marks the old parameter
as deprecated in the documentation and updates internal symbol names.
> diff --git a/init/main.c b/init/main.c
> --- a/init/main.c
> +++ b/init/main.c
[ ... ]
> @@ -1368,17 +1368,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))
[Severity: High]
This isn't a bug introduced by this patch, but does this strict byte-by-byte
comparison fail to account for the kernel's inherent hyphen-to-underscore
normalization in module names?
When an administrator specifies a module name containing a hyphen in the
module_denylist= parameter (e.g., usb-storage), the internal module name
will typically contain an underscore (e.g., usb_storage) because the kernel
build system automatically normalizes them.
Since memcmp() is used here in module_is_denylisted(), won't the comparison
fail, allowing the explicitly blocked module to load and bypassing the
intended security mechanism?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260718190121.378314-1-atomlin@atomlin.com?part=2
^ permalink raw reply [flat|nested] 4+ messages in thread
end of thread, other threads:[~2026-07-18 19:11 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-18 19:01 [PATCH v6 0/2] module: Extend blacklist parameter to support built-in modules Aaron Tomlin
2026-07-18 19:01 ` [PATCH v6 1/2] module: Extend module_blacklist parameter to " Aaron Tomlin
2026-07-18 19:01 ` [PATCH v6 2/2] module: Rename module_blacklist to module_denylist Aaron Tomlin
2026-07-18 19:11 ` sashiko-bot
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox