[PATCH] freevxfs: fix divide-by-zero in vxfs_bmap_ext4() causing oops on mount
From: Đức Cảnh Nguyễn
Date: Sat Aug 01 2026 - 06:46:03 EST
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) <0206canh@xxxxxxxxx>
---
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
diff --git a/fs/freevxfs/vxfs_bmap.c b/fs/freevxfs/vxfs_bmap.cinode 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) <0206canh@xxxxxxxxx>
---
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
--- 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;