[BUG] freevxfs: divide-by-zero in vxfs_bmap_ext4() causes kernel oops on mount

Đức Cảnh Nguyễn posted 1 patch 2 months ago
[BUG] freevxfs: divide-by-zero in vxfs_bmap_ext4() causes kernel oops on mount
Posted by Đức Cảnh Nguyễn 2 months ago
Subject: freevxfs: divide-by-zero in vxfs_bmap_ext4() causes kernel oops on
mount

To: Christoph Hellwig <hch@infradead.org>
Cc: linux-kernel@vger.kernel.org

Hi Christoph,

I found a divide-by-zero in vxfs_bmap_ext4() (fs/freevxfs/vxfs_bmap.c).
Mounting a crafted VxFS image triggers a kernel oops ("divide error")
right at mount time -- no read or ioctl needed. A full repro (image,
initramfs, QEMU script, logs) is here:

https://drive.google.com/file/d/1UKsDOxX8aUHeG2AVsk2VIcTLobWAORl9/view?usp=sharing

Summary of the bug:

- File: fs/freevxfs/vxfs_bmap.c, vxfs_bmap_ext4()
- Type: integer divide-by-zero -> kernel oops (DoS), local, needs
  CAP_SYS_ADMIN to mount a crafted image
- Present since: Linux-2.6.12-rc2 (1da177e4c3f4); never fixed

Two paths hit the divide-by-zero:

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

2. line 73: if block 0 isn't covered by the first direct extent (size == 0),
   bn stays 0 after the direct loop, and with indsize > 0 the expression
   (bn / indsize) % (indsize * bn) becomes 0 % 0.

The recent freevxfs fix (704d48d81dc4) only touched vxfs_bmap_typed(),
not vxfs_bmap_ext4(), so the bug is still present in mainline.

Reproduced in QEMU on Ubuntu 7.0.0-28-generic:

  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

The crash happens inside vxfs_fill_super(), i.e. during mount itself.

Suggested fix (also in the Drive zip):

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;

Environment note about the repro: it was built and tested on a machine
whose /boot already ships vmlinuz-7.0.0-28-generic (I did not build a
kernel from source). The image targets the Ubuntu struct vxfs_sb layout
(verified via module BTF: vs_bsize@32, vs_oltext[0]@368, vs_oltsize@376);
mainline offsets differ (vs_bsize@24, vs_oltext[0]@356, vs_oltsize@364)
and the PoC script documents both. The bug itself is in shared mainline
code and is independent of these offsets.

Thanks,
canhnguyen26 (Nguyen Duc Canh)