Follow @Openwall on Twitter for new release announcements and other news
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <20170126071010.GE23406@linaro.org>
Date: Thu, 26 Jan 2017 16:10:12 +0900
From: AKASHI Takahiro <takahiro.akashi@...aro.org>
To: Will Deacon <will.deacon@....com>
Cc: kpark3469@...il.com, kernel-hardening@...ts.openwall.com,
	catalin.marinas@....com, keescook@...omium.org,
	mark.rutland@....com, james.morse@....com, panand@...hat.com,
	keun-o.park@...kmatter.ae
Subject: Re: [PATCH] arm64: usercopy: Implement stack frame object validation

On Wed, Jan 25, 2017 at 01:54:10PM +0000, Will Deacon wrote:
> [Adding Akashi, since he'a had fun and games with arm64 stack unwinding
>  and I bet he can find a problem with this patch!]

I have had hard time to play with that :)

> On Wed, Jan 25, 2017 at 05:46:23PM +0400, 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.
> > 
> > Signed-off-by: Sahara <keun-o.park@...kmatter.ae>
> > ---
> >  arch/arm64/Kconfig                   |  1 +
> >  arch/arm64/include/asm/thread_info.h | 55 ++++++++++++++++++++++++++++++++++++
> >  2 files changed, 56 insertions(+)
> > 
> > diff --git a/arch/arm64/Kconfig b/arch/arm64/Kconfig
> > index 1117421..8bf70b4 100644
> > --- a/arch/arm64/Kconfig
> > +++ b/arch/arm64/Kconfig
> > @@ -97,6 +97,7 @@ config ARM64
> >  	select HAVE_SYSCALL_TRACEPOINTS
> >  	select HAVE_KPROBES
> >  	select HAVE_KRETPROBES if HAVE_KPROBES
> > +	select HAVE_ARCH_WITHIN_STACK_FRAMES
> >  	select IOMMU_DMA if IOMMU_SUPPORT
> >  	select IRQ_DOMAIN
> >  	select IRQ_FORCED_THREADING
> > 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)
> > + */
> > +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)

nitpick: s/#if defined()/#ifdef/, or just remove this guard?

> > +	const void *oldframe;
> > +	const void *callee_fp = NULL;
> > +	const void *caller_fp = NULL;
> > +
> > +	oldframe = __builtin_frame_address(1);
> > +	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']
> > +	 *                ^----------------^
> > +	 *               allow copies only within here
> > +	 */
> 
> Which compilers have you tested this with? The GCC folks don't guarantee a
> frame layout, and they have changed it in the past,

I don't know whether any changes have been made before or not, but

> so I suspect this is
> pretty fragile. In particularly, if __builtin_frame_address just points
> at the frame record, then I don't think you can make assumptions about the
> placement of local variables and arguments with respect to that.
> 
> Will

Yes and no.

AAPCS64 says,
- The stack implementation is full-descending (5.2.2)
- A process may only access (for reading or writing) the closed interval
  of the entire stack delimited by [SP, stack-base - 1]. (5.2.2.1)
- The location of the frame record within a stack frame is not specified
  (5.2.3)

To my best knowledge, dynamically allocated object (local variable) may be
allocated below the current frame pointer, decrementing the stack pointer.
Take a look at a simple example:

===8<===
#include <stdio.h>
#include <stdlib.h>

int main(int ac, char **av) {
	int array_size;
	register unsigned long sp asm("sp");

	if (ac < 2) {
		printf("No argument\n");
		return 1;
	}
	array_size = atoi(av[1]);
	printf("frame pointer: %lx\n", __builtin_frame_address(0));
	printf("Before dynamic alloc: %lx\n", sp);
	{
		long array[array_size];

		printf("After dynamic alloc: %lx\n", sp);
	}

	return 0;
}
===>8===

and the result (with gcc 5.3) is:
  frame pointer:        ffffe32bacd0
  Before dynamic alloc: ffffe32bacd0
  After dynamic alloc:  ffffe32bac70

Given this fact,

> > +	/*
> > +	 * low ----------------------------------------------> high
> > +	 * [callee_fp][lr][args][local vars][caller_fp'][lr']
> > +	 *                ^----------------^
> > +	 *               allow copies only within here
> > +	 */

this picture may not always be precise in that "local variables" are
local to the callee, OR possibly local to the *caller*.
However, the range check is done here in a while loop that walks through
the whole callstack chain, and so I assume that it would work in most cases
except the case that a callee function hits that usage.

I think there are a few (not many) places of such code in the kernel,
(around net IIRC, but don' know they call usercopy functions or not).

Thanks,
-Takahiro AKASHI

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.