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: Aaron Tomlin <atomlin@redhat.com>
Signed-off-by: Luis Chamberlain <mcgrof@kernel.org>
This commit is contained in:
Aaron Tomlin 2022-05-02 21:52:52 +01:00 committed by Luis Chamberlain
parent 6fb0538d01
commit 99bd995655
5 changed files with 99 additions and 0 deletions

View File

@ -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

View File

@ -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

View File

@ -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);

View File

@ -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)
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);

61
kernel/module/tracking.c Normal file
View File

@ -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);
}
}
}