When all the world's hackers were in sin city a few weeks ago, I was knee deep in the linux kernel networking subsystem. The fruits of this endeavour include CVE-2010-2959, a heap overflow vulnerability in an obscure socket family called "controller area networking". Sharing is caring, so here are the dirty details of this particular flaw.

A controller area network is backed by the AF_CAN datagram socket type. This socket is enabled by the CONFIG_CAN kernel configuration option, so any kernel compiled with CONFIG_CAN and CONFIG_CAN_BCM options were vulnerable. This included at least Ubuntu 10.04 and Debian 5.0 (i'm told a pre-release version of Red Hat was also affected).

The bug is an integer overflow in the sendmsg implementation for BCM (broadcast manager) AF_CAN sockets which results in controlled corruption of a kmalloc heap chunk. No physical CAN device is required to trigger the overflow.

The bcm_sendmsg function in net/can/bcm.c reads in a bcm_msg_head structure from a user-supplied iovec:
struct bcm_msg_head {
     __u32 opcode;
     __u32 flags;
    ...
     canid_t can_id;
     __u32 nframes;
     struct can_frame frames[0];
};
The opcode field dictates the type of message processing that should be performed by bcm_sendmsg. The vulnerability is in the RX_SETUP operation, which is backed by the bcm_rx_setup function in net/can/bcm.c (comments marked with BH):
#define CFSIZ sizeof(struct can_frame)

static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, int ifindex, struct sock *sk) {
 // BH: the ifindex parameter is set to zero if 
 // BH: msg->msg_name is NULL
 ...

 op = bcm_find_op(&bo->rx_ops, msg_head->can_id, 
                  ifindex);
 // BH: by setting can_id to 0xdeadbeef, a NULL op 
 // BH: is returned
 if (op) {
     ...
 }
 else {
    op = kzalloc(OPSIZ, GFP_KERNEL);

    if (!op)
       return -ENOMEM;

    op->can_id    = msg_head->can_id;
    op->nframes   = msg_head->nframes;
    // BH: nframes is controlled by the attacker

    if (msg_head->nframes > 1) {
       op->frames = kmalloc(
                      msg_head->nframes * CFSIZ,
                      GFP_KERNEL);
       // BH: integer overflow here, large nframes
       // BH: wraps around to cause a small alloc
       ...

    }...

    if (msg_head->nframes) {
       err = memcpy_fromiovec((u8 *)op->frames, 
                        msg->msg_iov,
                        msg_head->nframes * CFSIZ);
       // BH: size field overflows to same value as
       // BH: the allocation, no corruption
                      ...
    } ...
    
    do_rx_register = 1
 }

 ...
 if (do_rx_register) {
    if (ifindex) {           
       // BH: ifindex is zero, as noted above 
       ...
    } else
       err = can_rx_register(NULL, op->can_id,
                        REGMASK(op->can_id),
                        bcm_rx_handler, op, "bcm");

        // BH: can_rx_register explicitly
        // BH: allows registering to a NULL device
        
        if (err) {
           ...
        }
    }
...
Now at this point no memory corruption has occurred, but there is a bcm_op structure registered for the NULL device under a can_id of 0xdeadbeef with a large value for nframes (e.g. nframes = 268435458) and a small allocation for the frames buffer (e.g. 32 bytes). If the RX_SETUP operation is called again on this operation structure, but this time with a mid-sized nframes value (e.g. nframes = 512), then the following 'update' code in bcm_rx_setup is invoked:
op = bcm_find_op(&bo->rx_ops, msg_head->can_id, ifindex);
// BH: op struct for 0xdeadbeef is returned

if (op) {
   // BH: 512 < 268435458
   if (msg_head->nframes > op->nframes)
      return -E2BIG;

   if (msg_head->nframes) {
      // BH: writes 8192 attacker-controlled bytes 
      // BH: in to a 32-byte buffer
      err = memcpy_fromiovec((u8 *)op->frames,
                       msg->msg_iov,
                       msg_head->nframes * CFSIZ);
      ...
   }
}
This means that it's possible to corrupt any amount of data contiguous to the original 'frames' kmalloc chunk with an attacker-controlled value. The tough part from here is finding a good way of normalizing the heap layout to get a consistent (aka exploitable) crash. Needless to say that, with a bit of work, you can get an arbitrary kernel-space write.

Easy as that.

- hawkes@inertiawar.com (@benhawkes)