[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

Bug#643817: Fix for CVE-2011-2699 can result in crash in VM hosts



On Wed, 2011-12-28 at 01:11 +0100, Ben Hutchings wrote:
> On Sat, 2011-12-24 at 12:52 +0100, Moritz Mühlenhoff wrote:
> > On Fri, Sep 30, 2011 at 03:31:42AM +0100, Ben Hutchings wrote:
> > > Package: linux-2.6
> > > Version: 2.6.32-36
> > > Severity: serious
> > > Tags: security patch
> > > 
> > > VM guests using the virtio_net driver may take advantage of UFO (UDP
> > > fragmentation offload) which results in the VM host performing
> > > fragmentation.  As discussed in
> > > <http://thread.gmane.org/gmane.linux.kernel/1196272>, the new IPv6
> > > fragment ID generator will crash in this case because the expected
> > > routing context is missing.
> > > 
> > > No fix is yet available, so we should revert the original fix and
> > > sort this out properly later.
> > 
> > Do you know if a fix for 2.6.32 is now available?
> 
> I *think* that we should be able to use this fix from 3.0-stable:
> 
> commit a1b7ab0836a56fa4c9578f88ba1042398d7d9316
> Author: Jason Wang <jasowang@redhat.com>
> Date:   Sun Oct 9 10:56:44 2011 +0800
> 
>     ipv6: fix NULL dereference in udp6_ufo_fragment()

Try as I might, I couldn't reproduce the crash that this fixes.  But the
fix certainly seems reasonable.  And there shouldn't be any other
callers that need to be considered, as they would have caused a build
failure in 2.6.32-36.

I'm attaching my test program that sends a packet requiring UFO through
a tun device.  You will need to enable forwarding from the tun device to
some other device, add routes and select source and destination addresses
such that the kernel will try to forward the packet.

Ben.

-- 
Ben Hutchings
Hoare's Law of Large Problems:
        Inside every large problem is a small problem struggling to get out.

/*
 * tun_ufo.c - test for UFO regression fixed by
 * commit a1b7ab0836a56fa4c9578f88ba1042398d7d9316 in 3.0-stable
 *
 * Ben Hutchings, with checksum code cribbed from Linux.  GPLv2.
 */

#include <assert.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <unistd.h>
#include <sys/fcntl.h>
#include <sys/ioctl.h>

#include <net/if.h>
#include <netinet/in.h>
#include <netinet/ip6.h>
#include <netinet/udp.h>
#include <linux/if_tun.h>
#include <linux/virtio_net.h>

#define constant_htons(x) ((u_short)((u_short)(x) >> 8 | (u_short)(x) << 8))

static struct {
    struct virtio_net_hdr gso;
    struct ip6_hdr ip;
    struct udphdr udp;
    char body[2000];
} __attribute__((packed)) packet = {
    .gso = {
	.flags = VIRTIO_NET_HDR_F_NEEDS_CSUM,
	.gso_type = VIRTIO_NET_HDR_GSO_UDP,
	.hdr_len = sizeof(packet.ip) + sizeof(packet.udp),
	.gso_size = 1000,
	.csum_start = sizeof(packet.ip),
	.csum_offset = sizeof(packet.ip) + offsetof(struct udphdr, check),
    },
    .ip = {
	.ip6_plen = constant_htons(sizeof(packet.udp) + sizeof(packet.body)),
	.ip6_hlim = 10,
	.ip6_nxt = IPPROTO_UDP,
	.ip6_src.s6_addr = { 0xfe,0x80,0,0, 0,0,0,0,
			     0x50,0x54,0,0xff, 0xfe,0xa4,0x67,0xd7 },
	.ip6_dst.s6_addr = { 0xfe,0x80,0,0, 0,0,0,0,
			     0xfc,0x54,0,0xff, 0xfe,0xa4,0x67,0xd7 },
    },
    .udp = {
	.source = constant_htons(1234),
	.dest = constant_htons(5678),
	.len = constant_htons(sizeof(packet.udp) + sizeof(packet.body)),
    }
};

static unsigned short from32to16(unsigned long x)
{
	/* add up 16-bit and 16-bit for 16+c bit */
	x = (x & 0xffff) + (x >> 16);
	/* add up carry.. */
	x = (x & 0xffff) + (x >> 16);
	return x;
}

static unsigned int do_csum(const unsigned char *buff, int len)
{
	int odd, count;
	unsigned long result = 0;

	if (len <= 0)
		goto out;
	odd = 1 & (unsigned long) buff;
	if (odd) {
		result = *buff;
		len--;
		buff++;
	}
	count = len >> 1;		/* nr of 16-bit words.. */
	if (count) {
		if (2 & (unsigned long) buff) {
			result += *(unsigned short *) buff;
			count--;
			len -= 2;
			buff += 2;
		}
		count >>= 1;		/* nr of 32-bit words.. */
		if (count) {
			unsigned long carry = 0;
			do {
				unsigned long w = *(unsigned int *) buff;
				count--;
				buff += 4;
				result += carry;
				result += w;
				carry = (w > result);
			} while (count);
			result += carry;
			result = (result & 0xffff) + (result >> 16);
		}
		if (len & 2) {
			result += *(unsigned short *) buff;
			buff += 2;
		}
	}
	if (len & 1)
		result += *buff;
	result = from32to16(result);
	if (odd)
		result = ((result >> 8) & 0xff) | ((result & 0xff) << 8);
out:
	return result;
}

static u_short csum_fold(u_int csum)
{
	u_int sum = csum;
	sum = (sum & 0xffff) + (sum >> 16);
	sum = (sum & 0xffff) + (sum >> 16);
	return (u_short)~sum;
}

u_short ip_fast_csum(const void *iph, unsigned int ihl)
{
	return (u_short)~do_csum(iph, ihl*4);
}

static u_short csum_ipv6_magic(const struct in6_addr *saddr,
			       const struct in6_addr *daddr,
			       u_int len, unsigned short proto,
			       u_int csum)
{

	int carry;
	u_int ulen;
	u_int uproto;
	u_int sum = (u_int)csum;

	sum += (u_int)saddr->s6_addr32[0];
	carry = (sum < (u_int)saddr->s6_addr32[0]);
	sum += carry;

	sum += (u_int)saddr->s6_addr32[1];
	carry = (sum < (u_int)saddr->s6_addr32[1]);
	sum += carry;

	sum += (u_int)saddr->s6_addr32[2];
	carry = (sum < (u_int)saddr->s6_addr32[2]);
	sum += carry;

	sum += (u_int)saddr->s6_addr32[3];
	carry = (sum < (u_int)saddr->s6_addr32[3]);
	sum += carry;

	sum += (u_int)daddr->s6_addr32[0];
	carry = (sum < (u_int)daddr->s6_addr32[0]);
	sum += carry;

	sum += (u_int)daddr->s6_addr32[1];
	carry = (sum < (u_int)daddr->s6_addr32[1]);
	sum += carry;

	sum += (u_int)daddr->s6_addr32[2];
	carry = (sum < (u_int)daddr->s6_addr32[2]);
	sum += carry;

	sum += (u_int)daddr->s6_addr32[3];
	carry = (sum < (u_int)daddr->s6_addr32[3]);
	sum += carry;

	ulen = (u_int)htonl((u_int) len);
	sum += ulen;
	carry = (sum < ulen);
	sum += carry;

	uproto = (u_int)htonl(proto);
	sum += uproto;
	carry = (sum < uproto);
	sum += carry;

	return csum_fold((u_int)sum);
}

static void check(bool cond, const char *s)
{
    if (!cond)
    {
	perror(s);
	exit(1);
    }
}

int main(void)
{
    struct ifreq ifr;
    int tunfd, sockfd, err;
    ssize_t len;
    char dummy;

    tunfd = open("/dev/net/tun", O_RDWR);
    check(tunfd >= 0, "open");

    sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    check(sockfd >= 0, "socket");

    memset(&ifr, 0, sizeof(ifr));
    ifr.ifr_flags = IFF_TUN | IFF_NO_PI | IFF_VNET_HDR;
    err = ioctl(tunfd, TUNSETIFF, &ifr);
    check(err == 0, "TUNSETIFF");
    printf("interface name: %s\n", ifr.ifr_name);

    err = ioctl(tunfd, TUNSETOFFLOAD, TUN_F_CSUM | TUN_F_UFO);
    check(err == 0, "TUNSETOFFLOAD");

    err = ioctl(sockfd, SIOCGIFFLAGS, &ifr);
    check(err == 0, "SIOCGIFFLAGS");
    ifr.ifr_flags |= IFF_UP | IFF_RUNNING;
    err = ioctl(sockfd, SIOCSIFFLAGS, &ifr);
    check(err == 0, "SIOCSIFFLAGS");

    puts("Press Return to send UFO packet");
    len = read(0, &dummy, 1);
    check(len == 1, "read");

    packet.ip.ip6_vfc |= 0x60,
    packet.udp.check = csum_ipv6_magic(&packet.ip.ip6_src, &packet.ip.ip6_dst,
				       packet.ip.ip6_plen, packet.ip.ip6_nxt,
				       0);
    len = write(tunfd, &packet, sizeof(packet));
    if (len != sizeof(packet))
	perror("write");

    puts("Press Return to exit");
    len = read(0, &dummy, 1);
    check(len == 1, "read");

    return 0;
}

Attachment: signature.asc
Description: This is a digitally signed message part


Reply to: