diff options
Diffstat (limited to 'lib/vsprintf.c')
| -rw-r--r-- | lib/vsprintf.c | 194 | 
1 files changed, 146 insertions, 48 deletions
diff --git a/lib/vsprintf.c b/lib/vsprintf.c index 1746bae94d41..01c3957b2de6 100644 --- a/lib/vsprintf.c +++ b/lib/vsprintf.c @@ -33,6 +33,8 @@  #include <linux/uuid.h>  #include <linux/of.h>  #include <net/addrconf.h> +#include <linux/siphash.h> +#include <linux/compiler.h>  #ifdef CONFIG_BLOCK  #include <linux/blkdev.h>  #endif @@ -1343,6 +1345,59 @@ 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) +{ +	spec.base = 16; +	spec.flags |= SMALL; +	if (spec.field_width == -1) { +		spec.field_width = 2 * sizeof(ptr); +		spec.flags |= ZEROPAD; +	} + +	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()) +			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 number(buf, end, (unsigned long)ptr, spec); +} +  static noinline_for_stack  char *netdev_bits(char *buf, char *end, const void *addr, const char *fmt)  { @@ -1591,7 +1646,86 @@ char *device_node_string(char *buf, char *end, struct device_node *dn,  	return widen_string(buf, buf - buf_start, end, spec);  } -int kptr_restrict __read_mostly; +static noinline_for_stack +char *pointer_string(char *buf, char *end, const void *ptr, +		     struct printf_spec spec) +{ +	spec.base = 16; +	spec.flags |= SMALL; +	if (spec.field_width == -1) { +		spec.field_width = 2 * sizeof(ptr); +		spec.flags |= ZEROPAD; +	} + +	return number(buf, end, (unsigned long int)ptr, spec); +} + +static bool have_filled_random_ptr_key __read_mostly; +static siphash_key_t ptr_key __read_mostly; + +static void fill_random_ptr_key(struct random_ready_callback *unused) +{ +	get_random_bytes(&ptr_key, sizeof(ptr_key)); +	/* +	 * have_filled_random_ptr_key==true is dependent on get_random_bytes(). +	 * ptr_to_id() needs to see have_filled_random_ptr_key==true +	 * after get_random_bytes() returns. +	 */ +	smp_mb(); +	WRITE_ONCE(have_filled_random_ptr_key, true); +} + +static struct random_ready_callback random_ready = { +	.func = fill_random_ptr_key +}; + +static int __init initialize_ptr_random(void) +{ +	int ret = add_random_ready_callback(&random_ready); + +	if (!ret) { +		return 0; +	} else if (ret == -EALREADY) { +		fill_random_ptr_key(&random_ready); +		return 0; +	} + +	return ret; +} +early_initcall(initialize_ptr_random); + +/* Maps a pointer to a 32 bit unique identifier. */ +static char *ptr_to_id(char *buf, char *end, void *ptr, struct printf_spec spec) +{ +	unsigned long hashval; +	const int default_width = 2 * sizeof(ptr); + +	if (unlikely(!have_filled_random_ptr_key)) { +		spec.field_width = default_width; +		/* string length must be less than default_width */ +		return string(buf, end, "(ptrval)", spec); +	} + +#ifdef CONFIG_64BIT +	hashval = (unsigned long)siphash_1u64((u64)ptr, &ptr_key); +	/* +	 * Mask off the first 32 bits, this makes explicit that we have +	 * modified the address (and 32 bits is plenty for a unique ID). +	 */ +	hashval = hashval & 0xffffffff; +#else +	hashval = (unsigned long)siphash_1u32((u32)ptr, &ptr_key); +#endif + +	spec.flags |= SMALL; +	if (spec.field_width == -1) { +		spec.field_width = default_width; +		spec.flags |= ZEROPAD; +	} +	spec.base = 16; + +	return number(buf, end, hashval, spec); +}  /*   * Show a '%p' thing.  A kernel extension is that the '%p' is followed @@ -1698,11 +1832,16 @@ int kptr_restrict __read_mostly;   *                        c major compatible string   *                        C full compatible string   * + * - 'x' For printing the address. Equivalent to "%lx". + *   * ** Please update also Documentation/printk-formats.txt when making changes **   *   * Note: The difference between 'S' and 'F' is that on ia64 and ppc64   * function pointers are really function descriptors, which contain a   * pointer to the real address. + * + * Note: The default behaviour (unadorned %p) is to hash the address, + * rendering it useful as a unique identifier.   */  static noinline_for_stack  char *pointer(const char *fmt, char *buf, char *end, void *ptr, @@ -1792,47 +1931,9 @@ char *pointer(const char *fmt, char *buf, char *end, void *ptr,  			return buf;  		}  	case 'K': -		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 = default_width; -				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; +		if (!kptr_restrict)  			break; -		} -		break; - +		return restricted_pointer(buf, end, ptr, spec);  	case 'N':  		return netdev_bits(buf, end, ptr, fmt);  	case 'a': @@ -1857,15 +1958,12 @@ char *pointer(const char *fmt, char *buf, char *end, void *ptr,  		case 'F':  			return device_node_string(buf, end, ptr, spec, fmt + 1);  		} +	case 'x': +		return pointer_string(buf, end, ptr, spec);  	} -	spec.flags |= SMALL; -	if (spec.field_width == -1) { -		spec.field_width = default_width; -		spec.flags |= ZEROPAD; -	} -	spec.base = 16; -	return number(buf, end, (unsigned long) ptr, spec); +	/* default is to _not_ leak addresses, hash before printing */ +	return ptr_to_id(buf, end, ptr, spec);  }  /*  | 
