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


Reply via email to