lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<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

Powered by Openwall GNU/*/Linux Powered by OpenVZ