Currently, the only way for userspace to determine which symbol
namespaces a module imports is to locate the .ko file on disk (which may
not match the loaded module), then either parsing the binary manually
and handling any potential compression, or shelling-out to modinfo.

This is painful in cases where userspace wants to distinguish between
module variants that share the same name but import different
namespaces. For example, the nvidia-uvm module exists in both open and
closed source variants; the open source version imports the DMA_BUF
namespace while the closed source version does not, and networking
middleware may want to initialize itself differently depending on that.

Add /sys/module/*/import_ns to expose imported namespaces for loaded
modules. The file contains one namespace per line and only exists for
modules that import at least one namespace.

Signed-off-by: Nicholas Sielicki <[email protected]>
---
 Documentation/ABI/testing/sysfs-module |  9 ++++
 include/linux/module.h                 |  1 +
 kernel/module/main.c                   | 62 +++++++++++++++++++++++++-
 3 files changed, 71 insertions(+), 1 deletion(-)

diff --git a/Documentation/ABI/testing/sysfs-module 
b/Documentation/ABI/testing/sysfs-module
index 6bc9af6229f0..d8e2a33fd514 100644
--- a/Documentation/ABI/testing/sysfs-module
+++ b/Documentation/ABI/testing/sysfs-module
@@ -48,6 +48,15 @@ Contact:     Kay Sievers <[email protected]>
 Description:   Show the initialization state(live, coming, going) of
                the module.
 
+What:          /sys/module/*/import_ns
+Date:          January 2026
+KernelVersion: 6.20
+Contact:       [email protected]
+Description:   List of symbol namespaces imported by this module via
+               MODULE_IMPORT_NS(). Each namespace appears on a separate line.
+               This file only exists for modules that import at least one
+               namespace.
+
 What:          /sys/module/*/taint
 Date:          Jan 2012
 KernelVersion: 3.3
diff --git a/include/linux/module.h b/include/linux/module.h
index d80c3ea57472..f1bcca03f90b 100644
--- a/include/linux/module.h
+++ b/include/linux/module.h
@@ -419,6 +419,7 @@ struct module {
        struct module_attribute *modinfo_attrs;
        const char *version;
        const char *srcversion;
+       const char *imported_namespaces;
        struct kobject *holders_dir;
 
        /* Exported symbols */
diff --git a/kernel/module/main.c b/kernel/module/main.c
index 710ee30b3bea..6c41934f1135 100644
--- a/kernel/module/main.c
+++ b/kernel/module/main.c
@@ -607,6 +607,23 @@ static const struct module_attribute modinfo_##field = {   
           \
 MODINFO_ATTR(version);
 MODINFO_ATTR(srcversion);
 
+static ssize_t show_modinfo_import_ns(const struct module_attribute *mattr,
+                                     struct module_kobject *mk, char *buffer)
+{
+       return sysfs_emit(buffer, "%s\n", mk->mod->imported_namespaces);
+}
+
+static int modinfo_import_ns_exists(struct module *mod)
+{
+       return mod->imported_namespaces != NULL;
+}
+
+static const struct module_attribute modinfo_import_ns = {
+       .attr = { .name = "import_ns", .mode = 0444 },
+       .show = show_modinfo_import_ns,
+       .test = modinfo_import_ns_exists,
+};
+
 static struct {
        char name[MODULE_NAME_LEN];
        char taints[MODULE_FLAGS_BUF_SIZE];
@@ -1058,6 +1075,7 @@ const struct module_attribute *const modinfo_attrs[] = {
        &module_uevent,
        &modinfo_version,
        &modinfo_srcversion,
+       &modinfo_import_ns,
        &modinfo_initstate,
        &modinfo_coresize,
 #ifdef CONFIG_ARCH_WANTS_MODULES_DATA_IN_VMALLOC
@@ -1753,11 +1771,48 @@ static void module_license_taint_check(struct module 
*mod, const char *license)
        }
 }
 
+static int copy_modinfo_import_ns(struct module *mod, struct load_info *info)
+{
+       char *ns;
+       size_t len, total_len = 0;
+       char *buf, *p;
+
+       for_each_modinfo_entry(ns, info, "import_ns")
+               total_len += strlen(ns) + 1;
+
+       if (!total_len) {
+               mod->imported_namespaces = NULL;
+               return 0;
+       }
+
+       buf = kmalloc(total_len, GFP_KERNEL);
+       if (!buf)
+               return -ENOMEM;
+
+       p = buf;
+       for_each_modinfo_entry(ns, info, "import_ns") {
+               len = strlen(ns);
+               memcpy(p, ns, len);
+               p += len;
+               *p++ = '\n';
+       }
+       /* Replace trailing newline with null terminator. */
+       *(p - 1) = '\0';
+
+       mod->imported_namespaces = buf;
+       return 0;
+}
+
+static void free_modinfo_import_ns(struct module *mod)
+{
+       kfree(mod->imported_namespaces);
+}
+
 static int setup_modinfo(struct module *mod, struct load_info *info)
 {
        const struct module_attribute *attr;
        char *imported_namespace;
-       int i;
+       int i, err;
 
        for (i = 0; (attr = modinfo_attrs[i]); i++) {
                if (attr->setup)
@@ -1776,6 +1831,10 @@ static int setup_modinfo(struct module *mod, struct 
load_info *info)
                }
        }
 
+       err = copy_modinfo_import_ns(mod, info);
+       if (err)
+               return err;
+
        return 0;
 }
 
@@ -1788,6 +1847,7 @@ static void free_modinfo(struct module *mod)
                if (attr->free)
                        attr->free(mod);
        }
+       free_modinfo_import_ns(mod);
 }
 
 bool __weak module_init_section(const char *name)
-- 
2.52.0


Reply via email to