Commit 99bd9956 authored by Aaron Tomlin's avatar Aaron Tomlin Committed by Luis Chamberlain

module: Introduce module unload taint tracking

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: default avatarAaron Tomlin <atomlin@redhat.com>
Signed-off-by: default avatarLuis Chamberlain <mcgrof@kernel.org>
parent 6fb0538d
...@@ -2118,6 +2118,17 @@ config MODULE_FORCE_UNLOAD ...@@ -2118,6 +2118,17 @@ config MODULE_FORCE_UNLOAD
rmmod). This is mainly for kernel developers and desperate users. rmmod). This is mainly for kernel developers and desperate users.
If unsure, say N. 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 config MODVERSIONS
bool "Module versioning support" bool "Module versioning support"
help help
......
...@@ -18,3 +18,4 @@ obj-$(CONFIG_PROC_FS) += procfs.o ...@@ -18,3 +18,4 @@ obj-$(CONFIG_PROC_FS) += procfs.o
obj-$(CONFIG_SYSFS) += sysfs.o obj-$(CONFIG_SYSFS) += sysfs.o
obj-$(CONFIG_KGDB_KDB) += kdb.o obj-$(CONFIG_KGDB_KDB) += kdb.o
obj-$(CONFIG_MODVERSIONS) += version.o obj-$(CONFIG_MODVERSIONS) += version.o
obj-$(CONFIG_MODULE_UNLOAD_TAINT_TRACKING) += tracking.o
...@@ -145,6 +145,27 @@ static inline bool set_livepatch_module(struct module *mod) ...@@ -145,6 +145,27 @@ static inline bool set_livepatch_module(struct module *mod)
#endif #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 #ifdef CONFIG_MODULE_DECOMPRESS
int module_decompress(struct load_info *info, const void *buf, size_t size); int module_decompress(struct load_info *info, const void *buf, size_t size);
void module_decompress_cleanup(struct load_info *info); void module_decompress_cleanup(struct load_info *info);
......
...@@ -1190,6 +1190,9 @@ static void free_module(struct module *mod) ...@@ -1190,6 +1190,9 @@ static void free_module(struct module *mod)
module_bug_cleanup(mod); module_bug_cleanup(mod);
/* Wait for RCU-sched synchronizing before releasing mod->list and buglist. */ /* Wait for RCU-sched synchronizing before releasing mod->list and buglist. */
synchronize_rcu(); 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); mutex_unlock(&module_mutex);
/* Clean up CFI for the module. */ /* Clean up CFI for the module. */
...@@ -3125,6 +3128,8 @@ void print_modules(void) ...@@ -3125,6 +3128,8 @@ void print_modules(void)
continue; continue;
pr_cont(" %s%s", mod->name, module_flags(mod, buf)); pr_cont(" %s%s", mod->name, module_flags(mod, buf));
} }
print_unloaded_tainted_modules();
preempt_enable(); preempt_enable();
if (last_unloaded_module[0]) if (last_unloaded_module[0])
pr_cont(" [last unloaded: %s]", last_unloaded_module); pr_cont(" [last unloaded: %s]", last_unloaded_module);
......
// 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);
}
}
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment