lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
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

Powered by Openwall GNU/*/Linux Powered by OpenVZ