|
|
Message-ID: <CA+KhAHbmahpgPwjb1yOLDnT6Nw+W4kU+cJ0cO3LMBmxUeXyupA@mail.gmail.com>
Date: Thu, 2 Feb 2017 17:34:06 +0400
From: Keun-O Park <kpark3469@...il.com>
To: James Morse <james.morse@....com>
Cc: kernel-hardening@...ts.openwall.com,
Catalin Marinas <catalin.marinas@....com>, Kees Cook <keescook@...omium.org>,
Will Deacon <will.deacon@....com>, Mark Rutland <mark.rutland@....com>,
Pratyush Anand <panand@...hat.com>, keun-o.park@...kmatter.ae
Subject: Re: [PATCH] arm64: usercopy: Implement stack frame object validation
Hello James,
On Thu, Jan 26, 2017 at 7:23 PM, James Morse <james.morse@....com> wrote:
> Hi Sahara,
>
> On 25/01/17 13:46, kpark3469@...il.com wrote:
>> From: Sahara <keun-o.park@...kmatter.ae>
>>
>> This implements arch_within_stack_frames() for arm64 that should
>> validate if a given object is contained by a kernel stack frame.
>
> Thanks for wade-ing into this, walking the stack is horribly tricky!
>
>
>> diff --git a/arch/arm64/include/asm/thread_info.h b/arch/arm64/include/asm/thread_info.h
>> index 46c3b93..f610c44 100644
>> --- a/arch/arm64/include/asm/thread_info.h
>> +++ b/arch/arm64/include/asm/thread_info.h
>> @@ -68,7 +68,62 @@ struct thread_info {
>> #define thread_saved_fp(tsk) \
>> ((unsigned long)(tsk->thread.cpu_context.fp))
>>
>> +/*
>> + * Walks up the stack frames to make sure that the specified object is
>> + * entirely contained by a single stack frame.
>> + *
>> + * Returns:
>> + * 1 if within a frame
>> + * -1 if placed across a frame boundary (or outside stack)
>> + * 0 unable to determine (no frame pointers, etc)
>
> (Shame the enum in mm/usercopy.c that explains these isn't exposed)
I exposed the enum in mm/usercopy.c by creating and moving to
usercopy.h in v2 patch
>
>
>> + */
>> +static inline int arch_within_stack_frames(const void * const stack,
>> + const void * const stackend,
>> + const void *obj, unsigned long len)
>> +{
>> +#if defined(CONFIG_FRAME_POINTER)
>> + const void *oldframe;
>> + const void *callee_fp = NULL;
>> + const void *caller_fp = NULL;
>> +
>> + oldframe = __builtin_frame_address(1);
>
> So we always discard _our_ callers frame. This will vary depending on the
> compilers choice on whether or not to inline this function. Its either
> check_stack_object() (which is declared noinline) or __check_object_size().
> I think this is fine either way as obj should never be in the stack-frames of
> these functions.
Unfortunately Akashi reported that obj can be placed at the
stack-frame of these functions,
when dynamic array is used.
>
>
>> + if (oldframe) {
>> + callee_fp = __builtin_frame_address(2);
>> + if (callee_fp)
>> + caller_fp = __builtin_frame_address(3);
>> + }
>> + /*
>> + * low ----------------------------------------------> high
>> + * [callee_fp][lr][args][local vars][caller_fp'][lr']
>
> These are the caller's args and local_vars right?
No, unless my test is wrong, these are callee's args and local vars.
>
>
>> + * ^----------------^
>> + * allow copies only within here
>> + */
>> + while (stack <= callee_fp && callee_fp < stackend) {
>> + /*
>> + * If obj + len extends past the caller frame, this
>> + * check won't pass and the next frame will be 0,
>> + * causing us to bail out and correctly report
>> + * the copy as invalid.
>> + */
>> + if (!caller_fp) {
>
>> + if (obj + len <= stackend)
>
> Isn't this always true? check_stack_object() does this:
>> if (obj < stack || stackend < obj + len)
>> return BAD_STACK;
>
You're right. This is redundant check. Thanks.
>
>> + return (obj >= callee_fp + 2 * sizeof(void *)) ?
>> + 1 : -1;
>
> You do this twice (and its pretty complicated), can you make it a macro with an
> explanatory name? (I think its calculating caller_frame_end from callee_fp,
> having something like caller_frame_start and caller_frame_end would make this
> easier to follow).
>
As your suggestion, I added a macro like get_stack_start()
>
>> + else
>> + return -1;
>> + }
>> + if (obj + len <= caller_fp)
>> + return (obj >= callee_fp + 2 * sizeof(void *)) ? 1 : -1;
>> + callee_fp = caller_fp;
>> + caller_fp = *(const void * const *)caller_fp;
>
> You test caller_fp lies within the stack next time round the loop, what happens
> if it is miss-aligned? unwind_frame() tests the new fp to check it was 16-byte
> aligned. If not, its probably a corrupted stack frame.
I added missing check for misaligned frame pointer. Thanks.
>
> I wonder if you can make use of unwind_frame()? We have existing machinery for
> walking up the stack, (it takes a callback and you can stop the walk early).
> If you move this function into arch/arm64/kernel/stacktrace.c, you could make
> use of walk_stackframe().
Without modifying existing x86 code for arch_within_stack_frames and
mm/usercopy.c,
it looks to me that it might not be feasible to use unwind_frame().
But, I didn't meticulously consider this point.
>
> I guess you aren't using it because it doesn't give you start/end ranges for
> each stack frame, I think you can get away without this, the callback would only
> needs to test that the provided frame->fp doesn't lie between obj and (obj+len).
> Calculating the frame start and end is extra work for the bounds test - we
> already know obj is contained by the stack so its just a question of whether it
> overlaps an fp record.
Exactly. :-)
>
>
>> + }
>> + return -1;
>
> check_stack_object()'s use of task_stack_page(current) means you will never see
> this run on the irq-stack, so no need to worry about stepping between stacks.
>
> This looks correct to me, walking the stack is unavoidably complex, I wonder if
> we can avoid having another stack walker by using the existing framework?
>
>
>
>
> Thanks,
>
> James
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.