* [PATCH] init/main: Expose built-in initcalls and blacklist status via debugfs
@ 2026-05-10 6:13 Aaron Tomlin
2026-05-11 20:34 ` Aaron Tomlin
0 siblings, 1 reply; 2+ messages in thread
From: Aaron Tomlin @ 2026-05-10 6:13 UTC (permalink / raw)
To: arnd, mcgrof, petr.pavlu, da.gomez, samitolvanen
Cc: atomlin, neelx, sean, chjohnst, steve, mproche, nick.lane,
linux-arch, linux-modules, linux-kernel
At present, identifying the correct function name to supply to the
"initcall_blacklist=" kernel command-line parameter requires manual
inspection of the source code or kernel symbol tables. Furthermore,
administrators lack a reliable runtime mechanism to verify whether a
specified built-in module has been successfully blacklisted.
To resolve this, introduce a new debugfs interface at
/sys/kernel/debug/modules/builtin_initcalls. This file enumerates all
built-in modules alongside their corresponding initialisation callbacks
(e.g., those specified by module_init()) in a simple format:
"module_name init_callback". If a built-in module has been actively
blacklisted, the entry is explicitly appended with a " [blacklisted]"
suffix.
To achieve this without incurring runtime overhead, the
___define_initcall macro is expanded to statically allocate a
builtin_initcall_record structure (containing the module and function
name strings) within a dedicated .rodata_builtin_initcall_records
section.
This implementation incorporates several critical architectural safety
measures:
- The custom section name deliberately begins with ".rodata" to ensure
KASLR (Kernel Address Space Layout Randomisation) relocation tools
process and update the string pointers correctly during boot.
- The structural alignment is explicitly forced to 8 bytes to prevent
the compiler from inserting silent padding that would misalign the
linker script boundaries.
- The blacklisted_initcalls list head is stripped of its
__initdata_or_module annotation, ensuring the list remains safely
accessible in memory when queried from user space after early boot.
The creation of the debugfs file and the associated memory allocations
are strictly guarded by CONFIG_DEBUG_FS and CONFIG_KALLSYMS, as the
underlying initcall blacklisting mechanism is entirely dependent on
kallsyms being present.
Signed-off-by: Aaron Tomlin <atomlin@atomlin.com>
---
include/asm-generic/vmlinux.lds.h | 4 +++
include/linux/init.h | 21 ++++++++++++
init/main.c | 55 ++++++++++++++++++++++++++++++-
3 files changed, 79 insertions(+), 1 deletion(-)
diff --git a/include/asm-generic/vmlinux.lds.h b/include/asm-generic/vmlinux.lds.h
index 60c8c22fd3e4..d0fb971a8d5c 100644
--- a/include/asm-generic/vmlinux.lds.h
+++ b/include/asm-generic/vmlinux.lds.h
@@ -483,6 +483,10 @@
SCHED_DATA \
RO_AFTER_INIT_DATA /* Read only after init */ \
. = ALIGN(8); \
+ __builtin_initcall_records_start = .; \
+ KEEP(*(.rodata_builtin_initcall_records)) \
+ __builtin_initcall_records_end = .; \
+ . = ALIGN(8); \
BOUNDED_SECTION_BY(__tracepoints_ptrs, ___tracepoints_ptrs) \
*(__tracepoints_strings)/* Tracepoints: strings */ \
} \
diff --git a/include/linux/init.h b/include/linux/init.h
index 40331923b9f4..d682fe0d6064 100644
--- a/include/linux/init.h
+++ b/include/linux/init.h
@@ -182,6 +182,16 @@ extern struct module __this_module;
#ifndef __ASSEMBLY__
+#if defined(CONFIG_DEBUG_FS) && defined(CONFIG_KALLSYMS)
+#ifndef KBUILD_MODNAME
+#define KBUILD_MODNAME "unknown"
+#endif
+struct builtin_initcall_record {
+ const char *modname;
+ const char *fnname;
+};
+#endif
+
/*
* initcalls are now grouped by functionality into separate
* subsections. Ordering inside the subsections is determined
@@ -271,8 +281,19 @@ extern struct module __this_module;
__initcall_name(initcall, __iid, id), \
__initcall_section(__sec, __iid))
+#if defined(CONFIG_DEBUG_FS) && defined(CONFIG_KALLSYMS)
+#define ___define_initcall(fn, id, __sec) \
+ __unique_initcall(fn, id, __sec, __initcall_id(fn)); \
+ static const struct builtin_initcall_record \
+ __initcall_name(builtin_initcall_rec, __initcall_id(fn), id) \
+ __used __aligned(8) __section(".rodata_builtin_initcall_records") = { \
+ .modname = KBUILD_MODNAME, \
+ .fnname = #fn, \
+ };
+#else
#define ___define_initcall(fn, id, __sec) \
__unique_initcall(fn, id, __sec, __initcall_id(fn))
+#endif
#define __define_initcall(fn, id) ___define_initcall(fn, id, .initcall##id)
diff --git a/init/main.c b/init/main.c
index 96f93bb06c49..4cdf97306bf6 100644
--- a/init/main.c
+++ b/init/main.c
@@ -104,6 +104,8 @@
#include <linux/randomize_kstack.h>
#include <linux/pidfs.h>
#include <linux/ptdump.h>
+#include <linux/debugfs.h>
+#include <linux/seq_file.h>
#include <linux/time_namespace.h>
#include <linux/unaligned.h>
#include <linux/vdso_datastore.h>
@@ -1251,7 +1253,7 @@ struct blacklist_entry {
char *buf;
};
-static __initdata_or_module LIST_HEAD(blacklisted_initcalls);
+static LIST_HEAD(blacklisted_initcalls);
static int __init initcall_blacklist(char *str)
{
@@ -1316,6 +1318,57 @@ static bool __init_or_module initcall_blacklisted(initcall_t fn)
#endif
__setup("initcall_blacklist=", initcall_blacklist);
+#if defined(CONFIG_DEBUG_FS) && defined(CONFIG_KALLSYMS)
+extern struct builtin_initcall_record __builtin_initcall_records_start[];
+extern struct builtin_initcall_record __builtin_initcall_records_end[];
+
+static int builtin_initcalls_show(struct seq_file *m, void *v)
+{
+ struct builtin_initcall_record *rec;
+ bool blacklisted;
+ struct blacklist_entry *entry;
+
+ for (rec = __builtin_initcall_records_start; rec < __builtin_initcall_records_end; rec++) {
+ blacklisted = false;
+ list_for_each_entry(entry, &blacklisted_initcalls, next) {
+ if (!strcmp(rec->fnname, entry->buf)) {
+ blacklisted = true;
+ break;
+ }
+ }
+ seq_printf(m, "%s %s%s\n",
+ rec->modname,
+ rec->fnname,
+ blacklisted ? " [blacklisted]" : "");
+ }
+
+ return 0;
+}
+DEFINE_SHOW_ATTRIBUTE(builtin_initcalls);
+
+static int __init builtin_initcalls_debugfs_init(void)
+{
+ struct dentry *modules_dir;
+
+ /* Attempt to safely grab a reference to the existing directory */
+ modules_dir = debugfs_lookup("modules", NULL);
+
+ /* * Use modules_dir if found, otherwise dynamically create and use
+ * the fallback directory without.
+ */
+ debugfs_create_file("builtin_initcalls", 0444,
+ modules_dir ?: debugfs_create_dir("modules", NULL),
+ NULL, &builtin_initcalls_fops);
+
+ /* dput() safely ignores NULL if the lookup failed */
+ dput(modules_dir);
+
+ return 0;
+}
+late_initcall(builtin_initcalls_debugfs_init);
+#endif /* CONFIG_DEBUG_FS && CONFIG_KALLSYMS */
+
+
static __init_or_module void
trace_initcall_start_cb(void *data, initcall_t fn)
{
--
2.51.0
^ permalink raw reply related [flat|nested] 2+ messages in thread
* Re: [PATCH] init/main: Expose built-in initcalls and blacklist status via debugfs
2026-05-10 6:13 [PATCH] init/main: Expose built-in initcalls and blacklist status via debugfs Aaron Tomlin
@ 2026-05-11 20:34 ` Aaron Tomlin
0 siblings, 0 replies; 2+ messages in thread
From: Aaron Tomlin @ 2026-05-11 20:34 UTC (permalink / raw)
To: arnd, mcgrof, petr.pavlu, da.gomez, samitolvanen, sashal, leitao,
akpm
Cc: neelx, sean, chjohnst, steve, mproche, nick.lane, linux-arch,
linux-modules, linux-kernel
[-- Attachment #1: Type: text/plain, Size: 2091 bytes --]
On Sun, May 10, 2026 at 02:13:01AM -0400, Aaron Tomlin wrote:
> At present, identifying the correct function name to supply to the
> "initcall_blacklist=" kernel command-line parameter requires manual
> inspection of the source code or kernel symbol tables. Furthermore,
> administrators lack a reliable runtime mechanism to verify whether a
> specified built-in module has been successfully blacklisted.
>
> To resolve this, introduce a new debugfs interface at
> /sys/kernel/debug/modules/builtin_initcalls. This file enumerates all
> built-in modules alongside their corresponding initialisation callbacks
> (e.g., those specified by module_init()) in a simple format:
> "module_name init_callback". If a built-in module has been actively
> blacklisted, the entry is explicitly appended with a " [blacklisted]"
> suffix.
>
> To achieve this without incurring runtime overhead, the
> ___define_initcall macro is expanded to statically allocate a
> builtin_initcall_record structure (containing the module and function
> name strings) within a dedicated .rodata_builtin_initcall_records
> section.
>
> This implementation incorporates several critical architectural safety
> measures:
> - The custom section name deliberately begins with ".rodata" to ensure
> KASLR (Kernel Address Space Layout Randomisation) relocation tools
> process and update the string pointers correctly during boot.
> - The structural alignment is explicitly forced to 8 bytes to prevent
> the compiler from inserting silent padding that would misalign the
> linker script boundaries.
> - The blacklisted_initcalls list head is stripped of its
> __initdata_or_module annotation, ensuring the list remains safely
> accessible in memory when queried from user space after early boot.
Hi Sasha, Breno,
I thought this might be of interest to you for review, given your recent
work and interest on the "killswitch" proposal [1].
[1]: https://lore.kernel.org/lkml/20260508195749.1885522-1-sashal@kernel.org/
Kind regards,
--
Aaron Tomlin
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
^ permalink raw reply [flat|nested] 2+ messages in thread
end of thread, other threads:[~2026-05-11 20:34 UTC | newest]
Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-05-10 6:13 [PATCH] init/main: Expose built-in initcalls and blacklist status via debugfs Aaron Tomlin
2026-05-11 20:34 ` Aaron Tomlin
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox