diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2013-06-29 13:30:31 -0400 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2013-06-29 13:30:31 -0400 |
commit | 63edbce160c6d68c806e58db026b5881c6010e04 (patch) | |
tree | 1ada31d631d1a8ccec9b4aa9956c54db90c6efcf | |
parent | a61aef7fc00bac9068e98bf00aaa38e766f19a57 (diff) | |
parent | 605c912bb843c024b1ed173dc427cd5c08e5d54d (diff) |
Merge branch 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/viro/vfs
Pull ubifs fixes from Al Viro:
"A couple of ubifs readdir/lseek race fixes. Stable fodder, really
nasty..."
* 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/viro/vfs:
UBIFS: fix a horrid bug
UBIFS: prepare to fix a horrid bug
-rw-r--r-- | fs/ubifs/dir.c | 54 |
1 files changed, 39 insertions, 15 deletions
diff --git a/fs/ubifs/dir.c b/fs/ubifs/dir.c index de08c92f2e23..605af512aec2 100644 --- a/fs/ubifs/dir.c +++ b/fs/ubifs/dir.c | |||
@@ -349,31 +349,50 @@ static unsigned int vfs_dent_type(uint8_t type) | |||
349 | static int ubifs_readdir(struct file *file, void *dirent, filldir_t filldir) | 349 | static int ubifs_readdir(struct file *file, void *dirent, filldir_t filldir) |
350 | { | 350 | { |
351 | int err, over = 0; | 351 | int err, over = 0; |
352 | loff_t pos = file->f_pos; | ||
352 | struct qstr nm; | 353 | struct qstr nm; |
353 | union ubifs_key key; | 354 | union ubifs_key key; |
354 | struct ubifs_dent_node *dent; | 355 | struct ubifs_dent_node *dent; |
355 | struct inode *dir = file_inode(file); | 356 | struct inode *dir = file_inode(file); |
356 | struct ubifs_info *c = dir->i_sb->s_fs_info; | 357 | struct ubifs_info *c = dir->i_sb->s_fs_info; |
357 | 358 | ||
358 | dbg_gen("dir ino %lu, f_pos %#llx", dir->i_ino, file->f_pos); | 359 | dbg_gen("dir ino %lu, f_pos %#llx", dir->i_ino, pos); |
359 | 360 | ||
360 | if (file->f_pos > UBIFS_S_KEY_HASH_MASK || file->f_pos == 2) | 361 | if (pos > UBIFS_S_KEY_HASH_MASK || pos == 2) |
361 | /* | 362 | /* |
362 | * The directory was seek'ed to a senseless position or there | 363 | * The directory was seek'ed to a senseless position or there |
363 | * are no more entries. | 364 | * are no more entries. |
364 | */ | 365 | */ |
365 | return 0; | 366 | return 0; |
366 | 367 | ||
368 | if (file->f_version == 0) { | ||
369 | /* | ||
370 | * The file was seek'ed, which means that @file->private_data | ||
371 | * is now invalid. This may also be just the first | ||
372 | * 'ubifs_readdir()' invocation, in which case | ||
373 | * @file->private_data is NULL, and the below code is | ||
374 | * basically a no-op. | ||
375 | */ | ||
376 | kfree(file->private_data); | ||
377 | file->private_data = NULL; | ||
378 | } | ||
379 | |||
380 | /* | ||
381 | * 'generic_file_llseek()' unconditionally sets @file->f_version to | ||
382 | * zero, and we use this for detecting whether the file was seek'ed. | ||
383 | */ | ||
384 | file->f_version = 1; | ||
385 | |||
367 | /* File positions 0 and 1 correspond to "." and ".." */ | 386 | /* File positions 0 and 1 correspond to "." and ".." */ |
368 | if (file->f_pos == 0) { | 387 | if (pos == 0) { |
369 | ubifs_assert(!file->private_data); | 388 | ubifs_assert(!file->private_data); |
370 | over = filldir(dirent, ".", 1, 0, dir->i_ino, DT_DIR); | 389 | over = filldir(dirent, ".", 1, 0, dir->i_ino, DT_DIR); |
371 | if (over) | 390 | if (over) |
372 | return 0; | 391 | return 0; |
373 | file->f_pos = 1; | 392 | file->f_pos = pos = 1; |
374 | } | 393 | } |
375 | 394 | ||
376 | if (file->f_pos == 1) { | 395 | if (pos == 1) { |
377 | ubifs_assert(!file->private_data); | 396 | ubifs_assert(!file->private_data); |
378 | over = filldir(dirent, "..", 2, 1, | 397 | over = filldir(dirent, "..", 2, 1, |
379 | parent_ino(file->f_path.dentry), DT_DIR); | 398 | parent_ino(file->f_path.dentry), DT_DIR); |
@@ -389,7 +408,7 @@ static int ubifs_readdir(struct file *file, void *dirent, filldir_t filldir) | |||
389 | goto out; | 408 | goto out; |
390 | } | 409 | } |
391 | 410 | ||
392 | file->f_pos = key_hash_flash(c, &dent->key); | 411 | file->f_pos = pos = key_hash_flash(c, &dent->key); |
393 | file->private_data = dent; | 412 | file->private_data = dent; |
394 | } | 413 | } |
395 | 414 | ||
@@ -397,17 +416,16 @@ static int ubifs_readdir(struct file *file, void *dirent, filldir_t filldir) | |||
397 | if (!dent) { | 416 | if (!dent) { |
398 | /* | 417 | /* |
399 | * The directory was seek'ed to and is now readdir'ed. | 418 | * The directory was seek'ed to and is now readdir'ed. |
400 | * Find the entry corresponding to @file->f_pos or the | 419 | * Find the entry corresponding to @pos or the closest one. |
401 | * closest one. | ||
402 | */ | 420 | */ |
403 | dent_key_init_hash(c, &key, dir->i_ino, file->f_pos); | 421 | dent_key_init_hash(c, &key, dir->i_ino, pos); |
404 | nm.name = NULL; | 422 | nm.name = NULL; |
405 | dent = ubifs_tnc_next_ent(c, &key, &nm); | 423 | dent = ubifs_tnc_next_ent(c, &key, &nm); |
406 | if (IS_ERR(dent)) { | 424 | if (IS_ERR(dent)) { |
407 | err = PTR_ERR(dent); | 425 | err = PTR_ERR(dent); |
408 | goto out; | 426 | goto out; |
409 | } | 427 | } |
410 | file->f_pos = key_hash_flash(c, &dent->key); | 428 | file->f_pos = pos = key_hash_flash(c, &dent->key); |
411 | file->private_data = dent; | 429 | file->private_data = dent; |
412 | } | 430 | } |
413 | 431 | ||
@@ -419,7 +437,7 @@ static int ubifs_readdir(struct file *file, void *dirent, filldir_t filldir) | |||
419 | ubifs_inode(dir)->creat_sqnum); | 437 | ubifs_inode(dir)->creat_sqnum); |
420 | 438 | ||
421 | nm.len = le16_to_cpu(dent->nlen); | 439 | nm.len = le16_to_cpu(dent->nlen); |
422 | over = filldir(dirent, dent->name, nm.len, file->f_pos, | 440 | over = filldir(dirent, dent->name, nm.len, pos, |
423 | le64_to_cpu(dent->inum), | 441 | le64_to_cpu(dent->inum), |
424 | vfs_dent_type(dent->type)); | 442 | vfs_dent_type(dent->type)); |
425 | if (over) | 443 | if (over) |
@@ -435,9 +453,17 @@ static int ubifs_readdir(struct file *file, void *dirent, filldir_t filldir) | |||
435 | } | 453 | } |
436 | 454 | ||
437 | kfree(file->private_data); | 455 | kfree(file->private_data); |
438 | file->f_pos = key_hash_flash(c, &dent->key); | 456 | file->f_pos = pos = key_hash_flash(c, &dent->key); |
439 | file->private_data = dent; | 457 | file->private_data = dent; |
440 | cond_resched(); | 458 | cond_resched(); |
459 | |||
460 | if (file->f_version == 0) | ||
461 | /* | ||
462 | * The file was seek'ed meanwhile, lets return and start | ||
463 | * reading direntries from the new position on the next | ||
464 | * invocation. | ||
465 | */ | ||
466 | return 0; | ||
441 | } | 467 | } |
442 | 468 | ||
443 | out: | 469 | out: |
@@ -448,15 +474,13 @@ out: | |||
448 | 474 | ||
449 | kfree(file->private_data); | 475 | kfree(file->private_data); |
450 | file->private_data = NULL; | 476 | file->private_data = NULL; |
477 | /* 2 is a special value indicating that there are no more direntries */ | ||
451 | file->f_pos = 2; | 478 | file->f_pos = 2; |
452 | return 0; | 479 | return 0; |
453 | } | 480 | } |
454 | 481 | ||
455 | /* If a directory is seeked, we have to free saved readdir() state */ | ||
456 | static loff_t ubifs_dir_llseek(struct file *file, loff_t offset, int whence) | 482 | static loff_t ubifs_dir_llseek(struct file *file, loff_t offset, int whence) |
457 | { | 483 | { |
458 | kfree(file->private_data); | ||
459 | file->private_data = NULL; | ||
460 | return generic_file_llseek(file, offset, whence); | 484 | return generic_file_llseek(file, offset, whence); |
461 | } | 485 | } |
462 | 486 | ||