Re: [RFC PATCH] kmod: add whitelist option

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

 



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

[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