xorl %eax, %eax

FreeBSD Red Zone – Kernel Buffer Corruption Detector

leave a comment »

Since FreeBSD 7.0 this feature named “RedZone” is implemented inside the operating system’s kernel to detect buffer underflow and overflow bugs in kernel at run-time. It was developed and maintained by Pawel Jakub Dawidek and it’s placed in vm/redzone.c and vm/redzone.h in the FreeBSD’s kernel code.

The SYSCTL Interface
As you’ve probably read from the man page link I gave above, this can be tuned through ‘vm.redzone.panic’ and ‘vm.redzone.extra_mem’ SYSCTL variables. Inside vm/redzone.c we can find this:

SYSCTL_NODE(_vm, OID_AUTO, redzone, CTLFLAG_RW, NULL, "RedZone data");
static u_long redzone_extra_mem = 0;
SYSCTL_ULONG(_vm_redzone, OID_AUTO, extra_mem, CTLFLAG_RD, &redzone_extra_mem,
    0, "Extra memory allocated by redzone");     
static int redzone_panic = 0;
TUNABLE_INT("vm.redzone.panic", &redzone_panic);
SYSCTL_INT(_vm_redzone, OID_AUTO, panic, CTLFLAG_RW, &redzone_panic, 0,
    "Panic when buffer corruption is detected");     

Which shows what variables are changed from the kernel’s perspective using those SYSCTL variables. This isn’t really important but for completeness I decided to add it.

Setting Up a Red Zone
The code responsible for initializing a red-zone is inside redzone_setup() function which is shown below.

#define REDZONE_CHSIZE  (16)
#define REDZONE_CFSIZE  (16)
 ...
/*
 * Set redzones and remember allocation backtrace.
 */
void *
redzone_setup(caddr_t raddr, u_long nsize)
{
        struct stack st;
        caddr_t haddr, faddr;

        atomic_add_long(&redzone_extra_mem, redzone_size_ntor(nsize) - nsize);

        haddr = raddr + redzone_roundup(nsize) - REDZONE_HSIZE;
        faddr = haddr + REDZONE_HSIZE + nsize;

        /* Redzone header. */
        stack_save(&st);
        bcopy(&st, haddr, sizeof(st));
        haddr += sizeof(st);
        bcopy(&nsize, haddr, sizeof(nsize));
        haddr += sizeof(nsize);
        memset(haddr, 0x42, REDZONE_CHSIZE);
        haddr += REDZONE_CHSIZE;

        /* Redzone footer. */
        memset(faddr, 0x42, REDZONE_CFSIZE);
 
        return (haddr);
}

The algorithm here is fairly simple, after updating ‘redzone_extra_mem’ with the new size using atomic_add_long(), it initializes ‘haddr’ (header address) and ‘faddr’ (footer address) to point to the beginning and the end of the new space respectively. The current stack is placed in the header address followed by allocation size represented by ‘nsize’ unsigned long integer.

#define STACK_MAX       18      /* Don't change, stack_ktr relies on this. */

struct stack {
        int             depth;
        vm_offset_t     pcs[STACK_MAX];
};

The rest of the header and footer space are filled with ‘0x42’ (which is the equivalent hexadecimal value of ASCII character ‘B’). So, with this knowledge we can now understand that a red-zone in FreeBSD looks like this:

     +--------------------+ <--- Header
     |                    | 
     |   Current Stack    | 
     |                    |
     +--------------------+ <--- Header + sizeof(stack)
     |                    |
     |   Allocation size  |
     |                    |
     +--------------------+ <--- Header + nsize
     |     BBBBBBBBBB     |
     |       BBBBBB       |
     |                    |
     +--------------------+ <--- Footer
     |     BBBBBBBBBB     |
     |       BBBBBB       |
     |                    |
     +--------------------+ <--- Footer + REDZONE_CFSIZE

Red-Zone Checks
To check if a red-zone was corrupted which almost certainly means that an overflow occurred in a buffer close to it, redzone_check() function is used. Its argument is the address of the allocated space after the redzone and that’s why it will initially subtract the redzone’s header size to read its data and store it in some local variables as you can see in this code snippet:

/*
 * Verify redzones.
 * This function is called on free() and realloc().
 */
