Hi,
[This is now in my kspp -next tree, though I'd really love to add some
additional explicit Tested-bys, Reviewed-bys, or Acked-bys. If you've
looked through any part of this or have done any testing, please consider
sending an email with your "*-by:" line. :)]
This is a start of the mainline port of PAX_USERCOPY[1]. After 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, Laura, and other people's feedback along with
additional changes and clean-ups.
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
a valid 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 (excepting Reserved and CMA pages).
- if address is within the kernel text, reject it.
- everything else is accepted
The patches in the series are:
- Support for examination of CMA page types:
1- mm: Add is_migrate_cma_page
- Support for arch-specific stack frame checking (which will likely be
replaced in the future by Josh's more comprehensive unwinder):
2- mm: Implement stack frame object validation
- The core copy_to/from_user() checks, without the slab object checks:
3- mm: Hardened usercopy
- Per-arch enablement of the protection:
4- x86/uaccess: Enable hardened usercopy
5- ARM: uaccess: Enable hardened usercopy
6- arm64/uaccess: Enable hardened usercopy
7- ia64/uaccess: Enable hardened usercopy
8- powerpc/uaccess: Enable hardened usercopy
9- sparc/uaccess: Enable hardened usercopy
10- s390/uaccess: Enable hardened usercopy
- The heap allocator implementation of object size checking:
11- mm: SLAB hardened usercopy support
12- 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, though it has some conflicts
with KASAN that should be trivial to fix up. Also in -next are the
tests for this protection (in lkdtm), prefixed with USERCOPY_.
- 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. I would love if someone with greater familiarity with
perf could give this a spin and report results.
- 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,
but it seems Josh will have a good solution for this soon).
Thanks!
-Kees
[1] https://grsecurity.net/download.php "grsecurity - test kernel patch"
[2] http://www.openwall.com/lists/kernel-hardening/2016/05/19/5
v4:
- handle CMA pages, labbott
- update stack checker comments, labbott
- check for vmalloc addresses, labbott
- deal with KASAN in -next changing arm64 copy*user calls
- check for linear mappings at runtime instead of via CONFIG
v3:
- switch to using BUG for better Oops integration
- when checking page allocations, check each for Reserved
- use enums for the stack check return for readability
v2:
- added s390 support
- handle slub red zone
- disallow writes to rodata area
- stack frame walker now CONFIG-controlled arch-specific helper
From: Laura Abbott <redacted>
Code such as hardened user copy[1] needs a way to tell if a
page is CMA or not. Add is_migrate_cma_page in a similar way
to is_migrate_isolate_page.
[1]http://article.gmane.org/gmane.linux.kernel.mm/155238
Signed-off-by: Laura Abbott <redacted>
Signed-off-by: Kees Cook <redacted>
---
include/linux/mmzone.h | 2 ++
1 file changed, 2 insertions(+)
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 | 1 +
arch/arm64/include/asm/uaccess.h | 29 ++++++++++++++++++++++-------
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, 29 insertions(+), 13 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);
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 (excepting Reserved
and CMA ranges)
- if on the stack
- object must not extend before/after the current process stack
- object must be contained by a valid 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>
Tested-by: Valdis Kletnieks <redacted>
Tested-by: Michael Ellerman <mpe@ellerman.id.au>
---
include/linux/slab.h | 12 ++
include/linux/thread_info.h | 15 +++
mm/Makefile | 4 +
mm/usercopy.c | 268 ++++++++++++++++++++++++++++++++++++++++++++
security/Kconfig | 28 +++++
5 files changed, 327 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,268 @@+/*+*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>++enum{+BAD_STACK=-1,+NOT_STACK=0,+GOOD_FRAME,+GOOD_STACK,+};++/*+*Checksifagivenpointerandlengthiscontainedbythecurrent+*stackframe(ifpossible).+*+*Returns:+*NOT_STACK:notatallonthestack+*GOOD_FRAME:fullywithinavalidstackframe+*GOOD_STACK:fullyonthestack(whencan'tdoframe-checking)+*BAD_STACK: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)+returnNOT_STACK;++/*+*Reject:objectpartiallyoverlapsthestack(passingthe+*thecheckabovemeansatleastoneendiswithinthestack,+*soifthischeckfails,theotherendisoutsidethestack).+*/+if(obj<stack||stackend<obj+len)+returnBAD_STACK;++/* Check if object is safely within a valid frame. */+ret=arch_within_stack_frames(stack,stackend,obj,len);+if(ret)+returnret;++returnGOOD_STACK;+}++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);+/*+*Forgreatereffect,itwouldbenicetododo_group_exit(),+*butBUG()actuallyhooksallthelock-breakingandper-arch+*Oopscode,sothatisusedhereinstead.+*/+BUG();+}++/* 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;+unsignedlongtextlow_linear,texthigh_linear;++if(overlaps(ptr,n,textlow,texthigh))+return"<kernel text>";++/*+*Somearchitectureshavevirtualmemorymappingswithasecondary+*mappingofthekerneltext,i.e.thereismorethanonevirtual+*kerneladdressthatpointstothekernelimage.Itisusually+*whenthereisaseparatelinearphysicalmemorymapping,inthat+*__pa()isnotjustthereverseof__va().Thiscanbedetected+*andchecked:+*/+textlow_linear=(unsignedlong)__va(__pa(textlow));+/* No different mapping: we're done. */+if(textlow_linear==textlow)+returnNULL;++/* Check the secondary mapping... */+texthigh_linear=(unsignedlong)__va(__pa(texthigh));+if(overlaps(ptr,n,textlow_linear,texthigh_linear))+return"<linear kernel text>";++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;+boolis_reserved,is_cma;++/*+*Somearchitectures(arm64)returntrueforvirt_addr_valid()on+*vmallocedaddresses.Workaroundthisbycheckingforvmalloc+*first.+*/+if(is_vmalloc_addr(ptr))+returnNULL;++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;++/*+*RejectifrangeisentirelyeitherReserved(i.e.specialor+*devicememory),orCMA.Otherwise,rejectsincetheobjectspans+*severalindependentlyallocatedpages.+*/+is_reserved=PageReserved(page);+is_cma=is_migrate_cma_page(page);+if(!is_reserved&&!is_cma)+gotoreject;++for(ptr+=PAGE_SIZE;ptr<=end;ptr+=PAGE_SIZE){+page=virt_to_head_page(ptr);+if(is_reserved&&!PageReserved(page))+gotoreject;+if(is_cma&&!is_migrate_cma_page(page))+gotoreject;+}++returnNULL;++reject:+return"<spans multiple pages>";+}++/*+*Validatesthatthegivenobjectis:+*-notbogusaddress+*-known-safeheaporstackobject+*-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)){+caseNOT_STACK:+/* Object is not touching the current process stack. */+break;+caseGOOD_FRAME:+caseGOOD_STACK:+/*+*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);
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>
Tested-by: Valdis Kletnieks <redacted>
---
init/Kconfig | 1 +
mm/slab.c | 30 ++++++++++++++++++++++++++++++
2 files changed, 31 insertions(+)
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 discovered by Michael Ellerman.
Based on code from PaX and grsecurity.
Signed-off-by: Kees Cook <redacted>
Tested-by: Michael Ellerman <mpe@ellerman.id.au>
---
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;
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(+)
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>
Tested-by: Valdis Kletnieks <redacted>
---
arch/x86/Kconfig | 1 +
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, 11 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);
From: Laura Abbott <hidden> Date: 2016-07-23 00:36:46
On 07/20/2016 01:26 PM, Kees Cook wrote:
Hi,
[This is now in my kspp -next tree, though I'd really love to add some
additional explicit Tested-bys, Reviewed-bys, or Acked-bys. If you've
looked through any part of this or have done any testing, please consider
sending an email with your "*-by:" line. :)]
This is a start of the mainline port of PAX_USERCOPY[1]. After 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, Laura, and other people's feedback along with
additional changes and clean-ups.
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
a valid 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 (excepting Reserved and CMA pages).
- if address is within the kernel text, reject it.
- everything else is accepted
The patches in the series are:
- Support for examination of CMA page types:
1- mm: Add is_migrate_cma_page
- Support for arch-specific stack frame checking (which will likely be
replaced in the future by Josh's more comprehensive unwinder):
2- mm: Implement stack frame object validation
- The core copy_to/from_user() checks, without the slab object checks:
3- mm: Hardened usercopy
- Per-arch enablement of the protection:
4- x86/uaccess: Enable hardened usercopy
5- ARM: uaccess: Enable hardened usercopy
6- arm64/uaccess: Enable hardened usercopy
7- ia64/uaccess: Enable hardened usercopy
8- powerpc/uaccess: Enable hardened usercopy
9- sparc/uaccess: Enable hardened usercopy
10- s390/uaccess: Enable hardened usercopy
- The heap allocator implementation of object size checking:
11- mm: SLAB hardened usercopy support
12- 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, though it has some conflicts
with KASAN that should be trivial to fix up. Also in -next are the
tests for this protection (in lkdtm), prefixed with USERCOPY_.
- 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. I would love if someone with greater familiarity with
perf could give this a spin and report results.
- 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,
but it seems Josh will have a good solution for this soon).
Thanks!
-Kees
[1] https://grsecurity.net/download.php "grsecurity - test kernel patch"
[2] http://www.openwall.com/lists/kernel-hardening/2016/05/19/5
v4:
- handle CMA pages, labbott
- update stack checker comments, labbott
- check for vmalloc addresses, labbott
- deal with KASAN in -next changing arm64 copy*user calls
- check for linear mappings at runtime instead of via CONFIG
v3:
- switch to using BUG for better Oops integration
- when checking page allocations, check each for Reserved
- use enums for the stack check return for readability
v2:
- added s390 support
- handle slub red zone
- disallow writes to rodata area
- stack frame walker now CONFIG-controlled arch-specific helper
Do you have/plan to have LKDTM or the like tests for this? I started reviewing
the slub code and was about to write some test cases for myself. I did that
for CMA as well which is a decent indicator these should all go somewhere.
Thanks,
Laura
On Fri, Jul 22, 2016 at 5:36 PM, Laura Abbott [off-list ref] wrote:
On 07/20/2016 01:26 PM, Kees Cook wrote:
quoted
Hi,
[This is now in my kspp -next tree, though I'd really love to add some
additional explicit Tested-bys, Reviewed-bys, or Acked-bys. If you've
looked through any part of this or have done any testing, please consider
sending an email with your "*-by:" line. :)]
This is a start of the mainline port of PAX_USERCOPY[1]. After 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, Laura, and other people's feedback along with
additional changes and clean-ups.
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
a valid 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 (excepting Reserved and CMA pages).
- if address is within the kernel text, reject it.
- everything else is accepted
The patches in the series are:
- Support for examination of CMA page types:
1- mm: Add is_migrate_cma_page
- Support for arch-specific stack frame checking (which will likely be
replaced in the future by Josh's more comprehensive unwinder):
2- mm: Implement stack frame object validation
- The core copy_to/from_user() checks, without the slab object checks:
3- mm: Hardened usercopy
- Per-arch enablement of the protection:
4- x86/uaccess: Enable hardened usercopy
5- ARM: uaccess: Enable hardened usercopy
6- arm64/uaccess: Enable hardened usercopy
7- ia64/uaccess: Enable hardened usercopy
8- powerpc/uaccess: Enable hardened usercopy
9- sparc/uaccess: Enable hardened usercopy
10- s390/uaccess: Enable hardened usercopy
- The heap allocator implementation of object size checking:
11- mm: SLAB hardened usercopy support
12- 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, though it has some conflicts
with KASAN that should be trivial to fix up. Also in -next are the
tests for this protection (in lkdtm), prefixed with USERCOPY_.
- 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. I would love if someone with greater familiarity with
perf could give this a spin and report results.
- 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,
but it seems Josh will have a good solution for this soon).
Thanks!
-Kees
[1] https://grsecurity.net/download.php "grsecurity - test kernel patch"
[2] http://www.openwall.com/lists/kernel-hardening/2016/05/19/5
v4:
- handle CMA pages, labbott
- update stack checker comments, labbott
- check for vmalloc addresses, labbott
- deal with KASAN in -next changing arm64 copy*user calls
- check for linear mappings at runtime instead of via CONFIG
v3:
- switch to using BUG for better Oops integration
- when checking page allocations, check each for Reserved
- use enums for the stack check return for readability
v2:
- added s390 support
- handle slub red zone
- disallow writes to rodata area
- stack frame walker now CONFIG-controlled arch-specific helper
Do you have/plan to have LKDTM or the like tests for this? I started
reviewing
the slub code and was about to write some test cases for myself. I did that
for CMA as well which is a decent indicator these should all go somewhere.
Yeah, there is an entire section of tests in lkdtm for the usercopy
protection. I didn't add anything for CMA or multipage allocations
yet, though. Feel free to add those if you have a moment! :) It's on
my todo list.
-Kees
--
Kees Cook
Chrome OS & Brillo Security
From: Laura Abbott <hidden> Date: 2016-07-25 19:17:26
On 07/20/2016 01:27 PM, 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 discovered by Michael Ellerman.
Based on code from PaX and grsecurity.
Signed-off-by: Kees Cook <redacted>
Tested-by: Michael Ellerman <mpe@ellerman.id.au>
---
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 */+
I compared this against what check_valid_pointer does for SLUB_DEBUG
checking. I was hoping we could utilize that function to avoid
duplication but a) __check_heap_object needs to allow accesses anywhere
in the object, not just the beginning b) accessing page->objects
is racy without the addition of locking in SLUB_DEBUG.
Still, the ptr < page_address(page) check from __check_heap_object would
be good to add to avoid generating garbage large offsets and trying to
infer C math.
On Mon, Jul 25, 2016 at 12:16 PM, Laura Abbott [off-list ref] wrote:
quoted hunk
On 07/20/2016 01:27 PM, 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 discovered by Michael Ellerman.
Based on code from PaX and grsecurity.
Signed-off-by: Kees Cook <redacted>
Tested-by: Michael Ellerman <mpe@ellerman.id.au>
---
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
+/*
+ * Rejects objects that are incorrectly sized.
+ *
+ * Returns NULL if check passes, otherwise const char * to name of cache
+ * to indicate an error.
+ */
+const char *__check_heap_object(const void *ptr, unsigned long n,
+ struct page *page)
+{
+ struct kmem_cache *s;
+ unsigned long offset;
+ size_t object_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)
+ return s->name;
+ offset -= s->red_left_pad;
+ }
+
+ /* Allow address range falling entirely within object size. */
+ if (offset <= object_size && n <= object_size - offset)
+ return NULL;
+
+ return s->name;
+}
+#endif /* CONFIG_HARDENED_USERCOPY */
+
I compared this against what check_valid_pointer does for SLUB_DEBUG
checking. I was hoping we could utilize that function to avoid
duplication but a) __check_heap_object needs to allow accesses anywhere
in the object, not just the beginning b) accessing page->objects
is racy without the addition of locking in SLUB_DEBUG.
Still, the ptr < page_address(page) check from __check_heap_object would
be good to add to avoid generating garbage large offsets and trying to
infer C math.
unsigned long n,
s = page->slab_cache;
object_size = slab_ksize(s);
+ if (ptr < page_address(page))
+ return s->name;
+
/* Find offset within object. */
offset = (ptr - page_address(page)) % s->size;
With that, you can add
Reviwed-by: Laura Abbott [off-list ref]
Cool, I'll add that.
Should I add your reviewed-by for this patch only or for the whole series?
Thanks!
-Kees
From: Rik van Riel <hidden> Date: 2016-07-25 21:44:56
On Mon, 2016-07-25 at 12:16 -0700, Laura Abbott wrote:
quoted hunk
On 07/20/2016 01:27 PM, 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 discovered by Michael Ellerman.
Based on code from PaX and grsecurity.
Signed-off-by: Kees Cook <redacted>
Tested-by: Michael Ellerman <mpe@ellerman.id.au>
---
init/Kconfig | 1 +
mm/slub.c | 36 ++++++++++++++++++++++++++++++++++++
2 files changed, 37 insertions(+)
config SLUB
bool "SLUB (Unqueued Allocator)"
+ select HAVE_HARDENED_USERCOPY_ALLOCATOR
help
SLUB is a slab allocator that minimizes cache line
usage
instead of managing queues of cached objects (SLAB
approach).
flags, int node)
EXPORT_SYMBOL(__kmalloc_node);
#endif
+#ifdef CONFIG_HARDENED_USERCOPY
+/*
+ * Rejects objects that are incorrectly sized.
+ *
+ * Returns NULL if check passes, otherwise const char * to name of
cache
+ * to indicate an error.
+ */
+const char *__check_heap_object(const void *ptr, unsigned long n,
+ struct page *page)
+{
+ struct kmem_cache *s;
+ unsigned long offset;
+ size_t object_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)
+ return s->name;
+ offset -= s->red_left_pad;
+ }
+
+ /* Allow address range falling entirely within object
size. */
+ if (offset <= object_size && n <= object_size - offset)
+ return NULL;
+
+ return s->name;
+}
+#endif /* CONFIG_HARDENED_USERCOPY */
+
I compared this against what check_valid_pointer does for SLUB_DEBUG
checking. I was hoping we could utilize that function to avoid
duplication but a) __check_heap_object needs to allow accesses
anywhere
in the object, not just the beginning b) accessing page->objects
is racy without the addition of locking in SLUB_DEBUG.
Still, the ptr < page_address(page) check from __check_heap_object
would
be good to add to avoid generating garbage large offsets and trying
to
infer C math.
*ptr, unsigned long n,
s = page->slab_cache;
object_size = slab_ksize(s);
+ if (ptr < page_address(page))
+ return s->name;
+
/* Find offset within object. */
offset = (ptr - page_address(page)) % s->size;
I don't get it, isn't that already guaranteed because we
look for the page that ptr is in, before __check_heap_object
is called?
Specifically, in patch 3/12:
+ page = virt_to_head_page(ptr);
+
+ /* Check slab allocator for flags and size. */
+ if (PageSlab(page))
+ return __check_heap_object(ptr, n, page);
How can that generate a ptr that is not inside the page?
What am I overlooking? And, should it be in the changelog or
a comment? :)
--
All Rights Reversed.
From: Laura Abbott <hidden> Date: 2016-07-25 23:30:01
On 07/25/2016 02:42 PM, Rik van Riel wrote:
On Mon, 2016-07-25 at 12:16 -0700, Laura Abbott wrote:
quoted
On 07/20/2016 01:27 PM, 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 discovered by Michael Ellerman.
Based on code from PaX and grsecurity.
Signed-off-by: Kees Cook <redacted>
Tested-by: Michael Ellerman <mpe@ellerman.id.au>
---
init/Kconfig | 1 +
mm/slub.c | 36 ++++++++++++++++++++++++++++++++++++
2 files changed, 37 insertions(+)
flags, int node)
EXPORT_SYMBOL(__kmalloc_node);
#endif
+#ifdef CONFIG_HARDENED_USERCOPY
+/*
+ * Rejects objects that are incorrectly sized.
+ *
+ * Returns NULL if check passes, otherwise const char * to name of
cache
+ * to indicate an error.
+ */
+const char *__check_heap_object(const void *ptr, unsigned long n,
+ struct page *page)
+{
+ struct kmem_cache *s;
+ unsigned long offset;
+ size_t object_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)
+ return s->name;
+ offset -= s->red_left_pad;
+ }
+
+ /* Allow address range falling entirely within object
size. */
+ if (offset <= object_size && n <= object_size - offset)
+ return NULL;
+
+ return s->name;
+}
+#endif /* CONFIG_HARDENED_USERCOPY */
+
I compared this against what check_valid_pointer does for SLUB_DEBUG
checking. I was hoping we could utilize that function to avoid
duplication but a) __check_heap_object needs to allow accesses
anywhere
in the object, not just the beginning b) accessing page->objects
is racy without the addition of locking in SLUB_DEBUG.
Still, the ptr < page_address(page) check from __check_heap_object
would
be good to add to avoid generating garbage large offsets and trying
to
infer C math.
*ptr, unsigned long n,
s = page->slab_cache;
object_size = slab_ksize(s);
+ if (ptr < page_address(page))
+ return s->name;
+
/* Find offset within object. */
offset = (ptr - page_address(page)) % s->size;
I don't get it, isn't that already guaranteed because we
look for the page that ptr is in, before __check_heap_object
is called?
Specifically, in patch 3/12:
+ page = virt_to_head_page(ptr);
+
+ /* Check slab allocator for flags and size. */
+ if (PageSlab(page))
+ return __check_heap_object(ptr, n, page);
How can that generate a ptr that is not inside the page?
What am I overlooking? And, should it be in the changelog or
a comment? :)
I ran into the subtraction issue when the vmalloc detection wasn't
working on ARM64, somehow virt_to_head_page turned into a page
that happened to have PageSlab set. I agree if everything is working
properly this is redundant but given the type of feature this is, a
little bit of redundancy against a system running off into the weeds
or bad patches might be warranted.
I'm not super attached to the check if other maintainers think it
is redundant. Updating the __check_heap_object header comment
with a note of what we are assuming could work
Thanks,
Laura
From: Rik van Riel <hidden> Date: 2016-07-26 00:22:14
On Mon, 2016-07-25 at 16:29 -0700, Laura Abbott wrote:
On 07/25/2016 02:42 PM, Rik van Riel wrote:
quoted
On Mon, 2016-07-25 at 12:16 -0700, Laura Abbott wrote:
quoted
On 07/20/2016 01:27 PM, 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 discovered by Michael Ellerman.
Based on code from PaX and grsecurity.
Signed-off-by: Kees Cook <redacted>
Tested-by: Michael Ellerman <mpe@ellerman.id.au>
---
init/Kconfig | 1 +
mm/slub.c | 36 ++++++++++++++++++++++++++++++++++++
2 files changed, 37 insertions(+)
config SLUB
bool "SLUB (Unqueued Allocator)"
+ select HAVE_HARDENED_USERCOPY_ALLOCATOR
help
SLUB is a slab allocator that minimizes cache line
usage
instead of managing queues of cached objects (SLAB
approach).
flags, int node)
EXPORT_SYMBOL(__kmalloc_node);
#endif
+#ifdef CONFIG_HARDENED_USERCOPY
+/*
+ * Rejects objects that are incorrectly sized.
+ *
+ * Returns NULL if check passes, otherwise const char * to
name of
cache
+ * to indicate an error.
+ */
+const char *__check_heap_object(const void *ptr, unsigned long
n,
+ struct page *page)
+{
+ struct kmem_cache *s;
+ unsigned long offset;
+ size_t object_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)
+ return s->name;
+ offset -= s->red_left_pad;
+ }
+
+ /* Allow address range falling entirely within object
size. */
+ if (offset <= object_size && n <= object_size -
offset)
+ return NULL;
+
+ return s->name;
+}
+#endif /* CONFIG_HARDENED_USERCOPY */
+
I compared this against what check_valid_pointer does for
SLUB_DEBUG
checking. I was hoping we could utilize that function to avoid
duplication but a) __check_heap_object needs to allow accesses
anywhere
in the object, not just the beginning b) accessing page->objects
is racy without the addition of locking in SLUB_DEBUG.
Still, the ptr < page_address(page) check from
__check_heap_object
would
be good to add to avoid generating garbage large offsets and
trying
to
infer C math.
*ptr, unsigned long n,
s = page->slab_cache;
object_size = slab_ksize(s);
+ if (ptr < page_address(page))
+ return s->name;
+
/* Find offset within object. */
offset = (ptr - page_address(page)) % s->size;
I don't get it, isn't that already guaranteed because we
look for the page that ptr is in, before __check_heap_object
is called?
Specifically, in patch 3/12:
+ page = virt_to_head_page(ptr);
+
+ /* Check slab allocator for flags and size. */
+ if (PageSlab(page))
+ return __check_heap_object(ptr, n, page);
How can that generate a ptr that is not inside the page?
What am I overlooking? And, should it be in the changelog or
a comment? :)
I ran into the subtraction issue when the vmalloc detection wasn't
working on ARM64, somehow virt_to_head_page turned into a page
that happened to have PageSlab set. I agree if everything is working
properly this is redundant but given the type of feature this is, a
little bit of redundancy against a system running off into the weeds
or bad patches might be warranted.
That's fair. I have no objection to the check, but would
like to see it documented, since it does look a little out
of place.
--
All Rights Reversed.
From: Laura Abbott <hidden> Date: 2016-07-26 00:54:34
On 07/25/2016 01:45 PM, Kees Cook wrote:
On Mon, Jul 25, 2016 at 12:16 PM, Laura Abbott [off-list ref] wrote:
quoted
On 07/20/2016 01:27 PM, 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 discovered by Michael Ellerman.
Based on code from PaX and grsecurity.
Signed-off-by: Kees Cook <redacted>
Tested-by: Michael Ellerman <mpe@ellerman.id.au>
---
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
+/*
+ * Rejects objects that are incorrectly sized.
+ *
+ * Returns NULL if check passes, otherwise const char * to name of cache
+ * to indicate an error.
+ */
+const char *__check_heap_object(const void *ptr, unsigned long n,
+ struct page *page)
+{
+ struct kmem_cache *s;
+ unsigned long offset;
+ size_t object_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)
+ return s->name;
+ offset -= s->red_left_pad;
+ }
+
+ /* Allow address range falling entirely within object size. */
+ if (offset <= object_size && n <= object_size - offset)
+ return NULL;
+
+ return s->name;
+}
+#endif /* CONFIG_HARDENED_USERCOPY */
+
I compared this against what check_valid_pointer does for SLUB_DEBUG
checking. I was hoping we could utilize that function to avoid
duplication but a) __check_heap_object needs to allow accesses anywhere
in the object, not just the beginning b) accessing page->objects
is racy without the addition of locking in SLUB_DEBUG.
Still, the ptr < page_address(page) check from __check_heap_object would
be good to add to avoid generating garbage large offsets and trying to
infer C math.
unsigned long n,
s = page->slab_cache;
object_size = slab_ksize(s);
+ if (ptr < page_address(page))
+ return s->name;
+
/* Find offset within object. */
offset = (ptr - page_address(page)) % s->size;
With that, you can add
Reviwed-by: Laura Abbott [off-list ref]
Cool, I'll add that.
Should I add your reviewed-by for this patch only or for the whole series?
Thanks!
-Kees
Just this patch for now, I'm working through a couple of others