[PATCH] freevxfs: fix divide-by-zero in vxfs_bmap_ext4() causing oops on mount

Đức Cảnh Nguyễn <[email protected]>
Newsgroups gmane.linux.file-systems,gmane.linux.kernel
Message-ID <CALFmVm4k=ZKZ_f02L9fB4-kWyPVWrF2iDx0yFr2wsCcL8pizwQ@mail.gmail.com>
vxfs_bmap_ext4() divides by attacker-controlled values from the on-disk
inode without fully validating them.  A crafted VxFS image triggers a
kernel "divide error" (oops) as soon as it is mounted -- no read or
ioctl is required.  The crash is reached from vxfs_fill_super() ->
vxfs_iget() -> vxfs_get_page() -> vxfs_read_folio() -> vxfs_bmap1() ->
vxfs_bmap_ext4().

Two independent paths hit the division by zero:

1. ve4_indsize == 0: line 62 computes the denominator
   (indsize * indsize * bsize / 4) == 0.  The existing guard
   (line 52) only rejects indsize > s_blocksize, so 0 gets through.

2. bn == 0: if block 0 is not covered by the first direct extent
   (size == 0), bn stays 0 after the direct-extent loop.  With
   indsize > 0 the expression at line 73,
   indir[(bn / indsize) % (indsize * bn)], becomes 0 % 0.

Reproduced in QEMU on a 7.0.0-28-generic kernel:

  Oops: divide error: 0000 [#1] SMP NOPTI
  RIP: 0010:vxfs_bmap_ext4+0xfc/0x1c0 [freevxfs]
  RAX: 0000000000000000 RBX: 0000000000000000 RDX: 0000000000000000
  Call Trace:
    vxfs_bmap_ext4+0xfc/0x1c0 [freevxfs]
    vxfs_bmap1+0x42/0x70 [freevxfs]
    vxfs_getblk+0x17/0x70 [freevxfs]
    block_read_full_folio+0x109/0x270
    vxfs_read_folio+0x18/0x30 [freevxfs]
    vxfs_get_page+0x13/0x40 [freevxfs]
    __vxfs_iget+0x3a/0xd0 [freevxfs]
    vxfs_iget+0x5b/0x1a0 [freevxfs]
    vxfs_fill_super+0x159/0x340 [freevxfs]
    get_tree_bdev_flags+0x141/0x1e0
    ...
    __x64_sys_mount+0x12b/0x160

Reject indsize == 0 up front and bail out when the logical block is 0.

Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
Signed-off-by: canhnguyen26 (Nguyen Duc Canh) <[email protected]>
---
 fs/freevxfs/vxfs_bmap.c | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/fs/freevxfs/vxfs_bmap.c b/fs/freevxfs/vxfs_bmap.c
index e85222892038..4c2a5b42df3c 100644
--- a/fs/freevxfs/vxfs_bmap.c
+++ b/fs/freevxfs/vxfs_bmap.c
@@ -49,7 +49,7 @@ vxfs_bmap_ext4(struct inode *ip, long bn)
  unsigned long bsize = sb->s_blocksize;
  u32 indsize = fs32_to_cpu(sbi, vip->vii_ext4.ve4_indsize);
  int i;

- if (indsize > sb->s_blocksize)
+ if (indsize == 0 || indsize > sb->s_blocksize)
  goto fail_size;

  for (i = 0; i < VXFS_NDADDR; i++) {
@@ -62,7 +62,8 @@ vxfs_bmap_ext4(struct inode *ip, long bn)
  bn -= fs32_to_cpu(sbi, d->size);
  }

- if ((bn / (indsize * indsize * bsize / 4)) == 0) {
+ if (bn == 0)
+ goto fail_buf;
+ if ((bn / (indsize * indsize * bsize / 4)) == 0) {
  struct buffer_head *buf;
  daddr_t bno;
  __fs32 *indir;
-- 
2.39.5
freevxfs-fix-divide-by-zero.patch (text/x-patch, 740 B)
diff --git a/fs/freevxfs/vxfs_bmap.c b/fs/freevxfs/vxfs_bmap.c
--- a/fs/freevxfs/vxfs_bmap.c
+++ b/fs/freevxfs/vxfs_bmap.c
@@ -49,7 +49,7 @@ vxfs_bmap_ext4(struct inode *ip, long bn)
 	unsigned long bsize = sb->s_blocksize;
 	u32 indsize = fs32_to_cpu(sbi, vip->vii_ext4.ve4_indsize);
 	int i;
 
-	if (indsize > sb->s_blocksize)
+	if (indsize == 0 || indsize > sb->s_blocksize)
 		goto fail_size;
 
 	for (i = 0; i < VXFS_NDADDR; i++) {
@@ -62,7 +62,8 @@ vxfs_bmap_ext4(struct inode *ip, long bn)
 		bn -= fs32_to_cpu(sbi, d->size);
 	}
 
-	if ((bn / (indsize * indsize * bsize / 4)) == 0) {
+	if (bn == 0)
+		goto fail_buf;
+	if ((bn / (indsize * indsize * bsize / 4)) == 0) {
 		struct buffer_head *buf;
 		daddr_t	bno;
 		__fs32 *indir;
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.