.. | .. |
---|
20 | 20 | #include <linux/syscalls.h> |
---|
21 | 21 | #include <linux/unistd.h> |
---|
22 | 22 | #include <linux/compat.h> |
---|
23 | | - |
---|
24 | 23 | #include <linux/uaccess.h> |
---|
| 24 | + |
---|
| 25 | +#include <asm/unaligned.h> |
---|
| 26 | + |
---|
| 27 | +/* |
---|
| 28 | + * Note the "unsafe_put_user() semantics: we goto a |
---|
| 29 | + * label for errors. |
---|
| 30 | + */ |
---|
| 31 | +#define unsafe_copy_dirent_name(_dst, _src, _len, label) do { \ |
---|
| 32 | + char __user *dst = (_dst); \ |
---|
| 33 | + const char *src = (_src); \ |
---|
| 34 | + size_t len = (_len); \ |
---|
| 35 | + unsafe_put_user(0, dst+len, label); \ |
---|
| 36 | + unsafe_copy_to_user(dst, src, len, label); \ |
---|
| 37 | +} while (0) |
---|
| 38 | + |
---|
25 | 39 | |
---|
26 | 40 | int iterate_dir(struct file *file, struct dir_context *ctx) |
---|
27 | 41 | { |
---|
.. | .. |
---|
88 | 102 | * filename length, and the above "soft error" worry means |
---|
89 | 103 | * that it's probably better left alone until we have that |
---|
90 | 104 | * issue clarified. |
---|
| 105 | + * |
---|
| 106 | + * Note the PATH_MAX check - it's arbitrary but the real |
---|
| 107 | + * kernel limit on a possible path component, not NAME_MAX, |
---|
| 108 | + * which is the technical standard limit. |
---|
91 | 109 | */ |
---|
92 | 110 | static int verify_dirent_name(const char *name, int len) |
---|
93 | 111 | { |
---|
94 | | - if (!len) |
---|
| 112 | + if (len <= 0 || len >= PATH_MAX) |
---|
95 | 113 | return -EIO; |
---|
96 | 114 | if (memchr(name, '/', len)) |
---|
97 | 115 | return -EIO; |
---|
.. | .. |
---|
142 | 160 | } |
---|
143 | 161 | buf->result++; |
---|
144 | 162 | dirent = buf->dirent; |
---|
145 | | - if (!access_ok(VERIFY_WRITE, dirent, |
---|
| 163 | + if (!user_write_access_begin(dirent, |
---|
146 | 164 | (unsigned long)(dirent->d_name + namlen + 1) - |
---|
147 | 165 | (unsigned long)dirent)) |
---|
148 | 166 | goto efault; |
---|
149 | | - if ( __put_user(d_ino, &dirent->d_ino) || |
---|
150 | | - __put_user(offset, &dirent->d_offset) || |
---|
151 | | - __put_user(namlen, &dirent->d_namlen) || |
---|
152 | | - __copy_to_user(dirent->d_name, name, namlen) || |
---|
153 | | - __put_user(0, dirent->d_name + namlen)) |
---|
154 | | - goto efault; |
---|
| 167 | + unsafe_put_user(d_ino, &dirent->d_ino, efault_end); |
---|
| 168 | + unsafe_put_user(offset, &dirent->d_offset, efault_end); |
---|
| 169 | + unsafe_put_user(namlen, &dirent->d_namlen, efault_end); |
---|
| 170 | + unsafe_copy_dirent_name(dirent->d_name, name, namlen, efault_end); |
---|
| 171 | + user_write_access_end(); |
---|
155 | 172 | return 0; |
---|
| 173 | +efault_end: |
---|
| 174 | + user_write_access_end(); |
---|
156 | 175 | efault: |
---|
157 | 176 | buf->result = -EFAULT; |
---|
158 | 177 | return -EFAULT; |
---|
.. | .. |
---|
195 | 214 | struct getdents_callback { |
---|
196 | 215 | struct dir_context ctx; |
---|
197 | 216 | struct linux_dirent __user * current_dir; |
---|
198 | | - struct linux_dirent __user * previous; |
---|
| 217 | + int prev_reclen; |
---|
199 | 218 | int count; |
---|
200 | 219 | int error; |
---|
201 | 220 | }; |
---|
.. | .. |
---|
203 | 222 | static int filldir(struct dir_context *ctx, const char *name, int namlen, |
---|
204 | 223 | loff_t offset, u64 ino, unsigned int d_type) |
---|
205 | 224 | { |
---|
206 | | - struct linux_dirent __user * dirent; |
---|
| 225 | + struct linux_dirent __user *dirent, *prev; |
---|
207 | 226 | struct getdents_callback *buf = |
---|
208 | 227 | container_of(ctx, struct getdents_callback, ctx); |
---|
209 | 228 | unsigned long d_ino; |
---|
210 | 229 | int reclen = ALIGN(offsetof(struct linux_dirent, d_name) + namlen + 2, |
---|
211 | 230 | sizeof(long)); |
---|
| 231 | + int prev_reclen; |
---|
212 | 232 | |
---|
213 | 233 | buf->error = verify_dirent_name(name, namlen); |
---|
214 | 234 | if (unlikely(buf->error)) |
---|
.. | .. |
---|
221 | 241 | buf->error = -EOVERFLOW; |
---|
222 | 242 | return -EOVERFLOW; |
---|
223 | 243 | } |
---|
224 | | - dirent = buf->previous; |
---|
225 | | - if (dirent) { |
---|
226 | | - if (signal_pending(current)) |
---|
227 | | - return -EINTR; |
---|
228 | | - if (__put_user(offset, &dirent->d_off)) |
---|
229 | | - goto efault; |
---|
230 | | - } |
---|
| 244 | + prev_reclen = buf->prev_reclen; |
---|
| 245 | + if (prev_reclen && signal_pending(current)) |
---|
| 246 | + return -EINTR; |
---|
231 | 247 | dirent = buf->current_dir; |
---|
232 | | - if (__put_user(d_ino, &dirent->d_ino)) |
---|
| 248 | + prev = (void __user *) dirent - prev_reclen; |
---|
| 249 | + if (!user_write_access_begin(prev, reclen + prev_reclen)) |
---|
233 | 250 | goto efault; |
---|
234 | | - if (__put_user(reclen, &dirent->d_reclen)) |
---|
235 | | - goto efault; |
---|
236 | | - if (copy_to_user(dirent->d_name, name, namlen)) |
---|
237 | | - goto efault; |
---|
238 | | - if (__put_user(0, dirent->d_name + namlen)) |
---|
239 | | - goto efault; |
---|
240 | | - if (__put_user(d_type, (char __user *) dirent + reclen - 1)) |
---|
241 | | - goto efault; |
---|
242 | | - buf->previous = dirent; |
---|
243 | | - dirent = (void __user *)dirent + reclen; |
---|
244 | | - buf->current_dir = dirent; |
---|
| 251 | + |
---|
| 252 | + /* This might be 'dirent->d_off', but if so it will get overwritten */ |
---|
| 253 | + unsafe_put_user(offset, &prev->d_off, efault_end); |
---|
| 254 | + unsafe_put_user(d_ino, &dirent->d_ino, efault_end); |
---|
| 255 | + unsafe_put_user(reclen, &dirent->d_reclen, efault_end); |
---|
| 256 | + unsafe_put_user(d_type, (char __user *) dirent + reclen - 1, efault_end); |
---|
| 257 | + unsafe_copy_dirent_name(dirent->d_name, name, namlen, efault_end); |
---|
| 258 | + user_write_access_end(); |
---|
| 259 | + |
---|
| 260 | + buf->current_dir = (void __user *)dirent + reclen; |
---|
| 261 | + buf->prev_reclen = reclen; |
---|
245 | 262 | buf->count -= reclen; |
---|
246 | 263 | return 0; |
---|
| 264 | +efault_end: |
---|
| 265 | + user_write_access_end(); |
---|
247 | 266 | efault: |
---|
248 | 267 | buf->error = -EFAULT; |
---|
249 | 268 | return -EFAULT; |
---|
.. | .. |
---|
253 | 272 | struct linux_dirent __user *, dirent, unsigned int, count) |
---|
254 | 273 | { |
---|
255 | 274 | struct fd f; |
---|
256 | | - struct linux_dirent __user * lastdirent; |
---|
257 | 275 | struct getdents_callback buf = { |
---|
258 | 276 | .ctx.actor = filldir, |
---|
259 | 277 | .count = count, |
---|
260 | 278 | .current_dir = dirent |
---|
261 | 279 | }; |
---|
262 | 280 | int error; |
---|
263 | | - |
---|
264 | | - if (!access_ok(VERIFY_WRITE, dirent, count)) |
---|
265 | | - return -EFAULT; |
---|
266 | 281 | |
---|
267 | 282 | f = fdget_pos(fd); |
---|
268 | 283 | if (!f.file) |
---|
.. | .. |
---|
271 | 286 | error = iterate_dir(f.file, &buf.ctx); |
---|
272 | 287 | if (error >= 0) |
---|
273 | 288 | error = buf.error; |
---|
274 | | - lastdirent = buf.previous; |
---|
275 | | - if (lastdirent) { |
---|
| 289 | + if (buf.prev_reclen) { |
---|
| 290 | + struct linux_dirent __user * lastdirent; |
---|
| 291 | + lastdirent = (void __user *)buf.current_dir - buf.prev_reclen; |
---|
| 292 | + |
---|
276 | 293 | if (put_user(buf.ctx.pos, &lastdirent->d_off)) |
---|
277 | 294 | error = -EFAULT; |
---|
278 | 295 | else |
---|
.. | .. |
---|
285 | 302 | struct getdents_callback64 { |
---|
286 | 303 | struct dir_context ctx; |
---|
287 | 304 | struct linux_dirent64 __user * current_dir; |
---|
288 | | - struct linux_dirent64 __user * previous; |
---|
| 305 | + int prev_reclen; |
---|
289 | 306 | int count; |
---|
290 | 307 | int error; |
---|
291 | 308 | }; |
---|
.. | .. |
---|
293 | 310 | static int filldir64(struct dir_context *ctx, const char *name, int namlen, |
---|
294 | 311 | loff_t offset, u64 ino, unsigned int d_type) |
---|
295 | 312 | { |
---|
296 | | - struct linux_dirent64 __user *dirent; |
---|
| 313 | + struct linux_dirent64 __user *dirent, *prev; |
---|
297 | 314 | struct getdents_callback64 *buf = |
---|
298 | 315 | container_of(ctx, struct getdents_callback64, ctx); |
---|
299 | 316 | int reclen = ALIGN(offsetof(struct linux_dirent64, d_name) + namlen + 1, |
---|
300 | 317 | sizeof(u64)); |
---|
| 318 | + int prev_reclen; |
---|
301 | 319 | |
---|
302 | 320 | buf->error = verify_dirent_name(name, namlen); |
---|
303 | 321 | if (unlikely(buf->error)) |
---|
.. | .. |
---|
305 | 323 | buf->error = -EINVAL; /* only used if we fail.. */ |
---|
306 | 324 | if (reclen > buf->count) |
---|
307 | 325 | return -EINVAL; |
---|
308 | | - dirent = buf->previous; |
---|
309 | | - if (dirent) { |
---|
310 | | - if (signal_pending(current)) |
---|
311 | | - return -EINTR; |
---|
312 | | - if (__put_user(offset, &dirent->d_off)) |
---|
313 | | - goto efault; |
---|
314 | | - } |
---|
| 326 | + prev_reclen = buf->prev_reclen; |
---|
| 327 | + if (prev_reclen && signal_pending(current)) |
---|
| 328 | + return -EINTR; |
---|
315 | 329 | dirent = buf->current_dir; |
---|
316 | | - if (__put_user(ino, &dirent->d_ino)) |
---|
| 330 | + prev = (void __user *)dirent - prev_reclen; |
---|
| 331 | + if (!user_write_access_begin(prev, reclen + prev_reclen)) |
---|
317 | 332 | goto efault; |
---|
318 | | - if (__put_user(0, &dirent->d_off)) |
---|
319 | | - goto efault; |
---|
320 | | - if (__put_user(reclen, &dirent->d_reclen)) |
---|
321 | | - goto efault; |
---|
322 | | - if (__put_user(d_type, &dirent->d_type)) |
---|
323 | | - goto efault; |
---|
324 | | - if (copy_to_user(dirent->d_name, name, namlen)) |
---|
325 | | - goto efault; |
---|
326 | | - if (__put_user(0, dirent->d_name + namlen)) |
---|
327 | | - goto efault; |
---|
328 | | - buf->previous = dirent; |
---|
329 | | - dirent = (void __user *)dirent + reclen; |
---|
330 | | - buf->current_dir = dirent; |
---|
| 333 | + |
---|
| 334 | + /* This might be 'dirent->d_off', but if so it will get overwritten */ |
---|
| 335 | + unsafe_put_user(offset, &prev->d_off, efault_end); |
---|
| 336 | + unsafe_put_user(ino, &dirent->d_ino, efault_end); |
---|
| 337 | + unsafe_put_user(reclen, &dirent->d_reclen, efault_end); |
---|
| 338 | + unsafe_put_user(d_type, &dirent->d_type, efault_end); |
---|
| 339 | + unsafe_copy_dirent_name(dirent->d_name, name, namlen, efault_end); |
---|
| 340 | + user_write_access_end(); |
---|
| 341 | + |
---|
| 342 | + buf->prev_reclen = reclen; |
---|
| 343 | + buf->current_dir = (void __user *)dirent + reclen; |
---|
331 | 344 | buf->count -= reclen; |
---|
332 | 345 | return 0; |
---|
| 346 | + |
---|
| 347 | +efault_end: |
---|
| 348 | + user_write_access_end(); |
---|
333 | 349 | efault: |
---|
334 | 350 | buf->error = -EFAULT; |
---|
335 | 351 | return -EFAULT; |
---|
336 | 352 | } |
---|
337 | 353 | |
---|
338 | | -int ksys_getdents64(unsigned int fd, struct linux_dirent64 __user *dirent, |
---|
339 | | - unsigned int count) |
---|
| 354 | +SYSCALL_DEFINE3(getdents64, unsigned int, fd, |
---|
| 355 | + struct linux_dirent64 __user *, dirent, unsigned int, count) |
---|
340 | 356 | { |
---|
341 | 357 | struct fd f; |
---|
342 | | - struct linux_dirent64 __user * lastdirent; |
---|
343 | 358 | struct getdents_callback64 buf = { |
---|
344 | 359 | .ctx.actor = filldir64, |
---|
345 | 360 | .count = count, |
---|
346 | 361 | .current_dir = dirent |
---|
347 | 362 | }; |
---|
348 | 363 | int error; |
---|
349 | | - |
---|
350 | | - if (!access_ok(VERIFY_WRITE, dirent, count)) |
---|
351 | | - return -EFAULT; |
---|
352 | 364 | |
---|
353 | 365 | f = fdget_pos(fd); |
---|
354 | 366 | if (!f.file) |
---|
.. | .. |
---|
357 | 369 | error = iterate_dir(f.file, &buf.ctx); |
---|
358 | 370 | if (error >= 0) |
---|
359 | 371 | error = buf.error; |
---|
360 | | - lastdirent = buf.previous; |
---|
361 | | - if (lastdirent) { |
---|
| 372 | + if (buf.prev_reclen) { |
---|
| 373 | + struct linux_dirent64 __user * lastdirent; |
---|
362 | 374 | typeof(lastdirent->d_off) d_off = buf.ctx.pos; |
---|
363 | | - if (__put_user(d_off, &lastdirent->d_off)) |
---|
| 375 | + |
---|
| 376 | + lastdirent = (void __user *) buf.current_dir - buf.prev_reclen; |
---|
| 377 | + if (put_user(d_off, &lastdirent->d_off)) |
---|
364 | 378 | error = -EFAULT; |
---|
365 | 379 | else |
---|
366 | 380 | error = count - buf.count; |
---|
367 | 381 | } |
---|
368 | 382 | fdput_pos(f); |
---|
369 | 383 | return error; |
---|
370 | | -} |
---|
371 | | - |
---|
372 | | - |
---|
373 | | -SYSCALL_DEFINE3(getdents64, unsigned int, fd, |
---|
374 | | - struct linux_dirent64 __user *, dirent, unsigned int, count) |
---|
375 | | -{ |
---|
376 | | - return ksys_getdents64(fd, dirent, count); |
---|
377 | 384 | } |
---|
378 | 385 | |
---|
379 | 386 | #ifdef CONFIG_COMPAT |
---|
.. | .. |
---|
411 | 418 | } |
---|
412 | 419 | buf->result++; |
---|
413 | 420 | dirent = buf->dirent; |
---|
414 | | - if (!access_ok(VERIFY_WRITE, dirent, |
---|
| 421 | + if (!user_write_access_begin(dirent, |
---|
415 | 422 | (unsigned long)(dirent->d_name + namlen + 1) - |
---|
416 | 423 | (unsigned long)dirent)) |
---|
417 | 424 | goto efault; |
---|
418 | | - if ( __put_user(d_ino, &dirent->d_ino) || |
---|
419 | | - __put_user(offset, &dirent->d_offset) || |
---|
420 | | - __put_user(namlen, &dirent->d_namlen) || |
---|
421 | | - __copy_to_user(dirent->d_name, name, namlen) || |
---|
422 | | - __put_user(0, dirent->d_name + namlen)) |
---|
423 | | - goto efault; |
---|
| 425 | + unsafe_put_user(d_ino, &dirent->d_ino, efault_end); |
---|
| 426 | + unsafe_put_user(offset, &dirent->d_offset, efault_end); |
---|
| 427 | + unsafe_put_user(namlen, &dirent->d_namlen, efault_end); |
---|
| 428 | + unsafe_copy_dirent_name(dirent->d_name, name, namlen, efault_end); |
---|
| 429 | + user_write_access_end(); |
---|
424 | 430 | return 0; |
---|
| 431 | +efault_end: |
---|
| 432 | + user_write_access_end(); |
---|
425 | 433 | efault: |
---|
426 | 434 | buf->result = -EFAULT; |
---|
427 | 435 | return -EFAULT; |
---|
.. | .. |
---|
458 | 466 | struct compat_getdents_callback { |
---|
459 | 467 | struct dir_context ctx; |
---|
460 | 468 | struct compat_linux_dirent __user *current_dir; |
---|
461 | | - struct compat_linux_dirent __user *previous; |
---|
| 469 | + int prev_reclen; |
---|
462 | 470 | int count; |
---|
463 | 471 | int error; |
---|
464 | 472 | }; |
---|
.. | .. |
---|
466 | 474 | static int compat_filldir(struct dir_context *ctx, const char *name, int namlen, |
---|
467 | 475 | loff_t offset, u64 ino, unsigned int d_type) |
---|
468 | 476 | { |
---|
469 | | - struct compat_linux_dirent __user * dirent; |
---|
| 477 | + struct compat_linux_dirent __user *dirent, *prev; |
---|
470 | 478 | struct compat_getdents_callback *buf = |
---|
471 | 479 | container_of(ctx, struct compat_getdents_callback, ctx); |
---|
472 | 480 | compat_ulong_t d_ino; |
---|
473 | 481 | int reclen = ALIGN(offsetof(struct compat_linux_dirent, d_name) + |
---|
474 | 482 | namlen + 2, sizeof(compat_long_t)); |
---|
| 483 | + int prev_reclen; |
---|
475 | 484 | |
---|
| 485 | + buf->error = verify_dirent_name(name, namlen); |
---|
| 486 | + if (unlikely(buf->error)) |
---|
| 487 | + return buf->error; |
---|
476 | 488 | buf->error = -EINVAL; /* only used if we fail.. */ |
---|
477 | 489 | if (reclen > buf->count) |
---|
478 | 490 | return -EINVAL; |
---|
.. | .. |
---|
481 | 493 | buf->error = -EOVERFLOW; |
---|
482 | 494 | return -EOVERFLOW; |
---|
483 | 495 | } |
---|
484 | | - dirent = buf->previous; |
---|
485 | | - if (dirent) { |
---|
486 | | - if (signal_pending(current)) |
---|
487 | | - return -EINTR; |
---|
488 | | - if (__put_user(offset, &dirent->d_off)) |
---|
489 | | - goto efault; |
---|
490 | | - } |
---|
| 496 | + prev_reclen = buf->prev_reclen; |
---|
| 497 | + if (prev_reclen && signal_pending(current)) |
---|
| 498 | + return -EINTR; |
---|
491 | 499 | dirent = buf->current_dir; |
---|
492 | | - if (__put_user(d_ino, &dirent->d_ino)) |
---|
| 500 | + prev = (void __user *) dirent - prev_reclen; |
---|
| 501 | + if (!user_write_access_begin(prev, reclen + prev_reclen)) |
---|
493 | 502 | goto efault; |
---|
494 | | - if (__put_user(reclen, &dirent->d_reclen)) |
---|
495 | | - goto efault; |
---|
496 | | - if (copy_to_user(dirent->d_name, name, namlen)) |
---|
497 | | - goto efault; |
---|
498 | | - if (__put_user(0, dirent->d_name + namlen)) |
---|
499 | | - goto efault; |
---|
500 | | - if (__put_user(d_type, (char __user *) dirent + reclen - 1)) |
---|
501 | | - goto efault; |
---|
502 | | - buf->previous = dirent; |
---|
503 | | - dirent = (void __user *)dirent + reclen; |
---|
504 | | - buf->current_dir = dirent; |
---|
| 503 | + |
---|
| 504 | + unsafe_put_user(offset, &prev->d_off, efault_end); |
---|
| 505 | + unsafe_put_user(d_ino, &dirent->d_ino, efault_end); |
---|
| 506 | + unsafe_put_user(reclen, &dirent->d_reclen, efault_end); |
---|
| 507 | + unsafe_put_user(d_type, (char __user *) dirent + reclen - 1, efault_end); |
---|
| 508 | + unsafe_copy_dirent_name(dirent->d_name, name, namlen, efault_end); |
---|
| 509 | + user_write_access_end(); |
---|
| 510 | + |
---|
| 511 | + buf->prev_reclen = reclen; |
---|
| 512 | + buf->current_dir = (void __user *)dirent + reclen; |
---|
505 | 513 | buf->count -= reclen; |
---|
506 | 514 | return 0; |
---|
| 515 | +efault_end: |
---|
| 516 | + user_write_access_end(); |
---|
507 | 517 | efault: |
---|
508 | 518 | buf->error = -EFAULT; |
---|
509 | 519 | return -EFAULT; |
---|
.. | .. |
---|
513 | 523 | struct compat_linux_dirent __user *, dirent, unsigned int, count) |
---|
514 | 524 | { |
---|
515 | 525 | struct fd f; |
---|
516 | | - struct compat_linux_dirent __user * lastdirent; |
---|
517 | 526 | struct compat_getdents_callback buf = { |
---|
518 | 527 | .ctx.actor = compat_filldir, |
---|
519 | 528 | .current_dir = dirent, |
---|
520 | 529 | .count = count |
---|
521 | 530 | }; |
---|
522 | 531 | int error; |
---|
523 | | - |
---|
524 | | - if (!access_ok(VERIFY_WRITE, dirent, count)) |
---|
525 | | - return -EFAULT; |
---|
526 | 532 | |
---|
527 | 533 | f = fdget_pos(fd); |
---|
528 | 534 | if (!f.file) |
---|
.. | .. |
---|
531 | 537 | error = iterate_dir(f.file, &buf.ctx); |
---|
532 | 538 | if (error >= 0) |
---|
533 | 539 | error = buf.error; |
---|
534 | | - lastdirent = buf.previous; |
---|
535 | | - if (lastdirent) { |
---|
| 540 | + if (buf.prev_reclen) { |
---|
| 541 | + struct compat_linux_dirent __user * lastdirent; |
---|
| 542 | + lastdirent = (void __user *)buf.current_dir - buf.prev_reclen; |
---|
| 543 | + |
---|
536 | 544 | if (put_user(buf.ctx.pos, &lastdirent->d_off)) |
---|
537 | 545 | error = -EFAULT; |
---|
538 | 546 | else |
---|