[PATCH 2/2] vsprintf: Add support for userspace strings

From: Richard Weinberger
Date: Sun May 10 2015 - 15:42:57 EST


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 <richard@xxxxxx>
---
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 majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/