It's Bugs All the Way Down

Security Research by Dan Rosenberg

A Fun *BSD Bug

I recently reported and disclosed (at Summercon) a vulnerability in the 802.11 stack of the FreeBSD and NetBSD kernels that had some interesting properties.

The vulnerability was found ieee80211_ioctl_getchaninfo(), the handler for the IEEE80211_IOC_CHANINFO ioctl, which can be found in sys/net80211/ieee80211_ioctl.c:

static __noinline int
ieee80211_ioctl_getchaninfo(struct ieee80211vap *vap, struct ieee80211req *ireq)
{
        struct ieee80211com *ic = vap->iv_ic;
        int space;

        space = __offsetof(struct ieee80211req_chaninfo,
                        ic_chans[ic->ic_nchans]);
        if (space > ireq->i_len)
                space = ireq->i_len;
        /* XXX assumes compatible layout */
        return copyout(&ic->ic_nchans, ireq->i_data, space);
}

The i_len member of the ireq structure is a signed short type, so a negative value will pass the size check and assign space to be the user-supplied negative value. The remaining code tries to copy a very large unsigned amount of data back to the user, seemingly causing a major kernel memory disclosure.

At least that’s what it looks like at first glance. However, it turns out this is only exploitable on certain non-x86 architectures. To understand why, first we note that the negative signed short value provided by the user will eventually be sign-extended up to a long type. As a result, on 32-bit x86, the smallest unsigned value we can cause space to be set to when passing in a negative i_len field is 0xffff8000, the result of sign-extending 0x8000.

Next, we take a look at the checks built into the copyout function, in sys/i386/i386/support.s, edited and annotated:

ENTRY(copyout)
        movl    PCPU(CURPCB),%eax
        movl    $copyout_fault,PCB_ONFAULT(%eax)
        pushl   %esi
        pushl   %edi
        pushl   %ebx
        movl    16(%esp),%esi
        movl    20(%esp),%edi
        movl    24(%esp),%ebx
        testl   %ebx,%ebx                       /* anything to do? */
        jz      done_copyout

[1]     movl    %edi,%eax
        addl    %ebx,%eax
        jc      copyout_fault

[2]     cmpl    $VM_MAXUSER_ADDRESS,%eax
        ja      copyout_fault

At [1], it’s checked that dst + size does not overflow (carry). Then, at [2], this sum, which represents the end of the copied-out data, is confirmed to be smaller than VM_MAXUSER_ADDRESS, which is usually defined as 3 GB (0xc0000000 on 32-bit x86). If either of these conditions are not met, the copy is aborted.

Taking another look at our bug, it’s clear that this makes it impossible to exploit. Because the lowest size we can attain (while exploiting the signedness vulnerability) is 0xffff8000, we can’t possibly have values such that dst + size doesn’t wrap and stays below 3 GB.

But all is not lost! It turns out that other architectures, such as SPARC, do not have this restriction. SPARC, for example, has completely separate address spaces for kernel and user memory, so no checking is done on the upper boundary of data being copied to userspace (since there’s no possibility of writing into kernel memory). As a result, this vulnerability represents a serious kernel memory disclosure on these platforms.

The fix is simple: the i_len field is changed to an unsigned type, to avoid issues with signed comparison against a size field.

The other amusing thing about this vulnerability is related to the history of this particular file. Back in 2006, FreeBSD released a security advisory and patch for a memory disclosure issue in the 802.11 stack. On inspection, this older vulnerability was in ieee80211_ioctl_getchanlist(), which sits only a few lines above our new vulnerability! This further reinforces the point that when vulnerabilities are found, surrounding code should be audited, since similar vulnerabilities are likely to exist.

This entry was posted on Thursday, June 16th, 2011 at 12:46 am and is filed under Kernel. You can follow any responses to this entry through the RSS 2.0 feed. Both comments and pings are currently closed.