]> git.kernelconcepts.de Git - karo-tx-linux.git/commitdiff
[PATCH] USB serial ftdi_sio: Prevent userspace DoS (CVE-2006-2936)
authorIan Abbott <abbotti@mev.co.uk>
Mon, 26 Jun 2006 11:59:17 +0000 (12:59 +0100)
committerGreg Kroah-Hartman <gregkh@suse.de>
Mon, 17 Jul 2006 13:54:20 +0000 (06:54 -0700)
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.

Signed-off-by: Ian Abbott <abbotti@mev.co.uk>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
drivers/usb/serial/ftdi_sio.c

index c145e1ed8429d6bdf813bb534b441c2db1d149e0..b64b9d3542e6ecd54061e75c08b0429d33b97b29 100644 (file)
@@ -545,6 +545,10 @@ struct ftdi_private {
 
        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 */
+
+       spinlock_t tx_lock;     /* spinlock for transmit state */
+       unsigned long tx_outstanding_bytes;
+       unsigned long tx_outstanding_urbs;
 };
 
 /* Used for TIOCMIWAIT */
@@ -618,6 +622,9 @@ static struct usb_serial_driver ftdi_sio_device = {
 #define HIGH 1
 #define LOW 0
 
+/* number of outstanding urbs to prevent userspace DoS from happening */
+#define URB_UPPER_LIMIT        42
+
 /*
  * ***************************************************************************
  * Utlity functions
@@ -1149,6 +1156,7 @@ static int ftdi_sio_attach (struct usb_serial *serial)
        memset(priv, 0, sizeof(*priv));
 
        spin_lock_init(&priv->rx_lock);
+       spin_lock_init(&priv->tx_lock);
         init_waitqueue_head(&priv->delta_msr_wait);
        /* This will push the characters through immediately rather
           than queue a task to deliver them */
@@ -1365,6 +1373,7 @@ static int ftdi_write (struct usb_serial_port *port,
        int data_offset ;       /* will be 1 for the SIO and 0 otherwise */
        int status;
        int transfer_size;
+       unsigned long flags;
 
        dbg("%s port %d, %d bytes", __FUNCTION__, port->number, count);
 
@@ -1372,6 +1381,13 @@ static int ftdi_write (struct usb_serial_port *port,
                dbg("write request of 0 bytes");
                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;
         dbg("data_offset set to %d",data_offset);
@@ -1438,6 +1454,11 @@ static int ftdi_write (struct usb_serial_port *port,
                err("%s - failed submitting write urb, error %d", __FUNCTION__, status);
                count = status;
                kfree (buffer);
+       } else {
+               spin_lock_irqsave(&priv->tx_lock, flags);
+               ++priv->tx_outstanding_urbs;
+               priv->tx_outstanding_bytes += count;
+               spin_unlock_irqrestore(&priv->tx_lock, flags);
        }
 
        /* we are done with this urb, so let the host driver
@@ -1453,7 +1474,11 @@ static int ftdi_write (struct usb_serial_port *port,
 
 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 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 */
        kfree (urb->transfer_buffer);
@@ -1465,34 +1490,67 @@ static void ftdi_write_bulk_callback (struct urb *urb, struct pt_regs *regs)
                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);
+
        schedule_work(&port->work);
 } /* ftdi_write_bulk_callback */
 
 
 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);
 
-       /*
-        * We really can take anything the user throws at us
-        * but let's pick a nice big number to tell the tty
-        * layer that we have lots of free space
-        */
-       return 2048;
+       spin_lock_irqsave(&priv->tx_lock, flags);
+       if (priv->tx_outstanding_urbs < URB_UPPER_LIMIT) {
+               /*
+                * We really can take anything the user throws at us
+                * but let's pick a nice big number to tell the tty
+                * 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 */
 
 
 static int ftdi_chars_in_buffer (struct usb_serial_port *port)
 { /* 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);
 
-       /* 
-        * We can't really account for how much data we
-        * have sent out, but hasn't made it through to the
-        * device, so just tell the tty layer that everything
-        * is flushed.
-        */
-       return 0;
+       spin_lock_irqsave(&priv->tx_lock, flags);
+       buffered = (int)priv->tx_outstanding_bytes;
+       spin_unlock_irqrestore(&priv->tx_lock, flags);
+       if (buffered < 0) {
+               err("%s outstanding tx bytes is negative!", __FUNCTION__);
+               buffered = 0;
+       }
+       return buffered;
 } /* ftdi_chars_in_buffer */