|
|
![]() |
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@sota.gen.nz (@benhawkes) rss |