@@ -1076,6 +1076,121 @@ static char *module_options_concat(const char *opt, const char *xopt)
return r;
}
+/*
+ * Inspired by the next_arg() in kernel code.
+ * You can use " around spaces, but can't escape ".
+ */
+static unsigned int next_opt_index(const char *args)
+{
+ unsigned int i;
+ int in_quote = 0;
+
+ if (*args == '"')
+ in_quote = 1;
+
+ for (i = 0; args[i]; i++) {
+ if (isspace(args[i]) && !in_quote)
+ break;
+ if (args[i] == '"')
+ in_quote = !in_quote;
+ }
+
+ return i;
+}
+
+static unsigned int cnt_module_options(const char *args)
+{
+ unsigned int nr = 0;
+ unsigned int i = 0;
+
+ while (args[i]) {
+ nr++;
+ i += next_opt_index(args + i);
+ while (isspace(args[i]))
+ i++;
+ }
+ return nr;
+}
+
+struct module_option {
+ unsigned short begin;
+ unsigned short end;
+};
+
+static void parse_module_options(const char *args, unsigned int nr_opts,
+ struct module_option *opts)
+{
+ unsigned int nr;
+ unsigned int i;
+
+ for (nr = 0, i = 0; nr < nr_opts; nr++) {
+ opts[nr].begin = i;
+ i += next_opt_index(args + i);
+ opts[nr].end = i;
+
+ while (isspace(args[i]))
+ i++;
+ }
+}
+
+static bool is_options_equal(const char *args, struct module_option *left,
+ struct module_option *right)
+{
+ unsigned int left_len, right_len;
+
+ left_len = left->end - left->begin;
+ right_len = right->end - right->begin;
+ if (left_len != right_len)
+ return false;
+
+ return !memcmp(args+left->begin, args+right->begin, left_len);
+}
+
+KMOD_EXPORT int kmod_module_uniq_options(char **args)
+{
+ struct module_option *opts;
+ unsigned int nr_opts, i, j, k, delta;
+
+ if (!args || !*args)
+ return 0;
+
+ nr_opts = cnt_module_options(*args);
+ opts = calloc(nr_opts, sizeof(struct module_option));
+ if (!opts)
+ return -ENOMEM;
+ parse_module_options(*args, nr_opts, opts);
+
+ for (i = 0; i < nr_opts; i++) {
+ for (j = i+1; j < nr_opts; j++) {
+ if (is_options_equal(*args, &opts[i], &opts[j])) {
+ if (j + 1 < nr_opts) {
+ memmove(*args + opts[j].begin,
+ *args + opts[j+1].begin,
+ opts[nr_opts-1].end - opts[j+1].begin);
+
+ delta = opts[j+1].begin - opts[j].begin;
+ for (k = j+1; k < nr_opts; k++) {
+ opts[k].begin -= delta;
+ opts[k].end -= delta;
+ }
+
+ memmove(&opts[j], &opts[j+1],
+ (nr_opts-j-1) * sizeof(struct module_option));
+ }
+ nr_opts--;
+ j--;
+ }
+ }
+ }
+
+ if (nr_opts > 0)
+ if ((*args)[opts[nr_opts-1].end])
+ (*args)[opts[nr_opts-1].end] = '\0';
+
+ free(opts);
+ return 0;
+}
+
static int __kmod_module_get_probe_list(struct kmod_module *mod,
bool required,
bool ignorecmd,
@@ -1322,6 +1437,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 ?: "");
@@ -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);
@@ -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");
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@linux.alibaba.com> Cc: Lucas De Marchi <lucas.demarchi@intel.com> Cc: Lucas De Marchi <lucas.de.marchi@gmail.com> Cc: Topi Miettinen <toiwoton@gmail.com> Cc: Alexey Gladkov <gladkov.alexey@gmail.com> Cc: linux-modules@vger.kernel.org --- v2: - parameters can have spaces. - reduce memory usage. libkmod/libkmod-module.c | 121 +++++++++++++++++++++++++++++++++++++++++++++++ libkmod/libkmod.h | 1 + tools/insmod.c | 7 +++ 3 files changed, 129 insertions(+)