[PATCH v2] module: Extend module_blacklist parameter to built-in modules

Aaron Tomlin <[email protected]>
Newsgroups org.kernel.vger.linux-modules,org.kernel.vger.linux-arch,org.kernel.vger.linux-kernel
Message-ID <[email protected]>
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
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.