usercopy: Allow boot cmdline disabling of hardening

Enabling HARDENED_USERCOPY may cause measurable regressions in networking
performance: up to 8% under UDP flood.

I ran a small packet UDP flood using pktgen vs. a host b2b connected. On
the receiver side the UDP packets are processed by a simple user space
process that just reads and drops them:

https://github.com/netoptimizer/network-testing/blob/master/src/udp_sink.c

Not very useful from a functional PoV, but it helps to pin-point
bottlenecks in the networking stack.

When running a kernel with CONFIG_HARDENED_USERCOPY=y, I see a 5-8%
regression in the receive tput, compared to the same kernel without this
option enabled.

With CONFIG_HARDENED_USERCOPY=y, perf shows ~6% of CPU time spent
cumulatively in __check_object_size (~4%) and __virt_addr_valid (~2%).

The call-chain is:

__GI___libc_recvfrom
entry_SYSCALL_64_after_hwframe
do_syscall_64
__x64_sys_recvfrom
__sys_recvfrom
inet_recvmsg
udp_recvmsg
__check_object_size

udp_recvmsg() actually calls copy_to_iter() (inlined) and the latters
calls check_copy_size() (again, inlined).

A generic distro may want to enable HARDENED_USERCOPY in their default
kernel config, but at the same time, such distro may want to be able to
avoid the performance penalties in with the default configuration and
disable the stricter check on a per-boot basis.

This change adds a boot parameter that conditionally disables
HARDENED_USERCOPY via "hardened_usercopy=off".

Signed-off-by: Chris von Recklinghausen <crecklin@redhat.com>
Signed-off-by: Kees Cook <keescook@chromium.org>
This commit is contained in:
Chris von Recklinghausen 2018-07-03 15:43:08 -04:00 committed by Kees Cook
parent 6aa56f4425
commit b5cb15d937
3 changed files with 42 additions and 0 deletions

View File

@ -816,6 +816,17 @@
disable= [IPV6] disable= [IPV6]
See Documentation/networking/ipv6.txt. See Documentation/networking/ipv6.txt.
hardened_usercopy=
[KNL] Under CONFIG_HARDENED_USERCOPY, whether
hardening is enabled for this boot. Hardened
usercopy checking is used to protect the kernel
from reading or writing beyond known memory
allocation boundaries as a proactive defense
against bounds-checking flaws in the kernel's
copy_to_user()/copy_from_user() interface.
on Perform hardened usercopy checks (default).
off Disable hardened usercopy checks.
disable_radix [PPC] disable_radix [PPC]
Disable RADIX MMU mode on POWER9 Disable RADIX MMU mode on POWER9

View File

@ -299,12 +299,18 @@ struct static_key_false {
#define DEFINE_STATIC_KEY_TRUE(name) \ #define DEFINE_STATIC_KEY_TRUE(name) \
struct static_key_true name = STATIC_KEY_TRUE_INIT struct static_key_true name = STATIC_KEY_TRUE_INIT
#define DEFINE_STATIC_KEY_TRUE_RO(name) \
struct static_key_true name __ro_after_init = STATIC_KEY_TRUE_INIT
#define DECLARE_STATIC_KEY_TRUE(name) \ #define DECLARE_STATIC_KEY_TRUE(name) \
extern struct static_key_true name extern struct static_key_true name
#define DEFINE_STATIC_KEY_FALSE(name) \ #define DEFINE_STATIC_KEY_FALSE(name) \
struct static_key_false name = STATIC_KEY_FALSE_INIT struct static_key_false name = STATIC_KEY_FALSE_INIT
#define DEFINE_STATIC_KEY_FALSE_RO(name) \
struct static_key_false name __ro_after_init = STATIC_KEY_FALSE_INIT
#define DECLARE_STATIC_KEY_FALSE(name) \ #define DECLARE_STATIC_KEY_FALSE(name) \
extern struct static_key_false name extern struct static_key_false name

View File

@ -20,6 +20,8 @@
#include <linux/sched/task.h> #include <linux/sched/task.h>
#include <linux/sched/task_stack.h> #include <linux/sched/task_stack.h>
#include <linux/thread_info.h> #include <linux/thread_info.h>
#include <linux/atomic.h>
#include <linux/jump_label.h>
#include <asm/sections.h> #include <asm/sections.h>
/* /*
@ -240,6 +242,8 @@ static inline void check_heap_object(const void *ptr, unsigned long n,
} }
} }
static DEFINE_STATIC_KEY_FALSE_RO(bypass_usercopy_checks);
/* /*
* Validates that the given object is: * Validates that the given object is:
* - not bogus address * - not bogus address
@ -248,6 +252,9 @@ static inline void check_heap_object(const void *ptr, unsigned long n,
*/ */
void __check_object_size(const void *ptr, unsigned long n, bool to_user) void __check_object_size(const void *ptr, unsigned long n, bool to_user)
{ {
if (static_branch_unlikely(&bypass_usercopy_checks))
return;
/* Skip all tests if size is zero. */ /* Skip all tests if size is zero. */
if (!n) if (!n)
return; return;
@ -279,3 +286,21 @@ void __check_object_size(const void *ptr, unsigned long n, bool to_user)
check_kernel_text_object((const unsigned long)ptr, n, to_user); check_kernel_text_object((const unsigned long)ptr, n, to_user);
} }
EXPORT_SYMBOL(__check_object_size); EXPORT_SYMBOL(__check_object_size);
static bool enable_checks __initdata = true;
static int __init parse_hardened_usercopy(char *str)
{
return strtobool(str, &enable_checks);
}
__setup("hardened_usercopy=", parse_hardened_usercopy);
static int __init set_hardened_usercopy(void)
{
if (enable_checks == false)
static_branch_enable(&bypass_usercopy_checks);
return 1;
}
late_initcall(set_hardened_usercopy);