aboutsummaryrefslogtreecommitdiffstats
path: root/usb/usb-serial-ftdi_sio-prevent-userspace-dos.patch
blob: 4b4d9cfea17618b80d3ac785b701faeaf60141f1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
From ian.abbott@mev.co.uk Mon Jun 26 04:59:49 2006
Message-ID: <449FCC15.3020903@mev.co.uk>
Date: Mon, 26 Jun 2006 12:59:17 +0100
From: Ian Abbott <abbotti@mev.co.uk>
To: linux-usb-devel@lists.sourceforge.net
Cc: Greg KH <greg@kroah.com>, Guillaume Autran <gautran@mrv.com>
Subject: USB serial ftdi_sio: Prevent userspace DoS

From: Ian Abbott <abbotti@mev.co.uk>

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>

---
 drivers/usb/serial/ftdi_sio.c |  103 ++++++++++++++++++++++++++++++++++++------
 1 file changed, 90 insertions(+), 13 deletions(-)

--- gregkh-2.6.orig/drivers/usb/serial/ftdi_sio.c
+++ gregkh-2.6/drivers/usb/serial/ftdi_sio.c
@@ -550,11 +550,17 @@ struct ftdi_private {
 	spinlock_t rx_lock;	/* spinlock for receive state */
 	struct work_struct rx_work;
 	int rx_processed;
+	unsigned long rx_bytes;
 
 	__u16 interface;	/* FT2232C port interface (0 for FT232/245) */
 
 	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_bytes;
+	unsigned long tx_outstanding_bytes;
+	unsigned long tx_outstanding_urbs;
 };
 
 /* Used for TIOCMIWAIT */
@@ -628,6 +634,9 @@ static struct usb_serial_driver ftdi_sio
 #define HIGH 1
 #define LOW 0
 
+/* number of outstanding urbs to prevent userspace DoS from happening */
+#define URB_UPPER_LIMIT	42
+
 /*
  * ***************************************************************************
  * Utlity functions
@@ -1158,6 +1167,7 @@ static int ftdi_sio_attach (struct usb_s
 	}
 
 	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 */
@@ -1272,6 +1282,13 @@ static int  ftdi_open (struct usb_serial
 
 	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)
 		port->tty->low_latency = (priv->flags & ASYNC_LOW_LATENCY) ? 1 : 0;
 
@@ -1374,6 +1391,7 @@ static int ftdi_write (struct usb_serial
 	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);
 
@@ -1381,6 +1399,13 @@ static int ftdi_write (struct usb_serial
 		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);
@@ -1447,6 +1472,12 @@ static int ftdi_write (struct usb_serial
 		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;
+		priv->tx_bytes += count;
+		spin_unlock_irqrestore(&priv->tx_lock, flags);
 	}
 
 	/* we are done with this urb, so let the host driver
@@ -1462,7 +1493,11 @@ static int ftdi_write (struct usb_serial
 
 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);
@@ -1474,34 +1509,67 @@ static void ftdi_write_bulk_callback (st
 		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);
 } /* 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 */
 
 
@@ -1511,6 +1579,8 @@ static void ftdi_read_bulk_callback (str
 	struct usb_serial_port *port = (struct usb_serial_port *)urb->context;
 	struct tty_struct *tty;
 	struct ftdi_private *priv;
+	unsigned long countread;
+	unsigned long flags;
 
 	if (urb->number_of_packets > 0) {
 		err("%s transfer_buffer_length %d actual_length %d number of packets %d",__FUNCTION__,
@@ -1545,6 +1615,13 @@ static void ftdi_read_bulk_callback (str
 		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_read_bulk_callback */