Hi,
This is a start of the mainline port of PAX_USERCOPY[1]. After I started
writing tests (now in lkdtm in -next) for Casey's earlier port[2], I
kept tweaking things further and further until I ended up with a whole
new patch series. To that end, I took Rik's feedback and made a number
of other changes and clean-ups as well.
Based on my understanding, PAX_USERCOPY was designed to catch a
few classes of flaws (mainly bad bounds checking) around the use of
copy_to_user()/copy_from_user(). These changes don't touch get_user() and
put_user(), since these operate on constant sized lengths, and tend to be
much less vulnerable. There are effectively three distinct protections in
the whole series, each of which I've given a separate CONFIG, though this
patch set is only the first of the three intended protections. (Generally
speaking, PAX_USERCOPY covers what I'm calling CONFIG_HARDENED_USERCOPY
(this) and CONFIG_HARDENED_USERCOPY_WHITELIST (future), and
PAX_USERCOPY_SLABS covers CONFIG_HARDENED_USERCOPY_SPLIT_KMALLOC
(future).)
This series, which adds CONFIG_HARDENED_USERCOPY, checks that objects
being copied to/from userspace meet certain criteria:
- if address is a heap object, the size must not exceed the object's
allocated size. (This will catch all kinds of heap overflow flaws.)
- if address range is in the current process stack, it must be within the
current stack frame (if such checking is possible) or at least entirely
within the current process's stack. (This could catch large lengths that
would have extended beyond the current process stack, or overflows if
their length extends back into the original stack.)
- if the address range is part of kernel data, rodata, or bss, allow it.
- if address range is page-allocated, that it doesn't span multiple
allocations.
- if address is within the kernel text, reject it.
- everything else is accepted
The patches in the series are:
- Support for arch-specific stack frame checking:
1- mm: Implement stack frame object validation
- The core copy_to/from_user() checks, without the slab object checks:
2- mm: Hardened usercopy
- Per-arch enablement of the protection:
3- x86/uaccess: Enable hardened usercopy
4- ARM: uaccess: Enable hardened usercopy
5- arm64/uaccess: Enable hardened usercopy
6- ia64/uaccess: Enable hardened usercopy
7- powerpc/uaccess: Enable hardened usercopy
8- sparc/uaccess: Enable hardened usercopy
9- s390/uaccess: Enable hardened usercopy
- The heap allocator implementation of object size checking:
10- mm: SLAB hardened usercopy support
11- mm: SLUB hardened usercopy support
Some notes:
- This is expected to apply on top of -next which contains fixes for the
position of _etext on both arm and arm64.
- I couldn't detect a measurable performance change with these features
enabled. Kernel build times were unchanged, hackbench was unchanged,
etc. I think we could flip this to "on by default" at some point, but
for now, I'm leaving it off until I can get some more definitive
measurements.
- The SLOB support extracted from grsecurity seems entirely broken. I
have no idea what's going on there, I spent my time testing SLAB and
SLUB. Having someone else look at SLOB would be nice, but this series
doesn't depend on it.
Additional features that would be nice, but aren't blocking this series:
- Needs more architecture support for stack frame checking (only x86 now).
Thanks!
-Kees
[1] https://grsecurity.net/download.php "grsecurity - test kernel patch"
[2] http://www.openwall.com/lists/kernel-hardening/2016/05/19/5
v2:
- added s390 support
- handle slub red zone
- disallow writes to rodata area
- stack frame walker now CONFIG-controlled arch-specific helper
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 <redacted>
---
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(+)
From: Andy Lutomirski <luto@amacapital.net> Date: 2016-07-13 22:01:57
On Wed, Jul 13, 2016 at 2:55 PM, Kees Cook [off-list ref] 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 [off-list ref] wrote:
On Wed, Jul 13, 2016 at 2:55 PM, Kees Cook [off-list ref] wrote:
quoted
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
--
Kees Cook
Chrome OS & Brillo Security
On Wed, Jul 13, 2016 at 03:04:26PM -0700, Kees Cook wrote:
On Wed, Jul 13, 2016 at 3:01 PM, Andy Lutomirski [off-list ref] wrote:
quoted
On Wed, Jul 13, 2016 at 2:55 PM, Kees Cook [off-list ref] wrote:
quoted
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.
--
Josh
On Wed, Jul 13, 2016 at 10:48 PM, Josh Poimboeuf [off-list ref] wrote:
On Wed, Jul 13, 2016 at 03:04:26PM -0700, Kees Cook wrote:
quoted
On Wed, Jul 13, 2016 at 3:01 PM, Andy Lutomirski [off-list ref] wrote:
quoted
On Wed, Jul 13, 2016 at 2:55 PM, Kees Cook [off-list ref] wrote:
quoted
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
--
Kees Cook
Chrome OS & Brillo Security
On Thu, Jul 14, 2016 at 11:10:18AM -0700, Kees Cook wrote:
On Wed, Jul 13, 2016 at 10:48 PM, Josh Poimboeuf [off-list ref] wrote:
quoted
On Wed, Jul 13, 2016 at 03:04:26PM -0700, Kees Cook wrote:
quoted
On Wed, Jul 13, 2016 at 3:01 PM, Andy Lutomirski [off-list ref] wrote:
quoted
On Wed, Jul 13, 2016 at 2:55 PM, Kees Cook [off-list ref] wrote:
quoted
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!
quoted
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.
--
Josh
On Thu, Jul 14, 2016 at 12:23 PM, Josh Poimboeuf [off-list ref] wrote:
On Thu, Jul 14, 2016 at 11:10:18AM -0700, Kees Cook wrote:
quoted
On Wed, Jul 13, 2016 at 10:48 PM, Josh Poimboeuf [off-list ref] wrote:
quoted
On Wed, Jul 13, 2016 at 03:04:26PM -0700, Kees Cook wrote:
quoted
On Wed, Jul 13, 2016 at 3:01 PM, Andy Lutomirski [off-list ref] wrote:
quoted
On Wed, Jul 13, 2016 at 2:55 PM, Kees Cook [off-list ref] wrote:
quoted
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!
quoted
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.
quoted
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
--
Kees Cook
Chrome OS & Brillo Security
Enables CONFIG_HARDENED_USERCOPY checks on x86. This is done both in
copy_*_user() and __copy_*_user() because copy_*_user() actually calls
down to _copy_*_user() and not __copy_*_user().
Based on code from PaX and grsecurity.
Signed-off-by: Kees Cook <redacted>
---
arch/x86/Kconfig | 2 ++
arch/x86/include/asm/uaccess.h | 10 ++++++----
arch/x86/include/asm/uaccess_32.h | 2 ++
arch/x86/include/asm/uaccess_64.h | 2 ++
4 files changed, 12 insertions(+), 4 deletions(-)
@@ -762,9 +763,10 @@ copy_to_user(void __user *to, const void *from, unsigned long n)might_fault();/* See the comment in copy_from_user() above. */-if(likely(sz<0||sz>=n))+if(likely(sz<0||sz>=n)){+check_object_size(from,n,true);n=_copy_to_user(to,from,n);-elseif(__builtin_constant_p(n))+}elseif(__builtin_constant_p(n))copy_to_user_overflow();else__copy_to_user_overflow(sz,n);
This is the start of porting PAX_USERCOPY into the mainline kernel. This
is the first set of features, controlled by CONFIG_HARDENED_USERCOPY. The
work is based on code by PaX Team and Brad Spengler, and an earlier port
from Casey Schaufler. Additional non-slab page tests are from Rik van Riel.
This patch contains the logic for validating several conditions when
performing copy_to_user() and copy_from_user() on the kernel object
being copied to/from:
- address range doesn't wrap around
- address range isn't NULL or zero-allocated (with a non-zero copy size)
- if on the slab allocator:
- object size must be less than or equal to copy size (when check is
implemented in the allocator, which appear in subsequent patches)
- otherwise, object must not span page allocations
- if on the stack
- object must not extend before/after the current process task
- object must be contained by the current stack frame (when there is
arch/build support for identifying stack frames)
- object must not overlap with kernel text
Signed-off-by: Kees Cook <redacted>
---
arch/Kconfig | 7 ++
include/linux/slab.h | 12 +++
include/linux/thread_info.h | 15 +++
mm/Makefile | 4 +
mm/usercopy.c | 219 ++++++++++++++++++++++++++++++++++++++++++++
security/Kconfig | 27 ++++++
6 files changed, 284 insertions(+)
create mode 100644 mm/usercopy.c
@@ -21,6 +21,9 @@ KCOV_INSTRUMENT_memcontrol.o := nKCOV_INSTRUMENT_mmzone.o:=nKCOV_INSTRUMENT_vmstat.o:=n+# Since __builtin_frame_address does work as used, disable the warning.+CFLAGS_usercopy.o+=$(callcc-disable-warning,frame-address)+mmu-y:=nommu.ommu-$(CONFIG_MMU):=gup.ohighmem.omemory.omincore.o\mlock.ommap.omprotect.omremap.omsync.ormap.o\
@@ -0,0 +1,219 @@+/*+*ThisimplementsthevariouschecksforCONFIG_HARDENED_USERCOPY*,+*whicharedesignedtoprotectkernelmemoryfromneedlessexposure+*andoverwriteundermanyunintendedconditions.Thiscodeisbased+*onPAX_USERCOPY,whichis:+*+*Copyright(C)2001-2016PaXTeam,BradleySpengler,OpenSource+*SecurityInc.+*+*Thisprogramisfreesoftware;youcanredistributeitand/ormodify+*itunderthetermsoftheGNUGeneralPublicLicenseversion2as+*publishedbytheFreeSoftwareFoundation.+*+*/+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt++#include<linux/mm.h>+#include<linux/slab.h>+#include<asm/sections.h>++/*+*Checksifagivenpointerandlengthiscontainedbythecurrent+*stackframe(ifpossible).+*+*0:notatallonthestack+*1:fullywithinavalidstackframe+*2:fullyonthestack(whencan'tdoframe-checking)+*-1:errorcondition(invalidstackpositionorbadstackframe)+*/+staticnoinlineintcheck_stack_object(constvoid*obj,unsignedlonglen)+{+constvoid*conststack=task_stack_page(current);+constvoid*conststackend=stack+THREAD_SIZE;+intret;++/* Object is not on the stack at all. */+if(obj+len<=stack||stackend<=obj)+return0;++/*+*Reject:objectpartiallyoverlapsthestack(passingthe+*thecheckabovemeansatleastoneendiswithinthestack,+*soifthischeckfails,theotherendisoutsidethestack).+*/+if(obj<stack||stackend<obj+len)+return-1;++/* Check if object is safely within a valid frame. */+ret=arch_within_stack_frames(stack,stackend,obj,len);+if(ret)+returnret;++return2;+}++staticvoidreport_usercopy(constvoid*ptr,unsignedlonglen,+boolto_user,constchar*type)+{+pr_emerg("kernel memory %s attempt detected %s %p (%s) (%lu bytes)\n",+to_user?"exposure":"overwrite",+to_user?"from":"to",ptr,type?:"unknown",len);+dump_stack();+do_group_exit(SIGKILL);+}++/* Returns true if any portion of [ptr,ptr+n) over laps with [low,high). */+staticbooloverlaps(constvoid*ptr,unsignedlongn,unsignedlonglow,+unsignedlonghigh)+{+unsignedlongcheck_low=(uintptr_t)ptr;+unsignedlongcheck_high=check_low+n;++/* Does not overlap if entirely above or entirely below. */+if(check_low>=high||check_high<low)+returnfalse;++returntrue;+}++/* Is this address range in the kernel text area? */+staticinlineconstchar*check_kernel_text_object(constvoid*ptr,+unsignedlongn)+{+unsignedlongtextlow=(unsignedlong)_stext;+unsignedlongtexthigh=(unsignedlong)_etext;++if(overlaps(ptr,n,textlow,texthigh))+return"<kernel text>";++#ifdef HAVE_ARCH_LINEAR_KERNEL_MAPPING+/* Check against linear mapping as well. */+if(overlaps(ptr,n,(unsignedlong)__va(__pa(textlow)),+(unsignedlong)__va(__pa(texthigh))))+return"<linear kernel text>";+#endif++returnNULL;+}++staticinlineconstchar*check_bogus_address(constvoid*ptr,unsignedlongn)+{+/* Reject if object wraps past end of memory. */+if(ptr+n<ptr)+return"<wrapped address>";++/* Reject if NULL or ZERO-allocation. */+if(ZERO_OR_NULL_PTR(ptr))+return"<null>";++returnNULL;+}++staticinlineconstchar*check_heap_object(constvoid*ptr,unsignedlongn,+boolto_user)+{+structpage*page,*endpage;+constvoid*end=ptr+n-1;++if(!virt_addr_valid(ptr))+returnNULL;++page=virt_to_head_page(ptr);++/* Check slab allocator for flags and size. */+if(PageSlab(page))+return__check_heap_object(ptr,n,page);++/*+*SometimesthekerneldataregionsarenotmarkedReserved(see+*checkbelow).Andsometimes[_sdata,_edata)doesnotcover+*rodataand/orbss,socheckeachrangeexplicitly.+*/++/* Allow reads of kernel rodata region (if not marked as Reserved). */+if(ptr>=(constvoid*)__start_rodata&&+end<=(constvoid*)__end_rodata){+if(!to_user)+return"<rodata>";+returnNULL;+}++/* Allow kernel data region (if not marked as Reserved). */+if(ptr>=(constvoid*)_sdata&&end<=(constvoid*)_edata)+returnNULL;++/* Allow kernel bss region (if not marked as Reserved). */+if(ptr>=(constvoid*)__bss_start&&+end<=(constvoid*)__bss_stop)+returnNULL;++/* Is the object wholly within one base page? */+if(likely(((unsignedlong)ptr&(unsignedlong)PAGE_MASK)==+((unsignedlong)end&(unsignedlong)PAGE_MASK)))+returnNULL;++/* Allow if start and end are inside the same compound page. */+endpage=virt_to_head_page(end);+if(likely(endpage==page))+returnNULL;++/* Allow special areas, device memory, and sometimes kernel data. */+if(PageReserved(page)&&PageReserved(endpage))+returnNULL;++/* Uh oh. The "object" spans several independently allocated pages. */+return"<spans multiple pages>";+}++/*+*Validatesthatthegivenobjectisoneof:+*-knownsafeheapobject+*-knownsafestackobject+*-notinkerneltext+*/+void__check_object_size(constvoid*ptr,unsignedlongn,boolto_user)+{+constchar*err;++/* Skip all tests if size is zero. */+if(!n)+return;++/* Check for invalid addresses. */+err=check_bogus_address(ptr,n);+if(err)+gotoreport;++/* Check for bad heap object. */+err=check_heap_object(ptr,n,to_user);+if(err)+gotoreport;++/* Check for bad stack object. */+switch(check_stack_object(ptr,n)){+case0:+/* Object is not touching the current process stack. */+break;+case1:+case2:+/*+*Objectiseitherinthecorrectframe(whenit+*ispossibletocheck)orjustgenerallyonthe+*processstack(whenframecheckingnotavailable).+*/+return;+default:+err="<process stack>";+gotoreport;+}++/* Check for object in kernel to avoid text exposure. */+err=check_kernel_text_object(ptr,n);+if(!err)+return;++report:+report_usercopy(ptr,n,to_user,err);+}+EXPORT_SYMBOL(__check_object_size);
On Wed, Jul 13, 2016 at 02:55:55PM -0700, Kees Cook wrote:
quoted hunk
This is the start of porting PAX_USERCOPY into the mainline kernel. This
is the first set of features, controlled by CONFIG_HARDENED_USERCOPY. The
work is based on code by PaX Team and Brad Spengler, and an earlier port
from Casey Schaufler. Additional non-slab page tests are from Rik van Riel.
This patch contains the logic for validating several conditions when
performing copy_to_user() and copy_from_user() on the kernel object
being copied to/from:
- address range doesn't wrap around
- address range isn't NULL or zero-allocated (with a non-zero copy size)
- if on the slab allocator:
- object size must be less than or equal to copy size (when check is
implemented in the allocator, which appear in subsequent patches)
- otherwise, object must not span page allocations
- if on the stack
- object must not extend before/after the current process task
- object must be contained by the current stack frame (when there is
arch/build support for identifying stack frames)
- object must not overlap with kernel text
Signed-off-by: Kees Cook <redacted>
---
arch/Kconfig | 7 ++
include/linux/slab.h | 12 +++
include/linux/thread_info.h | 15 +++
mm/Makefile | 4 +
mm/usercopy.c | 219 ++++++++++++++++++++++++++++++++++++++++++++
security/Kconfig | 27 ++++++
6 files changed, 284 insertions(+)
create mode 100644 mm/usercopy.c
@@ -21,6 +21,9 @@ KCOV_INSTRUMENT_memcontrol.o := nKCOV_INSTRUMENT_mmzone.o:=nKCOV_INSTRUMENT_vmstat.o:=n+# Since __builtin_frame_address does work as used, disable the warning.+CFLAGS_usercopy.o+=$(callcc-disable-warning,frame-address)+mmu-y:=nommu.ommu-$(CONFIG_MMU):=gup.ohighmem.omemory.omincore.o\mlock.ommap.omprotect.omremap.omsync.ormap.o\
+ */
+static noinline int check_stack_object(const void *obj, unsigned long len)
+{
+ const void * const stack = task_stack_page(current);
+ const void * const stackend = stack + THREAD_SIZE;
+ int ret;
+
+ /* Object is not on the stack at all. */
+ if (obj + len <= stack || stackend <= obj)
+ return 0;
+
+ /*
+ * Reject: object partially overlaps the stack (passing the
+ * the check above means at least one end is within the stack,
+ * so if this check fails, the other end is outside the stack).
+ */
+ if (obj < stack || stackend < obj + len)
+ return -1;
+
+ /* Check if object is safely within a valid frame. */
+ ret = arch_within_stack_frames(stack, stackend, obj, len);
+ if (ret)
+ return ret;
+
+ return 2;
+}
+
+static void report_usercopy(const void *ptr, unsigned long len,
+ bool to_user, const char *type)
+{
+ pr_emerg("kernel memory %s attempt detected %s %p (%s) (%lu bytes)\n",
+ to_user ? "exposure" : "overwrite",
+ to_user ? "from" : "to", ptr, type ? : "unknown", len);
+ dump_stack();
+ do_group_exit(SIGKILL);
SIGKILL -- SIGBUS?
+}
+
+/* Returns true if any portion of [ptr,ptr+n) over laps with [low,high). */
+static bool overlaps(const void *ptr, unsigned long n, unsigned long low,
+ unsigned long high)
+{
+ unsigned long check_low = (uintptr_t)ptr;
+ unsigned long check_high = check_low + n;
+
+ /* Does not overlap if entirely above or entirely below. */
+ if (check_low >= high || check_high < low)
+ return false;
+
+ return true;
+}
+
+/* Is this address range in the kernel text area? */
+static inline const char *check_kernel_text_object(const void *ptr,
+ unsigned long n)
+{
+ unsigned long textlow = (unsigned long)_stext;
+ unsigned long texthigh = (unsigned long)_etext;
+
+ if (overlaps(ptr, n, textlow, texthigh))
+ return "<kernel text>";
+
+#ifdef HAVE_ARCH_LINEAR_KERNEL_MAPPING
+ /* Check against linear mapping as well. */
+ if (overlaps(ptr, n, (unsigned long)__va(__pa(textlow)),
+ (unsigned long)__va(__pa(texthigh))))
+ return "<linear kernel text>";
+#endif
+
+ return NULL;
+}
+
+static inline const char *check_bogus_address(const void *ptr, unsigned long n)
+{
+ /* Reject if object wraps past end of memory. */
+ if (ptr + n < ptr)
+ return "<wrapped address>";
+
+ /* Reject if NULL or ZERO-allocation. */
+ if (ZERO_OR_NULL_PTR(ptr))
+ return "<null>";
+
+ return NULL;
+}
+
+static inline const char *check_heap_object(const void *ptr, unsigned long n,
+ bool to_user)
+{
+ struct page *page, *endpage;
+ const void *end = ptr + n - 1;
+
+ if (!virt_addr_valid(ptr))
+ return NULL;
+
+ page = virt_to_head_page(ptr);
+
+ /* Check slab allocator for flags and size. */
+ if (PageSlab(page))
+ return __check_heap_object(ptr, n, page);
+
+ /*
+ * Sometimes the kernel data regions are not marked Reserved (see
+ * check below). And sometimes [_sdata,_edata) does not cover
+ * rodata and/or bss, so check each range explicitly.
+ */
+
+ /* Allow reads of kernel rodata region (if not marked as Reserved). */
+ if (ptr >= (const void *)__start_rodata &&
+ end <= (const void *)__end_rodata) {
+ if (!to_user)
+ return "<rodata>";
+ return NULL;
+ }
+
+ /* Allow kernel data region (if not marked as Reserved). */
+ if (ptr >= (const void *)_sdata && end <= (const void *)_edata)
+ return NULL;
+
+ /* Allow kernel bss region (if not marked as Reserved). */
+ if (ptr >= (const void *)__bss_start &&
+ end <= (const void *)__bss_stop)
+ return NULL;
+
+ /* Is the object wholly within one base page? */
+ if (likely(((unsigned long)ptr & (unsigned long)PAGE_MASK) ==
+ ((unsigned long)end & (unsigned long)PAGE_MASK)))
+ return NULL;
+
+ /* Allow if start and end are inside the same compound page. */
+ endpage = virt_to_head_page(end);
+ if (likely(endpage == page))
+ return NULL;
+
+ /* Allow special areas, device memory, and sometimes kernel data. */
+ if (PageReserved(page) && PageReserved(endpage))
+ return NULL;a
If we came here, it's likely that endpage > page, do we need to check
that only the first and last pages are reserved? What about the ones in
the middle?
+
+ /* Uh oh. The "object" spans several independently allocated pages. */
+ return "<spans multiple pages>";
+}
+
+/*
+ * Validates that the given object is one of:
+ * - known safe heap object
+ * - known safe stack object
+ * - not in kernel text
+ */
+void __check_object_size(const void *ptr, unsigned long n, bool to_user)
+{
+ const char *err;
+
+ /* Skip all tests if size is zero. */
+ if (!n)
+ return;
+
+ /* Check for invalid addresses. */
+ err = check_bogus_address(ptr, n);
+ if (err)
+ goto report;
+
+ /* Check for bad heap object. */
+ err = check_heap_object(ptr, n, to_user);
+ if (err)
+ goto report;
+
+ /* Check for bad stack object. */
+ switch (check_stack_object(ptr, n)) {
+ case 0:
+ /* Object is not touching the current process stack. */
+ break;
+ case 1:
+ case 2:
+ /*
+ * Object is either in the correct frame (when it
+ * is possible to check) or just generally on the
+ * process stack (when frame checking not available).
+ */
+ return;
+ default:
+ err = "<process stack>";
+ goto report;
+ }
+
+ /* Check for object in kernel to avoid text exposure. */
+ err = check_kernel_text_object(ptr, n);
+ if (!err)
+ return;
+
+report:
+ report_usercopy(ptr, n, to_user, err);
+}
From: Rik van Riel <hidden> Date: 2016-07-15 01:04:36
On Fri, 2016-07-15 at 09:20 +1000, Balbir Singh wrote:
quoted
==
+ ((unsigned long)end & (unsigned
long)PAGE_MASK)))
+ return NULL;
+
+ /* Allow if start and end are inside the same compound
page. */
+ endpage = virt_to_head_page(end);
+ if (likely(endpage == page))
+ return NULL;
+
+ /* Allow special areas, device memory, and sometimes
kernel data. */
+ if (PageReserved(page) && PageReserved(endpage))
+ return NULL;
If we came here, it's likely that endpage > page, do we need to check
that only the first and last pages are reserved? What about the ones
in
the middle?
I think this will be so rare, we can get away with just
checking the beginning and the end.
--
All Rights Reversed.
On Thu, Jul 14, 2016 at 09:04:18PM -0400, Rik van Riel wrote:
On Fri, 2016-07-15 at 09:20 +1000, Balbir Singh wrote:
quoted
quoted
==
+ ((unsigned long)end & (unsigned
long)PAGE_MASK)))
+ return NULL;
+
+ /* Allow if start and end are inside the same compound
page. */
+ endpage = virt_to_head_page(end);
+ if (likely(endpage == page))
+ return NULL;
+
+ /* Allow special areas, device memory, and sometimes
kernel data. */
+ if (PageReserved(page) && PageReserved(endpage))
+ return NULL;
If we came here, it's likely that endpage > page, do we need to check
that only the first and last pages are reserved? What about the ones
in
the middle?
I think this will be so rare, we can get away with just
checking the beginning and the end.
But do we want to leave a hole where an aware user space
can try a longer copy_* to avoid this check? If it is unlikely
should we just bite the bullet and do the check for the entire
range?
Balbir Singh.
On Thu, Jul 14, 2016 at 6:41 PM, Balbir Singh [off-list ref] wrote:
On Thu, Jul 14, 2016 at 09:04:18PM -0400, Rik van Riel wrote:
quoted
On Fri, 2016-07-15 at 09:20 +1000, Balbir Singh wrote:
quoted
quoted
==
+ ((unsigned long)end & (unsigned
long)PAGE_MASK)))
+ return NULL;
+
+ /* Allow if start and end are inside the same compound
page. */
+ endpage = virt_to_head_page(end);
+ if (likely(endpage == page))
+ return NULL;
+
+ /* Allow special areas, device memory, and sometimes
kernel data. */
+ if (PageReserved(page) && PageReserved(endpage))
+ return NULL;
If we came here, it's likely that endpage > page, do we need to check
that only the first and last pages are reserved? What about the ones
in
the middle?
I think this will be so rare, we can get away with just
checking the beginning and the end.
But do we want to leave a hole where an aware user space
can try a longer copy_* to avoid this check? If it is unlikely
should we just bite the bullet and do the check for the entire
range?
I'd be okay with expanding the test -- it should be an extremely rare
situation already since the common Reserved areas (kernel data) will
have already been explicitly tested.
What's the best way to do "next page"? Should it just be:
for ( ; page <= endpage ; ptr += PAGE_SIZE, page = virt_to_head_page(ptr) ) {
if (!PageReserved(page))
return "<spans multiple pages>";
}
return NULL;
?
--
Kees Cook
Chrome OS & Brillo Security
On Thu, Jul 14, 2016 at 9:05 PM, Kees Cook [off-list ref] wrote:
On Thu, Jul 14, 2016 at 6:41 PM, Balbir Singh [off-list ref] wrote:
quoted
On Thu, Jul 14, 2016 at 09:04:18PM -0400, Rik van Riel wrote:
quoted
On Fri, 2016-07-15 at 09:20 +1000, Balbir Singh wrote:
quoted
quoted
==
+ ((unsigned long)end & (unsigned
long)PAGE_MASK)))
+ return NULL;
+
+ /* Allow if start and end are inside the same compound
page. */
+ endpage = virt_to_head_page(end);
+ if (likely(endpage == page))
+ return NULL;
+
+ /* Allow special areas, device memory, and sometimes
kernel data. */
+ if (PageReserved(page) && PageReserved(endpage))
+ return NULL;
If we came here, it's likely that endpage > page, do we need to check
that only the first and last pages are reserved? What about the ones
in
the middle?
I think this will be so rare, we can get away with just
checking the beginning and the end.
But do we want to leave a hole where an aware user space
can try a longer copy_* to avoid this check? If it is unlikely
should we just bite the bullet and do the check for the entire
range?
I'd be okay with expanding the test -- it should be an extremely rare
situation already since the common Reserved areas (kernel data) will
have already been explicitly tested.
What's the best way to do "next page"? Should it just be:
for ( ; page <= endpage ; ptr += PAGE_SIZE, page = virt_to_head_page(ptr) ) {
if (!PageReserved(page))
return "<spans multiple pages>";
}
return NULL;
?
Er, I was testing the wrong thing. How about:
/*
* Reject if range is not Reserved (i.e. special or device memory),
* since then the object spans several independently allocated pages.
*/
for (; ptr <= end ; ptr += PAGE_SIZE, page = virt_to_head_page(ptr)) {
if (!PageReserved(page))
return "<spans multiple pages>";
}
return NULL;
--
Kees Cook
Chrome OS & Brillo Security
On Thu, Jul 14, 2016 at 09:53:31PM -0700, Kees Cook wrote:
On Thu, Jul 14, 2016 at 9:05 PM, Kees Cook [off-list ref] wrote:
quoted
On Thu, Jul 14, 2016 at 6:41 PM, Balbir Singh [off-list ref] wrote:
quoted
On Thu, Jul 14, 2016 at 09:04:18PM -0400, Rik van Riel wrote:
quoted
On Fri, 2016-07-15 at 09:20 +1000, Balbir Singh wrote:
quoted
quoted
==
+ ((unsigned long)end & (unsigned
long)PAGE_MASK)))
+ return NULL;
+
+ /* Allow if start and end are inside the same compound
page. */
+ endpage = virt_to_head_page(end);
+ if (likely(endpage == page))
+ return NULL;
+
+ /* Allow special areas, device memory, and sometimes
kernel data. */
+ if (PageReserved(page) && PageReserved(endpage))
+ return NULL;
If we came here, it's likely that endpage > page, do we need to check
that only the first and last pages are reserved? What about the ones
in
the middle?
I think this will be so rare, we can get away with just
checking the beginning and the end.
But do we want to leave a hole where an aware user space
can try a longer copy_* to avoid this check? If it is unlikely
should we just bite the bullet and do the check for the entire
range?
I'd be okay with expanding the test -- it should be an extremely rare
situation already since the common Reserved areas (kernel data) will
have already been explicitly tested.
What's the best way to do "next page"? Should it just be:
for ( ; page <= endpage ; ptr += PAGE_SIZE, page = virt_to_head_page(ptr) ) {
if (!PageReserved(page))
return "<spans multiple pages>";
}
return NULL;
?
Er, I was testing the wrong thing. How about:
/*
* Reject if range is not Reserved (i.e. special or device memory),
* since then the object spans several independently allocated pages.
*/
for (; ptr <= end ; ptr += PAGE_SIZE, page = virt_to_head_page(ptr)) {
if (!PageReserved(page))
return "<spans multiple pages>";
}
return NULL;
On Thu, Jul 14, 2016 at 4:20 PM, Balbir Singh [off-list ref] wrote:
On Wed, Jul 13, 2016 at 02:55:55PM -0700, Kees Cook wrote:
quoted
[...]
+++ b/mm/usercopy.c
@@ -0,0 +1,219 @@
[...]
+/*
+ * Checks if a given pointer and length is contained by the current
+ * stack frame (if possible).
+ *
+ * 0: not at all on the stack
+ * 1: fully within a valid stack frame
+ * 2: fully on the stack (when can't do frame-checking)
+ * -1: error condition (invalid stack position or bad stack frame)
I'd like to keep SIGKILL since it indicates a process fiddling with a
kernel bug. The real problem here is that there doesn't seem to be an
arch-independent way to Oops the kernel and kill a process ("die()" is
closest, but it's defined on a per-arch basis with varying arguments).
This could be a BUG, but I'd rather not panic the entire kernel.
-Kees
--
Kees Cook
Chrome OS & Brillo Security
From: Daniel Micay <hidden> Date: 2016-07-15 19:01:24
This could be a BUG, but I'd rather not panic the entire kernel.
It seems unlikely that it will panic without panic_on_oops and that's
an explicit opt-in to taking down the system on kernel logic errors
exactly like this. In grsecurity, it calls the kernel exploit handling
logic (panic if root, otherwise kill all process of that user and ban
them until reboot) but that same logic is also called for BUG via oops
handling so there's only really a distinction with panic_on_oops=1.
Does it make sense to be less fatal for a fatal assertion that's more
likely to be security-related? Maybe you're worried about having some
false positives for the whitelisting portion, but I don't think those
will lurk around very long with the way this works.
On Fri, Jul 15, 2016 at 12:00 PM, Daniel Micay [off-list ref] wrote:
quoted
This could be a BUG, but I'd rather not panic the entire kernel.
It seems unlikely that it will panic without panic_on_oops and that's
an explicit opt-in to taking down the system on kernel logic errors
exactly like this. In grsecurity, it calls the kernel exploit handling
logic (panic if root, otherwise kill all process of that user and ban
them until reboot) but that same logic is also called for BUG via oops
handling so there's only really a distinction with panic_on_oops=1.
Does it make sense to be less fatal for a fatal assertion that's more
likely to be security-related? Maybe you're worried about having some
false positives for the whitelisting portion, but I don't think those
will lurk around very long with the way this works.
I'd like it to dump stack and be fatal to the process involved, but
yeah, I guess BUG() would work. Creating an infrastructure for
handling security-related Oopses can be done separately from this (and
I'd like to see that added, since it's a nice bit of configurable
reactivity to possible attacks).
-Kees
--
Kees Cook
Chrome OS & Brillo Security
From: Daniel Micay <hidden> Date: 2016-07-15 19:19:53
I'd like it to dump stack and be fatal to the process involved, but
yeah, I guess BUG() would work. Creating an infrastructure for
handling security-related Oopses can be done separately from this
(and
I'd like to see that added, since it's a nice bit of configurable
reactivity to possible attacks).
In grsecurity, the oops handling also uses do_group_exit instead of
do_exit but both that change (or at least the option to do it) and the
exploit handling could be done separately from this without actually
needing special treatment for USERCOPY. Could expose is as something
like panic_on_oops=2 as a balance between the existing options.
On Fri, Jul 15, 2016 at 12:19 PM, Daniel Micay [off-list ref] wrote:
quoted
I'd like it to dump stack and be fatal to the process involved, but
yeah, I guess BUG() would work. Creating an infrastructure for
handling security-related Oopses can be done separately from this
(and
I'd like to see that added, since it's a nice bit of configurable
reactivity to possible attacks).
In grsecurity, the oops handling also uses do_group_exit instead of
do_exit but both that change (or at least the option to do it) and the
exploit handling could be done separately from this without actually
needing special treatment for USERCOPY. Could expose is as something
like panic_on_oops=2 as a balance between the existing options.
I'm also uncomfortable about BUG() being removed by unsetting
CONFIG_BUG, but that seems unlikely. :)
-Kees
--
Kees Cook
Chrome OS & Brillo Security
Enables CONFIG_HARDENED_USERCOPY checks on arm64. As done by KASAN in -next,
renames the low-level functions to __arch_copy_*_user() so a static inline
can do additional work before the copy.
Signed-off-by: Kees Cook <redacted>
---
arch/arm64/Kconfig | 2 ++
arch/arm64/include/asm/uaccess.h | 16 ++++++++++++++--
arch/arm64/kernel/arm64ksyms.c | 4 ++--
arch/arm64/lib/copy_from_user.S | 4 ++--
arch/arm64/lib/copy_to_user.S | 4 ++--
5 files changed, 22 insertions(+), 8 deletions(-)
@@ -34,8 +34,8 @@ EXPORT_SYMBOL(copy_page);EXPORT_SYMBOL(clear_page);/* user mem (segment) */-EXPORT_SYMBOL(__copy_from_user);-EXPORT_SYMBOL(__copy_to_user);+EXPORT_SYMBOL(__arch_copy_from_user);+EXPORT_SYMBOL(__arch_copy_to_user);EXPORT_SYMBOL(__clear_user);EXPORT_SYMBOL(__copy_in_user);
Under CONFIG_HARDENED_USERCOPY, this adds object size checking to the
SLUB allocator to catch any copies that may span objects. Includes a
redzone handling fix from Michael Ellerman.
Based on code from PaX and grsecurity.
Signed-off-by: Kees Cook <redacted>
---
init/Kconfig | 1 +
mm/slub.c | 36 ++++++++++++++++++++++++++++++++++++
2 files changed, 37 insertions(+)
@@ -3614,6 +3614,42 @@ void *__kmalloc_node(size_t size, gfp_t flags, int node)EXPORT_SYMBOL(__kmalloc_node);#endif+#ifdef CONFIG_HARDENED_USERCOPY+/*+*Rejectsobjectsthatareincorrectlysized.+*+*ReturnsNULLifcheckpasses,otherwiseconstchar*tonameofcache+*toindicateanerror.+*/+constchar*__check_heap_object(constvoid*ptr,unsignedlongn,+structpage*page)+{+structkmem_cache*s;+unsignedlongoffset;+size_tobject_size;++/* Find object and usable object size. */+s=page->slab_cache;+object_size=slab_ksize(s);++/* Find offset within object. */+offset=(ptr-page_address(page))%s->size;++/* Adjust for redzone and reject if within the redzone. */+if(kmem_cache_debug(s)&&s->flags&SLAB_RED_ZONE){+if(offset<s->red_left_pad)+returns->name;+offset-=s->red_left_pad;+}++/* Allow address range falling entirely within object size. */+if(offset<=object_size&&n<=object_size-offset)+returnNULL;++returns->name;+}+#endif /* CONFIG_HARDENED_USERCOPY */+staticsize_t__ksize(constvoid*object){structpage*page;
From: Michael Ellerman <mpe@ellerman.id.au> Date: 2016-07-14 10:07:04
Kees Cook [off-list ref] writes:
Under CONFIG_HARDENED_USERCOPY, this adds object size checking to the
SLUB allocator to catch any copies that may span objects. Includes a
redzone handling fix from Michael Ellerman.
Actually I think you wrote the fix, I just pointed you in that
direction. But anyway, this works for me, so if you like:
Tested-by: Michael Ellerman <mpe@ellerman.id.au>
cheers
On Wed, Jul 13, 2016 at 02:56:04PM -0700, Kees Cook wrote:
quoted hunk
Under CONFIG_HARDENED_USERCOPY, this adds object size checking to the
SLUB allocator to catch any copies that may span objects. Includes a
redzone handling fix from Michael Ellerman.
Based on code from PaX and grsecurity.
Signed-off-by: Kees Cook <redacted>
---
init/Kconfig | 1 +
mm/slub.c | 36 ++++++++++++++++++++++++++++++++++++
2 files changed, 37 insertions(+)
On Thu, Jul 14, 2016 at 7:05 PM, Balbir Singh [off-list ref] wrote:
On Wed, Jul 13, 2016 at 02:56:04PM -0700, Kees Cook wrote:
quoted
Under CONFIG_HARDENED_USERCOPY, this adds object size checking to the
SLUB allocator to catch any copies that may span objects. Includes a
redzone handling fix from Michael Ellerman.
Based on code from PaX and grsecurity.
Signed-off-by: Kees Cook <redacted>
---
init/Kconfig | 1 +
mm/slub.c | 36 ++++++++++++++++++++++++++++++++++++
2 files changed, 37 insertions(+)
Should this patch come in earlier from a build perspective? I think
patch 1 introduces and uses __check_heap_object.
__check_heap_object in patch 1 is protected by a check for
CONFIG_HAVE_HARDENED_USERCOPY_ALLOCATOR.
It seemed better to be to do arch enablement first, and then add the
per-allocator heap object size check since it was a distinct piece.
I'm happy to rearrange things, though, if there's a good reason.
-Kees
--
Kees Cook
Chrome OS & Brillo Security
Under CONFIG_HARDENED_USERCOPY, this adds object size checking to the
SLAB allocator to catch any copies that may span objects.
Based on code from PaX and grsecurity.
Signed-off-by: Kees Cook <redacted>
---
init/Kconfig | 1 +
mm/slab.c | 30 ++++++++++++++++++++++++++++++
2 files changed, 31 insertions(+)