module: Introduce module unload taint tracking
authorAaron Tomlin <atomlin@redhat.com>
Mon, 2 May 2022 20:52:52 +0000 (21:52 +0100)
committerLuis Chamberlain <mcgrof@kernel.org>
Thu, 12 May 2022 17:29:41 +0000 (10:29 -0700)
Currently, only the initial module that tainted the kernel is
recorded e.g. when an out-of-tree module is loaded.

The purpose of this patch is to allow the kernel to maintain a record of
each unloaded module that taints the kernel. So, in addition to
displaying a list of linked modules (see print_modules()) e.g. in the
event of a detected bad page, unloaded modules that carried a taint/or
taints are displayed too. A tainted module unload count is maintained.

The number of tracked modules is not fixed. This feature is disabled by
default.

Signed-off-by: Aaron Tomlin <atomlin@redhat.com>
Signed-off-by: Luis Chamberlain <mcgrof@kernel.org>
init/Kconfig
kernel/module/Makefile
kernel/module/internal.h
kernel/module/main.c
kernel/module/tracking.c [new file with mode: 0644]

index ddcbefe..6b30210 100644 (file)
@@ -2118,6 +2118,17 @@ config MODULE_FORCE_UNLOAD
          rmmod).  This is mainly for kernel developers and desperate users.
          If unsure, say N.
 
+config MODULE_UNLOAD_TAINT_TRACKING
+       bool "Tainted module unload tracking"
+       depends on MODULE_UNLOAD
+       default n
+       help
+         This option allows you to maintain a record of each unloaded
+         module that tainted the kernel. In addition to displaying a
+         list of linked (or loaded) modules e.g. on detection of a bad
+         page (see bad_page()), the aforementioned details are also
+         shown. If unsure, say N.
+
 config MODVERSIONS
        bool "Module versioning support"
        help
index d1ca799..948efea 100644 (file)
@@ -18,3 +18,4 @@ obj-$(CONFIG_PROC_FS) += procfs.o
 obj-$(CONFIG_SYSFS) += sysfs.o
 obj-$(CONFIG_KGDB_KDB) += kdb.o
 obj-$(CONFIG_MODVERSIONS) += version.o
+obj-$(CONFIG_MODULE_UNLOAD_TAINT_TRACKING) += tracking.o
index 0bdf64c..bc5507a 100644 (file)
@@ -145,6 +145,27 @@ static inline bool set_livepatch_module(struct module *mod)
 #endif
 }
 
+#ifdef CONFIG_MODULE_UNLOAD_TAINT_TRACKING
+struct mod_unload_taint {
+       struct list_head list;
+       char name[MODULE_NAME_LEN];
+       unsigned long taints;
+       u64 count;
+};
+
+int try_add_tainted_module(struct module *mod);
+void print_unloaded_tainted_modules(void);
+#else /* !CONFIG_MODULE_UNLOAD_TAINT_TRACKING */
+static inline int try_add_tainted_module(struct module *mod)
+{
+       return 0;
+}
+
+static inline void print_unloaded_tainted_modules(void)
+{
+}
+#endif /* CONFIG_MODULE_UNLOAD_TAINT_TRACKING */
+
 #ifdef CONFIG_MODULE_DECOMPRESS
 int module_decompress(struct load_info *info, const void *buf, size_t size);
 void module_decompress_cleanup(struct load_info *info);
index 7a04849..6c3b4a8 100644 (file)
@@ -1190,6 +1190,9 @@ static void free_module(struct module *mod)
        module_bug_cleanup(mod);
        /* Wait for RCU-sched synchronizing before releasing mod->list and buglist. */
        synchronize_rcu();
+       if (try_add_tainted_module(mod))
+               pr_err("%s: adding tainted module to the unloaded tainted modules list failed.\n",
+                      mod->name);
        mutex_unlock(&module_mutex);
 
        /* Clean up CFI for the module. */
@@ -3125,6 +3128,8 @@ void print_modules(void)
                        continue;
                pr_cont(" %s%s", mod->name, module_flags(mod, buf));
        }
+
+       print_unloaded_tainted_modules();
        preempt_enable();
        if (last_unloaded_module[0])
                pr_cont(" [last unloaded: %s]", last_unloaded_module);
diff --git a/kernel/module/tracking.c b/kernel/module/tracking.c
new file mode 100644 (file)
index 0000000..7f81330
--- /dev/null
@@ -0,0 +1,61 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Module taint unload tracking support
+ *
+ * Copyright (C) 2022 Aaron Tomlin
+ */
+
+#include <linux/module.h>
+#include <linux/string.h>
+#include <linux/printk.h>
+#include <linux/slab.h>
+#include <linux/list.h>
+#include <linux/rculist.h>
+#include "internal.h"
+
+static LIST_HEAD(unloaded_tainted_modules);
+
+int try_add_tainted_module(struct module *mod)
+{
+       struct mod_unload_taint *mod_taint;
+
+       module_assert_mutex_or_preempt();
+
+       list_for_each_entry_rcu(mod_taint, &unloaded_tainted_modules, list,
+                               lockdep_is_held(&module_mutex)) {
+               if (!strcmp(mod_taint->name, mod->name) &&
+                   mod_taint->taints & mod->taints) {
+                       mod_taint->count++;
+                       goto out;
+               }
+       }
+
+       mod_taint = kmalloc(sizeof(*mod_taint), GFP_KERNEL);
+       if (unlikely(!mod_taint))
+               return -ENOMEM;
+       strscpy(mod_taint->name, mod->name, MODULE_NAME_LEN);
+       mod_taint->taints = mod->taints;
+       list_add_rcu(&mod_taint->list, &unloaded_tainted_modules);
+       mod_taint->count = 1;
+out:
+       return 0;
+}
+
+void print_unloaded_tainted_modules(void)
+{
+       struct mod_unload_taint *mod_taint;
+       char buf[MODULE_FLAGS_BUF_SIZE];
+
+       if (!list_empty(&unloaded_tainted_modules)) {
+               printk(KERN_DEFAULT "Unloaded tainted modules:");
+               list_for_each_entry_rcu(mod_taint, &unloaded_tainted_modules,
+                                       list) {
+                       size_t l;
+
+                       l = module_flags_taint(mod_taint->taints, buf);
+                       buf[l++] = '\0';
+                       pr_cont(" %s(%s):%llu", mod_taint->name, buf,
+                               mod_taint->count);
+               }
+       }
+}