qemu-e2k/hw/usb-serial.c
David S. Ahern 4ab4183d76 segfault due to buffer overrun in usb-serial
This fixes a segfault due to buffer overrun in the usb-serial device.
The memcpy was incrementing the start location by recv_used yet, the
computation of first_size (how much to write at the end of the buffer
before wrapping to the front) was not accounting for it. This causes the
next element after the receive buffer (recv_ptr) to get overwritten with
random data.

Signed-off-by: David Ahern <daahern@cisco.com>
Signed-off-by: Anthony Liguori <aliguori@us.ibm.com>
2010-02-10 12:45:11 -06:00

669 lines
19 KiB
C

/*
* FTDI FT232BM Device emulation
*
* Copyright (c) 2006 CodeSourcery.
* Copyright (c) 2008 Samuel Thibault <samuel.thibault@ens-lyon.org>
* Written by Paul Brook, reused for FTDI by Samuel Thibault
*
* This code is licenced under the LGPL.
*/
#include "qemu-common.h"
#include "usb.h"
#include "qemu-char.h"
//#define DEBUG_Serial
#ifdef DEBUG_Serial
#define DPRINTF(fmt, ...) \
do { printf("usb-serial: " fmt , ## __VA_ARGS__); } while (0)
#else
#define DPRINTF(fmt, ...) do {} while(0)
#endif
#define RECV_BUF 384
/* Commands */
#define FTDI_RESET 0
#define FTDI_SET_MDM_CTRL 1
#define FTDI_SET_FLOW_CTRL 2
#define FTDI_SET_BAUD 3
#define FTDI_SET_DATA 4
#define FTDI_GET_MDM_ST 5
#define FTDI_SET_EVENT_CHR 6
#define FTDI_SET_ERROR_CHR 7
#define FTDI_SET_LATENCY 9
#define FTDI_GET_LATENCY 10
#define DeviceOutVendor ((USB_DIR_OUT|USB_TYPE_VENDOR|USB_RECIP_DEVICE)<<8)
#define DeviceInVendor ((USB_DIR_IN |USB_TYPE_VENDOR|USB_RECIP_DEVICE)<<8)
/* RESET */
#define FTDI_RESET_SIO 0
#define FTDI_RESET_RX 1
#define FTDI_RESET_TX 2
/* SET_MDM_CTRL */
#define FTDI_DTR 1
#define FTDI_SET_DTR (FTDI_DTR << 8)
#define FTDI_RTS 2
#define FTDI_SET_RTS (FTDI_RTS << 8)
/* SET_FLOW_CTRL */
#define FTDI_RTS_CTS_HS 1
#define FTDI_DTR_DSR_HS 2
#define FTDI_XON_XOFF_HS 4
/* SET_DATA */
#define FTDI_PARITY (0x7 << 8)
#define FTDI_ODD (0x1 << 8)
#define FTDI_EVEN (0x2 << 8)
#define FTDI_MARK (0x3 << 8)
#define FTDI_SPACE (0x4 << 8)
#define FTDI_STOP (0x3 << 11)
#define FTDI_STOP1 (0x0 << 11)
#define FTDI_STOP15 (0x1 << 11)
#define FTDI_STOP2 (0x2 << 11)
/* GET_MDM_ST */
/* TODO: should be sent every 40ms */
#define FTDI_CTS (1<<4) // CTS line status
#define FTDI_DSR (1<<5) // DSR line status
#define FTDI_RI (1<<6) // RI line status
#define FTDI_RLSD (1<<7) // Receive Line Signal Detect
/* Status */
#define FTDI_DR (1<<0) // Data Ready
#define FTDI_OE (1<<1) // Overrun Err
#define FTDI_PE (1<<2) // Parity Err
#define FTDI_FE (1<<3) // Framing Err
#define FTDI_BI (1<<4) // Break Interrupt
#define FTDI_THRE (1<<5) // Transmitter Holding Register
#define FTDI_TEMT (1<<6) // Transmitter Empty
#define FTDI_FIFO (1<<7) // Error in FIFO
typedef struct {
USBDevice dev;
uint32_t vendorid;
uint32_t productid;
uint8_t recv_buf[RECV_BUF];
uint16_t recv_ptr;
uint16_t recv_used;
uint8_t event_chr;
uint8_t error_chr;
uint8_t event_trigger;
QEMUSerialSetParams params;
int latency; /* ms */
CharDriverState *cs;
} USBSerialState;
static const uint8_t qemu_serial_dev_descriptor[] = {
0x12, /* u8 bLength; */
0x01, /* u8 bDescriptorType; Device */
0x00, 0x02, /* u16 bcdUSB; v2.0 */
0x00, /* u8 bDeviceClass; */
0x00, /* u8 bDeviceSubClass; */
0x00, /* u8 bDeviceProtocol; [ low/full speeds only ] */
0x08, /* u8 bMaxPacketSize0; 8 Bytes */
/* Vendor and product id are arbitrary. */
0x03, 0x04, /* u16 idVendor; */
0x00, 0xFF, /* u16 idProduct; */
0x00, 0x04, /* u16 bcdDevice */
0x01, /* u8 iManufacturer; */
0x02, /* u8 iProduct; */
0x03, /* u8 iSerialNumber; */
0x01 /* u8 bNumConfigurations; */
};
static const uint8_t qemu_serial_config_descriptor[] = {
/* one configuration */
0x09, /* u8 bLength; */
0x02, /* u8 bDescriptorType; Configuration */
0x20, 0x00, /* u16 wTotalLength; */
0x01, /* u8 bNumInterfaces; (1) */
0x01, /* u8 bConfigurationValue; */
0x00, /* u8 iConfiguration; */
0x80, /* u8 bmAttributes;
Bit 7: must be set,
6: Self-powered,
5: Remote wakeup,
4..0: resvd */
100/2, /* u8 MaxPower; */
/* one interface */
0x09, /* u8 if_bLength; */
0x04, /* u8 if_bDescriptorType; Interface */
0x00, /* u8 if_bInterfaceNumber; */
0x00, /* u8 if_bAlternateSetting; */
0x02, /* u8 if_bNumEndpoints; */
0xff, /* u8 if_bInterfaceClass; Vendor Specific */
0xff, /* u8 if_bInterfaceSubClass; Vendor Specific */
0xff, /* u8 if_bInterfaceProtocol; Vendor Specific */
0x02, /* u8 if_iInterface; */
/* Bulk-In endpoint */
0x07, /* u8 ep_bLength; */
0x05, /* u8 ep_bDescriptorType; Endpoint */
0x81, /* u8 ep_bEndpointAddress; IN Endpoint 1 */
0x02, /* u8 ep_bmAttributes; Bulk */
0x40, 0x00, /* u16 ep_wMaxPacketSize; */
0x00, /* u8 ep_bInterval; */
/* Bulk-Out endpoint */
0x07, /* u8 ep_bLength; */
0x05, /* u8 ep_bDescriptorType; Endpoint */
0x02, /* u8 ep_bEndpointAddress; OUT Endpoint 2 */
0x02, /* u8 ep_bmAttributes; Bulk */
0x40, 0x00, /* u16 ep_wMaxPacketSize; */
0x00 /* u8 ep_bInterval; */
};
static void usb_serial_reset(USBSerialState *s)
{
/* TODO: Set flow control to none */
s->event_chr = 0x0d;
s->event_trigger = 0;
s->recv_ptr = 0;
s->recv_used = 0;
/* TODO: purge in char driver */
}
static void usb_serial_handle_reset(USBDevice *dev)
{
USBSerialState *s = (USBSerialState *)dev;
DPRINTF("Reset\n");
usb_serial_reset(s);
/* TODO: Reset char device, send BREAK? */
}
static uint8_t usb_get_modem_lines(USBSerialState *s)
{
int flags;
uint8_t ret;
if (qemu_chr_ioctl(s->cs, CHR_IOCTL_SERIAL_GET_TIOCM, &flags) == -ENOTSUP)
return FTDI_CTS|FTDI_DSR|FTDI_RLSD;
ret = 0;
if (flags & CHR_TIOCM_CTS)
ret |= FTDI_CTS;
if (flags & CHR_TIOCM_DSR)
ret |= FTDI_DSR;
if (flags & CHR_TIOCM_RI)
ret |= FTDI_RI;
if (flags & CHR_TIOCM_CAR)
ret |= FTDI_RLSD;
return ret;
}
static int usb_serial_handle_control(USBDevice *dev, int request, int value,
int index, int length, uint8_t *data)
{
USBSerialState *s = (USBSerialState *)dev;
int ret = 0;
//DPRINTF("got control %x, value %x\n",request, value);
switch (request) {
case DeviceRequest | USB_REQ_GET_STATUS:
data[0] = (0 << USB_DEVICE_SELF_POWERED) |
(dev->remote_wakeup << USB_DEVICE_REMOTE_WAKEUP);
data[1] = 0x00;
ret = 2;
break;
case DeviceOutRequest | USB_REQ_CLEAR_FEATURE:
if (value == USB_DEVICE_REMOTE_WAKEUP) {
dev->remote_wakeup = 0;
} else {
goto fail;
}
ret = 0;
break;
case DeviceOutRequest | USB_REQ_SET_FEATURE:
if (value == USB_DEVICE_REMOTE_WAKEUP) {
dev->remote_wakeup = 1;
} else {
goto fail;
}
ret = 0;
break;
case DeviceOutRequest | USB_REQ_SET_ADDRESS:
dev->addr = value;
ret = 0;
break;
case DeviceRequest | USB_REQ_GET_DESCRIPTOR:
switch(value >> 8) {
case USB_DT_DEVICE:
memcpy(data, qemu_serial_dev_descriptor,
sizeof(qemu_serial_dev_descriptor));
data[8] = s->vendorid & 0xff;
data[9] = ((s->vendorid) >> 8) & 0xff;
data[10] = s->productid & 0xff;
data[11] = ((s->productid) >> 8) & 0xff;
ret = sizeof(qemu_serial_dev_descriptor);
break;
case USB_DT_CONFIG:
memcpy(data, qemu_serial_config_descriptor,
sizeof(qemu_serial_config_descriptor));
ret = sizeof(qemu_serial_config_descriptor);
break;
case USB_DT_STRING:
switch(value & 0xff) {
case 0:
/* language ids */
data[0] = 4;
data[1] = 3;
data[2] = 0x09;
data[3] = 0x04;
ret = 4;
break;
case 1:
/* vendor description */
ret = set_usb_string(data, "QEMU " QEMU_VERSION);
break;
case 2:
/* product description */
ret = set_usb_string(data, "QEMU USB SERIAL");
break;
case 3:
/* serial number */
ret = set_usb_string(data, "1");
break;
default:
goto fail;
}
break;
default:
goto fail;
}
break;
case DeviceRequest | USB_REQ_GET_CONFIGURATION:
data[0] = 1;
ret = 1;
break;
case DeviceOutRequest | USB_REQ_SET_CONFIGURATION:
ret = 0;
break;
case DeviceRequest | USB_REQ_GET_INTERFACE:
data[0] = 0;
ret = 1;
break;
case InterfaceOutRequest | USB_REQ_SET_INTERFACE:
ret = 0;
break;
case EndpointOutRequest | USB_REQ_CLEAR_FEATURE:
ret = 0;
break;
/* Class specific requests. */
case DeviceOutVendor | FTDI_RESET:
switch (value) {
case FTDI_RESET_SIO:
usb_serial_reset(s);
break;
case FTDI_RESET_RX:
s->recv_ptr = 0;
s->recv_used = 0;
/* TODO: purge from char device */
break;
case FTDI_RESET_TX:
/* TODO: purge from char device */
break;
}
break;
case DeviceOutVendor | FTDI_SET_MDM_CTRL:
{
static int flags;
qemu_chr_ioctl(s->cs,CHR_IOCTL_SERIAL_GET_TIOCM, &flags);
if (value & FTDI_SET_RTS) {
if (value & FTDI_RTS)
flags |= CHR_TIOCM_RTS;
else
flags &= ~CHR_TIOCM_RTS;
}
if (value & FTDI_SET_DTR) {
if (value & FTDI_DTR)
flags |= CHR_TIOCM_DTR;
else
flags &= ~CHR_TIOCM_DTR;
}
qemu_chr_ioctl(s->cs,CHR_IOCTL_SERIAL_SET_TIOCM, &flags);
break;
}
case DeviceOutVendor | FTDI_SET_FLOW_CTRL:
/* TODO: ioctl */
break;
case DeviceOutVendor | FTDI_SET_BAUD: {
static const int subdivisors8[8] = { 0, 4, 2, 1, 3, 5, 6, 7 };
int subdivisor8 = subdivisors8[((value & 0xc000) >> 14)
| ((index & 1) << 2)];
int divisor = value & 0x3fff;
/* chip special cases */
if (divisor == 1 && subdivisor8 == 0)
subdivisor8 = 4;
if (divisor == 0 && subdivisor8 == 0)
divisor = 1;
s->params.speed = (48000000 / 2) / (8 * divisor + subdivisor8);
qemu_chr_ioctl(s->cs, CHR_IOCTL_SERIAL_SET_PARAMS, &s->params);
break;
}
case DeviceOutVendor | FTDI_SET_DATA:
switch (value & FTDI_PARITY) {
case 0:
s->params.parity = 'N';
break;
case FTDI_ODD:
s->params.parity = 'O';
break;
case FTDI_EVEN:
s->params.parity = 'E';
break;
default:
DPRINTF("unsupported parity %d\n", value & FTDI_PARITY);
goto fail;
}
switch (value & FTDI_STOP) {
case FTDI_STOP1:
s->params.stop_bits = 1;
break;
case FTDI_STOP2:
s->params.stop_bits = 2;
break;
default:
DPRINTF("unsupported stop bits %d\n", value & FTDI_STOP);
goto fail;
}
qemu_chr_ioctl(s->cs, CHR_IOCTL_SERIAL_SET_PARAMS, &s->params);
/* TODO: TX ON/OFF */
break;
case DeviceInVendor | FTDI_GET_MDM_ST:
data[0] = usb_get_modem_lines(s) | 1;
data[1] = 0;
ret = 2;
break;
case DeviceOutVendor | FTDI_SET_EVENT_CHR:
/* TODO: handle it */
s->event_chr = value;
break;
case DeviceOutVendor | FTDI_SET_ERROR_CHR:
/* TODO: handle it */
s->error_chr = value;
break;
case DeviceOutVendor | FTDI_SET_LATENCY:
s->latency = value;
break;
case DeviceInVendor | FTDI_GET_LATENCY:
data[0] = s->latency;
ret = 1;
break;
default:
fail:
DPRINTF("got unsupported/bogus control %x, value %x\n", request, value);
ret = USB_RET_STALL;
break;
}
return ret;
}
static int usb_serial_handle_data(USBDevice *dev, USBPacket *p)
{
USBSerialState *s = (USBSerialState *)dev;
int ret = 0;
uint8_t devep = p->devep;
uint8_t *data = p->data;
int len = p->len;
int first_len;
switch (p->pid) {
case USB_TOKEN_OUT:
if (devep != 2)
goto fail;
qemu_chr_write(s->cs, data, len);
break;
case USB_TOKEN_IN:
if (devep != 1)
goto fail;
first_len = RECV_BUF - s->recv_ptr;
if (len <= 2) {
ret = USB_RET_NAK;
break;
}
*data++ = usb_get_modem_lines(s) | 1;
/* We do not have the uart details */
/* handle serial break */
if (s->event_trigger && s->event_trigger & FTDI_BI) {
s->event_trigger &= ~FTDI_BI;
*data++ = FTDI_BI;
ret = 2;
break;
} else {
*data++ = 0;
}
len -= 2;
if (len > s->recv_used)
len = s->recv_used;
if (!len) {
ret = USB_RET_NAK;
break;
}
if (first_len > len)
first_len = len;
memcpy(data, s->recv_buf + s->recv_ptr, first_len);
if (len > first_len)
memcpy(data + first_len, s->recv_buf, len - first_len);
s->recv_used -= len;
s->recv_ptr = (s->recv_ptr + len) % RECV_BUF;
ret = len + 2;
break;
default:
DPRINTF("Bad token\n");
fail:
ret = USB_RET_STALL;
break;
}
return ret;
}
static void usb_serial_handle_destroy(USBDevice *dev)
{
USBSerialState *s = (USBSerialState *)dev;
qemu_chr_close(s->cs);
}
static int usb_serial_can_read(void *opaque)
{
USBSerialState *s = opaque;
return RECV_BUF - s->recv_used;
}
static void usb_serial_read(void *opaque, const uint8_t *buf, int size)
{
USBSerialState *s = opaque;
int first_size, start;
/* room in the buffer? */
if (size > (RECV_BUF - s->recv_used))
size = RECV_BUF - s->recv_used;
start = s->recv_ptr + s->recv_used;
if (start < RECV_BUF) {
/* copy data to end of buffer */
first_size = RECV_BUF - start;
if (first_size > size)
first_size = size;
memcpy(s->recv_buf + start, buf, first_size);
/* wrap around to front if needed */
if (size > first_size)
memcpy(s->recv_buf, buf + first_size, size - first_size);
} else {
start -= RECV_BUF;
memcpy(s->recv_buf + start, buf, size);
}
s->recv_used += size;
}
static void usb_serial_event(void *opaque, int event)
{
USBSerialState *s = opaque;
switch (event) {
case CHR_EVENT_BREAK:
s->event_trigger |= FTDI_BI;
break;
case CHR_EVENT_FOCUS:
break;
case CHR_EVENT_OPENED:
usb_serial_reset(s);
/* TODO: Reset USB port */
break;
}
}
static int usb_serial_initfn(USBDevice *dev)
{
USBSerialState *s = DO_UPCAST(USBSerialState, dev, dev);
s->dev.speed = USB_SPEED_FULL;
qemu_chr_add_handlers(s->cs, usb_serial_can_read, usb_serial_read,
usb_serial_event, s);
usb_serial_handle_reset(dev);
return 0;
}
static USBDevice *usb_serial_init(const char *filename)
{
USBDevice *dev;
CharDriverState *cdrv;
uint32_t vendorid = 0, productid = 0;
char label[32];
static int index;
while (*filename && *filename != ':') {
const char *p;
char *e;
if (strstart(filename, "vendorid=", &p)) {
vendorid = strtol(p, &e, 16);
if (e == p || (*e && *e != ',' && *e != ':')) {
qemu_error("bogus vendor ID %s\n", p);
return NULL;
}
filename = e;
} else if (strstart(filename, "productid=", &p)) {
productid = strtol(p, &e, 16);
if (e == p || (*e && *e != ',' && *e != ':')) {
qemu_error("bogus product ID %s\n", p);
return NULL;
}
filename = e;
} else {
qemu_error("unrecognized serial USB option %s\n", filename);
return NULL;
}
while(*filename == ',')
filename++;
}
if (!*filename) {
qemu_error("character device specification needed\n");
return NULL;
}
filename++;
snprintf(label, sizeof(label), "usbserial%d", index++);
cdrv = qemu_chr_open(label, filename, NULL);
if (!cdrv)
return NULL;
dev = usb_create(NULL /* FIXME */, "usb-serial");
qdev_prop_set_chr(&dev->qdev, "chardev", cdrv);
if (vendorid)
qdev_prop_set_uint16(&dev->qdev, "vendorid", vendorid);
if (productid)
qdev_prop_set_uint16(&dev->qdev, "productid", productid);
qdev_init_nofail(&dev->qdev);
return dev;
}
static USBDevice *usb_braille_init(const char *unused)
{
USBDevice *dev;
CharDriverState *cdrv;
cdrv = qemu_chr_open("braille", "braille", NULL);
if (!cdrv)
return NULL;
dev = usb_create(NULL /* FIXME */, "usb-braille");
qdev_prop_set_chr(&dev->qdev, "chardev", cdrv);
qdev_init_nofail(&dev->qdev);
return dev;
}
static struct USBDeviceInfo serial_info = {
.product_desc = "QEMU USB Serial",
.qdev.name = "usb-serial",
.qdev.size = sizeof(USBSerialState),
.init = usb_serial_initfn,
.handle_packet = usb_generic_handle_packet,
.handle_reset = usb_serial_handle_reset,
.handle_control = usb_serial_handle_control,
.handle_data = usb_serial_handle_data,
.handle_destroy = usb_serial_handle_destroy,
.usbdevice_name = "serial",
.usbdevice_init = usb_serial_init,
.qdev.props = (Property[]) {
DEFINE_PROP_CHR("chardev", USBSerialState, cs),
DEFINE_PROP_HEX32("vendorid", USBSerialState, vendorid, 0x0403),
DEFINE_PROP_HEX32("productid", USBSerialState, productid, 0x6001),
DEFINE_PROP_END_OF_LIST(),
},
};
static struct USBDeviceInfo braille_info = {
.product_desc = "QEMU USB Braille",
.qdev.name = "usb-braille",
.qdev.size = sizeof(USBSerialState),
.init = usb_serial_initfn,
.handle_packet = usb_generic_handle_packet,
.handle_reset = usb_serial_handle_reset,
.handle_control = usb_serial_handle_control,
.handle_data = usb_serial_handle_data,
.handle_destroy = usb_serial_handle_destroy,
.usbdevice_name = "braille",
.usbdevice_init = usb_braille_init,
.qdev.props = (Property[]) {
DEFINE_PROP_CHR("chardev", USBSerialState, cs),
DEFINE_PROP_HEX32("vendorid", USBSerialState, vendorid, 0x0403),
DEFINE_PROP_HEX32("productid", USBSerialState, productid, 0xfe72),
DEFINE_PROP_END_OF_LIST(),
},
};
static void usb_serial_register_devices(void)
{
usb_qdev_register(&serial_info);
usb_qdev_register(&braille_info);
}
device_init(usb_serial_register_devices)