Skip to content

Commit 64a7ce7

Browse files
yangerkunbrauner
authored andcommitted
libfs: fix infinite directory reads for offset dir
After we switch tmpfs dir operations from simple_dir_operations to simple_offset_dir_operations, every rename happened will fill new dentry to dest dir's maple tree(&SHMEM_I(inode)->dir_offsets->mt) with a free key starting with octx->newx_offset, and then set newx_offset equals to free key + 1. This will lead to infinite readdir combine with rename happened at the same time, which fail generic/736 in xfstests(detail show as below). 1. create 5000 files(1 2 3...) under one dir 2. call readdir(man 3 readdir) once, and get one entry 3. rename(entry, "TEMPFILE"), then rename("TEMPFILE", entry) 4. loop 2~3, until readdir return nothing or we loop too many times(tmpfs break test with the second condition) We choose the same logic what commit 9b378f6 ("btrfs: fix infinite directory reads") to fix it, record the last_index when we open dir, and do not emit the entry which index >= last_index. The file->private_data now used in offset dir can use directly to do this, and we also update the last_index when we llseek the dir file. Fixes: a2e4595 ("shmem: stable directory offsets") Signed-off-by: yangerkun <[email protected]> Link: https://lore.kernel.org/r/[email protected] Reviewed-by: Chuck Lever <[email protected]> [brauner: only update last_index after seek when offset is zero like Jan suggested] Signed-off-by: Christian Brauner <[email protected]>
1 parent 42b0f8d commit 64a7ce7

File tree

1 file changed

+24
-11
lines changed

1 file changed

+24
-11
lines changed

fs/libfs.c

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -450,6 +450,14 @@ void simple_offset_destroy(struct offset_ctx *octx)
450450
mtree_destroy(&octx->mt);
451451
}
452452

453+
static int offset_dir_open(struct inode *inode, struct file *file)
454+
{
455+
struct offset_ctx *ctx = inode->i_op->get_offset_ctx(inode);
456+
457+
file->private_data = (void *)ctx->next_offset;
458+
return 0;
459+
}
460+
453461
/**
454462
* offset_dir_llseek - Advance the read position of a directory descriptor
455463
* @file: an open directory whose position is to be updated
@@ -463,6 +471,9 @@ void simple_offset_destroy(struct offset_ctx *octx)
463471
*/
464472
static loff_t offset_dir_llseek(struct file *file, loff_t offset, int whence)
465473
{
474+
struct inode *inode = file->f_inode;
475+
struct offset_ctx *ctx = inode->i_op->get_offset_ctx(inode);
476+
466477
switch (whence) {
467478
case SEEK_CUR:
468479
offset += file->f_pos;
@@ -476,7 +487,8 @@ static loff_t offset_dir_llseek(struct file *file, loff_t offset, int whence)
476487
}
477488

478489
/* In this case, ->private_data is protected by f_pos_lock */
479-
file->private_data = NULL;
490+
if (!offset)
491+
file->private_data = (void *)ctx->next_offset;
480492
return vfs_setpos(file, offset, LONG_MAX);
481493
}
482494

@@ -507,25 +519,29 @@ static bool offset_dir_emit(struct dir_context *ctx, struct dentry *dentry)
507519
inode->i_ino, fs_umode_to_dtype(inode->i_mode));
508520
}
509521

510-
static void *offset_iterate_dir(struct inode *inode, struct dir_context *ctx)
522+
static void offset_iterate_dir(struct inode *inode, struct dir_context *ctx, long last_index)
511523
{
512524
struct offset_ctx *octx = inode->i_op->get_offset_ctx(inode);
513525
struct dentry *dentry;
514526

515527
while (true) {
516528
dentry = offset_find_next(octx, ctx->pos);
517529
if (!dentry)
518-
return ERR_PTR(-ENOENT);
530+
return;
531+
532+
if (dentry2offset(dentry) >= last_index) {
533+
dput(dentry);
534+
return;
535+
}
519536

520537
if (!offset_dir_emit(ctx, dentry)) {
521538
dput(dentry);
522-
break;
539+
return;
523540
}
524541

525542
ctx->pos = dentry2offset(dentry) + 1;
526543
dput(dentry);
527544
}
528-
return NULL;
529545
}
530546

531547
/**
@@ -552,22 +568,19 @@ static void *offset_iterate_dir(struct inode *inode, struct dir_context *ctx)
552568
static int offset_readdir(struct file *file, struct dir_context *ctx)
553569
{
554570
struct dentry *dir = file->f_path.dentry;
571+
long last_index = (long)file->private_data;
555572

556573
lockdep_assert_held(&d_inode(dir)->i_rwsem);
557574

558575
if (!dir_emit_dots(file, ctx))
559576
return 0;
560577

561-
/* In this case, ->private_data is protected by f_pos_lock */
562-
if (ctx->pos == DIR_OFFSET_MIN)
563-
file->private_data = NULL;
564-
else if (file->private_data == ERR_PTR(-ENOENT))
565-
return 0;
566-
file->private_data = offset_iterate_dir(d_inode(dir), ctx);
578+
offset_iterate_dir(d_inode(dir), ctx, last_index);
567579
return 0;
568580
}
569581

570582
const struct file_operations simple_offset_dir_operations = {
583+
.open = offset_dir_open,
571584
.llseek = offset_dir_llseek,
572585
.iterate_shared = offset_readdir,
573586
.read = generic_read_dir,

0 commit comments

Comments
 (0)