4

I'm writing a unit test for my UTF8 manipulation library, and I want my test to segfault if a function goes into a buffer overflow. So I came up with the idea to mmap two consecutive memory pages, the first with PROT_READ | PROT_WRITE, and the second with PROT_NONE. That way, if any overflow occurs, a segfault is guaranteed. Here's an example:

void *addr1, *addr2; /* these are the pages; mmap call left out for simplicity */
char *p = (char *) (addr1 + getpagesize() - 8);

utf8_encode(aUtf8String, p, 8); // this shouldn't segfault

The problem is, when I map the second page, my program segfaults. Here's an example program that reproduces the problem (GNU/Linux):

#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <sys/mman.h>

void checkMap(void *p) 
{ 
    if(p == MAP_FAILED) {
        printf("error running mmap: %s\n", strerror(errno));
        exit(1);
    }   
}

int main(void)
{
    void *addr1, *addr2;
    size_t pagesize;

    pagesize = getpagesize();
    checkMap(addr1 = mmap(NULL, pagesize, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0));
    checkMap(addr2 = mmap(addr1 + pagesize, pagesize, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE | MAP_FIXED, -1, 0));  /* segfaults */
    munmap(addr1, pagesize);
    munmap(addr2, pagesize);

    return 0;
}

Interestingly enough, a printf() statement before the first mmap() causes the program to run successfully. Does anyone know why mmap is segfaulting? If my goal is unattainable using mmap(), does anyone have any other advice on how I could test my code for buffer overflow?

ivaigult
  • 6,198
  • 5
  • 38
  • 66
  • Your code works perfectly for me. The only thing is that you don't include unistd.h for getpagesize(). The compiler will guess the type. That could lead to problems on 64bit systems I think if it guesses a 32bit int. – Johannes Weiss Feb 27 '09 at 16:45
  • I tested it too, and I get the segfaulting, but not inside main(). Rather, it crashes in glibc's code, on exit ... Really strange, this one. I tested on a very plain 32-bit machine. – unwind Feb 27 '09 at 16:54
  • Just curious, which kernel version/distro are you running it on? I just tried it on a 64-bit (after #including unistd.h), and now it fails on munmap. –  Feb 27 '09 at 16:54
  • As a brief followup, it seems to only crash for the case when addr2 = addr1 + pagesize. I tried adding 2 * pagesize, and -2 * pagesize, and then the crashing goes away. Weird. – unwind Feb 27 '09 at 17:03
  • unwind- I noticed that as well; it's only when the pages are adjacent in memory. –  Feb 27 '09 at 17:11
  • Don't reinvent the wheel. Electric Fence and other debug heaps will do this for you. – Sanjaya R Feb 27 '09 at 21:45

3 Answers3

3

You can call mprotect() to change the protection flags on memory mapped by mmap(). This might be a better solution than trying to mmap() two adjacent pages with different protections, since this seems to be what's causing your problems.

(Linux allows you to call mprotect() on any page, but POSIX only allows pages that have already been allocated by mmap().)

This is one of the tricks Electric Fence uses to catch buffer overruns.

Commodore Jaeger
  • 32,280
  • 4
  • 54
  • 44
  • 1
    Note that it would also be valid to `mmap` 2 pages initially, then `MAP_FIXED` the new `PROT_NONE` page over top of the second page. What's not valid is just picking an address next to an existing page and mapping it with `MAP_FIXED`; you have no way of knowing it's not already occupied with another mapping (like part of libc, for instance). – R.. GitHub STOP HELPING ICE Sep 28 '11 at 00:03
0

This is not completely related to your question (unless your ultimate goal is indeed to get your test working), but, IMHO, relying on this kind of fault handling might be misleading (most notably if you're targeting multiple platforms, where the test should've failed just that the seg-fault wasn't triggered due to some unspecified behavior).

Perhaps a different approach would make more sense for you, for example, simply allocate a larger buffer than necessary, place a marker at the end of it, and check if it has been overwritten?

As others have mentioned, if you're willing to set up a more complicated testing environment, electric fence, valgrind, or other tools are probably more elaborate in their analysis.

falstro
  • 34,597
  • 9
  • 72
  • 86
0

As this answer suggests:

  • First, you need to map the entire region without using MAP_FIXED, so it is reserved.
  • And then re-map the second page again (perhaps with different flags).

That's why doubling the mapping len in the first mmap call fixes the problem:

#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <unistd.h>

void checkMap(void *p) 
{ 
    if(p == MAP_FAILED) {
        printf("error running mmap: %s\n", strerror(errno));
        exit(1);
    }   
}

int main(void)
{
    void *addr1, *addr2;
    size_t pagesize;

    pagesize = getpagesize();
    checkMap(addr1 = mmap(NULL, pagesize * 2, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0));
    checkMap(addr2 = mmap(addr1 + pagesize, pagesize, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE | MAP_FIXED, -1, 0));  /* works okay */
    munmap(addr1, pagesize);
    munmap(addr2, pagesize);

    return 0;
}

demo

ivaigult
  • 6,198
  • 5
  • 38
  • 66