Commit message: usb: check USB configuration descriptor object

When processing remote NDIS control message packets, the USB Net
device emulator checks to see if the USB configuration descriptor
object is of RNDIS type(2). But it does not check if it is null,
which leads to a null dereference error. Add check to avoid it.

Reported-by: Qinghao Tang <luodalongde@gmail.com>
Signed-off-by: Prasad J Pandit <pjp@fedoraproject.org>
Message-id: 1455188480-14688-1-git-send-email-ppandit@redhat.com
Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
CVE: CVE-2016-2392
Vulnerability description: The is_rndis function in the USB Net device emulator (hw/usb/dev-network.c) in QEMU before 2.5.1 does not properly validate USB configuration descriptor objects, which allows local guest OS administrators to cause a denial of service (NULL pointer dereference and QEMU process crash) via vectors involving a remote NDIS control message packet.

Project: qemu

 static int ndis_query(USBNetState *s, uint32_t oid,
                      size_t outlen)
{
    unsigned int i;

    switch (oid) {
    /* general oids (table 4-1) */
    /* mandatory */
    case OID_GEN_SUPPORTED_LIST:
        for (i = 0; i < ARRAY_SIZE(oid_supported_list); i++)
            ((le32 *) outbuf)[i] = cpu_to_le32(oid_supported_list[i]);
        return sizeof(oid_supported_list);

    /* mandatory */
    case OID_GEN_HARDWARE_STATUS:
        *((le32 *) outbuf) = cpu_to_le32(0);
        return sizeof(le32);

    /* mandatory */
    case OID_GEN_MEDIA_SUPPORTED:
        *((le32 *) outbuf) = cpu_to_le32(s->medium);
        return sizeof(le32);

    /* mandatory */
    case OID_GEN_MEDIA_IN_USE:
        *((le32 *) outbuf) = cpu_to_le32(s->medium);
        return sizeof(le32);

    /* mandatory */
    case OID_GEN_MAXIMUM_FRAME_SIZE:
        *((le32 *) outbuf) = cpu_to_le32(ETH_FRAME_LEN);
        return sizeof(le32);

    /* mandatory */
    case OID_GEN_LINK_SPEED:
        *((le32 *) outbuf) = cpu_to_le32(s->speed);
        return sizeof(le32);

    /* mandatory */
    case OID_GEN_TRANSMIT_BLOCK_SIZE:
        *((le32 *) outbuf) = cpu_to_le32(ETH_FRAME_LEN);
        return sizeof(le32);

    /* mandatory */
    case OID_GEN_RECEIVE_BLOCK_SIZE:
        *((le32 *) outbuf) = cpu_to_le32(ETH_FRAME_LEN);
        return sizeof(le32);

    /* mandatory */
    case OID_GEN_VENDOR_ID:
        *((le32 *) outbuf) = cpu_to_le32(s->vendorid);
        return sizeof(le32);

    /* mandatory */
    case OID_GEN_VENDOR_DESCRIPTION:
        pstrcpy((char *)outbuf, outlen, "QEMU USB RNDIS Net");
        return strlen((char *)outbuf) + 1;

    case OID_GEN_VENDOR_DRIVER_VERSION:
        *((le32 *) outbuf) = cpu_to_le32(1);
        return sizeof(le32);

    /* mandatory */
    case OID_GEN_CURRENT_PACKET_FILTER:
        *((le32 *) outbuf) = cpu_to_le32(s->filter);
        return sizeof(le32);

    /* mandatory */
    case OID_GEN_MAXIMUM_TOTAL_SIZE:
        *((le32 *) outbuf) = cpu_to_le32(RNDIS_MAX_TOTAL_SIZE);
        return sizeof(le32);

    /* mandatory */
    case OID_GEN_MEDIA_CONNECT_STATUS:
        *((le32 *) outbuf) = cpu_to_le32(s->media_state);
        return sizeof(le32);

    case OID_GEN_PHYSICAL_MEDIUM:
        *((le32 *) outbuf) = cpu_to_le32(0);
        return sizeof(le32);

    case OID_GEN_MAC_OPTIONS:
        *((le32 *) outbuf) = cpu_to_le32(
                        NDIS_MAC_OPTION_RECEIVE_SERIALIZED |
                        NDIS_MAC_OPTION_FULL_DUPLEX);
        return sizeof(le32);

    /* statistics OIDs (table 4-2) */
    /* mandatory */
    case OID_GEN_XMIT_OK:
        *((le32 *) outbuf) = cpu_to_le32(0);
        return sizeof(le32);

    /* mandatory */
    case OID_GEN_RCV_OK:
        *((le32 *) outbuf) = cpu_to_le32(0);
        return sizeof(le32);

    /* mandatory */
    case OID_GEN_XMIT_ERROR:
        *((le32 *) outbuf) = cpu_to_le32(0);
        return sizeof(le32);

    /* mandatory */
    case OID_GEN_RCV_ERROR:
        *((le32 *) outbuf) = cpu_to_le32(0);
        return sizeof(le32);

    /* mandatory */
    case OID_GEN_RCV_NO_BUFFER:
        *((le32 *) outbuf) = cpu_to_le32(0);
        return sizeof(le32);

    /* ieee802.3 OIDs (table 4-3) */
    /* mandatory */
    case OID_802_3_PERMANENT_ADDRESS:
        memcpy(outbuf, s->conf.macaddr.a, 6);
        return 6;

    /* mandatory */
    case OID_802_3_CURRENT_ADDRESS:
        memcpy(outbuf, s->conf.macaddr.a, 6);
        return 6;

    /* mandatory */
    case OID_802_3_MULTICAST_LIST:
        *((le32 *) outbuf) = cpu_to_le32(0xe0000000);
        return sizeof(le32);

    /* mandatory */
    case OID_802_3_MAXIMUM_LIST_SIZE:
        *((le32 *) outbuf) = cpu_to_le32(1);
        return sizeof(le32);

    case OID_802_3_MAC_OPTIONS:
        return 0;

    /* ieee802.3 statistics OIDs (table 4-4) */
    /* mandatory */
    case OID_802_3_RCV_ERROR_ALIGNMENT:
        *((le32 *) outbuf) = cpu_to_le32(0);
        return sizeof(le32);

    /* mandatory */
    case OID_802_3_XMIT_ONE_COLLISION:
        *((le32 *) outbuf) = cpu_to_le32(0);
        return sizeof(le32);

    /* mandatory */
    case OID_802_3_XMIT_MORE_COLLISIONS:
        *((le32 *) outbuf) = cpu_to_le32(0);
        return sizeof(le32);

    default:
        fprintf(stderr, "usbnet: unknown OID 0x%08x\n", oid);
        return 0;
    }
    return -1;
}

