xorl %eax, %eax

FreeBSD-SA-09:09: pipe(2) Integer Overflow

leave a comment »

This vulnerability was reported by Pieter de Boer and affects all of the FreeBSD versions. The bug is located at the “direct write” implementation for anonymous pipes which is an optimization feature that allows pipes to copy their data through virtual memory mapping instead of passing them through the kernel. Here is the buggy code from FreeBSD 7.2-RELEASE:

* Copyright (c) 1996 John S. Dyson
* All rights reserved.
       ...
/*
 * Map the sending processes' buffer into kernel space and wire it.
 * This is similar to a physical write operation.
 */
static int
pipe_build_write_buffer(wpipe, uio)
        struct pipe *wpipe;
        struct uio *uio;
{
        pmap_t pmap;
        u_int size;
        int i, j;
        vm_offset_t addr, endaddr;
       ...
        pmap = vmspace_pmap(curproc->p_vmspace);
        endaddr = round_page((vm_offset_t)uio->uio_iov->iov_base + size);
        addr = trunc_page((vm_offset_t)uio->uio_iov->iov_base);
        for (i = 0; addr < endaddr; addr += PAGE_SIZE, i++) {
                /*
                 * vm_fault_quick() can sleep.  Consequently,
                 * vm_page_lock_queue() and vm_page_unlock_queue()
                 * should not be performed outside of this loop.
                 */
        race:
                if (vm_fault_quick((caddr_t)addr, VM_PROT_READ) < 0) {
                        vm_page_lock_queues();
                        for (j = 0; j < i; j++)
                                vm_page_unhold(wpipe->pipe_map.ms[j]);
                        vm_page_unlock_queues();
                        return (EFAULT);
                }
                wpipe->pipe_map.ms[i] = pmap_extract_and_hold(pmap, addr,
                    VM_PROT_READ);
                if (wpipe->pipe_map.ms[i] == NULL)
                        goto race;
        }
       ...
        return (0);
}

This code can be found at sys/kern/sys_pipe.c where pipe(2) code is located. The problem with the above code is at the for loop where it iterates through the virtual memory pointer addr until it reaches endaddr. However, endaddr is simply addr+size as you can see in the above snippet and the calculation of the end address might overflow this integer and thus lead to iterating through pages that belong to other processes or kernel memory in the for loop. The data contained in those pages will be incorrectly leaked. To fix this the following patch was applied:

addr = trunc_page((vm_offset_t)uio->uio_iov->iov_base);
+ if (endaddr < addr) + return (EFAULT); for (i = 0; addr < endaddr; addr += PAGE_SIZE, i++) { [/sourcecode] Using this they always check that endaddr did not wrap around during the previous calculation using round_page(). This code can be reached utilizing direct write of FreeBSD kernel with write(2) and pipe(2) system calls.

Written by xorl

June 10, 2009 at 19:51

Posted in bugs, freebsd

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