Skip to content

Commit 9b378f6

Browse files
fdmananakdave
authored andcommitted
btrfs: fix infinite directory reads
The readdir implementation currently processes always up to the last index it finds. This however can result in an infinite loop if the directory has a large number of entries such that they won't all fit in the given buffer passed to the readdir callback, that is, dir_emit() returns a non-zero value. Because in that case readdir() will be called again and if in the meanwhile new directory entries were added and we still can't put all the remaining entries in the buffer, we keep repeating this over and over. The following C program and test script reproduce the problem: $ cat /mnt/readdir_prog.c #include <sys/types.h> #include <dirent.h> #include <stdio.h> int main(int argc, char *argv[]) { DIR *dir = opendir("."); struct dirent *dd; while ((dd = readdir(dir))) { printf("%s\n", dd->d_name); rename(dd->d_name, "TEMPFILE"); rename("TEMPFILE", dd->d_name); } closedir(dir); } $ gcc -o /mnt/readdir_prog /mnt/readdir_prog.c $ cat test.sh #!/bin/bash DEV=/dev/sdi MNT=/mnt/sdi mkfs.btrfs -f $DEV &> /dev/null #mkfs.xfs -f $DEV &> /dev/null #mkfs.ext4 -F $DEV &> /dev/null mount $DEV $MNT mkdir $MNT/testdir for ((i = 1; i <= 2000; i++)); do echo -n > $MNT/testdir/file_$i done cd $MNT/testdir /mnt/readdir_prog cd /mnt umount $MNT This behaviour is surprising to applications and it's unlike ext4, xfs, tmpfs, vfat and other filesystems, which always finish. In this case where new entries were added due to renames, some file names may be reported more than once, but this varies according to each filesystem - for example ext4 never reported the same file more than once while xfs reports the first 13 file names twice. So change our readdir implementation to track the last index number when opendir() is called and then make readdir() never process beyond that index number. This gives the same behaviour as ext4. Reported-by: Rob Landley <[email protected]> Link: https://lore.kernel.org/linux-btrfs/[email protected]/ Link: https://bugzilla.kernel.org/show_bug.cgi?id=217681 CC: [email protected] # 6.4+ Signed-off-by: Filipe Manana <[email protected]> Signed-off-by: David Sterba <[email protected]>
1 parent 92fb94b commit 9b378f6

File tree

4 files changed

+84
-54
lines changed

4 files changed

+84
-54
lines changed

fs/btrfs/ctree.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -443,6 +443,7 @@ struct btrfs_drop_extents_args {
443443

444444
struct btrfs_file_private {
445445
void *filldir_buf;
446+
u64 last_index;
446447
struct extent_state *llseek_cached_state;
447448
};
448449

fs/btrfs/delayed-inode.c

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1632,6 +1632,7 @@ int btrfs_inode_delayed_dir_index_count(struct btrfs_inode *inode)
16321632
}
16331633