Project: qemu
File: dev-network.c

static int rndis_query_response(USBNetState *s,
                rndis_query_msg_type *buf, unsigned int length)
{
    rndis_query_cmplt_type *resp;
    /* oid_supported_list is the largest data reply */
    uint8_t infobuf[sizeof(oid_supported_list)];
    uint32_t bufoffs, buflen;
    int infobuflen;
    unsigned int resplen;

    bufoffs = le32_to_cpu(buf->InformationBufferOffset) + 8;
    buflen = le32_to_cpu(buf->InformationBufferLength);
    if (bufoffs + buflen > length)
        return USB_RET_STALL;

    infobuflen = ndis_query(s, le32_to_cpu(buf->OID),
                            bufoffs + (uint8_t *) buf, buflen, infobuf,
                            sizeof(infobuf));
    resplen = sizeof(rndis_query_cmplt_type) +
            ((infobuflen < 0) ? 0 : infobuflen);
    resp = rndis_queue_response(s, resplen);
    if (!resp)
        return USB_RET_STALL;

    resp->MessageType = cpu_to_le32(RNDIS_QUERY_CMPLT);
    resp->RequestID = buf->RequestID; /* Still LE in msg buffer */
    resp->MessageLength = cpu_to_le32(resplen);

    if (infobuflen < 0) {
        /* OID not supported */
        resp->Status = cpu_to_le32(RNDIS_STATUS_NOT_SUPPORTED);
        resp->InformationBufferLength = cpu_to_le32(0);
        resp->InformationBufferOffset = cpu_to_le32(0);
        return 0;
    }

    resp->Status = cpu_to_le32(RNDIS_STATUS_SUCCESS);
    resp->InformationBufferOffset =
            cpu_to_le32(infobuflen ? sizeof(rndis_query_cmplt_type) - 8 : 0);
    resp->InformationBufferLength = cpu_to_le32(infobuflen);
    memcpy(resp + 1, infobuf, infobuflen);

    return 0;
}