void
redzone_check(caddr_t naddr)
{
        struct stack ast, fst;
        caddr_t haddr, faddr;
        u_int ncorruptions;
        u_long nsize;
        int i;
 
        haddr = naddr - REDZONE_HSIZE;
        bcopy(haddr, &ast, sizeof(ast));
        haddr += sizeof(ast);
        bcopy(haddr, &nsize, sizeof(nsize));
        haddr += sizeof(nsize);
 
        atomic_subtract_long(&redzone_extra_mem,
            redzone_size_ntor(nsize) - nsize);

Then, we can find a simple ‘for’ loop that will iterate for the header part of the redzone to ensure that no 0x42 entries where altered.

        /* Look for buffer underflow. */
        ncorruptions = 0;
        for (i = 0; i < REDZONE_CHSIZE; i++, haddr++) {
                if (*(u_char *)haddr != 0x42)
                        ncorruptions++;
         }

However, if one or more altered/corrupted Bytes where discovered it will result in executing the next part.

        if (ncorruptions > 0) {
                printf("REDZONE: Buffer underflow detected. %u byte%s "
                    "corrupted before %p (%lu bytes allocated).\n",
                    ncorruptions, ncorruptions == 1 ? "" : "s", naddr, nsize);
                printf("Allocation backtrace:\n");
                stack_print_ddb(&ast);
                printf("Free backtrace:\n");
                stack_save(&fst);
                stack_print_ddb(&fst);
                if (redzone_panic)
                        panic("Stopping here.");
        }

Which might panic the system (depends on the ‘redzone_panic’ constant which by default is set to 0 but it can be tuned using the SYSCTL interface) but before doing this, it will give a complete stack-trace that could help in detecting the bug. The next part of redzone_check() does the exact same task for the footer.

        faddr = naddr + nsize;
        /* Look for buffer overflow. */
         ncorruptions = 0;
        for (i = 0; i < REDZONE_CFSIZE; i++, faddr++) {
                if (*(u_char *)faddr != 0x42)
                        ncorruptions++;
        }

Once again, in case of one or more corrupted Bytes the result will be a complete stack-trace and depending on the ‘redzone_panic’ value, a system panic.

        if (ncorruptions > 0) {
                printf("REDZONE: Buffer overflow detected. %u byte%s corrupted "
                    "after %p (%lu bytes allocated).\n", ncorruptions,
                    ncorruptions == 1 ? "" : "s", naddr + nsize, nsize);
                printf("Allocation backtrace:\n");
                stack_print_ddb(&ast);
                printf("Free backtrace:\n");
                stack_save(&fst);
                stack_print_ddb(&fst);
                if (redzone_panic)
                        panic("Stopping here.");
        }
}

Red-Zone in FreeBSD’s code
The last step is to see how those routines are utilized in kernel memory allocation functions to provide the buffer overflow detection feature. All of the code snippets below are part of the kern/kern_malloc.c file which implements the kernel’s dynamic memory allocation mechanism of FreeBSD. The setup of each redzone is part of kernel’s malloc() function.

void *
malloc(unsigned long size, struct malloc_type *mtp, int flags)
{
        int indx;
        struct malloc_type_internal *mtip;
        caddr_t va;
        uma_zone_t zone;
#if defined(DIAGNOSTIC) || defined(DEBUG_REDZONE)
        unsigned long osize = size;
#endif
  ...
#ifdef DEBUG_REDZONE
        size = redzone_size_ntor(size);
#endif

        if (size <= KMEM_ZMAX) {
  ...
#ifdef DEBUG_REDZONE
        if (va != NULL)
                va = redzone_setup(va, osize);
#endif
        return ((void *) va);
}

If the kernel is compiled with ‘DEBUG_REDZONE’ enabled, it will use the redzone_ntor_size() routine to calculate the allocation size and before returning the newly allocated VA space it will pass it to redzone_setup() in order to initialize a new red-zone for it.
The checks as you might have guessed are performed in free() since realloc() also results in calling free as you can see here:

void *
realloc(void *addr, unsigned long size, struct malloc_type *mtp, int flags)
{
        uma_slab_t slab;
        unsigned long alloc;
        void *newaddr;
  ...
#ifdef DEBUG_REDZONE
        slab = NULL;
        alloc = redzone_get_size(addr);
#else
        slab = vtoslab((vm_offset_t)addr & ~(UMA_SLAB_MASK));
  ...
         /* Copy over original contents */
         bcopy(addr, newaddr, min(size, alloc));
         free(addr, mtp);
         return (newaddr);
}

Which by the end of the function frees the old memory space before returning the newly allocated one. The call to free() leads to the actual redzone check.

void
free(void *addr, struct malloc_type *mtp)
{
        uma_slab_t slab;
        u_long size;
  ...
#ifdef DEBUG_REDZONE
        redzone_check(addr);
        addr = redzone_addr_ntor(addr);
#endif
  ...
        malloc_type_freed(mtp, size);
}

So, it will check that there was no corruption in the red-zone that protected the space to be freed.

Although bypassing this this quite straightforward I won’t discuss it since there is no public resource demonstrating it and I always write only for information that is already publicly available.

Written by xorl

December 21, 2010 at 19:12

Posted in freebsd, security

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s