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.