Follow @Openwall on Twitter for new release announcements and other news
[<prev] [next>] [<thread-prev] [day] [month] [year] [list]
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.