Project: qemu
File: dev-network.c

static int rndis_set_response(USBNetState *s,
                rndis_set_msg_type *buf, unsigned int length)
{
    rndis_set_cmplt_type *resp =
            rndis_queue_response(s, sizeof(rndis_set_cmplt_type));
    uint32_t bufoffs, buflen;
    int ret;

    if (!resp)
        return USB_RET_STALL;

    bufoffs = le32_to_cpu(buf->InformationBufferOffset) + 8;
    buflen = le32_to_cpu(buf->InformationBufferLength);
    if (bufoffs + buflen > length)
        return USB_RET_STALL;

    ret = ndis_set(s, le32_to_cpu(buf->OID),
                    bufoffs + (uint8_t *) buf, buflen);
    resp->MessageType = cpu_to_le32(RNDIS_SET_CMPLT);
    resp->RequestID = buf->RequestID; /* Still LE in msg buffer */
    resp->MessageLength = cpu_to_le32(sizeof(rndis_set_cmplt_type));
    if (ret < 0) {
        /* OID not supported */
        resp->Status = cpu_to_le32(RNDIS_STATUS_NOT_SUPPORTED);
        return 0;
    }
    resp->Status = cpu_to_le32(RNDIS_STATUS_SUCCESS);

    return 0;
}

Project: qemu
File: dev-network.c

static void usb_net_class_initfn(ObjectClass *klass, void *data)
{
    DeviceClass *dc = DEVICE_CLASS(klass);
    USBDeviceClass *uc = USB_DEVICE_CLASS(klass);

    uc->realize        = usb_net_realize;
    uc->product_desc   = "QEMU USB Network Interface";
    uc->usb_desc       = &desc_net;
    uc->handle_reset   = usb_net_handle_reset;
    uc->handle_control = usb_net_handle_control;
    uc->handle_data    = usb_net_handle_data;
    uc->handle_destroy = usb_net_handle_destroy;
    set_bit(DEVICE_CATEGORY_NETWORK, dc->categories);
    dc->fw_name = "network";
    dc->vmsd = &vmstate_usb_net;
    dc->props = net_properties;
}

Project: qemu
File: dev-network.c

static void usb_net_handle_data(USBDevice *dev, USBPacket *p)
{
    USBNetState *s = (USBNetState *) dev;

    switch(p->pid) {
    case USB_TOKEN_IN:
        switch (p->ep->nr) {
        case 1:
            usb_net_handle_statusin(s, p);
            break;

        case 2:
            usb_net_handle_datain(s, p);
            break;

        default:
            goto fail;
        }
        break;

    case USB_TOKEN_OUT:
        switch (p->ep->nr) {
        case 2:
            usb_net_handle_dataout(s, p);
            break;

        default:
            goto fail;
        }
        break;

    default:
    fail:
        p->status = USB_RET_STALL;
        break;
    }

    if (p->status == USB_RET_STALL) {
        fprintf(stderr, "usbnet: failed data transaction: "
                        "pid 0x%x ep 0x%x len 0x%zx\n",
                        p->pid, p->ep->nr, p->iov.size);
    }
}

