From 6eea242f9bcdf828bb56334d8ee5c7cb466e4bcd Mon Sep 17 00:00:00 2001 From: Petr Mladek Date: Wed, 17 Apr 2019 13:53:41 +0200 Subject: vsprintf: Shuffle restricted_pointer() This is just a preparation step for further changes. The patch does not change the code. Link: http://lkml.kernel.org/r/20190417115350.20479-2-pmladek@suse.com To: Rasmus Villemoes Cc: Linus Torvalds Cc: "Tobin C . Harding" Cc: Joe Perches Cc: Andrew Morton Cc: Michal Hocko Cc: Steven Rostedt Cc: Sergey Senozhatsky Cc: linux-kernel@vger.kernel.org Reviewed-by: Andy Shevchenko Reviewed-by: Sergey Senozhatsky Signed-off-by: Petr Mladek --- lib/vsprintf.c | 98 +++++++++++++++++++++++++++++----------------------------- 1 file changed, 49 insertions(+), 49 deletions(-) (limited to 'lib/vsprintf.c') diff --git a/lib/vsprintf.c b/lib/vsprintf.c index 791b6fa36905..eb7b4a06e1f0 100644 --- a/lib/vsprintf.c +++ b/lib/vsprintf.c @@ -717,6 +717,55 @@ static char *ptr_to_id(char *buf, char *end, const void *ptr, return pointer_string(buf, end, (const void *)hashval, spec); } +int kptr_restrict __read_mostly; + +static noinline_for_stack +char *restricted_pointer(char *buf, char *end, const void *ptr, + struct printf_spec spec) +{ + switch (kptr_restrict) { + case 0: + /* Always print %pK values */ + break; + case 1: { + const struct cred *cred; + + /* + * kptr_restrict==1 cannot be used in IRQ context + * because its test for CAP_SYSLOG would be meaningless. + */ + if (in_irq() || in_serving_softirq() || in_nmi()) { + if (spec.field_width == -1) + spec.field_width = 2 * sizeof(ptr); + return string(buf, end, "pK-error", spec); + } + + /* + * Only print the real pointer value if the current + * process has CAP_SYSLOG and is running with the + * same credentials it started with. This is because + * access to files is checked at open() time, but %pK + * checks permission at read() time. We don't want to + * leak pointer values if a binary opens a file using + * %pK and then elevates privileges before reading it. + */ + cred = current_cred(); + if (!has_capability_noaudit(current, CAP_SYSLOG) || + !uid_eq(cred->euid, cred->uid) || + !gid_eq(cred->egid, cred->gid)) + ptr = NULL; + break; + } + case 2: + default: + /* Always print 0's for %pK */ + ptr = NULL; + break; + } + + return pointer_string(buf, end, ptr, spec); +} + static noinline_for_stack char *dentry_name(char *buf, char *end, const struct dentry *d, struct printf_spec spec, const char *fmt) @@ -1476,55 +1525,6 @@ char *uuid_string(char *buf, char *end, const u8 *addr, return string(buf, end, uuid, spec); } -int kptr_restrict __read_mostly; - -static noinline_for_stack -char *restricted_pointer(char *buf, char *end, const void *ptr, - struct printf_spec spec) -{ - switch (kptr_restrict) { - case 0: - /* Always print %pK values */ - break; - case 1: { - const struct cred *cred; - - /* - * kptr_restrict==1 cannot be used in IRQ context - * because its test for CAP_SYSLOG would be meaningless. - */ - if (in_irq() || in_serving_softirq() || in_nmi()) { - if (spec.field_width == -1) - spec.field_width = 2 * sizeof(ptr); - return string(buf, end, "pK-error", spec); - } - - /* - * Only print the real pointer value if the current - * process has CAP_SYSLOG and is running with the - * same credentials it started with. This is because - * access to files is checked at open() time, but %pK - * checks permission at read() time. We don't want to - * leak pointer values if a binary opens a file using - * %pK and then elevates privileges before reading it. - */ - cred = current_cred(); - if (!has_capability_noaudit(current, CAP_SYSLOG) || - !uid_eq(cred->euid, cred->uid) || - !gid_eq(cred->egid, cred->gid)) - ptr = NULL; - break; - } - case 2: - default: - /* Always print 0's for %pK */ - ptr = NULL; - break; - } - - return pointer_string(buf, end, ptr, spec); -} - static noinline_for_stack char *netdev_bits(char *buf, char *end, const void *addr, struct printf_spec spec, const char *fmt) -- cgit v1.2.3 From 1ac2f9789c4b76ad749870c25ffae0cbcd1f510f Mon Sep 17 00:00:00 2001 From: Petr Mladek Date: Wed, 17 Apr 2019 13:53:42 +0200 Subject: vsprintf: Consistent %pK handling for kptr_restrict == 0 restricted_pointer() pretends that it prints the address when kptr_restrict is set to zero. But it is never called in this situation. Instead, pointer() falls back to ptr_to_id() and hashes the pointer. This patch removes the potential confusion. klp_restrict is checked only in restricted_pointer(). It actually fixes a small race when the address might get printed unhashed: CPU0 CPU1 pointer() if (!kptr_restrict) /* for example set to 2 */ restricted_pointer() /* echo 0 >/proc/sys/kernel/kptr_restrict */ proc_dointvec_minmax_sysadmin() klpr_restrict = 0; switch(kptr_restrict) case 0: break: number() Fixes: ef0010a30935de4e0211 ("vsprintf: don't use 'restricted_pointer()' when not restricting") Link: http://lkml.kernel.org/r/20190417115350.20479-3-pmladek@suse.com To: Andy Shevchenko To: Rasmus Villemoes Cc: Linus Torvalds Cc: "Tobin C . Harding" Cc: Joe Perches Cc: Andrew Morton Cc: Michal Hocko Cc: Steven Rostedt Cc: Sergey Senozhatsky Cc: linux-kernel@vger.kernel.org Cc: Kees Cook Reviewed-by: Andy Shevchenko Reviewed-by: Steven Rostedt (VMware) Reviewed-by: Sergey Senozhatsky Signed-off-by: Petr Mladek --- lib/vsprintf.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) (limited to 'lib/vsprintf.c') diff --git a/lib/vsprintf.c b/lib/vsprintf.c index eb7b4a06e1f0..2af48948a973 100644 --- a/lib/vsprintf.c +++ b/lib/vsprintf.c @@ -725,8 +725,8 @@ char *restricted_pointer(char *buf, char *end, const void *ptr, { switch (kptr_restrict) { case 0: - /* Always print %pK values */ - break; + /* Handle as %p, hash and do _not_ leak addresses. */ + return ptr_to_id(buf, end, ptr, spec); case 1: { const struct cred *cred; @@ -2041,8 +2041,6 @@ char *pointer(const char *fmt, char *buf, char *end, void *ptr, return buf; } case 'K': - if (!kptr_restrict) - break; return restricted_pointer(buf, end, ptr, spec); case 'N': return netdev_bits(buf, end, ptr, spec, fmt); -- cgit v1.2.3 From d529ac4194f2c346b2f62f0f473a578a7357039b Mon Sep 17 00:00:00 2001 From: Petr Mladek Date: Wed, 17 Apr 2019 13:53:43 +0200 Subject: vsprintf: Do not check address of well-known strings We are going to check the address using probe_kernel_address(). It will be more expensive and it does not make sense for well known address. This patch splits the string() function. The variant without the check is then used on locations that handle string constants or strings defined as local variables. This patch does not change the existing behavior. Link: http://lkml.kernel.org/r/20190417115350.20479-4-pmladek@suse.com To: Rasmus Villemoes Cc: Linus Torvalds Cc: "Tobin C . Harding" Cc: Joe Perches Cc: Andrew Morton Cc: Michal Hocko Cc: Steven Rostedt Cc: Sergey Senozhatsky Cc: linux-kernel@vger.kernel.org Reviewed-by: Andy Shevchenko Signed-off-by: Petr Mladek Reviewed-by: Sergey Senozhatsky --- lib/vsprintf.c | 81 +++++++++++++++++++++++++++++++--------------------------- 1 file changed, 44 insertions(+), 37 deletions(-) (limited to 'lib/vsprintf.c') diff --git a/lib/vsprintf.c b/lib/vsprintf.c index 2af48948a973..c9c9a1179870 100644 --- a/lib/vsprintf.c +++ b/lib/vsprintf.c @@ -593,15 +593,13 @@ char *widen_string(char *buf, int n, char *end, struct printf_spec spec) return buf; } -static noinline_for_stack -char *string(char *buf, char *end, const char *s, struct printf_spec spec) +/* Handle string from a well known address. */ +static char *string_nocheck(char *buf, char *end, const char *s, + struct printf_spec spec) { int len = 0; size_t lim = spec.precision; - if ((unsigned long)s < PAGE_SIZE) - s = "(null)"; - while (lim--) { char c = *s++; if (!c) @@ -615,6 +613,15 @@ char *string(char *buf, char *end, const char *s, struct printf_spec spec) } static noinline_for_stack +char *string(char *buf, char *end, const char *s, + struct printf_spec spec) +{ + if ((unsigned long)s < PAGE_SIZE) + s = "(null)"; + + return string_nocheck(buf, end, s, spec); +} + char *pointer_string(char *buf, char *end, const void *ptr, struct printf_spec spec) { @@ -701,7 +708,7 @@ static char *ptr_to_id(char *buf, char *end, const void *ptr, if (static_branch_unlikely(¬_filled_random_ptr_key)) { spec.field_width = 2 * sizeof(ptr); /* string length must be less than default_width */ - return string(buf, end, str, spec); + return string_nocheck(buf, end, str, spec); } #ifdef CONFIG_64BIT @@ -737,7 +744,7 @@ char *restricted_pointer(char *buf, char *end, const void *ptr, if (in_irq() || in_serving_softirq() || in_nmi()) { if (spec.field_width == -1) spec.field_width = 2 * sizeof(ptr); - return string(buf, end, "pK-error", spec); + return string_nocheck(buf, end, "pK-error", spec); } /* @@ -851,7 +858,7 @@ char *symbol_string(char *buf, char *end, void *ptr, else sprint_symbol_no_offset(sym, value); - return string(buf, end, sym, spec); + return string_nocheck(buf, end, sym, spec); #else return special_hex_number(buf, end, value, sizeof(void *)); #endif @@ -937,27 +944,27 @@ char *resource_string(char *buf, char *end, struct resource *res, *p++ = '['; if (res->flags & IORESOURCE_IO) { - p = string(p, pend, "io ", str_spec); + p = string_nocheck(p, pend, "io ", str_spec); specp = &io_spec; } else if (res->flags & IORESOURCE_MEM) { - p = string(p, pend, "mem ", str_spec); + p = string_nocheck(p, pend, "mem ", str_spec); specp = &mem_spec; } else if (res->flags & IORESOURCE_IRQ) { - p = string(p, pend, "irq ", str_spec); + p = string_nocheck(p, pend, "irq ", str_spec); specp = &default_dec_spec; } else if (res->flags & IORESOURCE_DMA) { - p = string(p, pend, "dma ", str_spec); + p = string_nocheck(p, pend, "dma ", str_spec); specp = &default_dec_spec; } else if (res->flags & IORESOURCE_BUS) { - p = string(p, pend, "bus ", str_spec); + p = string_nocheck(p, pend, "bus ", str_spec); specp = &bus_spec; } else { - p = string(p, pend, "??? ", str_spec); + p = string_nocheck(p, pend, "??? ", str_spec); specp = &mem_spec; decode = 0; } if (decode && res->flags & IORESOURCE_UNSET) { - p = string(p, pend, "size ", str_spec); + p = string_nocheck(p, pend, "size ", str_spec); p = number(p, pend, resource_size(res), *specp); } else { p = number(p, pend, res->start, *specp); @@ -968,21 +975,21 @@ char *resource_string(char *buf, char *end, struct resource *res, } if (decode) { if (res->flags & IORESOURCE_MEM_64) - p = string(p, pend, " 64bit", str_spec); + p = string_nocheck(p, pend, " 64bit", str_spec); if (res->flags & IORESOURCE_PREFETCH) - p = string(p, pend, " pref", str_spec); + p = string_nocheck(p, pend, " pref", str_spec); if (res->flags & IORESOURCE_WINDOW) - p = string(p, pend, " window", str_spec); + p = string_nocheck(p, pend, " window", str_spec); if (res->flags & IORESOURCE_DISABLED) - p = string(p, pend, " disabled", str_spec); + p = string_nocheck(p, pend, " disabled", str_spec); } else { - p = string(p, pend, " flags ", str_spec); + p = string_nocheck(p, pend, " flags ", str_spec); p = number(p, pend, res->flags, default_flag_spec); } *p++ = ']'; *p = '\0'; - return string(buf, end, sym, spec); + return string_nocheck(buf, end, sym, spec); } static noinline_for_stack @@ -1150,7 +1157,7 @@ char *mac_address_string(char *buf, char *end, u8 *addr, } *p = '\0'; - return string(buf, end, mac_addr, spec); + return string_nocheck(buf, end, mac_addr, spec); } static noinline_for_stack @@ -1313,7 +1320,7 @@ char *ip6_addr_string(char *buf, char *end, const u8 *addr, else ip6_string(ip6_addr, addr, fmt); - return string(buf, end, ip6_addr, spec); + return string_nocheck(buf, end, ip6_addr, spec); } static noinline_for_stack @@ -1324,7 +1331,7 @@ char *ip4_addr_string(char *buf, char *end, const u8 *addr, ip4_string(ip4_addr, addr, fmt); - return string(buf, end, ip4_addr, spec); + return string_nocheck(buf, end, ip4_addr, spec); } static noinline_for_stack @@ -1386,7 +1393,7 @@ char *ip6_addr_string_sa(char *buf, char *end, const struct sockaddr_in6 *sa, } *p = '\0'; - return string(buf, end, ip6_addr, spec); + return string_nocheck(buf, end, ip6_addr, spec); } static noinline_for_stack @@ -1421,7 +1428,7 @@ char *ip4_addr_string_sa(char *buf, char *end, const struct sockaddr_in *sa, } *p = '\0'; - return string(buf, end, ip4_addr, spec); + return string_nocheck(buf, end, ip4_addr, spec); } static noinline_for_stack @@ -1522,7 +1529,7 @@ char *uuid_string(char *buf, char *end, const u8 *addr, *p = 0; - return string(buf, end, uuid, spec); + return string_nocheck(buf, end, uuid, spec); } static noinline_for_stack @@ -1736,13 +1743,13 @@ char *device_node_gen_full_name(const struct device_node *np, char *buf, char *e /* special case for root node */ if (!parent) - return string(buf, end, "/", default_str_spec); + return string_nocheck(buf, end, "/", default_str_spec); for (depth = 0; parent->parent; depth++) parent = parent->parent; for ( ; depth >= 0; depth--) { - buf = string(buf, end, "/", default_str_spec); + buf = string_nocheck(buf, end, "/", default_str_spec); buf = string(buf, end, device_node_name_for_depth(np, depth), default_str_spec); } @@ -1770,10 +1777,10 @@ char *device_node_string(char *buf, char *end, struct device_node *dn, str_spec.field_width = -1; if (!IS_ENABLED(CONFIG_OF)) - return string(buf, end, "(!OF)", spec); + return string_nocheck(buf, end, "(!OF)", spec); if ((unsigned long)dn < PAGE_SIZE) - return string(buf, end, "(null)", spec); + return string_nocheck(buf, end, "(null)", spec); /* simple case without anything any more format specifiers */ fmt++; @@ -1814,7 +1821,7 @@ char *device_node_string(char *buf, char *end, struct device_node *dn, tbuf[2] = of_node_check_flag(dn, OF_POPULATED) ? 'P' : '-'; tbuf[3] = of_node_check_flag(dn, OF_POPULATED_BUS) ? 'B' : '-'; tbuf[4] = 0; - buf = string(buf, end, tbuf, str_spec); + buf = string_nocheck(buf, end, tbuf, str_spec); break; case 'c': /* major compatible string */ ret = of_property_read_string(dn, "compatible", &p); @@ -1825,10 +1832,10 @@ char *device_node_string(char *buf, char *end, struct device_node *dn, has_mult = false; of_property_for_each_string(dn, "compatible", prop, p) { if (has_mult) - buf = string(buf, end, ",", str_spec); - buf = string(buf, end, "\"", str_spec); + buf = string_nocheck(buf, end, ",", str_spec); + buf = string_nocheck(buf, end, "\"", str_spec); buf = string(buf, end, p, str_spec); - buf = string(buf, end, "\"", str_spec); + buf = string_nocheck(buf, end, "\"", str_spec); has_mult = true; } @@ -1966,7 +1973,7 @@ char *pointer(const char *fmt, char *buf, char *end, void *ptr, */ if (spec.field_width == -1) spec.field_width = default_width; - return string(buf, end, "(null)", spec); + return string_nocheck(buf, end, "(null)", spec); } switch (*fmt) { @@ -2022,7 +2029,7 @@ char *pointer(const char *fmt, char *buf, char *end, void *ptr, case AF_INET6: return ip6_addr_string_sa(buf, end, &sa->v6, spec, fmt); default: - return string(buf, end, "(invalid address)", spec); + return string_nocheck(buf, end, "(invalid address)", spec); }} } break; -- cgit v1.2.3 From f00cc102b862be688fe090aec30e08d61a8f5e63 Mon Sep 17 00:00:00 2001 From: Petr Mladek Date: Wed, 17 Apr 2019 13:53:44 +0200 Subject: vsprintf: Factor out %p[iI] handler as ip_addr_string() Move the non-trivial code from the long pointer() function. We are going to improve error handling that will make it even more complicated. This patch does not change the existing behavior. Link: http://lkml.kernel.org/r/20190417115350.20479-5-pmladek@suse.com To: Rasmus Villemoes Cc: Linus Torvalds Cc: "Tobin C . Harding" Cc: Joe Perches Cc: Andrew Morton Cc: Michal Hocko Cc: Steven Rostedt Cc: Sergey Senozhatsky Cc: linux-kernel@vger.kernel.org Reviewed-by: Sergey Senozhatsky Reviewed-by: Andy Shevchenko Signed-off-by: Petr Mladek --- lib/vsprintf.c | 52 ++++++++++++++++++++++++++++++---------------------- 1 file changed, 30 insertions(+), 22 deletions(-) (limited to 'lib/vsprintf.c') diff --git a/lib/vsprintf.c b/lib/vsprintf.c index c9c9a1179870..8ca29bc0d786 100644 --- a/lib/vsprintf.c +++ b/lib/vsprintf.c @@ -1431,6 +1431,35 @@ char *ip4_addr_string_sa(char *buf, char *end, const struct sockaddr_in *sa, return string_nocheck(buf, end, ip4_addr, spec); } +static noinline_for_stack +char *ip_addr_string(char *buf, char *end, const void *ptr, + struct printf_spec spec, const char *fmt) +{ + switch (fmt[1]) { + case '6': + return ip6_addr_string(buf, end, ptr, spec, fmt); + case '4': + return ip4_addr_string(buf, end, ptr, spec, fmt); + case 'S': { + const union { + struct sockaddr raw; + struct sockaddr_in v4; + struct sockaddr_in6 v6; + } *sa = ptr; + + switch (sa->raw.sa_family) { + case AF_INET: + return ip4_addr_string_sa(buf, end, &sa->v4, spec, fmt); + case AF_INET6: + return ip6_addr_string_sa(buf, end, &sa->v6, spec, fmt); + default: + return string_nocheck(buf, end, "(invalid address)", spec); + }} + } + + return ptr_to_id(buf, end, ptr, spec); +} + static noinline_for_stack char *escaped_string(char *buf, char *end, u8 *addr, struct printf_spec spec, const char *fmt) @@ -2011,28 +2040,7 @@ char *pointer(const char *fmt, char *buf, char *end, void *ptr, * 4: 001.002.003.004 * 6: 000102...0f */ - switch (fmt[1]) { - case '6': - return ip6_addr_string(buf, end, ptr, spec, fmt); - case '4': - return ip4_addr_string(buf, end, ptr, spec, fmt); - case 'S': { - const union { - struct sockaddr raw; - struct sockaddr_in v4; - struct sockaddr_in6 v6; - } *sa = ptr; - - switch (sa->raw.sa_family) { - case AF_INET: - return ip4_addr_string_sa(buf, end, &sa->v4, spec, fmt); - case AF_INET6: - return ip6_addr_string_sa(buf, end, &sa->v6, spec, fmt); - default: - return string_nocheck(buf, end, "(invalid address)", spec); - }} - } - break; + return ip_addr_string(buf, end, ptr, spec, fmt); case 'E': return escaped_string(buf, end, ptr, spec, fmt); case 'U': -- cgit v1.2.3 From 45c3e93d751ea50861c796da3cbfc848fa6ddf55 Mon Sep 17 00:00:00 2001 From: Petr Mladek Date: Wed, 17 Apr 2019 13:53:45 +0200 Subject: vsprintf: Factor out %pV handler as va_format() Move the code from the long pointer() function. We are going to improve error handling that will make it more complicated. This patch does not change the existing behavior. Link: http://lkml.kernel.org/r/20190417115350.20479-6-pmladek@suse.com To: Rasmus Villemoes Cc: Linus Torvalds Cc: "Tobin C . Harding" Cc: Joe Perches Cc: Andrew Morton Cc: Michal Hocko Cc: Steven Rostedt Cc: Sergey Senozhatsky Cc: linux-kernel@vger.kernel.org Reviewed-by: Sergey Senozhatsky Reviewed-by: Andy Shevchenko Signed-off-by: Petr Mladek --- lib/vsprintf.c | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) (limited to 'lib/vsprintf.c') diff --git a/lib/vsprintf.c b/lib/vsprintf.c index 8ca29bc0d786..12b71a4d4613 100644 --- a/lib/vsprintf.c +++ b/lib/vsprintf.c @@ -1520,6 +1520,17 @@ char *escaped_string(char *buf, char *end, u8 *addr, struct printf_spec spec, return buf; } +static char *va_format(char *buf, char *end, struct va_format *va_fmt) +{ + va_list va; + + va_copy(va, *va_fmt->va); + buf += vsnprintf(buf, end > buf ? end - buf : 0, va_fmt->fmt, va); + va_end(va); + + return buf; +} + static noinline_for_stack char *uuid_string(char *buf, char *end, const u8 *addr, struct printf_spec spec, const char *fmt) @@ -2046,15 +2057,7 @@ char *pointer(const char *fmt, char *buf, char *end, void *ptr, case 'U': return uuid_string(buf, end, ptr, spec, fmt); case 'V': - { - va_list va; - - va_copy(va, *((struct va_format *)ptr)->va); - buf += vsnprintf(buf, end > buf ? end - buf : 0, - ((struct va_format *)ptr)->fmt, va); - va_end(va); - return buf; - } + return va_format(buf, end, ptr); case 'K': return restricted_pointer(buf, end, ptr, spec); case 'N': -- cgit v1.2.3 From 798cc27a305e7b35b7bff3a71257e6fe57f70bc1 Mon Sep 17 00:00:00 2001 From: Petr Mladek Date: Wed, 17 Apr 2019 13:53:46 +0200 Subject: vsprintf: Factor out %pO handler as kobject_string() Move code from the long pointer() function. We are going to improve error handling that will make it even more complicated. This patch does not change the existing behavior. Link: http://lkml.kernel.org/r/20190417115350.20479-7-pmladek@suse.com To: Rasmus Villemoes Cc: Linus Torvalds Cc: "Tobin C . Harding" Cc: Joe Perches Cc: Andrew Morton Cc: Michal Hocko Cc: Steven Rostedt Cc: Sergey Senozhatsky Cc: linux-kernel@vger.kernel.org Cc: Kees Cook Reviewed-by: Sergey Senozhatsky Reviewed-by: Andy Shevchenko Signed-off-by: Petr Mladek --- lib/vsprintf.c | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) (limited to 'lib/vsprintf.c') diff --git a/lib/vsprintf.c b/lib/vsprintf.c index 12b71a4d4613..9817d171f608 100644 --- a/lib/vsprintf.c +++ b/lib/vsprintf.c @@ -1888,6 +1888,17 @@ char *device_node_string(char *buf, char *end, struct device_node *dn, return widen_string(buf, buf - buf_start, end, spec); } +static char *kobject_string(char *buf, char *end, void *ptr, + struct printf_spec spec, const char *fmt) +{ + switch (fmt[1]) { + case 'F': + return device_node_string(buf, end, ptr, spec, fmt + 1); + } + + return ptr_to_id(buf, end, ptr, spec); +} + /* * Show a '%p' thing. A kernel extension is that the '%p' is followed * by an extra set of alphanumeric characters that are extended format @@ -2082,11 +2093,7 @@ char *pointer(const char *fmt, char *buf, char *end, void *ptr, case 'G': return flags_string(buf, end, ptr, fmt); case 'O': - switch (fmt[1]) { - case 'F': - return device_node_string(buf, end, ptr, spec, fmt + 1); - } - break; + return kobject_string(buf, end, ptr, spec, fmt); case 'x': return pointer_string(buf, end, ptr, spec); } -- cgit v1.2.3 From 0b74d4d763fd4ee9daa53889324300587c015338 Mon Sep 17 00:00:00 2001 From: Petr Mladek Date: Wed, 17 Apr 2019 13:53:47 +0200 Subject: vsprintf: Consolidate handling of unknown pointer specifiers There are few printk formats that make sense only with two or more specifiers. Also some specifiers make sense only when a kernel feature is enabled. The handling of unknown specifiers is inconsistent and not helpful. Using WARN() looks like an overkill for this type of error. pr_warn() is not good either. It would by handled via printk_safe buffer and it might be hard to match it with the problematic string. A reasonable compromise seems to be writing the unknown format specifier into the original string with a question mark, for example (%pC?). It should be self-explaining enough. Note that it is in brackets to follow the (null) style. Note that it introduces a warning about that test_hashed() function is unused. It is going to be used again by a later patch. Link: http://lkml.kernel.org/r/20190417115350.20479-8-pmladek@suse.com To: Rasmus Villemoes Cc: Linus Torvalds Cc: "Tobin C . Harding" Cc: Joe Perches Cc: Andrew Morton Cc: Michal Hocko Cc: Steven Rostedt Cc: Sergey Senozhatsky Cc: linux-kernel@vger.kernel.org Reviewed-by: Sergey Senozhatsky Reviewed-by: Andy Shevchenko Signed-off-by: Petr Mladek --- lib/vsprintf.c | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) (limited to 'lib/vsprintf.c') diff --git a/lib/vsprintf.c b/lib/vsprintf.c index 9817d171f608..f471a658422f 100644 --- a/lib/vsprintf.c +++ b/lib/vsprintf.c @@ -1435,6 +1435,8 @@ static noinline_for_stack char *ip_addr_string(char *buf, char *end, const void *ptr, struct printf_spec spec, const char *fmt) { + char *err_fmt_msg; + switch (fmt[1]) { case '6': return ip6_addr_string(buf, end, ptr, spec, fmt); @@ -1457,7 +1459,8 @@ char *ip_addr_string(char *buf, char *end, const void *ptr, }} } - return ptr_to_id(buf, end, ptr, spec); + err_fmt_msg = fmt[0] == 'i' ? "(%pi?)" : "(%pI?)"; + return string_nocheck(buf, end, err_fmt_msg, spec); } static noinline_for_stack @@ -1585,7 +1588,7 @@ char *netdev_bits(char *buf, char *end, const void *addr, size = sizeof(netdev_features_t); break; default: - return ptr_to_id(buf, end, addr, spec); + return string_nocheck(buf, end, "(%pN?)", spec); } return special_hex_number(buf, end, num, size); @@ -1689,7 +1692,7 @@ char *time_and_date(char *buf, char *end, void *ptr, struct printf_spec spec, case 'R': return rtc_str(buf, end, (const struct rtc_time *)ptr, fmt); default: - return ptr_to_id(buf, end, ptr, spec); + return string_nocheck(buf, end, "(%ptR?)", spec); } } @@ -1697,7 +1700,10 @@ static noinline_for_stack char *clock(char *buf, char *end, struct clk *clk, struct printf_spec spec, const char *fmt) { - if (!IS_ENABLED(CONFIG_HAVE_CLK) || !clk) + if (!IS_ENABLED(CONFIG_HAVE_CLK)) + return string_nocheck(buf, end, "(%pC?)", spec); + + if (!clk) return string(buf, end, NULL, spec); switch (fmt[1]) { @@ -1706,7 +1712,7 @@ char *clock(char *buf, char *end, struct clk *clk, struct printf_spec spec, #ifdef CONFIG_COMMON_CLK return string(buf, end, __clk_get_name(clk), spec); #else - return ptr_to_id(buf, end, clk, spec); + return string_nocheck(buf, end, "(%pC?)", spec); #endif } } @@ -1739,7 +1745,8 @@ char *format_flags(char *buf, char *end, unsigned long flags, } static noinline_for_stack -char *flags_string(char *buf, char *end, void *flags_ptr, const char *fmt) +char *flags_string(char *buf, char *end, void *flags_ptr, + struct printf_spec spec, const char *fmt) { unsigned long flags; const struct trace_print_flags *names; @@ -1760,8 +1767,7 @@ char *flags_string(char *buf, char *end, void *flags_ptr, const char *fmt) names = gfpflag_names; break; default: - WARN_ONCE(1, "Unsupported flags modifier: %c\n", fmt[1]); - return buf; + return string_nocheck(buf, end, "(%pG?)", spec); } return format_flags(buf, end, flags, names); @@ -1817,7 +1823,7 @@ char *device_node_string(char *buf, char *end, struct device_node *dn, str_spec.field_width = -1; if (!IS_ENABLED(CONFIG_OF)) - return string_nocheck(buf, end, "(!OF)", spec); + return string_nocheck(buf, end, "(%pOF?)", spec); if ((unsigned long)dn < PAGE_SIZE) return string_nocheck(buf, end, "(null)", spec); @@ -1896,7 +1902,7 @@ static char *kobject_string(char *buf, char *end, void *ptr, return device_node_string(buf, end, ptr, spec, fmt + 1); } - return ptr_to_id(buf, end, ptr, spec); + return string_nocheck(buf, end, "(%pO?)", spec); } /* @@ -2091,7 +2097,7 @@ char *pointer(const char *fmt, char *buf, char *end, void *ptr, #endif case 'G': - return flags_string(buf, end, ptr, fmt); + return flags_string(buf, end, ptr, spec, fmt); case 'O': return kobject_string(buf, end, ptr, spec, fmt); case 'x': -- cgit v1.2.3 From 3e5903eb9cff707301712498aed9e34b3e2ee883 Mon Sep 17 00:00:00 2001 From: Petr Mladek Date: Wed, 17 Apr 2019 13:53:48 +0200 Subject: vsprintf: Prevent crash when dereferencing invalid pointers We already prevent crash when dereferencing some obviously broken pointers. But the handling is not consistent. Sometimes we print "(null)" only for pure NULL pointer, sometimes for pointers in the first page and sometimes also for pointers in the last page (error codes). Note that printk() call this code under logbuf_lock. Any recursive printks are redirected to the printk_safe implementation and the messages are stored into per-CPU buffers. These buffers might be eventually flushed in printk_safe_flush_on_panic() but it is not guaranteed. This patch adds a check using probe_kernel_read(). It is not a full-proof test. But it should help to see the error message in 99% situations where the kernel would silently crash otherwise. Also it makes the error handling unified for "%s" and the many %p* specifiers that need to read the data from a given address. We print: + (null) when accessing data on pure pure NULL address + (efault) when accessing data on an invalid address It does not affect the %p* specifiers that just print the given address in some form, namely %pF, %pf, %pS, %ps, %pB, %pK, %px, and plain %p. Note that we print (efault) from security reasons. In fact, the real address can be seen only by %px or eventually %pK. Link: http://lkml.kernel.org/r/20190417115350.20479-9-pmladek@suse.com To: Rasmus Villemoes Cc: Linus Torvalds Cc: "Tobin C . Harding" Cc: Joe Perches Cc: Andrew Morton Cc: Michal Hocko Cc: Steven Rostedt Cc: Sergey Senozhatsky Cc: linux-kernel@vger.kernel.org Reviewed-by: Sergey Senozhatsky Reviewed-by: Andy Shevchenko Signed-off-by: Petr Mladek --- lib/vsprintf.c | 136 ++++++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 101 insertions(+), 35 deletions(-) (limited to 'lib/vsprintf.c') diff --git a/lib/vsprintf.c b/lib/vsprintf.c index f471a658422f..b989f1e8f35b 100644 --- a/lib/vsprintf.c +++ b/lib/vsprintf.c @@ -612,12 +612,45 @@ static char *string_nocheck(char *buf, char *end, const char *s, return widen_string(buf, len, end, spec); } +/* + * This is not a fool-proof test. 99% of the time that this will fault is + * due to a bad pointer, not one that crosses into bad memory. Just test + * the address to make sure it doesn't fault due to a poorly added printk + * during debugging. + */ +static const char *check_pointer_msg(const void *ptr) +{ + char byte; + + if (!ptr) + return "(null)"; + + if (probe_kernel_address(ptr, byte)) + return "(efault)"; + + return NULL; +} + +static int check_pointer(char **buf, char *end, const void *ptr, + struct printf_spec spec) +{ + const char *err_msg; + + err_msg = check_pointer_msg(ptr); + if (err_msg) { + *buf = string_nocheck(*buf, end, err_msg, spec); + return -EFAULT; + } + + return 0; +} + static noinline_for_stack char *string(char *buf, char *end, const char *s, struct printf_spec spec) { - if ((unsigned long)s < PAGE_SIZE) - s = "(null)"; + if (check_pointer(&buf, end, s, spec)) + return buf; return string_nocheck(buf, end, s, spec); } @@ -792,6 +825,11 @@ char *dentry_name(char *buf, char *end, const struct dentry *d, struct printf_sp rcu_read_lock(); for (i = 0; i < depth; i++, d = p) { + if (check_pointer(&buf, end, d, spec)) { + rcu_read_unlock(); + return buf; + } + p = READ_ONCE(d->d_parent); array[i] = READ_ONCE(d->d_name.name); if (p == d) { @@ -822,8 +860,12 @@ static noinline_for_stack char *bdev_name(char *buf, char *end, struct block_device *bdev, struct printf_spec spec, const char *fmt) { - struct gendisk *hd = bdev->bd_disk; - + struct gendisk *hd; + + if (check_pointer(&buf, end, bdev, spec)) + return buf; + + hd = bdev->bd_disk; buf = string(buf, end, hd->disk_name, spec); if (bdev->bd_part->partno) { if (isdigit(hd->disk_name[strlen(hd->disk_name)-1])) { @@ -942,6 +984,9 @@ char *resource_string(char *buf, char *end, struct resource *res, int decode = (fmt[0] == 'R') ? 1 : 0; const struct printf_spec *specp; + if (check_pointer(&buf, end, res, spec)) + return buf; + *p++ = '['; if (res->flags & IORESOURCE_IO) { p = string_nocheck(p, pend, "io ", str_spec); @@ -1004,9 +1049,8 @@ char *hex_string(char *buf, char *end, u8 *addr, struct printf_spec spec, /* nothing to print */ return buf; - if (ZERO_OR_NULL_PTR(addr)) - /* NULL pointer */ - return string(buf, end, NULL, spec); + if (check_pointer(&buf, end, addr, spec)) + return buf; switch (fmt[1]) { case 'C': @@ -1053,6 +1097,9 @@ char *bitmap_string(char *buf, char *end, unsigned long *bitmap, int i, chunksz; bool first = true; + if (check_pointer(&buf, end, bitmap, spec)) + return buf; + /* reused to print numbers */ spec = (struct printf_spec){ .flags = SMALL | ZEROPAD, .base = 16 }; @@ -1094,6 +1141,9 @@ char *bitmap_list_string(char *buf, char *end, unsigned long *bitmap, int cur, rbot, rtop; bool first = true; + if (check_pointer(&buf, end, bitmap, spec)) + return buf; + rbot = cur = find_first_bit(bitmap, nr_bits); while (cur < nr_bits) { rtop = cur; @@ -1132,6 +1182,9 @@ char *mac_address_string(char *buf, char *end, u8 *addr, char separator; bool reversed = false; + if (check_pointer(&buf, end, addr, spec)) + return buf; + switch (fmt[1]) { case 'F': separator = '-'; @@ -1437,6 +1490,9 @@ char *ip_addr_string(char *buf, char *end, const void *ptr, { char *err_fmt_msg; + if (check_pointer(&buf, end, ptr, spec)) + return buf; + switch (fmt[1]) { case '6': return ip6_addr_string(buf, end, ptr, spec, fmt); @@ -1475,9 +1531,8 @@ char *escaped_string(char *buf, char *end, u8 *addr, struct printf_spec spec, if (spec.field_width == 0) return buf; /* nothing to print */ - if (ZERO_OR_NULL_PTR(addr)) - return string(buf, end, NULL, spec); /* NULL pointer */ - + if (check_pointer(&buf, end, addr, spec)) + return buf; do { switch (fmt[count++]) { @@ -1523,10 +1578,14 @@ char *escaped_string(char *buf, char *end, u8 *addr, struct printf_spec spec, return buf; } -static char *va_format(char *buf, char *end, struct va_format *va_fmt) +static char *va_format(char *buf, char *end, struct va_format *va_fmt, + struct printf_spec spec, const char *fmt) { va_list va; + if (check_pointer(&buf, end, va_fmt, spec)) + return buf; + va_copy(va, *va_fmt->va); buf += vsnprintf(buf, end > buf ? end - buf : 0, va_fmt->fmt, va); va_end(va); @@ -1544,6 +1603,9 @@ char *uuid_string(char *buf, char *end, const u8 *addr, const u8 *index = uuid_index; bool uc = false; + if (check_pointer(&buf, end, addr, spec)) + return buf; + switch (*(++fmt)) { case 'L': uc = true; /* fall-through */ @@ -1582,6 +1644,9 @@ char *netdev_bits(char *buf, char *end, const void *addr, unsigned long long num; int size; + if (check_pointer(&buf, end, addr, spec)) + return buf; + switch (fmt[1]) { case 'F': num = *(const netdev_features_t *)addr; @@ -1595,11 +1660,15 @@ char *netdev_bits(char *buf, char *end, const void *addr, } static noinline_for_stack -char *address_val(char *buf, char *end, const void *addr, const char *fmt) +char *address_val(char *buf, char *end, const void *addr, + struct printf_spec spec, const char *fmt) { unsigned long long num; int size; + if (check_pointer(&buf, end, addr, spec)) + return buf; + switch (fmt[1]) { case 'd': num = *(const dma_addr_t *)addr; @@ -1651,12 +1720,16 @@ char *time_str(char *buf, char *end, const struct rtc_time *tm, bool r) } static noinline_for_stack -char *rtc_str(char *buf, char *end, const struct rtc_time *tm, const char *fmt) +char *rtc_str(char *buf, char *end, const struct rtc_time *tm, + struct printf_spec spec, const char *fmt) { bool have_t = true, have_d = true; bool raw = false; int count = 2; + if (check_pointer(&buf, end, tm, spec)) + return buf; + switch (fmt[count]) { case 'd': have_t = false; @@ -1690,7 +1763,7 @@ char *time_and_date(char *buf, char *end, void *ptr, struct printf_spec spec, { switch (fmt[1]) { case 'R': - return rtc_str(buf, end, (const struct rtc_time *)ptr, fmt); + return rtc_str(buf, end, (const struct rtc_time *)ptr, spec, fmt); default: return string_nocheck(buf, end, "(%ptR?)", spec); } @@ -1703,8 +1776,8 @@ char *clock(char *buf, char *end, struct clk *clk, struct printf_spec spec, if (!IS_ENABLED(CONFIG_HAVE_CLK)) return string_nocheck(buf, end, "(%pC?)", spec); - if (!clk) - return string(buf, end, NULL, spec); + if (check_pointer(&buf, end, clk, spec)) + return buf; switch (fmt[1]) { case 'n': @@ -1751,6 +1824,9 @@ char *flags_string(char *buf, char *end, void *flags_ptr, unsigned long flags; const struct trace_print_flags *names; + if (check_pointer(&buf, end, flags_ptr, spec)) + return buf; + switch (fmt[1]) { case 'p': flags = *(unsigned long *)flags_ptr; @@ -1825,8 +1901,8 @@ char *device_node_string(char *buf, char *end, struct device_node *dn, if (!IS_ENABLED(CONFIG_OF)) return string_nocheck(buf, end, "(%pOF?)", spec); - if ((unsigned long)dn < PAGE_SIZE) - return string_nocheck(buf, end, "(null)", spec); + if (check_pointer(&buf, end, dn, spec)) + return buf; /* simple case without anything any more format specifiers */ fmt++; @@ -2021,18 +2097,6 @@ static noinline_for_stack char *pointer(const char *fmt, char *buf, char *end, void *ptr, struct printf_spec spec) { - const int default_width = 2 * sizeof(void *); - - if (!ptr && *fmt != 'K' && *fmt != 'x') { - /* - * Print (null) with the same width as a pointer so it makes - * tabular output look nice. - */ - if (spec.field_width == -1) - spec.field_width = default_width; - return string_nocheck(buf, end, "(null)", spec); - } - switch (*fmt) { case 'F': case 'f': @@ -2074,13 +2138,13 @@ char *pointer(const char *fmt, char *buf, char *end, void *ptr, case 'U': return uuid_string(buf, end, ptr, spec, fmt); case 'V': - return va_format(buf, end, ptr); + return va_format(buf, end, ptr, spec, fmt); case 'K': return restricted_pointer(buf, end, ptr, spec); case 'N': return netdev_bits(buf, end, ptr, spec, fmt); case 'a': - return address_val(buf, end, ptr, fmt); + return address_val(buf, end, ptr, spec, fmt); case 'd': return dentry_name(buf, end, ptr, spec, fmt); case 't': @@ -2714,11 +2778,13 @@ int vbin_printf(u32 *bin_buf, size_t size, const char *fmt, va_list args) case FORMAT_TYPE_STR: { const char *save_str = va_arg(args, char *); + const char *err_msg; size_t len; - if ((unsigned long)save_str > (unsigned long)-PAGE_SIZE - || (unsigned long)save_str < PAGE_SIZE) - save_str = "(null)"; + err_msg = check_pointer_msg(save_str); + if (err_msg) + save_str = err_msg; + len = strlen(save_str) + 1; if (str + len < end) memcpy(str, save_str, len); -- cgit v1.2.3 From 635720ac75a51092b456bed517ff170047883252 Mon Sep 17 00:00:00 2001 From: Petr Mladek Date: Wed, 17 Apr 2019 13:53:49 +0200 Subject: vsprintf: Avoid confusion between invalid address and value We are able to detect invalid values handled by %p[iI] printk specifier. The current error message is "invalid address". It might cause confusion against "(efault)" reported by the generic valid_pointer_address() check. Let's unify the style and use the more appropriate error code description "(einval)". Link: http://lkml.kernel.org/r/20190417115350.20479-10-pmladek@suse.com To: Rasmus Villemoes Cc: Linus Torvalds Cc: "Tobin C . Harding" Cc: Joe Perches Cc: Andrew Morton Cc: Michal Hocko Cc: Steven Rostedt Cc: Sergey Senozhatsky Cc: linux-kernel@vger.kernel.org Reviewed-by: Sergey Senozhatsky Reviewed-by: Andy Shevchenko Signed-off-by: Petr Mladek --- lib/vsprintf.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/vsprintf.c') diff --git a/lib/vsprintf.c b/lib/vsprintf.c index b989f1e8f35b..4e5666035b74 100644 --- a/lib/vsprintf.c +++ b/lib/vsprintf.c @@ -1511,7 +1511,7 @@ char *ip_addr_string(char *buf, char *end, const void *ptr, case AF_INET6: return ip6_addr_string_sa(buf, end, &sa->v6, spec, fmt); default: - return string_nocheck(buf, end, "(invalid address)", spec); + return string_nocheck(buf, end, "(einval)", spec); }} } -- cgit v1.2.3 From c8c3b584343cb7522fc00322769a9f288305743f Mon Sep 17 00:00:00 2001 From: Petr Mladek Date: Wed, 17 Apr 2019 13:53:50 +0200 Subject: vsprintf: Limit the length of inlined error messages The inlined error messages must be used carefully because they need to fit into the given buffer. Handle them using a custom wrapper that makes people aware of the problem. Also define a reasonable hard limit to avoid a completely insane usage. Suggested-by: Sergey Senozhatsky Link: http://lkml.kernel.org/r/20190417115350.20479-11-pmladek@suse.com To: Rasmus Villemoes Cc: Linus Torvalds Cc: "Tobin C . Harding" Cc: Joe Perches Cc: Andrew Morton Cc: Michal Hocko Cc: Steven Rostedt Cc: Sergey Senozhatsky Cc: linux-kernel@vger.kernel.org Reviewed-by: Sergey Senozhatsky Reviewed-by: Andy Shevchenko Signed-off-by: Petr Mladek --- lib/vsprintf.c | 39 +++++++++++++++++++++++++++------------ 1 file changed, 27 insertions(+), 12 deletions(-) (limited to 'lib/vsprintf.c') diff --git a/lib/vsprintf.c b/lib/vsprintf.c index 4e5666035b74..1f367f3a7e2b 100644 --- a/lib/vsprintf.c +++ b/lib/vsprintf.c @@ -612,6 +612,21 @@ static char *string_nocheck(char *buf, char *end, const char *s, return widen_string(buf, len, end, spec); } +/* Be careful: error messages must fit into the given buffer. */ +static char *error_string(char *buf, char *end, const char *s, + struct printf_spec spec) +{ + /* + * Hard limit to avoid a completely insane messages. It actually + * works pretty well because most error messages are in + * the many pointer format modifiers. + */ + if (spec.precision == -1) + spec.precision = 2 * sizeof(void *); + + return string_nocheck(buf, end, s, spec); +} + /* * This is not a fool-proof test. 99% of the time that this will fault is * due to a bad pointer, not one that crosses into bad memory. Just test @@ -638,7 +653,7 @@ static int check_pointer(char **buf, char *end, const void *ptr, err_msg = check_pointer_msg(ptr); if (err_msg) { - *buf = string_nocheck(*buf, end, err_msg, spec); + *buf = error_string(*buf, end, err_msg, spec); return -EFAULT; } @@ -741,7 +756,7 @@ static char *ptr_to_id(char *buf, char *end, const void *ptr, if (static_branch_unlikely(¬_filled_random_ptr_key)) { spec.field_width = 2 * sizeof(ptr); /* string length must be less than default_width */ - return string_nocheck(buf, end, str, spec); + return error_string(buf, end, str, spec); } #ifdef CONFIG_64BIT @@ -777,7 +792,7 @@ char *restricted_pointer(char *buf, char *end, const void *ptr, if (in_irq() || in_serving_softirq() || in_nmi()) { if (spec.field_width == -1) spec.field_width = 2 * sizeof(ptr); - return string_nocheck(buf, end, "pK-error", spec); + return error_string(buf, end, "pK-error", spec); } /* @@ -1511,12 +1526,12 @@ char *ip_addr_string(char *buf, char *end, const void *ptr, case AF_INET6: return ip6_addr_string_sa(buf, end, &sa->v6, spec, fmt); default: - return string_nocheck(buf, end, "(einval)", spec); + return error_string(buf, end, "(einval)", spec); }} } err_fmt_msg = fmt[0] == 'i' ? "(%pi?)" : "(%pI?)"; - return string_nocheck(buf, end, err_fmt_msg, spec); + return error_string(buf, end, err_fmt_msg, spec); } static noinline_for_stack @@ -1653,7 +1668,7 @@ char *netdev_bits(char *buf, char *end, const void *addr, size = sizeof(netdev_features_t); break; default: - return string_nocheck(buf, end, "(%pN?)", spec); + return error_string(buf, end, "(%pN?)", spec); } return special_hex_number(buf, end, num, size); @@ -1765,7 +1780,7 @@ char *time_and_date(char *buf, char *end, void *ptr, struct printf_spec spec, case 'R': return rtc_str(buf, end, (const struct rtc_time *)ptr, spec, fmt); default: - return string_nocheck(buf, end, "(%ptR?)", spec); + return error_string(buf, end, "(%ptR?)", spec); } } @@ -1774,7 +1789,7 @@ char *clock(char *buf, char *end, struct clk *clk, struct printf_spec spec, const char *fmt) { if (!IS_ENABLED(CONFIG_HAVE_CLK)) - return string_nocheck(buf, end, "(%pC?)", spec); + return error_string(buf, end, "(%pC?)", spec); if (check_pointer(&buf, end, clk, spec)) return buf; @@ -1785,7 +1800,7 @@ char *clock(char *buf, char *end, struct clk *clk, struct printf_spec spec, #ifdef CONFIG_COMMON_CLK return string(buf, end, __clk_get_name(clk), spec); #else - return string_nocheck(buf, end, "(%pC?)", spec); + return error_string(buf, end, "(%pC?)", spec); #endif } } @@ -1843,7 +1858,7 @@ char *flags_string(char *buf, char *end, void *flags_ptr, names = gfpflag_names; break; default: - return string_nocheck(buf, end, "(%pG?)", spec); + return error_string(buf, end, "(%pG?)", spec); } return format_flags(buf, end, flags, names); @@ -1899,7 +1914,7 @@ char *device_node_string(char *buf, char *end, struct device_node *dn, str_spec.field_width = -1; if (!IS_ENABLED(CONFIG_OF)) - return string_nocheck(buf, end, "(%pOF?)", spec); + return error_string(buf, end, "(%pOF?)", spec); if (check_pointer(&buf, end, dn, spec)) return buf; @@ -1978,7 +1993,7 @@ static char *kobject_string(char *buf, char *end, void *ptr, return device_node_string(buf, end, ptr, spec, fmt + 1); } - return string_nocheck(buf, end, "(%pO?)", spec); + return error_string(buf, end, "(%pO?)", spec); } /* -- cgit v1.2.3 From ce9d3eceb7ffb74445a8d892ca0685395a93a7e2 Mon Sep 17 00:00:00 2001 From: YueHaibing Date: Sat, 27 Apr 2019 00:46:30 +0800 Subject: lib/vsprintf: Make function pointer_string static Fix sparse warning: lib/vsprintf.c:673:6: warning: symbol 'pointer_string' was not declared. Should it be static? Link: http://lkml.kernel.org/r/20190426164630.22104-1-yuehaibing@huawei.com To: To: To: To: Signed-off-by: YueHaibing Signed-off-by: Petr Mladek --- lib/vsprintf.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'lib/vsprintf.c') diff --git a/lib/vsprintf.c b/lib/vsprintf.c index 1f367f3a7e2b..7b0a6140bfad 100644 --- a/lib/vsprintf.c +++ b/lib/vsprintf.c @@ -670,8 +670,9 @@ char *string(char *buf, char *end, const char *s, return string_nocheck(buf, end, s, spec); } -char *pointer_string(char *buf, char *end, const void *ptr, - struct printf_spec spec) +static char *pointer_string(char *buf, char *end, + const void *ptr, + struct printf_spec spec) { spec.base = 16; spec.flags |= SMALL; -- cgit v1.2.3 From 2ac5a3bf7042a1c4abbcce1b6f0ec61e5d3786c2 Mon Sep 17 00:00:00 2001 From: Petr Mladek Date: Fri, 10 May 2019 10:42:13 +0200 Subject: vsprintf: Do not break early boot with probing addresses The commit 3e5903eb9cff70730 ("vsprintf: Prevent crash when dereferencing invalid pointers") broke boot on several architectures. The common pattern is that probe_kernel_read() is not working during early boot because userspace access framework is not ready. It is a generic problem. We have to avoid any complex external functions in vsprintf() code, especially in the common path. They might break printk() easily and are hard to debug. Replace probe_kernel_read() with some simple checks for obvious problems. Details: 1. Report on Power: Kernel crashes very early during boot with with CONFIG_PPC_KUAP and CONFIG_JUMP_LABEL_FEATURE_CHECK_DEBUG The problem is the combination of some new code called via printk(), check_pointer() which calls probe_kernel_read(). That then calls allow_user_access() (PPC_KUAP) and that uses mmu_has_feature() too early (before we've patched features). With the JUMP_LABEL debug enabled that causes us to call printk() & dump_stack() and we end up recursing and overflowing the stack. Because it happens so early you don't get any output, just an apparently dead system. The stack trace (which you don't see) is something like: ... dump_stack+0xdc probe_kernel_read+0x1a4 check_pointer+0x58 string+0x3c vsnprintf+0x1bc vscnprintf+0x20 printk_safe_log_store+0x7c printk+0x40 dump_stack_print_info+0xbc dump_stack+0x8 probe_kernel_read+0x1a4 probe_kernel_read+0x19c check_pointer+0x58 string+0x3c vsnprintf+0x1bc vscnprintf+0x20 vprintk_store+0x6c vprintk_emit+0xec vprintk_func+0xd4 printk+0x40 cpufeatures_process_feature+0xc8 scan_cpufeatures_subnodes+0x380 of_scan_flat_dt_subnodes+0xb4 dt_cpu_ftrs_scan_callback+0x158 of_scan_flat_dt+0xf0 dt_cpu_ftrs_scan+0x3c early_init_devtree+0x360 early_setup+0x9c 2. Report on s390: vsnprintf invocations, are broken on s390. For example, the early boot output now looks like this where the first (efault) should be the linux_banner: [ 0.099985] (efault) [ 0.099985] setup: Linux is running as a z/VM guest operating system in 64-bit mode [ 0.100066] setup: The maximum memory size is 8192MB [ 0.100070] cma: Reserved 4 MiB at (efault) [ 0.100100] numa: NUMA mode: (efault) The reason for this, is that the code assumes that probe_kernel_address() works very early. This however is not true on at least s390. Uaccess on KERNEL_DS works only after page tables have been setup on s390, which happens with setup_arch()->paging_init(). Any probe_kernel_address() invocation before that will return -EFAULT. Fixes: 3e5903eb9cff70730 ("vsprintf: Prevent crash when dereferencing invalid pointers") Link: http://lkml.kernel.org/r/20190510084213.22149-1-pmladek@suse.com Cc: Andy Shevchenko Cc: Rasmus Villemoes Cc: "Tobin C . Harding" Cc: Michal Hocko Cc: Sergey Senozhatsky Cc: Steven Rostedt Cc: linux-kernel@vger.kernel.org Cc: Michael Ellerman Cc: linuxppc-dev@lists.ozlabs.org Cc: Russell Currey Cc: Christophe Leroy Cc: Stephen Rothwell Cc: Heiko Carstens Cc: linux-arch@vger.kernel.org Cc: linux-s390@vger.kernel.org Cc: Martin Schwidefsky Cc: Petr Mladek Reviewed-by: Sergey Senozhatsky Signed-off-by: Petr Mladek --- lib/vsprintf.c | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) (limited to 'lib/vsprintf.c') diff --git a/lib/vsprintf.c b/lib/vsprintf.c index 7b0a6140bfad..2f003cfe340e 100644 --- a/lib/vsprintf.c +++ b/lib/vsprintf.c @@ -628,19 +628,16 @@ static char *error_string(char *buf, char *end, const char *s, } /* - * This is not a fool-proof test. 99% of the time that this will fault is - * due to a bad pointer, not one that crosses into bad memory. Just test - * the address to make sure it doesn't fault due to a poorly added printk - * during debugging. + * Do not call any complex external code here. Nested printk()/vsprintf() + * might cause infinite loops. Failures might break printk() and would + * be hard to debug. */ static const char *check_pointer_msg(const void *ptr) { - char byte; - if (!ptr) return "(null)"; - if (probe_kernel_address(ptr, byte)) + if ((unsigned long)ptr < PAGE_SIZE || IS_ERR_VALUE(ptr)) return "(efault)"; return NULL; -- cgit v1.2.3