On Tue, Nov 6, 2012 at 10:03 AM, Milan Broz <mbroz@xxxxxxxxxx> wrote: > 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) humn... this is confusing. We had a "kmod_list" namespace for managing the lists. Any other option? filter/filterlist maybe? > { > 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) module_is_whitelisted > +{ > + 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; and don't forget to change the returns :-) > +} > + > /** > * 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; So this is applied to all modules, including the dependencies. Why doesn't it follow the blacklist naming of adding a _ALL suffix? Besides that. If the user explicitly put a module in a whitelist - shouldn't it be allowed to load even if the module then starts depending on a new module? Let's say in kernel X.Y we had mod-bla-a, mod-bla-b, mod-bla-c. And in kernel X.(Y+1) some functionality were put together in mod-bla-common and the others started to depend on it. You will not load mod-bla-a regardless if it is in a blacklist. That would be a surprising effect, much more than "hey, this new module is loaded and it wasn't in previous kernel". > + > if (flags & KMOD_PROBE_APPLY_BLACKLIST_ALL) { > - struct kmod_list *filtered = NULL; > if you do this remove the blank line, too. > 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> Please remove the example. I don't want to encourage people to use it. > + <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> This means that if you have a file /etc/modprobe.d/bla.conf that contains 1 single whitelist will turn all other config files moot. Then for each install command in *other files* you need also a whitelist. Confusing... to say the least. And I need more strong words here saying to take care with this option, especially because it can render the machine unbootable in a kernel upgrade: because a module changed its name or because it started to depend on a new one. There's also no way to easily check if a module name in a whitelist became obsolete. > + </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 */ I realized this was copied from test-blacklist.c, but there's no need to keep this comment. It's this way by design. User is not supposed to build lists from outside the library. You can do this as a first step, removing from test-blacklist.c if you want. > +#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", > -- Lucas De Marchi -- 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