Project: qemu
File: dev-network.c

static void usb_net_handle_dataout(USBNetState *s, USBPacket *p)
{
    int sz = sizeof(s->out_buf) - s->out_ptr;
    struct rndis_packet_msg_type *msg =
            (struct rndis_packet_msg_type *) s->out_buf;
    uint32_t len;

#ifdef TRAFFIC_DEBUG
    fprintf(stderr, "usbnet: data out len %zu\n", p->iov.size);
    iov_hexdump(p->iov.iov, p->iov.niov, stderr, "usbnet", p->iov.size);
#endif

    if (sz > p->iov.size) {
        sz = p->iov.size;
    }
    usb_packet_copy(p, &s->out_buf[s->out_ptr], sz);
    s->out_ptr += sz;

    if (!is_rndis(s)) {
        if (p->iov.size < 64) {
            qemu_send_packet(qemu_get_queue(s->nic), s->out_buf, s->out_ptr);
            s->out_ptr = 0;
        }
        return;
    }
    len = le32_to_cpu(msg->MessageLength);
    if (s->out_ptr < 8 || s->out_ptr < len) {
        return;
    }
    if (le32_to_cpu(msg->MessageType) == RNDIS_PACKET_MSG) {
        uint32_t offs = 8 + le32_to_cpu(msg->DataOffset);
        uint32_t size = le32_to_cpu(msg->DataLength);
        if (offs + size <= len)
            qemu_send_packet(qemu_get_queue(s->nic), s->out_buf + offs, size);
    }
    s->out_ptr -= len;
    memmove(s->out_buf, &s->out_buf[len], s->out_ptr);
}

Project: qemu
File: dev-network.c

static void usb_net_handle_destroy(USBDevice *dev)
{
    USBNetState *s = (USBNetState *) dev;

    /* TODO: remove the nd_table[] entry */
    rndis_clear_responsequeue(s);
    qemu_del_nic(s->nic);
}

Project: qemu
File: dev-network.c

static void usb_net_handle_reset(USBDevice *dev)
{
}

Project: qemu
File: dev-network.c

static void usb_net_handle_statusin(USBNetState *s, USBPacket *p)
{
    le32 buf[2];

    if (p->iov.size < 8) {
        p->status = USB_RET_STALL;
        return;
    }

    buf[0] = cpu_to_le32(1);
    buf[1] = cpu_to_le32(0);
    usb_packet_copy(p, buf, 8);
    if (!s->rndis_resp.tqh_first) {
        p->status = USB_RET_NAK;
    }

#ifdef TRAFFIC_DEBUG
    fprintf(stderr, "usbnet: interrupt poll len %zu return %d",
            p->iov.size, p->status);
    iov_hexdump(p->iov.iov, p->iov.niov, stderr, "usbnet", p->status);
#endif
}

Project: qemu
File: dev-network.c

static USBDevice *usb_net_init(USBBus *bus, const char *cmdline)
{
    Error *local_err = NULL;
    USBDevice *dev;
    QemuOpts *opts;
    int idx;

    opts = qemu_opts_parse_noisily(qemu_find_opts("net"), cmdline, false);
    if (!opts) {
        return NULL;
    }
    qemu_opt_set(opts, "type", "nic", &error_abort);
    qemu_opt_set(opts, "model", "usb", &error_abort);

    idx = net_client_init(opts, 0, &local_err);
    if (local_err) {
        error_report_err(local_err);
        return NULL;
    }

    dev = usb_create(bus, "usb-net");
    qdev_set_nic_properties(&dev->qdev, &nd_table[idx]);
    return dev;
}

Project: qemu
File: dev-network.c

static void usb_net_instance_init(Object *obj)
{
    USBDevice *dev = USB_DEVICE(obj);
    USBNetState *s = USB_NET(dev);

    device_add_bootindex_property(obj, &s->conf.bootindex,
                                  "bootindex", "/ethernet-phy@0",
                                  &dev->qdev, NULL);
}

