Message ID | 1468446964-22213-2-git-send-email-keescook@chromium.org (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
On Wed, Jul 13, 2016 at 2:55 PM, Kees Cook <keescook@chromium.org> wrote: > This creates per-architecture function arch_within_stack_frames() that > should validate if a given object is contained by a kernel stack frame. > Initial implementation is on x86. > > This is based on code from PaX. > This, along with Josh's livepatch work, are two examples of unwinders that matter for correctness instead of just debugging. ISTM this should just use Josh's code directly once it's been written. --Andy
On Wed, Jul 13, 2016 at 3:01 PM, Andy Lutomirski <luto@amacapital.net> wrote: > On Wed, Jul 13, 2016 at 2:55 PM, Kees Cook <keescook@chromium.org> wrote: >> This creates per-architecture function arch_within_stack_frames() that >> should validate if a given object is contained by a kernel stack frame. >> Initial implementation is on x86. >> >> This is based on code from PaX. >> > > This, along with Josh's livepatch work, are two examples of unwinders > that matter for correctness instead of just debugging. ISTM this > should just use Josh's code directly once it's been written. Do you have URL for Josh's code? I'd love to see what happening there. In the meantime, usercopy can use this... -Kees
On Wed, Jul 13, 2016 at 03:04:26PM -0700, Kees Cook wrote: > On Wed, Jul 13, 2016 at 3:01 PM, Andy Lutomirski <luto@amacapital.net> wrote: > > On Wed, Jul 13, 2016 at 2:55 PM, Kees Cook <keescook@chromium.org> wrote: > >> This creates per-architecture function arch_within_stack_frames() that > >> should validate if a given object is contained by a kernel stack frame. > >> Initial implementation is on x86. > >> > >> This is based on code from PaX. > >> > > > > This, along with Josh's livepatch work, are two examples of unwinders > > that matter for correctness instead of just debugging. ISTM this > > should just use Josh's code directly once it's been written. > > Do you have URL for Josh's code? I'd love to see what happening there. The code is actually going to be 100% different next time around, but FWIW, here's the last attempt: https://lkml.kernel.org/r/4d34d452bf8f85c7d6d5f93db1d3eeb4cba335c7.1461875890.git.jpoimboe@redhat.com In the meantime I've realized the need to rewrite the x86 core stack walking code to something much more manageable so we don't need all these unwinders everywhere. I'll probably post the patches in the next week or so. I'll add you to the CC list. With the new interface I think you'll be able to do something like: struct unwind_state; unwind_start(&state, current, NULL, NULL); unwind_next_frame(&state); oldframe = unwind_get_stack_pointer(&state); unwind_next_frame(&state); frame = unwind_get_stack_pointer(&state); do { if (obj + len <= frame) return blah; oldframe = frame; frame = unwind_get_stack_pointer(&state); } while (unwind_next_frame(&state); And then at the end there'll be some (still TBD) way to query whether it reached the last syscall pt_regs frame, or if it instead encountered a bogus frame pointer along the way and had to bail early.
On Wed, Jul 13, 2016 at 10:48 PM, Josh Poimboeuf <jpoimboe@redhat.com> wrote: > On Wed, Jul 13, 2016 at 03:04:26PM -0700, Kees Cook wrote: >> On Wed, Jul 13, 2016 at 3:01 PM, Andy Lutomirski <luto@amacapital.net> wrote: >> > On Wed, Jul 13, 2016 at 2:55 PM, Kees Cook <keescook@chromium.org> wrote: >> >> This creates per-architecture function arch_within_stack_frames() that >> >> should validate if a given object is contained by a kernel stack frame. >> >> Initial implementation is on x86. >> >> >> >> This is based on code from PaX. >> >> >> > >> > This, along with Josh's livepatch work, are two examples of unwinders >> > that matter for correctness instead of just debugging. ISTM this >> > should just use Josh's code directly once it's been written. >> >> Do you have URL for Josh's code? I'd love to see what happening there. > > The code is actually going to be 100% different next time around, but > FWIW, here's the last attempt: > > https://lkml.kernel.org/r/4d34d452bf8f85c7d6d5f93db1d3eeb4cba335c7.1461875890.git.jpoimboe@redhat.com > > In the meantime I've realized the need to rewrite the x86 core stack > walking code to something much more manageable so we don't need all > these unwinders everywhere. I'll probably post the patches in the next > week or so. I'll add you to the CC list. Awesome! > With the new interface I think you'll be able to do something like: > > struct unwind_state; > > unwind_start(&state, current, NULL, NULL); > unwind_next_frame(&state); > oldframe = unwind_get_stack_pointer(&state); > > unwind_next_frame(&state); > frame = unwind_get_stack_pointer(&state); > > do { > if (obj + len <= frame) > return blah; > oldframe = frame; > frame = unwind_get_stack_pointer(&state); > > } while (unwind_next_frame(&state); > > And then at the end there'll be some (still TBD) way to query whether it > reached the last syscall pt_regs frame, or if it instead encountered a > bogus frame pointer along the way and had to bail early. Sounds good to me. Will there be any frame size information available? Right now, the unwinder from PaX just drops 2 pointers (saved frame, saved ip) from the delta of frame address to find the size of the actual stack area used by the function. If I could shave things like padding and possible stack canaries off the size too, that would be great. Since I'm aiming the hardened usercopy series for 4.8, I figure I'll just leave this unwinder in for now, and once yours lands, I can rip it out again. -Kees
On Thu, Jul 14, 2016 at 11:10:18AM -0700, Kees Cook wrote: > On Wed, Jul 13, 2016 at 10:48 PM, Josh Poimboeuf <jpoimboe@redhat.com> wrote: > > On Wed, Jul 13, 2016 at 03:04:26PM -0700, Kees Cook wrote: > >> On Wed, Jul 13, 2016 at 3:01 PM, Andy Lutomirski <luto@amacapital.net> wrote: > >> > On Wed, Jul 13, 2016 at 2:55 PM, Kees Cook <keescook@chromium.org> wrote: > >> >> This creates per-architecture function arch_within_stack_frames() that > >> >> should validate if a given object is contained by a kernel stack frame. > >> >> Initial implementation is on x86. > >> >> > >> >> This is based on code from PaX. > >> >> > >> > > >> > This, along with Josh's livepatch work, are two examples of unwinders > >> > that matter for correctness instead of just debugging. ISTM this > >> > should just use Josh's code directly once it's been written. > >> > >> Do you have URL for Josh's code? I'd love to see what happening there. > > > > The code is actually going to be 100% different next time around, but > > FWIW, here's the last attempt: > > > > https://lkml.kernel.org/r/4d34d452bf8f85c7d6d5f93db1d3eeb4cba335c7.1461875890.git.jpoimboe@redhat.com > > > > In the meantime I've realized the need to rewrite the x86 core stack > > walking code to something much more manageable so we don't need all > > these unwinders everywhere. I'll probably post the patches in the next > > week or so. I'll add you to the CC list. > > Awesome! > > > With the new interface I think you'll be able to do something like: > > > > struct unwind_state; > > > > unwind_start(&state, current, NULL, NULL); > > unwind_next_frame(&state); > > oldframe = unwind_get_stack_pointer(&state); > > > > unwind_next_frame(&state); > > frame = unwind_get_stack_pointer(&state); > > > > do { > > if (obj + len <= frame) > > return blah; > > oldframe = frame; > > frame = unwind_get_stack_pointer(&state); > > > > } while (unwind_next_frame(&state); > > > > And then at the end there'll be some (still TBD) way to query whether it > > reached the last syscall pt_regs frame, or if it instead encountered a > > bogus frame pointer along the way and had to bail early. > > Sounds good to me. Will there be any frame size information available? > Right now, the unwinder from PaX just drops 2 pointers (saved frame, > saved ip) from the delta of frame address to find the size of the > actual stack area used by the function. If I could shave things like > padding and possible stack canaries off the size too, that would be > great. For x86, stacks are aligned at long word boundaries, so there's no real stack padding. Also the CC_STACKPROTECTOR stack canaries are created by a gcc feature which only affects certain functions (and thus certain frames) and I don't know of any reliable way to find them. So with frame pointers, I think the best you can do is just assume that the frame data area is always two words smaller than the total frame size. > Since I'm aiming the hardened usercopy series for 4.8, I figure I'll > just leave this unwinder in for now, and once yours lands, I can rip > it out again. Sure, sounds fine to me. If your code lands before I post mine, I can convert it myself.
On Thu, Jul 14, 2016 at 12:23 PM, Josh Poimboeuf <jpoimboe@redhat.com> wrote: > On Thu, Jul 14, 2016 at 11:10:18AM -0700, Kees Cook wrote: >> On Wed, Jul 13, 2016 at 10:48 PM, Josh Poimboeuf <jpoimboe@redhat.com> wrote: >> > On Wed, Jul 13, 2016 at 03:04:26PM -0700, Kees Cook wrote: >> >> On Wed, Jul 13, 2016 at 3:01 PM, Andy Lutomirski <luto@amacapital.net> wrote: >> >> > On Wed, Jul 13, 2016 at 2:55 PM, Kees Cook <keescook@chromium.org> wrote: >> >> >> This creates per-architecture function arch_within_stack_frames() that >> >> >> should validate if a given object is contained by a kernel stack frame. >> >> >> Initial implementation is on x86. >> >> >> >> >> >> This is based on code from PaX. >> >> >> >> >> > >> >> > This, along with Josh's livepatch work, are two examples of unwinders >> >> > that matter for correctness instead of just debugging. ISTM this >> >> > should just use Josh's code directly once it's been written. >> >> >> >> Do you have URL for Josh's code? I'd love to see what happening there. >> > >> > The code is actually going to be 100% different next time around, but >> > FWIW, here's the last attempt: >> > >> > https://lkml.kernel.org/r/4d34d452bf8f85c7d6d5f93db1d3eeb4cba335c7.1461875890.git.jpoimboe@redhat.com >> > >> > In the meantime I've realized the need to rewrite the x86 core stack >> > walking code to something much more manageable so we don't need all >> > these unwinders everywhere. I'll probably post the patches in the next >> > week or so. I'll add you to the CC list. >> >> Awesome! >> >> > With the new interface I think you'll be able to do something like: >> > >> > struct unwind_state; >> > >> > unwind_start(&state, current, NULL, NULL); >> > unwind_next_frame(&state); >> > oldframe = unwind_get_stack_pointer(&state); >> > >> > unwind_next_frame(&state); >> > frame = unwind_get_stack_pointer(&state); >> > >> > do { >> > if (obj + len <= frame) >> > return blah; >> > oldframe = frame; >> > frame = unwind_get_stack_pointer(&state); >> > >> > } while (unwind_next_frame(&state); >> > >> > And then at the end there'll be some (still TBD) way to query whether it >> > reached the last syscall pt_regs frame, or if it instead encountered a >> > bogus frame pointer along the way and had to bail early. >> >> Sounds good to me. Will there be any frame size information available? >> Right now, the unwinder from PaX just drops 2 pointers (saved frame, >> saved ip) from the delta of frame address to find the size of the >> actual stack area used by the function. If I could shave things like >> padding and possible stack canaries off the size too, that would be >> great. > > For x86, stacks are aligned at long word boundaries, so there's no real > stack padding. Well, I guess I meant the possible padding between variables and the aligned pointers, but that's a really minor concern in my mind (as far as being a potential kernel memory exposure on a bad usercopy). > Also the CC_STACKPROTECTOR stack canaries are created by a gcc feature > which only affects certain functions (and thus certain frames) and I > don't know of any reliable way to find them. Okay, that's fine. I had a horrible idea to just have the unwinder look at the value stored in front of the saved ip, and if it matches the known canary (for current anyway), then reduce the frame size by another long word. ;) > So with frame pointers, I think the best you can do is just assume that > the frame data area is always two words smaller than the total frame > size. Yeah, that's what's happening here currently. Cool. >> Since I'm aiming the hardened usercopy series for 4.8, I figure I'll >> just leave this unwinder in for now, and once yours lands, I can rip >> it out again. > > Sure, sounds fine to me. If your code lands before I post mine, I can > convert it myself. Awesome, I'll keep you posted. Thanks! -Kees
diff --git a/arch/Kconfig b/arch/Kconfig index d794384a0404..5e2776562035 100644 --- a/arch/Kconfig +++ b/arch/Kconfig @@ -424,6 +424,15 @@ config CC_STACKPROTECTOR_STRONG endchoice +config HAVE_ARCH_WITHIN_STACK_FRAMES + bool + help + An architecture should select this if it can walk the kernel stack + frames to determine if an object is part of either the arguments + or local variables (i.e. that it excludes saved return addresses, + and similar) by implementing an inline arch_within_stack_frames(), + which is used by CONFIG_HARDENED_USERCOPY. + config HAVE_CONTEXT_TRACKING bool help diff --git a/arch/x86/Kconfig b/arch/x86/Kconfig index 0a7b885964ba..4407f596b72c 100644 --- a/arch/x86/Kconfig +++ b/arch/x86/Kconfig @@ -91,6 +91,7 @@ config X86 select HAVE_ARCH_SOFT_DIRTY if X86_64 select HAVE_ARCH_TRACEHOOK select HAVE_ARCH_TRANSPARENT_HUGEPAGE + select HAVE_ARCH_WITHIN_STACK_FRAMES select HAVE_EBPF_JIT if X86_64 select HAVE_CC_STACKPROTECTOR select HAVE_CMPXCHG_DOUBLE diff --git a/arch/x86/include/asm/thread_info.h b/arch/x86/include/asm/thread_info.h index 30c133ac05cd..ab386f1336f2 100644 --- a/arch/x86/include/asm/thread_info.h +++ b/arch/x86/include/asm/thread_info.h @@ -180,6 +180,50 @@ static inline unsigned long current_stack_pointer(void) return sp; } +/* + * 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) + const void *frame = NULL; + const void *oldframe; + + oldframe = __builtin_frame_address(1); + if (oldframe) + frame = __builtin_frame_address(2); + /* + * low ----------------------------------------------> high + * [saved bp][saved ip][args][local vars][saved bp][saved ip] + * ^----------------^ + * allow copies only within here + */ + while (stack <= frame && frame < stackend) { + /* + * If obj + len extends past the last 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 (obj + len <= frame) + return obj >= oldframe + 2 * sizeof(void *) ? 1 : -1; + oldframe = frame; + frame = *(const void * const *)frame; + } + return -1; +#else + return 0; +#endif +} + #else /* !__ASSEMBLY__ */ #ifdef CONFIG_X86_64 diff --git a/include/linux/thread_info.h b/include/linux/thread_info.h index b4c2a485b28a..3d5c80b4391d 100644 --- a/include/linux/thread_info.h +++ b/include/linux/thread_info.h @@ -146,6 +146,15 @@ static inline bool test_and_clear_restore_sigmask(void) #error "no set_restore_sigmask() provided and default one won't work" #endif +#ifndef CONFIG_HAVE_ARCH_WITHIN_STACK_FRAMES +static inline int arch_within_stack_frames(const void * const stack, + const void * const stackend, + const void *obj, unsigned long len) +{ + return 0; +} +#endif + #endif /* __KERNEL__ */ #endif /* _LINUX_THREAD_INFO_H */
This creates per-architecture function arch_within_stack_frames() that should validate if a given object is contained by a kernel stack frame. Initial implementation is on x86. This is based on code from PaX. Signed-off-by: Kees Cook <keescook@chromium.org> --- arch/Kconfig | 9 ++++++++ arch/x86/Kconfig | 1 + arch/x86/include/asm/thread_info.h | 44 ++++++++++++++++++++++++++++++++++++++ include/linux/thread_info.h | 9 ++++++++ 4 files changed, 63 insertions(+)