Add a new struct kernel_param_ops::get callback whose signature takes a struct seq_buf instead of a raw char buffer:
int (*get)(struct seq_buf *sb, const struct kernel_param *kp); The previously-legacy .get field is now .get_str (char *buffer); .get is the new seq_buf-aware form. param_attr_show() prefers .get when set, otherwise falls back to .get_str. WARN_ON_ONCE() if both are set. Return contract for .get: < 0 : errno propagated to userspace; seq_buf contents discarded = 0 : success; length derived from seq_buf_used() > 0 : forbidden; the dispatcher WARN_ON_ONCE()s and treats as 0 The default policy on seq_buf_has_overflowed() is silent truncation, matching scnprintf()/sysfs_emit() behaviour. Callbacks that want a specific overflow errno can check seq_buf_has_overflowed() and return their preferred error. No callbacks use .get yet; the legacy path is still the only one in use after this commit. A subsequent commit teaches DEFINE_KERNEL_PARAM_OPS to route initializers by type. Signed-off-by: Kees Cook <[email protected]> --- include/linux/moduleparam.h | 13 ++++++++++++- kernel/params.c | 26 ++++++++++++++++++++++++-- 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/include/linux/moduleparam.h b/include/linux/moduleparam.h index f5f4148e2504..c52120f6ac28 100644 --- a/include/linux/moduleparam.h +++ b/include/linux/moduleparam.h @@ -7,6 +7,7 @@ #include <linux/build_bug.h> #include <linux/compiler.h> #include <linux/init.h> +#include <linux/seq_buf.h> #include <linux/stringify.h> #include <linux/sysfs.h> #include <linux/types.h> @@ -62,7 +63,17 @@ struct kernel_param_ops { unsigned int flags; /* Returns 0, or -errno. arg is in kp->arg. */ int (*set)(const char *val, const struct kernel_param *kp); - /* Returns length written or -errno. Buffer is 4k (ie. be short!) */ + /* + * Format the parameter's value into @s. Return 0 on success + * (length derived from seq_buf_used()) or -errno on error. + * Exactly one of .get and .get_str should be set; the dispatcher + * WARNs and prefers .get if both are. + */ + int (*get)(struct seq_buf *s, const struct kernel_param *kp); + /* + * Returns length written or -errno. Buffer is 4k (ie. be short!). + * Deprecated: callbacks should implement .get instead. + */ int (*get_str)(char *buffer, const struct kernel_param *kp); /* Optional function to free kp->arg when module unloaded. */ void (*free)(void *arg); diff --git a/kernel/params.c b/kernel/params.c index 6852caea1785..4eda2d23ddf2 100644 --- a/kernel/params.c +++ b/kernel/params.c @@ -553,12 +553,34 @@ static ssize_t param_attr_show(const struct module_attribute *mattr, { int count; const struct param_attribute *attribute = to_param_attr(mattr); + const struct kernel_param_ops *ops = attribute->param->ops; - if (!attribute->param->ops->get_str) + if (!ops->get && !ops->get_str) return -EPERM; + WARN_ON_ONCE(ops->get && ops->get_str); + kernel_param_lock(mk->mod); - count = attribute->param->ops->get_str(buf, attribute->param); + if (ops->get) { + struct seq_buf s; + + seq_buf_init(&s, buf, PAGE_SIZE); + count = ops->get(&s, attribute->param); + if (count >= 0) { + WARN_ON_ONCE(count > 0); + count = seq_buf_used(&s); + /* Make sure string is terminated. */ + seq_buf_str(&s); + /* + * If overflowed, reduce count by 1 for trailing + * NUL byte. + */ + if (seq_buf_has_overflowed(&s)) + count--; + } + } else { + count = ops->get_str(buf, attribute->param); + } kernel_param_unlock(mk->mod); return count; } -- 2.34.1
