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