Linux Modules
 help / color / mirror / Atom feed
From: Aaron Tomlin <atomlin@atomlin.com>
To: arnd@arndb.de, mcgrof@kernel.org, petr.pavlu@suse.com,
	da.gomez@kernel.org, samitolvanen@google.com,
	peterz@infradead.org
Cc: mhiramat@kernel.org, atomlin@atomlin.com, neelx@suse.com,
	da.anzani@gmail.com, sean@ashe.io, chjohnst@mail.com,
	steve@abita.co, mproche@mail.com, nick.lane@mail.com,
	linux-arch@vger.kernel.org, linux-modules@vger.kernel.org,
	linux-kernel@vger.kernel.org
Subject: [PATCH v2] module: Extend module_blacklist parameter to built-in modules
Date: Mon, 22 Jun 2026 10:02:59 -0400	[thread overview]
Message-ID: <20260622140259.2974-1-atomlin@atomlin.com> (raw)

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 <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=" 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/
---
 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


             reply	other threads:[~2026-06-22 14:03 UTC|newest]

Thread overview: 2+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-06-22 14:02 Aaron Tomlin [this message]
2026-06-22 14:17 ` [PATCH v2] module: Extend module_blacklist parameter to built-in modules sashiko-bot

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260622140259.2974-1-atomlin@atomlin.com \
    --to=atomlin@atomlin.com \
    --cc=arnd@arndb.de \
    --cc=chjohnst@mail.com \
    --cc=da.anzani@gmail.com \
    --cc=da.gomez@kernel.org \
    --cc=linux-arch@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-modules@vger.kernel.org \
    --cc=mcgrof@kernel.org \
    --cc=mhiramat@kernel.org \
    --cc=mproche@mail.com \
    --cc=neelx@suse.com \
    --cc=nick.lane@mail.com \
    --cc=peterz@infradead.org \
    --cc=petr.pavlu@suse.com \
    --cc=samitolvanen@google.com \
    --cc=sean@ashe.io \
    --cc=steve@abita.co \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox