aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMarko Kohtala <marko.kohtala@gmail.com>2006-01-06 03:19:43 -0500
committerLinus Torvalds <torvalds@g5.osdl.org>2006-01-06 11:33:56 -0500
commitc660629059abbbd0eb56e12f9bb4494f01800bbc (patch)
treeda6a8d3a38e0c2e91b45dda210dc9f56306027a0
parent39ee059affaf57a152c64cd3a0adc3f48f02ed71 (diff)
[PATCH] parport: buffer overflow fix
Fix potential buffer overflow in case the device ID did not end in semicolon. Also might fail to negotiate back to IEEE1284_MODE_COMPAT in case of failure. parport_device_id did not return what Documentation/parport-lowlevel.txt said, so I changed it to match it. Determining device ID length is overly complicated, but Tim Waugh recalled on linux-parport seeing some buggy device that might need it. Signed-off-by: Marko Kohtala <marko.kohtala@gmail.com> Signed-off-by: Andrew Morton <akpm@osdl.org> Signed-off-by: Linus Torvalds <torvalds@osdl.org>
-rw-r--r--drivers/parport/probe.c193
1 files changed, 130 insertions, 63 deletions
diff --git a/drivers/parport/probe.c b/drivers/parport/probe.c
index 4b48b31ec235..5c29e8222211 100644
--- a/drivers/parport/probe.c
+++ b/drivers/parport/probe.c
@@ -128,8 +128,131 @@ static void parse_data(struct parport *port, int device, char *str)
128 kfree(txt); 128 kfree(txt);
129} 129}
130 130
131/* Read up to count-1 bytes of device id. Terminate buffer with
132 * '\0'. Buffer begins with two Device ID length bytes as given by
133 * device. */
134static ssize_t parport_read_device_id (struct parport *port, char *buffer,
135 size_t count)
136{
137 unsigned char length[2];
138 unsigned lelen, belen;
139 size_t idlens[4];
140 unsigned numidlens;
141 unsigned current_idlen;
142 ssize_t retval;
143 size_t len;
144
145 /* First two bytes are MSB,LSB of inclusive length. */
146 retval = parport_read (port, length, 2);
147
148 if (retval < 0)
149 return retval;
150 if (retval != 2)
151 return -EIO;
152
153 if (count < 2)
154 return 0;
155 memcpy(buffer, length, 2);
156 len = 2;
157
158 /* Some devices wrongly send LE length, and some send it two
159 * bytes short. Construct a sorted array of lengths to try. */
160 belen = (length[0] << 8) + length[1];
161 lelen = (length[1] << 8) + length[0];
162 idlens[0] = min(belen, lelen);
163 idlens[1] = idlens[0]+2;
164 if (belen != lelen) {
165 int off = 2;
166 /* Don't try lenghts of 0x100 and 0x200 as 1 and 2 */
167 if (idlens[0] <= 2)
168 off = 0;
169 idlens[off] = max(belen, lelen);
170 idlens[off+1] = idlens[off]+2;
171 numidlens = off+2;
172 }
173 else {
174 /* Some devices don't truly implement Device ID, but
175 * just return constant nibble forever. This catches
176 * also those cases. */
177 if (idlens[0] == 0 || idlens[0] > 0xFFF) {
178 printk (KERN_DEBUG "%s: reported broken Device ID"
179 " length of %#zX bytes\n",
180 port->name, idlens[0]);
181 return -EIO;
182 }
183 numidlens = 2;
184 }
185
186 /* Try to respect the given ID length despite all the bugs in
187 * the ID length. Read according to shortest possible ID
188 * first. */
189 for (current_idlen = 0; current_idlen < numidlens; ++current_idlen) {
190 size_t idlen = idlens[current_idlen];
191 if (idlen+1 >= count)
192 break;
193
194 retval = parport_read (port, buffer+len, idlen-len);
195
196 if (retval < 0)
197 return retval;
198 len += retval;
199
200 if (port->physport->ieee1284.phase != IEEE1284_PH_HBUSY_DAVAIL) {
201 if (belen != len) {
202 printk (KERN_DEBUG "%s: Device ID was %d bytes"
203 " while device told it would be %d"
204 " bytes\n",
205 port->name, len, belen);
206 }
207 goto done;
208 }
209
210 /* This might end reading the Device ID too
211 * soon. Hopefully the needed fields were already in
212 * the first 256 bytes or so that we must have read so
213 * far. */
214 if (buffer[len-1] == ';') {
215 printk (KERN_DEBUG "%s: Device ID reading stopped"
216 " before device told data not available. "
217 "Current idlen %d of %d, len bytes %02X %02X\n",
218 port->name, current_idlen, numidlens,
219 length[0], length[1]);
220 goto done;
221 }
222 }
223 if (current_idlen < numidlens) {
224 /* Buffer not large enough, read to end of buffer. */
225 size_t idlen, len2;
226 if (len+1 < count) {
227 retval = parport_read (port, buffer+len, count-len-1);
228 if (retval < 0)
229 return retval;
230 len += retval;
231 }
232 /* Read the whole ID since some devices would not
233 * otherwise give back the Device ID from beginning
234 * next time when asked. */
235 idlen = idlens[current_idlen];
236 len2 = len;
237 while(len2 < idlen && retval > 0) {
238 char tmp[4];
239 retval = parport_read (port, tmp,
240 min(sizeof tmp, idlen-len2));
241 if (retval < 0)
242 return retval;
243 len2 += retval;
244 }
245 }
246 /* In addition, there are broken devices out there that don't
247 even finish off with a semi-colon. We do not need to care
248 about those at this time. */
249 done:
250 buffer[len] = '\0';
251 return len;
252}
253
131/* Get Std 1284 Device ID. */ 254/* Get Std 1284 Device ID. */
132ssize_t parport_device_id (int devnum, char *buffer, size_t len) 255ssize_t parport_device_id (int devnum, char *buffer, size_t count)
133{ 256{
134 ssize_t retval = -ENXIO; 257 ssize_t retval = -ENXIO;
135 struct pardevice *dev = parport_open (devnum, "Device ID probe", 258 struct pardevice *dev = parport_open (devnum, "Device ID probe",
@@ -139,76 +262,20 @@ ssize_t parport_device_id (int devnum, char *buffer, size_t len)
139 262
140 parport_claim_or_block (dev); 263 parport_claim_or_block (dev);
141 264
142 /* Negotiate to compatibility mode, and then to device ID mode. 265 /* Negotiate to compatibility mode, and then to device ID
143 * (This is in case we are already in device ID mode.) */ 266 * mode. (This so that we start form beginning of device ID if
267 * already in device ID mode.) */
144 parport_negotiate (dev->port, IEEE1284_MODE_COMPAT); 268 parport_negotiate (dev->port, IEEE1284_MODE_COMPAT);
145 retval = parport_negotiate (dev->port, 269 retval = parport_negotiate (dev->port,
146 IEEE1284_MODE_NIBBLE | IEEE1284_DEVICEID); 270 IEEE1284_MODE_NIBBLE | IEEE1284_DEVICEID);
147 271
148 if (!retval) { 272 if (!retval) {
149 int idlen; 273 retval = parport_read_device_id (dev->port, buffer, count);
150 unsigned char length[2];
151
152 /* First two bytes are MSB,LSB of inclusive length. */
153 retval = parport_read (dev->port, length, 2);
154
155 if (retval != 2) goto end_id;
156
157 idlen = (length[0] << 8) + length[1] - 2;
158 /*
159 * Check if the caller-allocated buffer is large enough
160 * otherwise bail out or there will be an at least off by one.
161 */
162 if (idlen + 1 < len)
163 len = idlen;
164 else {
165 retval = -EINVAL;
166 goto out;
167 }
168 retval = parport_read (dev->port, buffer, len);
169
170 if (retval != len)
171 printk (KERN_DEBUG "%s: only read %Zd of %Zd ID bytes\n",
172 dev->port->name, retval,
173 len);
174
175 /* Some printer manufacturers mistakenly believe that
176 the length field is supposed to be _exclusive_.
177 In addition, there are broken devices out there
178 that don't even finish off with a semi-colon. */
179 if (buffer[len - 1] != ';') {
180 ssize_t diff;
181 diff = parport_read (dev->port, buffer + len, 2);
182 retval += diff;
183
184 if (diff)
185 printk (KERN_DEBUG
186 "%s: device reported incorrect "
187 "length field (%d, should be %Zd)\n",
188 dev->port->name, idlen, retval);
189 else {
190 /* One semi-colon short of a device ID. */
191 buffer[len++] = ';';
192 printk (KERN_DEBUG "%s: faking semi-colon\n",
193 dev->port->name);
194
195 /* If we get here, I don't think we
196 need to worry about the possible
197 standard violation of having read
198 more than we were told to. The
199 device is non-compliant anyhow. */
200 }
201 }
202
203 end_id:
204 buffer[len] = '\0';
205 parport_negotiate (dev->port, IEEE1284_MODE_COMPAT); 274 parport_negotiate (dev->port, IEEE1284_MODE_COMPAT);
275 if (retval > 2)
276 parse_data (dev->port, dev->daisy, buffer+2);
206 } 277 }
207 278
208 if (retval > 2)
209 parse_data (dev->port, dev->daisy, buffer);
210
211out:
212 parport_release (dev); 279 parport_release (dev);
213 parport_close (dev); 280 parport_close (dev);
214 return retval; 281 return retval;