As in commit 2045cdfa1b40 ("netfilter: nf_conntrack: Fix possible possible crash on module loading"), Loading the nf_conntrack module with doubled hashsize parameter, i.e. modprobe nf_conntrack hashsize=12345 hashsize=12345 causes NULL-ptr deref. If 'hashsize' specified twice, the nf_conntrack_set_hashsize() function will be called also twice. In addition, we may also construct the doubled hashsize parameter scenario by using the following configuration: $ cat /etc/modprobe.d/firewalld-sysctls.conf install nf_conntrack /usr/sbin/modprobe --ignore-install nf_conntrack $CMDLINE_OPTS && /usr/sbin/sysctl --quiet --pattern 'net[.]netfilter[.]nf_conntrack.*' --system $ cat /etc/modprobe.d/nf_conntrack.conf options nf_conntrack hashsize=187600 $ sudo modprobe nf_conntrack -v install /usr/sbin/modprobe --ignore-install nf_conntrack $CMDLINE_OPTS && /usr/sbin/sysctl --quiet --pattern 'net[.]netfilter[.]nf_conntrack.*' --system hashsize=187500 insmod /path/to/nf_conntrack.ko hashsize=187500 hashsize=187500 Passing multiple repeated parameters to the kernel may trigger kernel bugs and affect the performance of loading kernel modules. We introduce the nf_conntrack_set_hashsize() and hope to solve this problem. Signed-off-by: Wen Yang <wenyang@xxxxxxxxxxxxxxxxx> Cc: Lucas De Marchi <lucas.demarchi@xxxxxxxxx> Cc: Lucas De Marchi <lucas.de.marchi@xxxxxxxxx> Cc: Topi Miettinen <toiwoton@xxxxxxxxx> Cc: Alexey Gladkov <gladkov.alexey@xxxxxxxxx> Cc: linux-modules@xxxxxxxxxxxxxxx --- libkmod/libkmod-module.c | 72 ++++++++++++++++++++++++++++++++++++++++++++++++ libkmod/libkmod.h | 1 + tools/insmod.c | 7 +++++ 3 files changed, 80 insertions(+) diff --git a/libkmod/libkmod-module.c b/libkmod/libkmod-module.c index 76a6dc3..f42beb8 100644 --- a/libkmod/libkmod-module.c +++ b/libkmod/libkmod-module.c @@ -1076,6 +1076,72 @@ static char *module_options_concat(const char *opt, const char *xopt) return r; } +static unsigned int module_options_cnt(const char *opts) +{ + int nr_opts = 0; + char *saveptr, *tok, *buf; + + if (!opts) + return 0; + + buf = strdup(opts); + for (tok = strtok_r(buf, " ", &saveptr); tok != NULL; + tok = strtok_r(NULL, " ", &saveptr), nr_opts++) {} + + free(buf); + return nr_opts; +} + +KMOD_EXPORT int kmod_module_uniq_options(char **opts) +{ + char *buf; + char **array; + bool duplicated; + char *saveptr, *tok; + int nr_opts, len, i, j; + + if (!opts || !*opts) + return 0; + + buf = strdup(*opts); + if (!buf) + return -ENOMEM; + + len = strlen(buf); + nr_opts = module_options_cnt(buf); + array = calloc(nr_opts, sizeof(char *)); + if (!buf) + return -ENOMEM; + + for (tok = strtok_r(buf, " ", &saveptr), i = 0; tok != NULL; + tok = strtok_r(NULL, " ", &saveptr), i++) { + array[i] = tok; + } + + (*opts)[0] = '\0'; + len = 0; + for (i = 0; i < nr_opts; i++) { + duplicated = false; + for (j = 0; j < i; j++) { + if (strcmp(array[i], array[j]) == 0) { + duplicated = true; + break; + } + } + + if (!duplicated) { + strcat(*opts, array[i]); + strcat(*opts, " "); + len += strlen(array[i]) + 1; + } + } + + (*opts)[len] = '\0'; + free(buf); + free(array); + return 0; +} + static int __kmod_module_get_probe_list(struct kmod_module *mod, bool required, bool ignorecmd, @@ -1322,6 +1388,12 @@ KMOD_EXPORT int kmod_module_probe_insert_module(struct kmod_module *mod, options = module_options_concat(moptions, m == mod ? extra_options : NULL); + err = kmod_module_uniq_options(&options); + if (err < 0) { + free(options); + return err; + } + if (cmd != NULL && !m->ignorecmd) { if (print_action != NULL) print_action(m, true, options ?: ""); diff --git a/libkmod/libkmod.h b/libkmod/libkmod.h index 3cab2e5..774e52a 100644 --- a/libkmod/libkmod.h +++ b/libkmod/libkmod.h @@ -186,6 +186,7 @@ int kmod_module_probe_insert_module(struct kmod_module *mod, const char *kmod_module_get_name(const struct kmod_module *mod); const char *kmod_module_get_path(const struct kmod_module *mod); const char *kmod_module_get_options(const struct kmod_module *mod); +int kmod_module_uniq_options(char **opts); const char *kmod_module_get_install_commands(const struct kmod_module *mod); const char *kmod_module_get_remove_commands(const struct kmod_module *mod); struct kmod_list *kmod_module_get_dependencies(const struct kmod_module *mod); diff --git a/tools/insmod.c b/tools/insmod.c index c422971..2dc0c22 100644 --- a/tools/insmod.c +++ b/tools/insmod.c @@ -132,6 +132,13 @@ static int do_insmod(int argc, char *argv[]) opts[optslen] = '\0'; } + err = kmod_module_uniq_options(&opts); + if (err < 0) { + ERR("kmod_module_uniq_options() failed!\n"); + free(opts); + return err; + } + ctx = kmod_new(NULL, &null_config); if (!ctx) { ERR("kmod_new() failed!\n"); -- 1.8.3.1