sysfs: Add sysfs_emit and sysfs_emit_at to format sysfs output
authorJoe Perches <joe@perches.com>
Wed, 16 Sep 2020 20:40:38 +0000 (13:40 -0700)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Fri, 2 Oct 2020 10:02:30 +0000 (12:02 +0200)
Output defects can exist in sysfs content using sprintf and snprintf.

sprintf does not know the PAGE_SIZE maximum of the temporary buffer
used for outputting sysfs content and it's possible to overrun the
PAGE_SIZE buffer length.

Add a generic sysfs_emit function that knows that the size of the
temporary buffer and ensures that no overrun is done.

Add a generic sysfs_emit_at function that can be used in multiple
call situations that also ensures that no overrun is done.

Validate the output buffer argument to be page aligned.
Validate the offset len argument to be within the PAGE_SIZE buf.

Signed-off-by: Joe Perches <joe@perches.com>
Link: https://lore.kernel.org/r/884235202216d464d61ee975f7465332c86f76b2.1600285923.git.joe@perches.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Documentation/filesystems/sysfs.rst
fs/sysfs/file.c
include/linux/sysfs.h

index ab0f7795792b5bcd647f60905cbf1020e246e3a7..d44249050f4aae7f96502744a093d452c0bbd3c6 100644 (file)
@@ -242,12 +242,10 @@ Other notes:
   is 4096.
 
 - show() methods should return the number of bytes printed into the
-  buffer. This is the return value of scnprintf().
+  buffer.
 
-- show() must not use snprintf() when formatting the value to be
-  returned to user space. If you can guarantee that an overflow
-  will never happen you can use sprintf() otherwise you must use
-  scnprintf().
+- show() should only use sysfs_emit() or sysfs_emit_at() when formatting
+  the value to be returned to user space.
 
 - store() should return the number of bytes used from the buffer. If the
   entire buffer has been used, just return the count argument.
index eb6897ab78e79a9d1b0bbd1e194bd4b14c4750ad..96d0da65e0887ce8192a7c4719aadfb9cd76d7c5 100644 (file)
@@ -15,6 +15,7 @@
 #include <linux/list.h>
 #include <linux/mutex.h>
 #include <linux/seq_file.h>
+#include <linux/mm.h>
 
 #include "sysfs.h"
 
@@ -707,3 +708,57 @@ int sysfs_change_owner(struct kobject *kobj, kuid_t kuid, kgid_t kgid)
        return 0;
 }
 EXPORT_SYMBOL_GPL(sysfs_change_owner);
+
+/**
+ *     sysfs_emit - scnprintf equivalent, aware of PAGE_SIZE buffer.
+ *     @buf:   start of PAGE_SIZE buffer.
+ *     @fmt:   format
+ *     @...:   optional arguments to @format
+ *
+ *
+ * Returns number of characters written to @buf.
+ */
+int sysfs_emit(char *buf, const char *fmt, ...)
+{
+       va_list args;
+       int len;
+
+       if (WARN(!buf || offset_in_page(buf),
+                "invalid sysfs_emit: buf:%p\n", buf))
+               return 0;
+
+       va_start(args, fmt);
+       len = vscnprintf(buf, PAGE_SIZE, fmt, args);
+       va_end(args);
+
+       return len;
+}
+EXPORT_SYMBOL_GPL(sysfs_emit);
+
+/**
+ *     sysfs_emit_at - scnprintf equivalent, aware of PAGE_SIZE buffer.
+ *     @buf:   start of PAGE_SIZE buffer.
+ *     @at:    offset in @buf to start write in bytes
+ *             @at must be >= 0 && < PAGE_SIZE
+ *     @fmt:   format
+ *     @...:   optional arguments to @fmt
+ *
+ *
+ * Returns number of characters written starting at &@buf[@at].
+ */
+int sysfs_emit_at(char *buf, int at, const char *fmt, ...)
+{
+       va_list args;
+       int len;
+
+       if (WARN(!buf || offset_in_page(buf) || at < 0 || at >= PAGE_SIZE,
+                "invalid sysfs_emit_at: buf:%p at:%d\n", buf, at))
+               return 0;
+
+       va_start(args, fmt);
+       len = vscnprintf(buf + at, PAGE_SIZE - at, fmt, args);
+       va_end(args);
+
+       return len;
+}
+EXPORT_SYMBOL_GPL(sysfs_emit_at);
index 34e84122f635f21ea32df2df4b8c21460ce073c6..2caa34c1ca1ade441589d74508a0ff6b2d47d2ce 100644 (file)
@@ -329,6 +329,10 @@ int sysfs_groups_change_owner(struct kobject *kobj,
 int sysfs_group_change_owner(struct kobject *kobj,
                             const struct attribute_group *groups, kuid_t kuid,
                             kgid_t kgid);
+__printf(2, 3)
+int sysfs_emit(char *buf, const char *fmt, ...);
+__printf(3, 4)
+int sysfs_emit_at(char *buf, int at, const char *fmt, ...);
 
 #else /* CONFIG_SYSFS */
 
@@ -576,6 +580,17 @@ static inline int sysfs_group_change_owner(struct kobject *kobj,
        return 0;
 }
 
+__printf(2, 3)
+static inline int sysfs_emit(char *buf, const char *fmt, ...)
+{
+       return 0;
+}
+
+__printf(3, 4)
+static inline int sysfs_emit_at(char *buf, int at, const char *fmt, ...)
+{
+       return 0;
+}
 #endif /* CONFIG_SYSFS */
 
 static inline int __must_check sysfs_create_file(struct kobject *kobj,