[RFC PATCH] kmod: add whitelist option

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



The whitelist option allows setup of hardened systems, only
modules on whitelist are allowed to load.

This patch adds "whitelist" option and definition to libkmod
allowing to work with whitelist (similar to blakclist, except
whitelist, once defined, is unconditional).

Without defined whitelist, there is no change.

If at least one whitelist command is specified, module loading
is limited to module matching loaded whitelist.

For more context see bug
https://bugzilla.redhat.com/show_bug.cgi?id=560084
---
 Makefile.am                                        |    4 +-
 libkmod/docs/libkmod-sections.txt                  |    1 +
 libkmod/libkmod-config.c                           |   72 ++++++++++++-
 libkmod/libkmod-module.c                           |   43 +++++++-
 libkmod/libkmod-private.h                          |    3 +-
 libkmod/libkmod.h                                  |    3 +
 libkmod/libkmod.sym                                |    5 +
 man/modprobe.d.xml                                 |   27 +++++
 .../test-whitelist/etc/modprobe.d/modprobe.conf    |    2 +
 testsuite/test-whitelist.c                         |  114 ++++++++++++++++++++
 tools/modprobe.c                                   |   12 ++-
 11 files changed, 275 insertions(+), 11 deletions(-)
 create mode 100644 testsuite/rootfs-pristine/test-whitelist/etc/modprobe.d/modprobe.conf
 create mode 100644 testsuite/test-whitelist.c

diff --git a/Makefile.am b/Makefile.am
index e4c1a83..03aa421 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -176,7 +176,7 @@ testsuite_libtestsuite_la_LIBADD = -lrt
 
 TESTSUITE = testsuite/test-init testsuite/test-testsuite testsuite/test-loaded \
 	    testsuite/test-modinfo testsuite/test-alias testsuite/test-new-module \
-	    testsuite/test-modprobe testsuite/test-blacklist \
+	    testsuite/test-modprobe testsuite/test-blacklist testsuite/test-whitelist \
 	    testsuite/test-dependencies testsuite/test-depmod
 
 check_PROGRAMS = $(TESTSUITE)
@@ -198,6 +198,8 @@ testsuite_test_modprobe_LDADD = $(TESTSUITE_LDADD)
 testsuite_test_modprobe_CPPFLAGS = $(TESTSUITE_CPPFLAGS)
 testsuite_test_blacklist_LDADD = $(TESTSUITE_LDADD)
 testsuite_test_blacklist_CPPFLAGS = $(TESTSUITE_CPPFLAGS)
+testsuite_test_whitelist_LDADD = $(TESTSUITE_LDADD)
+testsuite_test_whitelist_CPPFLAGS = $(TESTSUITE_CPPFLAGS)
 testsuite_test_dependencies_LDADD = $(TESTSUITE_LDADD)
 testsuite_test_dependencies_CPPFLAGS = $(TESTSUITE_CPPFLAGS)
 testsuite_test_depmod_LDADD = $(TESTSUITE_LDADD)
diff --git a/libkmod/docs/libkmod-sections.txt b/libkmod/docs/libkmod-sections.txt
index e59ab7a..bf7c0d9 100644
--- a/libkmod/docs/libkmod-sections.txt
+++ b/libkmod/docs/libkmod-sections.txt
@@ -31,6 +31,7 @@ kmod_list_prev
 <FILE>libkmod-config</FILE>
 kmod_config_iter
 kmod_config_get_blacklists
+kmod_config_get_whitelists
 kmod_config_get_install_commands
 kmod_config_get_remove_commands
 kmod_config_get_aliases
diff --git a/libkmod/libkmod-config.c b/libkmod/libkmod-config.c
index 398468e..f1f1181 100644
--- a/libkmod/libkmod-config.c
+++ b/libkmod/libkmod-config.c
@@ -56,7 +56,7 @@ struct kmod_softdep {
 	unsigned int n_post;
 };
 
-const char *kmod_blacklist_get_modname(const struct kmod_list *l)
+const char *kmod_list_get_modname(const struct kmod_list *l)
 {
 	return l->data;
 }
@@ -303,6 +303,38 @@ static void kmod_config_free_blacklist(struct kmod_config *config,
 	config->blacklists = kmod_list_remove(l);
 }
 
