|
Message-ID: <CAGXu5jLhqFx5gZWB7GFdz_zfphJbnUgwrjHNSxHEFBt8ujtPPg@mail.gmail.com> Date: Tue, 14 Feb 2017 12:36:48 -0800 From: Kees Cook <keescook@...omium.org> To: Hoeun Ryu <hoeun.ryu@...il.com>, Mark Rutland <mark.rutland@....com> Cc: 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 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 -- 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.