Skip to content

Commit ad67b74

Browse files
committed
printk: hash addresses printed with %p
Currently there exist approximately 14 000 places in the kernel where addresses are being printed using an unadorned %p. This potentially leaks sensitive information regarding the Kernel layout in memory. Many of these calls are stale, instead of fixing every call lets hash the address by default before printing. This will of course break some users, forcing code printing needed addresses to be updated. Code that _really_ needs the address will soon be able to use the new printk specifier %px to print the address. For what it's worth, usage of unadorned %p can be broken down as follows (thanks to Joe Perches). $ git grep -E '%p[^A-Za-z0-9]' | cut -f1 -d"/" | sort | uniq -c 1084 arch 20 block 10 crypto 32 Documentation 8121 drivers 1221 fs 143 include 101 kernel 69 lib 100 mm 1510 net 40 samples 7 scripts 11 security 166 sound 152 tools 2 virt Add function ptr_to_id() to map an address to a 32 bit unique identifier. Hash any unadorned usage of specifier %p and any malformed specifiers. Signed-off-by: Tobin C. Harding <[email protected]>
1 parent 57e7344 commit ad67b74

File tree

3 files changed

+155
-46
lines changed

3 files changed

+155
-46
lines changed

Documentation/printk-formats.txt

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ How to get printk format specifiers right
55
:Author: Randy Dunlap <[email protected]>
66
:Author: Andrew Murray <[email protected]>
77

8-
98
Integer types
109
=============
1110

@@ -45,6 +44,17 @@ return from vsnprintf.
4544
Raw pointer value SHOULD be printed with %p. The kernel supports
4645
the following extended format specifiers for pointer types:
4746

47+
Pointer Types
48+
=============
49+
50+
Pointers printed without a specifier extension (i.e unadorned %p) are
51+
hashed to give a unique identifier without leaking kernel addresses to user
52+
space. On 64 bit machines the first 32 bits are zeroed.
53+
54+
::
55+
56+
%p abcdef12 or 00000000abcdef12
57+
4858
Symbols/Function Pointers
4959
=========================
5060

lib/test_printf.c

Lines changed: 70 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -24,24 +24,6 @@
2424
#define PAD_SIZE 16
2525
#define FILL_CHAR '$'
2626

27-
#define PTR1 ((void*)0x01234567)
28-
#define PTR2 ((void*)(long)(int)0xfedcba98)
29-
30-
#if BITS_PER_LONG == 64
31-
#define PTR1_ZEROES "000000000"
32-
#define PTR1_SPACES " "
33-
#define PTR1_STR "1234567"
34-
#define PTR2_STR "fffffffffedcba98"
35-
#define PTR_WIDTH 16
36-
#else
37-
#define PTR1_ZEROES "0"
38-
#define PTR1_SPACES " "
39-
#define PTR1_STR "1234567"
40-
#define PTR2_STR "fedcba98"
41-
#define PTR_WIDTH 8
42-
#endif
43-
#define PTR_WIDTH_STR stringify(PTR_WIDTH)
44-
4527
static unsigned total_tests __initdata;
4628
static unsigned failed_tests __initdata;
4729
static char *test_buffer __initdata;
@@ -217,30 +199,79 @@ test_string(void)
217199
test("a | | ", "%-3.s|%-3.0s|%-3.*s", "a", "b", 0, "c");
218200
}
219201