+static int kmod_config_add_whitelist(struct kmod_config *config,
+							const char *modname)
+{
+	char *p;
+	struct kmod_list *list;
+
+	DBG(config->ctx, "modname=%s\n", modname);
+
+	p = strdup(modname);
+	if (!p)
+		goto oom_error_init;
+
+	list = kmod_list_append(config->whitelists, p);
+	if (!list)
+		goto oom_error;
+	config->whitelists = list;
+	return 0;
+
+oom_error:
+	free(p);
+oom_error_init:
+	ERR(config->ctx, "out-of-memory modname=%s\n", modname);
+	return -ENOMEM;
+}
+
+static void kmod_config_free_whitelist(struct kmod_config *config,
+							struct kmod_list *l)
+{
+	free(l->data);
+	config->whitelists = kmod_list_remove(l);
+}
+
 static int kmod_config_add_softdep(struct kmod_config *config,
 							const char *modname,
 							const char *line)
@@ -634,6 +666,14 @@ static int kmod_config_parse(struct kmod_config *config, int fd,
 
 			kmod_config_add_blacklist(config,
 						underscores(ctx, modname));
+		} else if (streq(cmd, "whitelist")) {
+			char *modname = strtok_r(NULL, "\t ", &saveptr);
+
+			if (modname == NULL)
+				goto syntax_error;
+
+			kmod_config_add_whitelist(config,
+						underscores(ctx, modname));
 		} else if (streq(cmd, "options")) {
 			char *modname = strtok_r(NULL, "\t ", &saveptr);
 			char *options = strtok_r(NULL, "\0", &saveptr);
@@ -703,6 +743,9 @@ void kmod_config_free(struct kmod_config *config)
 	while (config->blacklists)
 		kmod_config_free_blacklist(config, config->blacklists);
 
+	while (config->whitelists)
+		kmod_config_free_whitelist(config, config->whitelists);
+
 	while (config->options)
 		kmod_config_free_options(config, config->options);
 
@@ -955,6 +998,7 @@ enum config_type {
 	CONFIG_TYPE_ALIAS,
 	CONFIG_TYPE_OPTION,
 	CONFIG_TYPE_SOFTDEP,
+	CONFIG_TYPE_WHITELIST,
 };
 
 struct kmod_config_iter {
@@ -987,7 +1031,11 @@ static struct kmod_config_iter *kmod_config_iter_new(const struct kmod_ctx* ctx,
 	switch (type) {
 	case CONFIG_TYPE_BLACKLIST:
 		iter->list = config->blacklists;
-		iter->get_key = kmod_blacklist_get_modname;
+		iter->get_key = kmod_list_get_modname;
+		break;
+	case CONFIG_TYPE_WHITELIST:
+		iter->list = config->whitelists;
+		iter->get_key = kmod_list_get_modname;
 		break;
 	case CONFIG_TYPE_INSTALL:
 		iter->list = config->install_commands;
@@ -1046,6 +1094,26 @@ KMOD_EXPORT struct kmod_config_iter *kmod_config_get_blacklists(const struct kmo
 }
 
 /**
+ * kmod_config_get_whitelists:
+ * @ctx: kmod library context
+ *
+ * Retrieve an iterator to deal with the whitelist maintained inside the
+ * library. See kmod_config_iter_get_key(), kmod_config_iter_get_value() and
+ * kmod_config_iter_next(). At least one call to kmod_config_iter_next() must
+ * be made to initialize the iterator and check if it's valid.
+ *
+ * Returns: a new iterator over the whitelists or NULL on failure. Free it
+ * with kmod_config_iter_free_iter().
+ */
+KMOD_EXPORT struct kmod_config_iter *kmod_config_get_whitelists(const struct kmod_ctx *ctx)
+{
+	if (ctx == NULL)
+		return NULL;
+
+	return kmod_config_iter_new(ctx, CONFIG_TYPE_WHITELIST);
+}
+
+/**
  * kmod_config_get_install_commands:
  * @ctx: kmod library context
  *
diff --git a/libkmod/libkmod-module.c b/libkmod/libkmod-module.c
index 0d87ce1..cf68fae 100644
--- a/libkmod/libkmod-module.c
+++ b/libkmod/libkmod-module.c
@@ -850,7 +850,7 @@ static bool module_is_blacklisted(struct kmod_module *mod)
 	const struct kmod_list *l;
 
 	kmod_list_foreach(l, bl) {
-		const char *modname = kmod_blacklist_get_modname(l);
+		const char *modname = kmod_list_get_modname(l);
 
 		if (streq(modname, mod->name))
 			return true;
@@ -859,6 +859,26 @@ static bool module_is_blacklisted(struct kmod_module *mod)
 	return false;
 }
 
+static bool module_is_not_whitelisted(struct kmod_module *mod)
+{
+	struct kmod_ctx *ctx = mod->ctx;
+	const struct kmod_config *config = kmod_get_config(ctx);
+	const struct kmod_list *wl = config->whitelists;
+	const struct kmod_list *l;
+
+	if (!wl)
+		return false;
+
+	kmod_list_foreach(l, wl) {
+		const char *modname = kmod_list_get_modname(l);
+
+		if (streq(modname, mod->name))
+			return false;
+	}
+
+	return true;
+}
+
 /**
  * kmod_module_apply_filter
  * @ctx: kmod library context
@@ -897,6 +917,10 @@ KMOD_EXPORT int kmod_module_apply_filter(const struct kmod_ctx *ctx,
 		if ((filter_type & KMOD_FILTER_BUILTIN) && mod->builtin)
 			continue;
 
+		if ((filter_type & KMOD_FILTER_WHITELIST) &&
+				module_is_not_whitelisted(mod))
+			continue;
+
 		node = kmod_list_append(*output, mod);
 		if (node == NULL)
 			goto fail;
@@ -1143,7 +1167,7 @@ static int kmod_module_get_probe_list(struct kmod_module *mod,
  * output or in dry-run mode.
  *
  * Insert a module in Linux kernel resolving dependencies, soft dependencies,
- * install commands and applying blacklist.
+ * install commands and applying blacklist and whitelist.
  *
  * If @run_install is NULL, this function will fork and exec by calling
  * system(3). Don't pass a NULL argument in @run_install if your binary is
@@ -1163,6 +1187,7 @@ KMOD_EXPORT int kmod_module_probe_insert_module(struct kmod_module *mod,
 						const char *options))
 {
 	struct kmod_list *list = NULL, *l;
+	struct kmod_list *filtered = NULL;
 	struct probe_insert_cb cb;
 	int err;
 
@@ -1195,8 +1220,20 @@ KMOD_EXPORT int kmod_module_probe_insert_module(struct kmod_module *mod,
 	if (err < 0)
 		return err;
 
+	/* Removes modules NOT in whitelist */
+	err = kmod_module_apply_filter(mod->ctx,
+			KMOD_FILTER_WHITELIST, list, &filtered);
+	if (err < 0)
+		return err;
+
+	kmod_module_unref_list(list);
+	if (filtered == NULL)
+		return KMOD_PROBE_APPLY_WHITELIST;
+
+	list = filtered;
+	filtered = NULL;
+
 	if (flags & KMOD_PROBE_APPLY_BLACKLIST_ALL) {
-		struct kmod_list *filtered = NULL;
 
 		err = kmod_module_apply_filter(mod->ctx,
 				KMOD_FILTER_BLACKLIST, list, &filtered);
diff --git a/libkmod/libkmod-private.h b/libkmod/libkmod-private.h
index 4760733..0651d52 100644
--- a/libkmod/libkmod-private.h
+++ b/libkmod/libkmod-private.h
@@ -102,6 +102,7 @@ struct kmod_config {
 	struct kmod_ctx *ctx;
 	struct kmod_list *aliases;
 	struct kmod_list *blacklists;
+	struct kmod_list *whitelists;
 	struct kmod_list *options;
 	struct kmod_list *remove_commands;
 	struct kmod_list *install_commands;
@@ -112,7 +113,7 @@ struct kmod_config {
 
 int kmod_config_new(struct kmod_ctx *ctx, struct kmod_config **config, const char * const *config_paths) __attribute__((nonnull(1, 2,3)));
 void kmod_config_free(struct kmod_config *config) __attribute__((nonnull(1)));
-const char *kmod_blacklist_get_modname(const struct kmod_list *l) __attribute__((nonnull(1)));
+const char *kmod_list_get_modname(const struct kmod_list *l) __attribute__((nonnull(1)));
 const char *kmod_alias_get_name(const struct kmod_list *l) __attribute__((nonnull(1)));
 const char *kmod_alias_get_modname(const struct kmod_list *l) __attribute__((nonnull(1)));
 const char *kmod_option_get_options(const struct kmod_list *l) __attribute__((nonnull(1)));
diff --git a/libkmod/libkmod.h b/libkmod/libkmod.h
index d03ab19..18721f3 100644
--- a/libkmod/libkmod.h
+++ b/libkmod/libkmod.h
@@ -106,6 +106,7 @@ struct kmod_list *kmod_list_last(const struct kmod_list *list);
  */
 struct kmod_config_iter;
 struct kmod_config_iter *kmod_config_get_blacklists(const struct kmod_ctx *ctx);
+struct kmod_config_iter *kmod_config_get_whitelists(const struct kmod_ctx *ctx);
 struct kmod_config_iter *kmod_config_get_install_commands(const struct kmod_ctx *ctx);
 struct kmod_config_iter *kmod_config_get_remove_commands(const struct kmod_ctx *ctx);
 struct kmod_config_iter *kmod_config_get_aliases(const struct kmod_ctx *ctx);
@@ -162,12 +163,14 @@ enum kmod_probe {
 	KMOD_PROBE_APPLY_BLACKLIST_ALL =	0x10000,
 	KMOD_PROBE_APPLY_BLACKLIST =		0x20000,
 	KMOD_PROBE_APPLY_BLACKLIST_ALIAS_ONLY =	0x40000,
+	KMOD_PROBE_APPLY_WHITELIST =		0x80000,
 };
 
 /* Flags to kmod_module_apply_filter() */
 enum kmod_filter {
 	KMOD_FILTER_BLACKLIST = 0x00001,
 	KMOD_FILTER_BUILTIN = 0x00002,
+	KMOD_FILTER_WHITELIST = 0x00004,
 };
 
 int kmod_module_remove_module(struct kmod_module *mod, unsigned int flags);
diff --git a/libkmod/libkmod.sym b/libkmod/libkmod.sym
index 854d257..28c8462 100644
--- a/libkmod/libkmod.sym
+++ b/libkmod/libkmod.sym
@@ -86,3 +86,8 @@ LIBKMOD_6 {
 global:
 	kmod_module_apply_filter;
 } LIBKMOD_5;
+
+LIBKMOD_11 {
+global:
+	kmod_config_get_whitelists;
+} LIBKMOD_6;
diff --git a/man/modprobe.d.xml b/man/modprobe.d.xml
index dc19b23..2140c9b 100644
--- a/man/modprobe.d.xml
+++ b/man/modprobe.d.xml
@@ -112,6 +112,33 @@
         </listitem>
       </varlistentry>
       <varlistentry>
+        <term>whitelist <replaceable>modulename</replaceable>
+        </term>
+        <listitem>
+         <para>
+           If at least one <command>whitelist</command> command is specified,
+           module loading and scripts specified
+           using <command>install</command> are limited to module names
+           matching a glob <replaceable>pattern</replaceable> specified in one
+           of the <command>whitelist</command> commands.
+         </para>
+         <para>
+           You can prepare whitelist of currently loaded modules e.g. by this
+           command:
+         </para>
+         <para>
+           lsmod | tail -n +2 | cut -d ' ' -f 1 | sed 's/^/whitelist /'
+         </para>
+         <para>
+           Note that the <command>whitelist</command> command is not a direct
+           opposite of the <command>blacklist</command> command.
+           The <command>blacklist</command> command affects the selection
+           of a module to be loaded or <command>install</command> script to
+           be performed.
+         </para>
+        </listitem>
+      </varlistentry>
+      <varlistentry>
         <term>install <replaceable>modulename</replaceable> <replaceable>command...</replaceable>
         </term>
         <listitem>
diff --git a/testsuite/rootfs-pristine/test-whitelist/etc/modprobe.d/modprobe.conf b/testsuite/rootfs-pristine/test-whitelist/etc/modprobe.d/modprobe.conf
new file mode 100644
index 0000000..9dc0ced
--- /dev/null
+++ b/testsuite/rootfs-pristine/test-whitelist/etc/modprobe.d/modprobe.conf
@@ -0,0 +1,2 @@
+whitelist floppy
+whitelist pcspkr
diff --git a/testsuite/test-whitelist.c b/testsuite/test-whitelist.c
new file mode 100644
index 0000000..49a3082
--- /dev/null
+++ b/testsuite/test-whitelist.c
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2011-2012  ProFUSION embedded systems
+ * Copyright (C) 2012 Red Hat, Inc. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <errno.h>
+#include <unistd.h>
+#include <inttypes.h>
+#include <string.h>
+#include <libkmod.h>
+
+/* good luck bulding a kmod_list outside of the library... makes this blacklist
+ * function rather pointless */
+#include <libkmod-private.h>
+
+/* FIXME: hack, change name so we don't clash */
+#undef ERR
+#include "testsuite.h"
+
+static int whitelist_1(const struct test *t)
+{
+	struct kmod_ctx *ctx;
+	struct kmod_list *list = NULL, *l, *filtered;
+	struct kmod_module *mod;
+	int err;
+	size_t len = 0;
+
+	const char *names[] = { "pcspkr", "pcspkr2", "floppy", "ext4", NULL };
+	const char **name;
+
+	ctx = kmod_new(NULL, NULL);
+	if (ctx == NULL)
+		exit(EXIT_FAILURE);
+
+	for(name = names; *name; name++) {
+		err = kmod_module_new_from_name(ctx, *name, &mod);
+		if (err < 0)
+			goto fail_lookup;
+		list = kmod_list_append(list, mod);
+	}
+
+	err = kmod_module_apply_filter(ctx, KMOD_FILTER_WHITELIST, list,
+								&filtered);
+	if (err < 0) {
+		ERR("Could not filter: %s\n", strerror(-err));
+		goto fail;
+	}
+	if (filtered == NULL) {
+		ERR("All modules were filtered out!\n");
+		goto fail;
+	}
+
+	kmod_list_foreach(l, filtered) {
+		const char *modname;
+		mod = kmod_module_get_module(l);
+		modname = kmod_module_get_name(mod);
+		/* These are not on whitelist, must be rejected */
+		if (strcmp("pcspkr2", modname) == 0 || strcmp("ext4", modname) == 0)
+			goto fail;
+		/* These are on whitelist, must be in list */
+		if (strcmp("pcspkr", modname) && strcmp("floppy", modname))
+			goto fail;
+		len++;
+		kmod_module_unref(mod);
+	}
+
+	if (len != 2)
+		goto fail;
+
+	kmod_module_unref_list(filtered);
+	kmod_module_unref_list(list);
+	kmod_unref(ctx);
+
+	return EXIT_SUCCESS;
+
+fail:
+	kmod_module_unref_list(list);
+fail_lookup:
+	kmod_unref(ctx);
+	return EXIT_FAILURE;
+}
+static const struct test swhitelist_1 = {
+	.name = "whitelist_1",
+	.description = "check if modules are correctly whitelisted",
+	.func = whitelist_1,
+	.config = {
+		[TC_ROOTFS] = TESTSUITE_ROOTFS "test-whitelist/",
+	},
+	.need_spawn = true,
+};
+
+static const struct test *tests[] = {
+	&swhitelist_1,
+	NULL,
+};
+
+TESTSUITE_MAIN(tests);
diff --git a/tools/modprobe.c b/tools/modprobe.c
index 58f6df9..81b8442 100644
--- a/tools/modprobe.c
+++ b/tools/modprobe.c
@@ -638,10 +638,14 @@ static int insmod(struct kmod_ctx *ctx, const char *alias,
 					extra_options, NULL, NULL, show);
 		}
 
-		if (err >= 0)
-			/* ignore flag return values such as a mod being blacklisted */
-			err = 0;
-		else {
+		if (err >= 0) {
+			if (err & KMOD_PROBE_APPLY_WHITELIST)
+				ERR("could not insert '%s': Module not on whitelist\n",
+							kmod_module_get_name(mod));
+			else
+				/* ignore flag return values such as a mod being blacklisted */
+				err = 0;
+		} else {
 			switch (err) {
 			case -EEXIST:
 				ERR("could not insert '%s': Module already in kernel\n",
-- 
1.7.10.4

--
To unsubscribe from this list: send the line "unsubscribe linux-modules" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[Index of Archives]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Big List of Linux Books]

  Powered by Linux