Skip to content

Commit 99bd995

Browse files
Aaron Tomlinmcgrof
authored andcommitted
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>
1 parent 6fb0538 commit 99bd995

File tree

5 files changed

+99
-0
lines changed

5 files changed

+99
-0
lines changed

init/Kconfig

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2118,6 +2118,17 @@ config MODULE_FORCE_UNLOAD
21182118
rmmod). This is mainly for kernel developers and desperate users.
21192119
If unsure, say N.
21202120

2121+
config MODULE_UNLOAD_TAINT_TRACKING
2122+
bool "Tainted module unload tracking"
2123+
depends on MODULE_UNLOAD
2124+
default n
2125+
help
2126+
This option allows you to maintain a record of each unloaded
2127+
module that tainted the kernel. In addition to displaying a
2128+
list of linked (or loaded) modules e.g. on detection of a bad
2129+
page (see bad_page()), the aforementioned details are also
2130+
shown. If unsure, say N.
2131+
21212132
config MODVERSIONS
21222133
bool "Module versioning support"
21232134
help

kernel/module/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,4 @@ obj-$(CONFIG_PROC_FS) += procfs.o
1818
obj-$(CONFIG_SYSFS) += sysfs.o
1919
obj-$(CONFIG_KGDB_KDB) += kdb.o
2020
obj-$(CONFIG_MODVERSIONS) += version.o
21+
obj-$(CONFIG_MODULE_UNLOAD_TAINT_TRACKING) += tracking.o

kernel/module/internal.h

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,27 @@ static inline bool set_livepatch_module(struct module *mod)
145145
#endif
146146
}
147147

148+
#ifdef CONFIG_MODULE_UNLOAD_TAINT_TRACKING
149+
struct mod_unload_taint {
150+
struct list_head list;
151+
char name[MODULE_NAME_LEN];
152+
unsigned long taints;
153+
u64 count;
154+
};
155+
156+
int try_add_tainted_module(struct module *mod);
157+
void print_unloaded_tainted_modules(void);
158+
#else /* !CONFIG_MODULE_UNLOAD_TAINT_TRACKING */
159+
static inline int try_add_tainted_module(struct module *mod)
160+
{
161+
return 0;
162+
}
163+
164+
static inline void print_unloaded_tainted_modules(void)
165+
{
166+
}
167+
#endif /* CONFIG_MODULE_UNLOAD_TAINT_TRACKING */
168+
148169
#ifdef CONFIG_MODULE_DECOMPRESS
149170
int module_decompress(struct load_info *info, const void *buf, size_t size);
150171
void module_decompress_cleanup(struct load_info *info);

kernel/module/main.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1190,6 +1190,9 @@ static void free_module(struct module *mod)
11901190
module_bug_cleanup(mod);
11911191
/* Wait for RCU-sched synchronizing before releasing mod->list and buglist. */
11921192
synchronize_rcu();
1193+
if (try_add_tainted_module(mod))
1194+
pr_err("%s: adding tainted module to the unloaded tainted modules list failed.\n",
1195+
mod->name);
11931196
mutex_unlock(&module_mutex);
11941197

11951198
/* Clean up CFI for the module. */
@@ -3125,6 +3128,8 @@ void print_modules(void)
31253128
continue;
31263129
pr_cont(" %s%s", mod->name, module_flags(mod, buf));
31273130
}
3131+
3132+
print_unloaded_tainted_modules();
31283133
preempt_enable();
31293134
if (last_unloaded_module[0])
31303135
pr_cont(" [last unloaded: %s]", last_unloaded_module);

kernel/module/tracking.c

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// SPDX-License-Identifier: GPL-2.0-or-later
2+
/*
3+
* Module taint unload tracking support
4+
*
5+
* Copyright (C) 2022 Aaron Tomlin
6+
*/
7+
8+
#include <linux/module.h>
9+
#include <linux/string.h>
10+
#include <linux/printk.h>
11+
#include <linux/slab.h>
12+
#include <linux/list.h>
13+
#include <linux/rculist.h>
14+
#include "internal.h"
15+
16+
static LIST_HEAD(unloaded_tainted_modules);
17+
18+
int try_add_tainted_module(struct module *mod)
19+
{
20+
struct mod_unload_taint *mod_taint;
21+
22+
module_assert_mutex_or_preempt();
23+
24+
list_for_each_entry_rcu(mod_taint, &unloaded_tainted_modules, list,
25+
lockdep_is_held(&module_mutex)) {
26+
if (!strcmp(mod_taint->name, mod->name) &&
27+
mod_taint->taints & mod->taints) {
28+
mod_taint->count++;
29+
goto out;
30+
}
31+
}
32+
33+
mod_taint = kmalloc(sizeof(*mod_taint), GFP_KERNEL);
34+
if (unlikely(!mod_taint))
35+
return -ENOMEM;
36+
strscpy(mod_taint->name, mod->name, MODULE_NAME_LEN);
37+
mod_taint->taints = mod->taints;
38+
list_add_rcu(&mod_taint->list, &unloaded_tainted_modules);
39+
mod_taint->count = 1;
40+
out:
41+
return 0;
42+
}
43+
44+
void print_unloaded_tainted_modules(void)
45+
{
46+
struct mod_unload_taint *mod_taint;
47+
char buf[MODULE_FLAGS_BUF_SIZE];
48+
49+
if (!list_empty(&unloaded_tainted_modules)) {
50+
printk(KERN_DEFAULT "Unloaded tainted modules:");
51+
list_for_each_entry_rcu(mod_taint, &unloaded_tainted_modules,
52+
list) {
53+
size_t l;
54+
55+
l = module_flags_taint(mod_taint->taints, buf);
56+
buf[l++] = '\0';
57+
pr_cont(" %s(%s):%llu", mod_taint->name, buf,
58+
mod_taint->count);
59+
}
60+
}
61+
}

0 commit comments

Comments
 (0)