[PATCH] bfs: Handle directory bfs_inode->i_eoffset == 0xffffffff

Nikola Z. Ivanov posted 1 patch 1 month, 2 weeks ago
fs/bfs/inode.c | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
[PATCH] bfs: Handle directory bfs_inode->i_eoffset == 0xffffffff
Posted by Nikola Z. Ivanov 1 month, 2 weeks ago
Implement bfs behavior for interpreting i_eoffset == 0xffffffff
as "unused". When the field is marked 0xffffffff calculate the
inode->i_size field from the number of blocks allocated.

This prevents spurious reads outside the device during bfs_readdir and
bfs_lookup. This is the only on-disk value of i_eoffset that will pass
the initial corruption check if it is larger than the actual disk size.

Syzbot stumbles upon the bug by mounting a bfs filesystem with root
directory having said i_eoffset, which passes the initial sanity
check that compares it with the total filesystem size:

i_eoff = le32_to_cpu(di->i_eoffset);
s_size = le32_to_cpu(bfs_sb->s_end);
(i_eoff != le32_to_cpu(-1) && i_eoff > s_size) // This is part of the
					       // corruption checks in
					       // bfs_fill_super

Since BFS_FILESIZE expands to i_eoffset + 1 - sblock * BFS_BSIZE
when sblock != 0 it wraps around back to a large value (~4Gb).

Later when listing the directory or looking up a file we start
attempting to read past the end of device multiple times,
which takes some time and we are stuck failing again and again.

Testing:
  As this is a relatively simple filesystem it's operations were
  "manually" tested, meaning filling all 512 inodes with random
  data, random reads, writes, deletes, etc.

Reported-by: syzbot+e7be6bf3e45b7b463bfa@syzkaller.appspotmail.com
Closes: https://syzkaller.appspot.com/bug?extid=e7be6bf3e45b7b463bfa
Signed-off-by: Nikola Z. Ivanov <zlatistiv@gmail.com>
---
 fs/bfs/inode.c | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/fs/bfs/inode.c b/fs/bfs/inode.c
index 1d41ce477df5..aa5e66290f6b 100644
--- a/fs/bfs/inode.c
+++ b/fs/bfs/inode.c
@@ -79,8 +79,13 @@ struct inode *bfs_iget(struct super_block *sb, unsigned long ino)
 	i_uid_write(inode, le32_to_cpu(di->i_uid));
 	i_gid_write(inode,  le32_to_cpu(di->i_gid));
 	set_nlink(inode, le32_to_cpu(di->i_nlink));
-	inode->i_size = BFS_FILESIZE(di);
 	inode->i_blocks = BFS_FILEBLOCKS(di);
+	/* For directories i_eoffset == 0xffffffff means "unused" */
+	/* Calculate file size from number of used blocks instead */
+	if (S_ISDIR(inode->i_mode) && le32_to_cpu(di->i_eoffset) == U32_MAX)
+		inode->i_size = inode->i_blocks * BFS_BSIZE;
+	else
+		inode->i_size = BFS_FILESIZE(di);
 	inode_set_atime(inode, le32_to_cpu(di->i_atime), 0);
 	inode_set_mtime(inode, le32_to_cpu(di->i_mtime), 0);
 	inode_set_ctime(inode, le32_to_cpu(di->i_ctime), 0);
-- 
2.51.0