summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Documentation/core-api/printk-formats.rst7
-rw-r--r--lib/test_printf.c22
-rw-r--r--lib/vsprintf.c136
3 files changed, 129 insertions, 36 deletions
diff --git a/Documentation/core-api/printk-formats.rst b/Documentation/core-api/printk-formats.rst
index c37ec7cd9c06..b2cac8d76b66 100644
--- a/Documentation/core-api/printk-formats.rst
+++ b/Documentation/core-api/printk-formats.rst
@@ -58,6 +58,13 @@ A raw pointer value may be printed with %p which will hash the address
before printing. The kernel also supports extended specifiers for printing
pointers of different types.
+Some of the extended specifiers print the data on the given address instead
+of printing the address itself. In this case, the following error messages
+might be printed instead of the unreachable information::
+
+ (null) data on plain NULL address
+ (efault) data on invalid address
+
Plain Pointers
--------------
diff --git a/lib/test_printf.c b/lib/test_printf.c
index 250ee864b8b8..359ae4fb1ece 100644
--- a/lib/test_printf.c
+++ b/lib/test_printf.c
@@ -239,6 +239,7 @@ plain_format(void)
#define PTR ((void *)0x456789ab)
#define PTR_STR "456789ab"
#define PTR_VAL_NO_CRNG "(ptrval)"
+#define ZEROS ""
static int __init
plain_format(void)
@@ -268,7 +269,6 @@ plain_hash_to_buffer(const void *p, char *buf, size_t len)
return 0;
}
-
static int __init
plain_hash(void)
{
@@ -326,6 +326,24 @@ test_hashed(const char *fmt, const void *p)
}
static void __init
+null_pointer(void)
+{
+ test_hashed("%p", NULL);
+ test(ZEROS "00000000", "%px", NULL);
+ test("(null)", "%pE", NULL);
+}
+
+#define PTR_INVALID ((void *)0x000000ab)
+
+static void __init
+invalid_pointer(void)
+{
+ test_hashed("%p", PTR_INVALID);
+ test(ZEROS "000000ab", "%px", PTR_INVALID);
+ test("(efault)", "%pE", PTR_INVALID);
+}
+
+static void __init
symbol_ptr(void)
{
}
@@ -571,6 +589,8 @@ static void __init
test_pointer(void)
{
plain();
+ null_pointer();
+ invalid_pointer();
symbol_ptr();
kernel_ptr();
struct_resource();
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);