|
Message-Id: <E113B6A6-F18E-4413-AFFA-9956FAB361F0@gmail.com> Date: Wed, 15 Feb 2017 08:42:37 +0900 From: Hoeun Ryu <hoeun.ryu@...il.com> To: Kees Cook <keescook@...omium.org> Cc: Mark Rutland <mark.rutland@....com>, LKML <linux-kernel@...r.kernel.org>, "kernel-hardening@...ts.openwall.com" <kernel-hardening@...ts.openwall.com> Subject: Re: [PATCH] usercopy: add testcases to check zeroing on failure of usercopy > On Feb 15, 2017, at 5:36 AM, Kees Cook <keescook@...omium.org> wrote: > >> On Mon, Feb 13, 2017 at 5:44 PM, Hoeun Ryu <hoeun.ryu@...il.com> wrote: >> >> >>>> On Feb 14, 2017, at 4:24 AM, Kees Cook <keescook@...omium.org> wrote: >>>> >>>>> On Mon, Feb 13, 2017 at 10:33 AM, Kees Cook <keescook@...omium.org> wrote: >>>>> On Sat, Feb 11, 2017 at 10:13 PM, Hoeun Ryu <hoeun.ryu@...il.com> wrote: >>>>> In the hardend usercopy, the destination buffer will be zeroed if >>>>> copy_from_user/get_user fails. This patch adds testcases for it. >>>>> The destination buffer is set with non-zero value before illegal >>>>> copy_from_user/get_user is executed and the buffer is compared to >>>>> zero after usercopy is done. >>>>> >>>>> Signed-off-by: Hoeun Ryu <hoeun.ryu@...il.com> >>>>> --- >>>>> lib/test_user_copy.c | 17 +++++++++++++++++ >>>>> 1 file changed, 17 insertions(+) >>>>> >>>>> diff --git a/lib/test_user_copy.c b/lib/test_user_copy.c >>>>> index 0ecef3e..54bd898 100644 >>>>> --- a/lib/test_user_copy.c >>>>> +++ b/lib/test_user_copy.c >>>>> @@ -41,11 +41,18 @@ static int __init test_user_copy_init(void) >>>>> char *bad_usermem; >>>>> unsigned long user_addr; >>>>> unsigned long value = 0x5A; >>>>> + char *zerokmem; >>>>> >>>>> kmem = kmalloc(PAGE_SIZE * 2, GFP_KERNEL); >>>>> if (!kmem) >>>>> return -ENOMEM; >>>>> >>>>> + zerokmem = kzalloc(PAGE_SIZE * 2, GFP_KERNEL); >>>>> + if (!zerokmem) { >>>>> + kfree(kmem); >>>>> + return -ENOMEM; >>>>> + } >>>>> + >>>>> user_addr = vm_mmap(NULL, 0, PAGE_SIZE * 2, >>>>> PROT_READ | PROT_WRITE | PROT_EXEC, >>>>> MAP_ANONYMOUS | MAP_PRIVATE, 0); >>>>> @@ -69,25 +76,35 @@ static int __init test_user_copy_init(void) >>>>> "legitimate put_user failed"); >>>>> >>>>> /* Invalid usage: none of these should succeed. */ >>>>> + memset(kmem, 0x5A, PAGE_SIZE); >>>>> ret |= test(!copy_from_user(kmem, (char __user *)(kmem + PAGE_SIZE), >>>>> PAGE_SIZE), >>>>> "illegal all-kernel copy_from_user passed"); >>>>> + ret |= test(memcmp(zerokmem, kmem, PAGE_SIZE), >>>>> + "zeroing failure for illegal all-kernel copy_from_user"); >>>>> + memset(bad_usermem, 0x5A, PAGE_SIZE); >>>> >>>> Oh, actually, ha-ha: this isn't legal: it's a direct copy from kernel >>>> to userspace. :) This needs a copy_to_user()... (and same for the >>>> memcmp...) >> >> I just came up with that usercopy doesn't check the buffer is valid when zeroing happens. I mean if the buffer is wrong address pointing other kernel objects or user space address, is it possible for zeroing to overwrite the address ? > > The overwrite happening even when the address is "wrong" seems like a > bug to me, but it's sort of already too late (a bad kernel address > would have already been a target for a userspace copy), but if > something has gone really wrong (i.e. attacker doesn't have control > over the source buffer) this does give a "write 0" primitive. > > Mark Rutland noticed some order-of-operations issues here too, and his > solution is pretty straight forward: move the checks outside the > failure path. If the kernel target is demonstrably bad, then the > process will be killed before the write 0 happens. (In the non-const > case at least...) > > (Oh, btw, I just noticed that x86's copy_from_user() already does the > check before _copy_from_user() can do the memset, so x86 is already > "ok" in this regard.) > >>>>> + ret |= test(memcmp(zerokmem, kmem, sizeof(value)), >>>>> + "zeroing failure for illegal get_user"); >> >> Actually on my x86_64 (qemu), this testcase fails. >> The generic get_user has zeroing but the one of arch x86 does not. >> Do we need to propagate zeroing to the other arch specific get_user code ? > > Hm, this didn't fail for me on x86 nor arm. Or, at least, my updated > test doesn't fail: > > value = 0x5A; > ret |= test(!get_user(value, (unsigned long __user *)kmem), > "illegal get_user passed"); > ret |= test(value != 0, "zeroing failure for illegal get_user"); > > I see the zeroing in the x86 uaccess.h, though it's a bit obfuscated: > > #define get_user(x, ptr) \ > ({ \ > int __ret_gu; \ > register __inttype(*(ptr)) __val_gu asm("%"_ASM_DX); \ > register void *__sp asm(_ASM_SP); \ > __chk_user_ptr(ptr); \ > might_fault(); \ > asm volatile("call __get_user_%P4" \ > : "=a" (__ret_gu), "=r" (__val_gu), "+r" (__sp) \ > : "0" (ptr), "i" (sizeof(*(ptr)))); \ > (x) = (__force __typeof__(*(ptr))) __val_gu; \ > __builtin_expect(__ret_gu, 0); \ > }) > > __ret_gu is the 0 or -EFAULT (from the __get_user_* assembly), and x > is set unconditionally to __val_gu, which gets zero-set by the same > assembly. > > Regardless, I'll expand the tests to check all get_user() sizes... > > -Kees Thank you for your detailed explanations. > -- > Kees Cook > Pixel Security
Powered by blists - more mailing lists
Confused about mailing lists and their use? Read about mailing lists on Wikipedia and check out these guidelines on proper formatting of your messages.