202+
#define PLAIN_BUF_SIZE 64 /* leave some space so we don't oops */
203+
204+
#if BITS_PER_LONG == 64
205+
206+
#define PTR_WIDTH 16
207+
#define PTR ((void *)0xffff0123456789ab)
208+
#define PTR_STR "ffff0123456789ab"
209+
#define ZEROS "00000000" /* hex 32 zero bits */
210+
211+
static int __init
212+
plain_format(void)
213+
{
214+
char buf[PLAIN_BUF_SIZE];
215+
int nchars;
216+
217+
nchars = snprintf(buf, PLAIN_BUF_SIZE, "%p", PTR);
218+
219+
if (nchars != PTR_WIDTH || strncmp(buf, ZEROS, strlen(ZEROS)) != 0)
220+
return -1;
221+
222+
return 0;
223+
}
224+
225+
#else
226+
227+
#define PTR_WIDTH 8
228+
#define PTR ((void *)0x456789ab)
229+
#define PTR_STR "456789ab"
230+
231+
static int __init
232+
plain_format(void)
233+
{
234+
/* Format is implicitly tested for 32 bit machines by plain_hash() */
235+
return 0;
236+
}
237+
238+
#endif /* BITS_PER_LONG == 64 */
239+
240+
static int __init
241+
plain_hash(void)
242+
{
243+
char buf[PLAIN_BUF_SIZE];
244+
int nchars;
245+
246+
nchars = snprintf(buf, PLAIN_BUF_SIZE, "%p", PTR);
247+
248+
if (nchars != PTR_WIDTH || strncmp(buf, PTR_STR, PTR_WIDTH) == 0)
249+
return -1;
250+
251+
return 0;
252+
}
253+
254+
/*
255+
* We can't use test() to test %p because we don't know what output to expect
256+
* after an address is hashed.
257+
*/
220258
static void __init
221259
plain(void)
222260
{
223-
test(PTR1_ZEROES PTR1_STR " " PTR2_STR, "%p %p", PTR1, PTR2);
224-
/*
225-
* The field width is overloaded for some %p extensions to
226-
* pass another piece of information. For plain pointers, the
227-
* behaviour is slightly odd: One cannot pass either the 0
228-
* flag nor a precision to %p without gcc complaining, and if
229-
* one explicitly gives a field width, the number is no longer
230-
* zero-padded.
231-
*/
232-
test("|" PTR1_STR PTR1_SPACES " | " PTR1_SPACES PTR1_STR "|",
233-
"|%-*p|%*p|", PTR_WIDTH+2, PTR1, PTR_WIDTH+2, PTR1);
234-
test("|" PTR2_STR " | " PTR2_STR "|",
235-
"|%-*p|%*p|", PTR_WIDTH+2, PTR2, PTR_WIDTH+2, PTR2);
261+
int err;
236262

237-
/*
238-
* Unrecognized %p extensions are treated as plain %p, but the
239-
* alphanumeric suffix is ignored (that is, does not occur in
240-
* the output.)
241-
*/
242-
test("|"PTR1_ZEROES PTR1_STR"|", "|%p0y|", PTR1);
243-
test("|"PTR2_STR"|", "|%p0y|", PTR2);
263+
err = plain_hash();
264+
if (err) {
265+
pr_warn("plain 'p' does not appear to be hashed\n");
266+
failed_tests++;
267+
return;
268+
}
269+
270+
err = plain_format();
271+
if (err) {
272+
pr_warn("hashing plain 'p' has unexpected format\n");
273+
failed_tests++;
274+
}
244275
}
245276

246277
static void __init
@@ -251,6 +282,7 @@ symbol_ptr(void)
251282
static void __init
252283
kernel_ptr(void)
253284
{
285+
/* We can't test this without access to kptr_restrict. */
254286
}
255287

256288
static void __init

lib/vsprintf.c

Lines changed: 74 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@
3333
#include <linux/uuid.h>
3434
#include <linux/of.h>
3535
#include <net/addrconf.h>
36+
#include <linux/siphash.h>
37+
#include <linux/compiler.h>
3638
#ifdef CONFIG_BLOCK
3739
#include <linux/blkdev.h>
3840
#endif
@@ -1644,6 +1646,73 @@ char *device_node_string(char *buf, char *end, struct device_node *dn,
16441646
return widen_string(buf, buf - buf_start, end, spec);
16451647
}
16461648

