[<prev] [next>] [day] [month] [year] [list]
Message-ID: <20251104172122.4029513-1-zlatistiv@gmail.com>
Date: Tue, 4 Nov 2025 19:21:22 +0200
From: "Nikola Z. Ivanov" <zlatistiv@...il.com>
To: aivazian.tigran@...il.com
Cc: linux-kernel@...r.kernel.org,
skhan@...uxfoundation.org,
david.hunter.linux@...il.com,
khalid@...nel.org,
linux-kernel-mentees@...ts.linuxfoundation.org,
"Nikola Z. Ivanov" <zlatistiv@...il.com>,
syzbot+e7be6bf3e45b7b463bfa@...kaller.appspotmail.com
Subject: [PATCH] bfs: Handle directory bfs_inode->i_eoffset == 0xffffffff
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@...kaller.appspotmail.com
Closes: https://syzkaller.appspot.com/bug?extid=e7be6bf3e45b7b463bfa
Signed-off-by: Nikola Z. Ivanov <zlatistiv@...il.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
Powered by blists - more mailing lists