Project: qemu
File: dev-network.c

static void usb_net_realize(USBDevice *dev, Error **errrp)
{
    USBNetState *s = USB_NET(dev);

    usb_desc_create_serial(dev);
    usb_desc_init(dev);

    s->rndis_state = RNDIS_UNINITIALIZED;
    QTAILQ_INIT(&s->rndis_resp);

    s->medium = 0;	/* NDIS_MEDIUM_802_3 */
    s->speed = 1000000; /* 100MBps, in 100Bps units */
    s->media_state = 0;	/* NDIS_MEDIA_STATE_CONNECTED */;
    s->filter = 0;
    s->vendorid = 0x1234;
    s->intr = usb_ep_get(dev, USB_TOKEN_IN, 1);

    qemu_macaddr_default_if_unset(&s->conf.macaddr);
    s->nic = qemu_new_nic(&net_usbnet_info, &s->conf,
                          object_get_typename(OBJECT(s)), s->dev.qdev.id, s);
    qemu_format_nic_info_str(qemu_get_queue(s->nic), s->conf.macaddr.a);
    snprintf(s->usbstring_mac, sizeof(s->usbstring_mac),
             "%02x%02x%02x%02x%02x%02x",
             0x40,
             s->conf.macaddr.a[1],
             s->conf.macaddr.a[2],
             s->conf.macaddr.a[3],
             s->conf.macaddr.a[4],
             s->conf.macaddr.a[5]);
    usb_desc_set_string(dev, STRING_ETHADDR, s->usbstring_mac);
}

Project: qemu
File: dev-network.c

static void usb_net_register_types(void)
{
    type_register_static(&net_info);
    usb_legacy_register(TYPE_USB_NET, "net", usb_net_init);
}

Project: qemu
File: dev-network.c

static void usb_net_reset_in_buf(USBNetState *s)
{
    s->in_ptr = s->in_len = 0;
    qemu_flush_queued_packets(qemu_get_queue(s->nic));
}

Project: qemu
File: dev-network.c

static void usbnet_cleanup(NetClientState *nc)
{
    USBNetState *s = qemu_get_nic_opaque(nc);

    s->nic = NULL;
}

Project: qemu
File: dev-network.c

static ssize_t usbnet_receive(NetClientState *nc, const uint8_t *buf, size_t size)
{
    USBNetState *s = qemu_get_nic_opaque(nc);
    uint8_t *in_buf = s->in_buf;
    size_t total_size = size;

    if (!s->dev.config) {
        return -1;
    }

    if (is_rndis(s)) {
        if (s->rndis_state != RNDIS_DATA_INITIALIZED) {
            return -1;
        }
        total_size += sizeof(struct rndis_packet_msg_type);
    }
    if (total_size > sizeof(s->in_buf)) {
        return -1;
    }

    /* Only accept packet if input buffer is empty */
    if (s->in_len > 0) {
        return 0;
    }

    if (is_rndis(s)) {
        struct rndis_packet_msg_type *msg;

        msg = (struct rndis_packet_msg_type *)in_buf;
        memset(msg, 0, sizeof(struct rndis_packet_msg_type));
        msg->MessageType = cpu_to_le32(RNDIS_PACKET_MSG);
        msg->MessageLength = cpu_to_le32(size + sizeof(*msg));
        msg->DataOffset = cpu_to_le32(sizeof(*msg) - 8);
        msg->DataLength = cpu_to_le32(size);
        /* msg->OOBDataOffset;
         * msg->OOBDataLength;
         * msg->NumOOBDataElements;
         * msg->PerPacketInfoOffset;
         * msg->PerPacketInfoLength;
         * msg->VcHandle;
         * msg->Reserved;
         */
        in_buf += sizeof(*msg);
    }

    memcpy(in_buf, buf, size);
    s->in_len = total_size;
    s->in_ptr = 0;
    return size;
}