1649+
static bool have_filled_random_ptr_key __read_mostly;
1650+
static siphash_key_t ptr_key __read_mostly;
1651+
1652+
static void fill_random_ptr_key(struct random_ready_callback *unused)
1653+
{
1654+
get_random_bytes(&ptr_key, sizeof(ptr_key));
1655+
/*
1656+
* have_filled_random_ptr_key==true is dependent on get_random_bytes().
1657+
* ptr_to_id() needs to see have_filled_random_ptr_key==true
1658+
* after get_random_bytes() returns.
1659+
*/
1660+
smp_mb();
1661+
WRITE_ONCE(have_filled_random_ptr_key, true);
1662+
}
1663+
1664+
static struct random_ready_callback random_ready = {
1665+
.func = fill_random_ptr_key
1666+
};
1667+
1668+
static int __init initialize_ptr_random(void)
1669+
{
1670+
int ret = add_random_ready_callback(&random_ready);
1671+
1672+
if (!ret) {
1673+
return 0;
1674+
} else if (ret == -EALREADY) {
1675+
fill_random_ptr_key(&random_ready);
1676+
return 0;
1677+
}
1678+
1679+
return ret;
1680+
}
1681+
early_initcall(initialize_ptr_random);
1682+
1683+
/* Maps a pointer to a 32 bit unique identifier. */
1684+
static char *ptr_to_id(char *buf, char *end, void *ptr, struct printf_spec spec)
1685+
{
1686+
unsigned long hashval;
1687+
const int default_width = 2 * sizeof(ptr);
1688+
1689+
if (unlikely(!have_filled_random_ptr_key)) {
1690+
spec.field_width = default_width;
1691+
/* string length must be less than default_width */
1692+
return string(buf, end, "(ptrval)", spec);
1693+
}
1694+
1695+
#ifdef CONFIG_64BIT
1696+
hashval = (unsigned long)siphash_1u64((u64)ptr, &ptr_key);
1697+
/*
1698+
* Mask off the first 32 bits, this makes explicit that we have
1699+
* modified the address (and 32 bits is plenty for a unique ID).
1700+
*/
1701+
hashval = hashval & 0xffffffff;
1702+
#else
1703+
hashval = (unsigned long)siphash_1u32((u32)ptr, &ptr_key);
1704+
#endif
1705+
1706+
spec.flags |= SMALL;
1707+
if (spec.field_width == -1) {
1708+
spec.field_width = default_width;
1709+
spec.flags |= ZEROPAD;
1710+
}
1711+
spec.base = 16;
1712+
1713+
return number(buf, end, hashval, spec);
1714+
}
1715+
16471716
/*
16481717
* Show a '%p' thing. A kernel extension is that the '%p' is followed
16491718
* by an extra set of alphanumeric characters that are extended format
@@ -1754,6 +1823,9 @@ char *device_node_string(char *buf, char *end, struct device_node *dn,
17541823
* Note: The difference between 'S' and 'F' is that on ia64 and ppc64
17551824
* function pointers are really function descriptors, which contain a
17561825
* pointer to the real address.
1826+
*
1827+
* Note: The default behaviour (unadorned %p) is to hash the address,
1828+
* rendering it useful as a unique identifier.
17571829
*/
17581830
static noinline_for_stack
17591831
char *pointer(const char *fmt, char *buf, char *end, void *ptr,
@@ -1869,14 +1941,9 @@ char *pointer(const char *fmt, char *buf, char *end, void *ptr,
18691941
return device_node_string(buf, end, ptr, spec, fmt + 1);
18701942
}
18711943
}
1872-
spec.flags |= SMALL;
1873-
if (spec.field_width == -1) {
1874-
spec.field_width = default_width;
1875-
spec.flags |= ZEROPAD;
1876-
}
1877-
spec.base = 16;
18781944

1879-
return number(buf, end, (unsigned long) ptr, spec);
1945+
/* default is to _not_ leak addresses, hash before printing */
1946+
return ptr_to_id(buf, end, ptr, spec);
18801947
}
18811948

18821949
/*

0 commit comments

Comments
 (0)