Currently, the module_blacklist= parameter only prevents the dynamic loading of external modules. It possesses no mechanism to intercept or prevent the initialisation of built-in modules, as their associated initcalls are invoked unconditionally during system boot.
This patch extends the blacklisting behaviour to encompass built-in modules. It introduces a dedicated ".initcall.modnames" section into the linker script, systematically mapping each initcall to its originating module name. During the boot sequence, do_one_initcall() interrogates this mapping; should the executing initcall belong to a blacklisted module, its execution is explicitly bypassed. Furthermore, to preserve the efficacy of Kernel Address Space Layout Randomisation (KASLR) and prevent binary bloat, the mapping mechanism rigorously adheres to CONFIG_HAVE_ARCH_PREL32_RELOCATIONS. Rather than storing absolute function pointers, it employs 32-bit relative offsets, successfully avoiding the generation of thousands of absolute relocations. Signed-off-by: Aaron Tomlin <[email protected]> -- 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=" 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/[email protected]/ --- include/asm-generic/vmlinux.lds.h | 7 +++++++ include/linux/init.h | 31 +++++++++++++++++++++++++++++-- include/linux/module.h | 2 ++ init/main.c | 27 +++++++++++++++++++++++++++ kernel/module/main.c | 4 ++-- 5 files changed, 67 insertions(+), 4 deletions(-) diff --git a/include/asm-generic/vmlinux.lds.h b/include/asm-generic/vmlinux.lds.h index 5659f4b5a125..ac0e5f4f2893 100644 --- a/include/asm-generic/vmlinux.lds.h +++ b/include/asm-generic/vmlinux.lds.h @@ -957,6 +957,12 @@ #define CON_INITCALL \ BOUNDED_SECTION_POST_LABEL(.con_initcall.init, __con_initcall, _start, _end) +#define INITCALL_MODNAMES \ + . = ALIGN(8); \ + __start_initcall_modnames = .; \ + KEEP(*(.initcall.modnames)) \ + __stop_initcall_modnames = .; + #define NAMED_SECTION(name) \ . = ALIGN(8); \ name : AT(ADDR(name) - LOAD_OFFSET) \ @@ -1166,6 +1172,7 @@ INIT_SETUP(initsetup_align) \ INIT_CALLS \ CON_INITCALL \ + INITCALL_MODNAMES \ INIT_RAM_FS \ } diff --git a/include/linux/init.h b/include/linux/init.h index 40331923b9f4..212f64a07c73 100644 --- a/include/linux/init.h +++ b/include/linux/init.h @@ -125,6 +125,16 @@ static inline initcall_t initcall_from_entry(initcall_entry_t *entry) } #endif +struct initcall_modname { +#ifdef CONFIG_HAVE_ARCH_PREL32_RELOCATIONS + initcall_entry_t initcall_fn; + int modname_offset; +#else + initcall_t initcall_fn; + const char *modname; +#endif +}; + extern initcall_entry_t __con_initcall_start[], __con_initcall_end[]; /* Used for constructor calls. */ @@ -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" \ + ".long __initcall_modstr_" #fn #id " - .\n" \ + ".popsection\n" \ + ".pushsection .init.rodata, \"a\"\n" \ + "__initcall_modstr_" #fn #id ": .string \"" KBUILD_MODNAME "\"\n" \ + ".popsection\n"); +#else #define ___define_initcall(fn, id, __sec) \ - __unique_initcall(fn, id, __sec, __initcall_id(fn)) + __unique_initcall(fn, id, __sec, __initcall_id(fn)); \ + static struct initcall_modname __initcall_modname_##fn##id __used \ + __section(".initcall.modnames") = { \ + .initcall_fn = fn, \ + .modname = KBUILD_MODNAME \ + }; +#endif #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..7e25fcd61b50 100644 --- a/include/linux/module.h +++ b/include/linux/module.h @@ -756,6 +756,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); + extern void print_modules(void); static inline bool module_requested_async_probing(struct module *module) diff --git a/init/main.c b/init/main.c index e363232b428b..fbdc42859791 100644 --- a/init/main.c +++ b/init/main.c @@ -1334,12 +1334,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); + 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; diff --git a/kernel/module/main.c b/kernel/module/main.c index 46dd8d25a605..02deee5a7480 100644 --- a/kernel/module/main.c +++ b/kernel/module/main.c @@ -2921,7 +2921,7 @@ int __weak module_frob_arch_sections(Elf_Ehdr *hdr, /* module_blacklist is a comma-separated list of module names */ static char *module_blacklist; -static bool blacklisted(const char *module_name) +bool module_is_blacklisted(const char *module_name) { const char *p; size_t len; @@ -3391,7 +3391,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.51.0

