[RFC 09/10] kmod: add helpers for getting kmod count and limit

From: Luis R. Rodriguez
Date: Thu Dec 08 2016 - 14:49:35 EST


This adds helpers for getting access to the kmod count and limit from
userspace. While at it, this also lets userspace fine tune the kmod
limit after boot, it uses the shiny new proc_douintvec_minmax().

These knobs should help userspace more gracefully and deterministically
handle module loading.

Signed-off-by: Luis R. Rodriguez <mcgrof@xxxxxxxxxx>
---
include/linux/kmod.h | 8 +++++
kernel/kmod.c | 83 ++++++++++++++++++++++++++++++++++++++++++++++++++--
kernel/sysctl.c | 14 +++++++++
3 files changed, 103 insertions(+), 2 deletions(-)

diff --git a/include/linux/kmod.h b/include/linux/kmod.h
index 15783cd7f056..94c7379cff94 100644
--- a/include/linux/kmod.h
+++ b/include/linux/kmod.h
@@ -39,13 +39,21 @@ int __request_module(bool wait, const char *name, ...);
#define try_then_request_module(x, mod...) \
((x) ?: (__request_module(true, mod), (x)))
void init_kmod_umh(void);
+unsigned int get_kmod_umh_limit(void);
+int sysctl_kmod_count(struct ctl_table *table, int write,
+ void __user *buffer, size_t *lenp, loff_t *ppos);
+int sysctl_kmod_limit(struct ctl_table *table, int write,
+ void __user *buffer, size_t *lenp, loff_t *ppos);
#else
static inline int request_module(const char *name, ...) { return -ENOSYS; }
static inline int request_module_nowait(const char *name, ...) { return -ENOSYS; }
static inline void init_kmod_umh(void) { }
+static unsigned int get_kmod_umh_limit(void) { return 0; }
#define try_then_request_module(x, mod...) (x)
#endif

+#define get_kmod_umh_limit get_kmod_umh_limit
+
struct cred;
struct file;

diff --git a/kernel/kmod.c b/kernel/kmod.c
index ef65f4c3578a..a0f449f77ed7 100644
--- a/kernel/kmod.c
+++ b/kernel/kmod.c
@@ -138,6 +138,27 @@ static void kmod_umh_threads_put(void)
}

/**
+ * get_kmod_umh_limit - get concurrent modprobe thread limit
+ *
+ * Returns the number of allowed concurrent modprobe calls.
+ */
+unsigned int get_kmod_umh_limit(void)
+{
+ return max_modprobes;
+}
+EXPORT_SYMBOL_GPL(get_kmod_umh_limit);
+
+/**
+ * get_kmod_umh_count - get number of concurrent modprobe calls running
+ *
+ * Returns the number of concurrent modprobe calls currently running.
+ */
+int get_kmod_umh_count(void)
+{
+ return atomic_read(&kmod_concurrent);
+}
+
+/**
* __request_module - try to load a kernel module
* @wait: wait (or not) for the operation to complete
* @fmt: printf style format string for the name of the module
@@ -196,6 +217,11 @@ int __request_module(bool wait, const char *fmt, ...)
}
EXPORT_SYMBOL(__request_module);

+static void __set_max_modprobes(unsigned int suggested)
+{
+ max_modprobes = min((unsigned int) max_threads/2, suggested);
+}
+
/*
* If modprobe needs a service that is in a module, we get a recursive
* loop. Limit the number of running kmod threads to max_threads/2 or
@@ -212,12 +238,65 @@ EXPORT_SYMBOL(__request_module);
* 4096 concurrent modprobe instances:
*
* kmod.max_modprobes=4096
+ *
+ * You can also set the limit via sysctl:
+ *
+ * echo 4096 > /proc/sys/kernel/kmod-limit
+ *
+ * You can also set the query the current thread count:
+ *
+ * cat /proc/sys/kernel/kmod-count
+ *
+ * These knobs should enable userspace to more gracefully and
+ * deterministically handle module loading.
*/
void __init init_kmod_umh(void)
{
if (!max_modprobes)
- max_modprobes = min(max_threads/2,
- 2 << CONFIG_MAX_KMOD_CONCURRENT);
+ __set_max_modprobes(2 << CONFIG_MAX_KMOD_CONCURRENT);
+}
+
+int sysctl_kmod_count(struct ctl_table *table, int write,
+ void __user *buffer, size_t *lenp, loff_t *ppos)
+{
+ struct ctl_table t;
+ int ret = 0;
+ int count = get_kmod_umh_count();
+
+ t = *table;
+ t.data = &count;
+
+ if (write)
+ return -EPERM;
+
+ ret = proc_dointvec_minmax(&t, write, buffer, lenp, ppos);
+
+ return ret;
+}
+
+int sysctl_kmod_limit(struct ctl_table *table, int write,
+ void __user *buffer, size_t *lenp, loff_t *ppos)
+{
+ struct ctl_table t;
+ int ret;
+ unsigned int local_max_modprobes = max_modprobes;
+ unsigned int min = 0;
+ unsigned int max = max_threads/2;
+
+ t = *table;
+ t.data = &local_max_modprobes;
+ t.extra1 = &min;
+ t.extra2 = &max;
+
+ ret = proc_douintvec_minmax(&t, write, buffer, lenp, ppos);
+ if (ret == -ERANGE)
+ pr_err("modprobe thread valid range: %u - %u\n", min, max);
+ if (ret || !write)
+ return ret;
+
+ __set_max_modprobes((unsigned int) local_max_modprobes);
+
+ return 0;
}

#endif /* CONFIG_MODULES */
diff --git a/kernel/sysctl.c b/kernel/sysctl.c
index 06711e648fa3..0ba56001e49b 100644
--- a/kernel/sysctl.c
+++ b/kernel/sysctl.c
@@ -660,6 +660,20 @@ static struct ctl_table kern_table[] = {
.extra1 = &one,
.extra2 = &one,
},
+ {
+ .procname = "kmod-count",
+ .data = NULL, /* filled in by handler */
+ .maxlen = sizeof(int),
+ .mode = 0444,
+ .proc_handler = sysctl_kmod_count,
+ },
+ {
+ .procname = "kmod-limit",
+ .data = NULL, /* filled in by handler */
+ .maxlen = sizeof(unsigned int),
+ .mode = 0644,
+ .proc_handler = sysctl_kmod_limit,
+ },
#endif
#ifdef CONFIG_UEVENT_HELPER
{
--
2.10.1