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

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
Loading
Loading
Loading
Loading
+11 −0
Original line number Original line Diff line number Diff line
@@ -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
+1 −0
Original line number Original line Diff line number Diff line
@@ -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
+21 −0
Original line number Original line Diff line number Diff line
@@ -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);
+5 −0
Original line number Original line Diff line number Diff line
@@ -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 −0
Original line number Original line Diff line number Diff line
// 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);
		}
	}
}