[PATCH] USB serial ftdi_sio: Prevent userspace DoS

This patch limits the amount of outstanding 'write' data that can be
queued up for the ftdi_sio driver, to prevent userspace DoS attacks (or
simple accidents) that use up all the system memory by writing lots of
data to the serial port.

The original patch was by Guillaume Autran, who in turn based it on the
same mechanism implemented in the 'visor' driver.  I (Ian Abbott)
re-targeted the patch to the latest sources, fixed a couple of errors,
renamed his new structure members, and updated the implementations of
the 'write_room' and 'chars_in_buffer' methods to take account of the
number of outstanding 'write' bytes.  It seems to work fine, though at
low baud rates it is still possible to queue up an amount of data that
takes an age to shift (a job for another day!).

Signed-off-by: Ian Abbott <abbotti@mev.co.uk>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
This commit is contained in:
Ian Abbott 2006-06-26 12:59:17 +01:00 committed by Greg Kroah-Hartman
parent 00d6058ac9
commit 224654004c

View file

@ -550,11 +550,17 @@ struct ftdi_private {
spinlock_t rx_lock; /* spinlock for receive state */ spinlock_t rx_lock; /* spinlock for receive state */
struct work_struct rx_work; struct work_struct rx_work;
int rx_processed; int rx_processed;
unsigned long rx_bytes;
__u16 interface; /* FT2232C port interface (0 for FT232/245) */ __u16 interface; /* FT2232C port interface (0 for FT232/245) */
int force_baud; /* if non-zero, force the baud rate to this value */ int force_baud; /* if non-zero, force the baud rate to this value */
int force_rtscts; /* if non-zero, force RTS-CTS to always be enabled */ int force_rtscts; /* if non-zero, force RTS-CTS to always be enabled */
spinlock_t tx_lock; /* spinlock for transmit state */
unsigned long tx_bytes;
unsigned long tx_outstanding_bytes;
unsigned long tx_outstanding_urbs;
}; };
/* Used for TIOCMIWAIT */ /* Used for TIOCMIWAIT */
@ -628,6 +634,9 @@ static struct usb_serial_driver ftdi_sio_device = {
#define HIGH 1 #define HIGH 1
#define LOW 0 #define LOW 0
/* number of outstanding urbs to prevent userspace DoS from happening */
#define URB_UPPER_LIMIT 42
/* /*
* *************************************************************************** * ***************************************************************************
* Utlity functions * Utlity functions
@ -1158,6 +1167,7 @@ static int ftdi_sio_attach (struct usb_serial *serial)
} }
spin_lock_init(&priv->rx_lock); spin_lock_init(&priv->rx_lock);
spin_lock_init(&priv->tx_lock);
init_waitqueue_head(&priv->delta_msr_wait); init_waitqueue_head(&priv->delta_msr_wait);
/* This will push the characters through immediately rather /* This will push the characters through immediately rather
than queue a task to deliver them */ than queue a task to deliver them */
@ -1272,6 +1282,13 @@ static int ftdi_open (struct usb_serial_port *port, struct file *filp)
dbg("%s", __FUNCTION__); dbg("%s", __FUNCTION__);
spin_lock_irqsave(&priv->tx_lock, flags);
priv->tx_bytes = 0;
spin_unlock_irqrestore(&priv->tx_lock, flags);
spin_lock_irqsave(&priv->rx_lock, flags);
priv->rx_bytes = 0;
spin_unlock_irqrestore(&priv->rx_lock, flags);
if (port->tty) if (port->tty)
port->tty->low_latency = (priv->flags & ASYNC_LOW_LATENCY) ? 1 : 0; port->tty->low_latency = (priv->flags & ASYNC_LOW_LATENCY) ? 1 : 0;
@ -1374,6 +1391,7 @@ static int ftdi_write (struct usb_serial_port *port,
int data_offset ; /* will be 1 for the SIO and 0 otherwise */ int data_offset ; /* will be 1 for the SIO and 0 otherwise */
int status; int status;
int transfer_size; int transfer_size;
unsigned long flags;
dbg("%s port %d, %d bytes", __FUNCTION__, port->number, count); dbg("%s port %d, %d bytes", __FUNCTION__, port->number, count);
@ -1381,6 +1399,13 @@ static int ftdi_write (struct usb_serial_port *port,
dbg("write request of 0 bytes"); dbg("write request of 0 bytes");
return 0; return 0;
} }
spin_lock_irqsave(&priv->tx_lock, flags);
if (priv->tx_outstanding_urbs > URB_UPPER_LIMIT) {
spin_unlock_irqrestore(&priv->tx_lock, flags);
dbg("%s - write limit hit\n", __FUNCTION__);
return 0;
}
spin_unlock_irqrestore(&priv->tx_lock, flags);
data_offset = priv->write_offset; data_offset = priv->write_offset;
dbg("data_offset set to %d",data_offset); dbg("data_offset set to %d",data_offset);
@ -1447,6 +1472,12 @@ static int ftdi_write (struct usb_serial_port *port,
err("%s - failed submitting write urb, error %d", __FUNCTION__, status); err("%s - failed submitting write urb, error %d", __FUNCTION__, status);
count = status; count = status;
kfree (buffer); kfree (buffer);
} else {
spin_lock_irqsave(&priv->tx_lock, flags);
++priv->tx_outstanding_urbs;
priv->tx_outstanding_bytes += count;
priv->tx_bytes += count;
spin_unlock_irqrestore(&priv->tx_lock, flags);
} }
/* we are done with this urb, so let the host driver /* we are done with this urb, so let the host driver
@ -1462,7 +1493,11 @@ static int ftdi_write (struct usb_serial_port *port,
static void ftdi_write_bulk_callback (struct urb *urb, struct pt_regs *regs) static void ftdi_write_bulk_callback (struct urb *urb, struct pt_regs *regs)
{ {
unsigned long flags;
struct usb_serial_port *port = (struct usb_serial_port *)urb->context; struct usb_serial_port *port = (struct usb_serial_port *)urb->context;
struct ftdi_private *priv;
int data_offset; /* will be 1 for the SIO and 0 otherwise */
unsigned long countback;
/* free up the transfer buffer, as usb_free_urb() does not do this */ /* free up the transfer buffer, as usb_free_urb() does not do this */
kfree (urb->transfer_buffer); kfree (urb->transfer_buffer);
@ -1474,34 +1509,67 @@ static void ftdi_write_bulk_callback (struct urb *urb, struct pt_regs *regs)
return; return;
} }
priv = usb_get_serial_port_data(port);
if (!priv) {
dbg("%s - bad port private data pointer - exiting", __FUNCTION__);
return;
}
/* account for transferred data */
countback = urb->actual_length;
data_offset = priv->write_offset;
if (data_offset > 0) {
/* Subtract the control bytes */
countback -= (data_offset * ((countback + (PKTSZ - 1)) / PKTSZ));
}
spin_lock_irqsave(&priv->tx_lock, flags);
--priv->tx_outstanding_urbs;
priv->tx_outstanding_bytes -= countback;
spin_unlock_irqrestore(&priv->tx_lock, flags);
usb_serial_port_softint(port); usb_serial_port_softint(port);
} /* ftdi_write_bulk_callback */ } /* ftdi_write_bulk_callback */
static int ftdi_write_room( struct usb_serial_port *port ) static int ftdi_write_room( struct usb_serial_port *port )
{ {
struct ftdi_private *priv = usb_get_serial_port_data(port);
int room;
unsigned long flags;
dbg("%s - port %d", __FUNCTION__, port->number); dbg("%s - port %d", __FUNCTION__, port->number);
/* spin_lock_irqsave(&priv->tx_lock, flags);
* We really can take anything the user throws at us if (priv->tx_outstanding_urbs < URB_UPPER_LIMIT) {
* but let's pick a nice big number to tell the tty /*
* layer that we have lots of free space * We really can take anything the user throws at us
*/ * but let's pick a nice big number to tell the tty
return 2048; * layer that we have lots of free space
*/
room = 2048;
} else {
room = 0;
}
spin_unlock_irqrestore(&priv->tx_lock, flags);
return room;
} /* ftdi_write_room */ } /* ftdi_write_room */
static int ftdi_chars_in_buffer (struct usb_serial_port *port) static int ftdi_chars_in_buffer (struct usb_serial_port *port)
{ /* ftdi_chars_in_buffer */ { /* ftdi_chars_in_buffer */
struct ftdi_private *priv = usb_get_serial_port_data(port);
int buffered;
unsigned long flags;
dbg("%s - port %d", __FUNCTION__, port->number); dbg("%s - port %d", __FUNCTION__, port->number);
/* spin_lock_irqsave(&priv->tx_lock, flags);
* We can't really account for how much data we buffered = (int)priv->tx_outstanding_bytes;
* have sent out, but hasn't made it through to the spin_unlock_irqrestore(&priv->tx_lock, flags);
* device, so just tell the tty layer that everything if (buffered < 0) {
* is flushed. err("%s outstanding tx bytes is negative!", __FUNCTION__);
*/ buffered = 0;
return 0; }
return buffered;
} /* ftdi_chars_in_buffer */ } /* ftdi_chars_in_buffer */
@ -1511,6 +1579,8 @@ static void ftdi_read_bulk_callback (struct urb *urb, struct pt_regs *regs)
struct usb_serial_port *port = (struct usb_serial_port *)urb->context; struct usb_serial_port *port = (struct usb_serial_port *)urb->context;
struct tty_struct *tty; struct tty_struct *tty;
struct ftdi_private *priv; struct ftdi_private *priv;
unsigned long countread;
unsigned long flags;
if (urb->number_of_packets > 0) { if (urb->number_of_packets > 0) {
err("%s transfer_buffer_length %d actual_length %d number of packets %d",__FUNCTION__, err("%s transfer_buffer_length %d actual_length %d number of packets %d",__FUNCTION__,
@ -1545,6 +1615,13 @@ static void ftdi_read_bulk_callback (struct urb *urb, struct pt_regs *regs)
return; return;
} }
/* count data bytes, but not status bytes */
countread = urb->actual_length;
countread -= 2 * ((countread + (PKTSZ - 1)) / PKTSZ);
spin_lock_irqsave(&priv->rx_lock, flags);
priv->rx_bytes += countread;
spin_unlock_irqrestore(&priv->rx_lock, flags);
ftdi_process_read(port); ftdi_process_read(port);
} /* ftdi_read_bulk_callback */ } /* ftdi_read_bulk_callback */