Add %pL format string to print userspace strings.
It works like %s but does copy_from_user() instead
of a memcpy().

Signed-off-by: Richard Weinberger <[email protected]>
---
 Documentation/printk-formats.txt |  6 +++
 lib/vsprintf.c                   | 89 ++++++++++++++++++++++++++++++++++------
 2 files changed, 82 insertions(+), 13 deletions(-)

diff --git a/Documentation/printk-formats.txt b/Documentation/printk-formats.txt
index 2216eb1..74f6038 100644
--- a/Documentation/printk-formats.txt
+++ b/Documentation/printk-formats.txt
@@ -284,6 +284,12 @@ bitmap and its derivatives such as cpumask and nodemask:
 
        Passed by reference.
 
+userspace string:
+
+       %pL
+
+       Like %s but works on strings located in userspace.
+
 Thank you for your cooperation and attention.
 
 
diff --git a/lib/vsprintf.c b/lib/vsprintf.c
index 092d5a7..4138340 100644
--- a/lib/vsprintf.c
+++ b/lib/vsprintf.c
@@ -355,6 +355,7 @@ int num_to_str(char *buf, int size, unsigned long long num)
 #define ZEROPAD        16              /* pad with zero, must be 16 == '0' - ' 
' */
 #define SMALL  32              /* use lowercase in hex (must be 32 == 0x20) */
 #define SPECIAL        64              /* prefix hex with "0x", octal with "0" 
*/
+#define USER   128             /* value points to user memory */
 
 enum format_type {
        FORMAT_TYPE_NONE, /* Just a string part */
@@ -510,12 +511,27 @@ static noinline_for_stack
 char *string(char *buf, char *end, const char *s, struct printf_spec spec)
 {
        int len, i;
+       int is_user = spec.flags & USER;
+
+       if (is_user) {
+               int max_len = min_t(unsigned long, spec.precision, PAGE_SIZE);
+
+               len = strnlen_user(s, max_len);
+               if (len == 0) {
+                       s = "(bad address)";
+                       len = strnlen(s, spec.precision);
+                       is_user = 0;
+               } else {
+                       /* strnlen_user() counts also the NUL byte */
+                       len--;
+               }
+       } else {
+               if ((unsigned long)s > (unsigned long)-PAGE_SIZE ||
+                   (unsigned long)s < PAGE_SIZE)
+                       s = "(null)";
 
-       if ((unsigned long)s > (unsigned long)-PAGE_SIZE ||
-           (unsigned long)s < PAGE_SIZE)
-               s = "(null)";
-
-       len = strnlen(s, spec.precision);
+               len = strnlen(s, spec.precision);
+       }
 
        if (!(spec.flags & LEFT)) {
                while (len < spec.field_width--) {
@@ -525,8 +541,17 @@ char *string(char *buf, char *end, const char *s, struct 
printf_spec spec)
                }
        }
        for (i = 0; i < len; ++i) {
-               if (buf < end)
+               if (buf >= end)
+                       goto next;
+
+               if (is_user) {
+                       if (get_user(*buf, s)) {
+                               /* get_user() failed, add a blank */
+                               *buf = ' ';
+                       }
+               } else
                        *buf = *s;
+next:
                ++buf; ++s;
        }
        while (len < spec.field_width--) {
@@ -1743,7 +1768,19 @@ qualifier:
 
        case 'p':
                spec->type = FORMAT_TYPE_PTR;
-               return ++fmt - start;
+               fmt++;
+               /*
+                * handle %pL here and turn it into a special FORMAT_TYPE_STR
+                * type.
+                * %pL is much like the %s case. This way we keep vbin_printf()
+                * straight forward.
+                */
+               if (*fmt == 'L') {
+                       spec->type = FORMAT_TYPE_STR;
+                       spec->flags |= USER;
+                       fmt++;
+               }
+               return fmt - start;
 
        case '%':
                spec->type = FORMAT_TYPE_PERCENT_CHAR;
@@ -2204,14 +2241,33 @@ do {                                                    
                \
 
                case FORMAT_TYPE_STR: {
                        const char *save_str = va_arg(args, char *);
+                       int is_user = spec.flags & USER;
                        size_t len;
 
-                       if ((unsigned long)save_str > (unsigned long)-PAGE_SIZE
-                                       || (unsigned long)save_str < PAGE_SIZE)
-                               save_str = "(null)";
-                       len = strlen(save_str) + 1;
-                       if (str + len < end)
-                               memcpy(str, save_str, len);
+                       if (is_user) {
+                               len = strnlen_user(save_str, PAGE_SIZE);
+                               if (len == 0) {
+                                       save_str = "(bad address)";
+                                       is_user = 0;
+                               } else {
+                                       if (copy_from_user(str, save_str, len)) 
{
+                                               save_str = "(bad address)";
+                                               is_user = 0;
+                                       }
+                               }
+                       }
+
+                       if (!is_user) {
+                               if ((unsigned long)save_str > (unsigned 
long)-PAGE_SIZE
+                                               || (unsigned long)save_str < 
PAGE_SIZE)
+                                       save_str = "(null)";
+
+                               len = strlen(save_str) + 1;
+
+                               if (str + len < end)
+                                       memcpy(str, save_str, len);
+                       }
+
                        str += len;
                        break;
                }
@@ -2364,6 +2420,13 @@ int bstr_printf(char *buf, size_t size, const char *fmt, 
const u32 *bin_buf)
                case FORMAT_TYPE_STR: {
                        const char *str_arg = args;
                        args += strlen(str_arg) + 1;
+
+                       /*
+                        * vbin_printf() sanitized the user provided string
+                        * for us and copied it into our binary buffer.
+                        */
+                       spec.flags &= ~USER;
+
                        str = string(str, end, (char *)str_arg, spec);
                        break;
                }
-- 
1.8.4.5

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to [email protected]
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

Reply via email to