16341634
bool btrfs_readdir_get_delayed_items(struct inode *inode,
1635+
u64 last_index,
16351636
struct list_head *ins_list,
16361637
struct list_head *del_list)
16371638
{
@@ -1651,14 +1652,14 @@ bool btrfs_readdir_get_delayed_items(struct inode *inode,
16511652

16521653
mutex_lock(&delayed_node->mutex);
16531654
item = __btrfs_first_delayed_insertion_item(delayed_node);
1654-
while (item) {
1655+
while (item && item->index <= last_index) {
16551656
refcount_inc(&item->refs);
16561657
list_add_tail(&item->readdir_list, ins_list);
16571658
item = __btrfs_next_delayed_item(item);
16581659
}
16591660

16601661
item = __btrfs_first_delayed_deletion_item(delayed_node);
1661-
while (item) {
1662+
while (item && item->index <= last_index) {
16621663
refcount_inc(&item->refs);
16631664
list_add_tail(&item->readdir_list, del_list);
16641665
item = __btrfs_next_delayed_item(item);

fs/btrfs/delayed-inode.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@ void btrfs_destroy_delayed_inodes(struct btrfs_fs_info *fs_info);
148148

149149
/* Used for readdir() */
150150
bool btrfs_readdir_get_delayed_items(struct inode *inode,
151+
u64 last_index,
151152
struct list_head *ins_list,
152153
struct list_head *del_list);
153154
void btrfs_readdir_put_delayed_items(struct inode *inode,

fs/btrfs/inode.c

Lines changed: 79 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -5872,6 +5872,74 @@ static struct dentry *btrfs_lookup(struct inode *dir, struct dentry *dentry,
58725872
return d_splice_alias(inode, dentry);
58735873
}
58745874

5875+
/*
5876+
* Find the highest existing sequence number in a directory and then set the
5877+
* in-memory index_cnt variable to the first free sequence number.
5878+
*/
5879+
static int btrfs_set_inode_index_count(struct btrfs_inode *inode)
5880+
{
5881+
struct btrfs_root *root = inode->root;
5882+
struct btrfs_key key, found_key;
5883+
struct btrfs_path *path;
5884+
struct extent_buffer *leaf;
5885+
int ret;
5886+
5887+
key.objectid = btrfs_ino(inode);
5888+
key.type = BTRFS_DIR_INDEX_KEY;
5889+
key.offset = (u64)-1;
5890+
5891+
path = btrfs_alloc_path();
5892+
if (!path)
5893+
return -ENOMEM;
5894+
5895+
ret = btrfs_search_slot(NULL, root, &key, path, 0, 0);
5896+
if (ret < 0)
5897+
goto out;
5898+
/* FIXME: we should be able to handle this */
5899+
if (ret == 0)
5900+
goto out;
5901+
ret = 0;
5902+
5903+
if (path->slots[0] == 0) {
5904+
inode->index_cnt = BTRFS_DIR_START_INDEX;
5905+
goto out;
5906+
}
5907+
5908+
path->slots[0]--;
5909+
5910+
leaf = path->nodes[0];
5911+
btrfs_item_key_to_cpu(leaf, &found_key, path->slots[0]);
5912+
5913+
if (found_key.objectid != btrfs_ino(inode) ||
5914+
found_key.type != BTRFS_DIR_INDEX_KEY) {
5915+
inode->index_cnt = BTRFS_DIR_START_INDEX;
5916+
goto out;
5917+
}
5918+
5919+
inode->index_cnt = found_key.offset + 1;
5920+
out:
5921+
btrfs_free_path(path);
5922+
return ret;
5923+
}
5924+
5925+
static int btrfs_get_dir_last_index(struct btrfs_inode *dir, u64 *index)
5926+
{
5927+
if (dir->index_cnt == (u64)-1) {
5928+
int ret;
5929+
5930+
ret = btrfs_inode_delayed_dir_index_count(dir);
5931+
if (ret) {
5932+
ret = btrfs_set_inode_index_count(dir);
5933+
if (ret)
5934+
return ret;
5935+
}
5936+
}
5937+
5938+
*index = dir->index_cnt;
5939+
5940+
return 0;
5941+
}
5942+
58755943
/*
58765944
* All this infrastructure exists because dir_emit can fault, and we are holding
58775945
* the tree lock when doing readdir. For now just allocate a buffer and copy
@@ -5884,10 +5952,17 @@ static struct dentry *btrfs_lookup(struct inode *dir, struct dentry *dentry,
58845952
static int btrfs_opendir(struct inode *inode, struct file *file)
58855953
{
58865954
struct btrfs_file_private *private;
5955+
u64 last_index;
5956+
int ret;
5957+
5958+
ret = btrfs_get_dir_last_index(BTRFS_I(inode), &last_index);
5959+
if (ret)
5960+
return ret;
58875961

58885962
private = kzalloc(sizeof(struct btrfs_file_private), GFP_KERNEL);
58895963
if (!private)
58905964
return -ENOMEM;
5965+
private->last_index = last_index;
58915966
private->filldir_buf = kzalloc(PAGE_SIZE, GFP_KERNEL);
58925967
if (!private->filldir_buf) {
58935968
kfree(private);
@@ -5954,7 +6029,8 @@ static int btrfs_real_readdir(struct file *file, struct dir_context *ctx)
59546029

59556030
INIT_LIST_HEAD(&ins_list);
59566031
INIT_LIST_HEAD(&del_list);
5957-
put = btrfs_readdir_get_delayed_items(inode, &ins_list, &del_list);
6032+
put = btrfs_readdir_get_delayed_items(inode, private->last_index,
6033+
&ins_list, &del_list);
59586034

59596035
again:
59606036
key.type = BTRFS_DIR_INDEX_KEY;
@@ -5972,6 +6048,8 @@ static int btrfs_real_readdir(struct file *file, struct dir_context *ctx)
59726048
break;
59736049
if (found_key.offset < ctx->pos)
59746050
continue;
6051+
if (found_key.offset > private->last_index)
6052+
break;
59756053
if (btrfs_should_delete_dir_index(&del_list, found_key.offset))
59766054
continue;
59776055
di = btrfs_item_ptr(leaf, path->slots[0], struct btrfs_dir_item);
@@ -6107,57 +6185,6 @@ static int btrfs_update_time(struct inode *inode, struct timespec64 *now,
61076185
return dirty ? btrfs_dirty_inode(BTRFS_I(inode)) : 0;
61086186
}
61096187

6110-
/*
6111-
* find the highest existing sequence number in a directory
6112-
* and then set the in-memory index_cnt variable to reflect
6113-
* free sequence numbers
6114-
*/
6115-
static int btrfs_set_inode_index_count(struct btrfs_inode *inode)
6116-
{
6117-
struct btrfs_root *root = inode->root;
6118-
struct btrfs_key key, found_key;
6119-
struct btrfs_path *path;
6120-
struct extent_buffer *leaf;
6121-
int ret;
6122-
6123-
key.objectid = btrfs_ino(inode);
6124-
key.type = BTRFS_DIR_INDEX_KEY;
6125-
key.offset = (u64)-1;
6126-
6127-
path = btrfs_alloc_path();
6128-
if (!path)
6129-
return -ENOMEM;
6130-
6131-
ret = btrfs_search_slot(NULL, root, &key, path, 0, 0);
6132-
if (ret < 0)
6133-
goto out;
6134-
/* FIXME: we should be able to handle this */
6135-
if (ret == 0)
6136-
goto out;
6137-
ret = 0;
6138-
6139-
if (path->slots[0] == 0) {
6140-
inode->index_cnt = BTRFS_DIR_START_INDEX;
6141-
goto out;
6142-
}
6143-
6144-
path->slots[0]--;
6145-
6146-
leaf = path->nodes[0];
6147-
btrfs_item_key_to_cpu(leaf, &found_key, path->slots[0]);
6148-
6149-
if (found_key.objectid != btrfs_ino(inode) ||
6150-
found_key.type != BTRFS_DIR_INDEX_KEY) {
6151-
inode->index_cnt = BTRFS_DIR_START_INDEX;
6152-
goto out;
6153-
}
6154-
6155-
inode->index_cnt = found_key.offset + 1;
6156-
out:
6157-
btrfs_free_path(path);
6158-
return ret;
6159-
}
6160-
61616188
/*
61626189
* helper to find a free sequence number in a given directory. This current
61636190
* code is very simple, later versions will do smarter things in the btree

0 commit comments

Comments
 (0)