Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 63edbce

Browse files
committed
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
2 parents a61aef7 + 605c912 commit 63edbce

File tree

1 file changed

+39
-15
lines changed

1 file changed

+39
-15
lines changed

fs/ubifs/dir.c

Lines changed: 39 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -349,31 +349,50 @@ static unsigned int vfs_dent_type(uint8_t type)
349349
static int ubifs_readdir(struct file *file, void *dirent, filldir_t filldir)
350350
{
351351
int err, over = 0;
352+
loff_t pos = file->f_pos;
352353
struct qstr nm;
353354
union ubifs_key key;
354355
struct ubifs_dent_node *dent;
355356
struct inode *dir = file_inode(file);
356357
struct ubifs_info *c = dir->i_sb->s_fs_info;
357358

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);
359360

360-
if (file->f_pos > UBIFS_S_KEY_HASH_MASK || file->f_pos == 2)
361+
if (pos > UBIFS_S_KEY_HASH_MASK || pos == 2)
361362
/*
362363
* The directory was seek'ed to a senseless position or there
363364
* are no more entries.
364365
*/
365366
return 0;
366367

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+
367386
/* File positions 0 and 1 correspond to "." and ".." */
368-
if (file->f_pos == 0) {
387+
if (pos == 0) {
369388
ubifs_assert(!file->private_data);
370389
over = filldir(dirent, ".", 1, 0, dir->i_ino, DT_DIR);
371390
if (over)
372391
return 0;
373-
file->f_pos = 1;
392+
file->f_pos = pos = 1;
374393
}
375394

376-
if (file->f_pos == 1) {
395+
if (pos == 1) {
377396
ubifs_assert(!file->private_data);
378397
over = filldir(dirent, "..", 2, 1,
379398
parent_ino(file->f_path.dentry), DT_DIR);
@@ -389,25 +408,24 @@ static int ubifs_readdir(struct file *file, void *dirent, filldir_t filldir)
389408
goto out;
390409
}
391410

392-
file->f_pos = key_hash_flash(c, &dent->key);
411+
file->f_pos = pos = key_hash_flash(c, &dent->key);
393412
file->private_data = dent;
394413
}
395414

396415
dent = file->private_data;
397416
if (!dent) {
398417
/*
399418
* The directory was seek'ed to and is now readdir'ed.
400-
* Find the entry corresponding to @file->f_pos or the
401-
* closest one.
419+
* Find the entry corresponding to @pos or the closest one.
402420
*/
403-
dent_key_init_hash(c, &key, dir->i_ino, file->f_pos);
421+
dent_key_init_hash(c, &key, dir->i_ino, pos);
404422
nm.name = NULL;
405423
dent = ubifs_tnc_next_ent(c, &key, &nm);
406424
if (IS_ERR(dent)) {
407425
err = PTR_ERR(dent);
408426
goto out;
409427
}
410-
file->f_pos = key_hash_flash(c, &dent->key);
428+
file->f_pos = pos = key_hash_flash(c, &dent->key);
411429
file->private_data = dent;
412430
}
413431

@@ -419,7 +437,7 @@ static int ubifs_readdir(struct file *file, void *dirent, filldir_t filldir)
419437
ubifs_inode(dir)->creat_sqnum);
420438

421439
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,
423441
le64_to_cpu(dent->inum),
424442
vfs_dent_type(dent->type));
425443
if (over)
@@ -435,9 +453,17 @@ static int ubifs_readdir(struct file *file, void *dirent, filldir_t filldir)
435453
}
436454

437455
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);
439457
file->private_data = dent;
440458
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;
441467
}
442468

443469
out:
@@ -448,15 +474,13 @@ static int ubifs_readdir(struct file *file, void *dirent, filldir_t filldir)
448474

449475
kfree(file->private_data);
450476
file->private_data = NULL;
477+
/* 2 is a special value indicating that there are no more direntries */
451478
file->f_pos = 2;
452479
return 0;
453480
}
454481

455-
/* If a directory is seeked, we have to free saved readdir() state */
456482
static loff_t ubifs_dir_llseek(struct file *file, loff_t offset, int whence)
457483
{
458-
kfree(file->private_data);
459-
file->private_data = NULL;
460484
return generic_file_llseek(file, offset, whence);
461485
}
462486

0 commit comments

Comments
 (0)