Summary
make qemu-test panics on current main (verified at 62c638a) while loading test 10/39 (t_gid). The root cause is in the ext2 driver: files containing sparse holes (block pointer 0 within i_size) fail to read, even though the on-disk image is valid and Linux-legal.
This is a recurrence of #56 (2024, t_alarm, identical arithmetic: a file whose size is N full blocks + a few trailing bytes). That issue was closed after PR #59 without the sparse-hole root cause being identified, and the maintainer asked for a follow-up if it resurfaced. It resurfaces whenever a file created by the standard image build has an all-zero trailing partial block — currently t_gid, tomorrow any other binary.
Reproduction
cmake .. && make && make qemu-test
- Serial output:
[SYSLOG] Running test (10/39): t_gid
[ER | kernel/src/fs/ext2.c:1047 ] You are trying to read an invalid block index (0).
[ER | kernel/src/fs/ext2.c:1919 ] Failed to read the inode block 27 of inode 70
[ER | kernel/src/elf/elf.c:309 ] Failed to read 110596 bytes from the file `t_gid`.
[CR | kernel/src/mem/page_fault.c:252] ERR(0): Page directory entry not present (000)
... kernel PANIC: Page fault! (EIP 0xc00026f9, cr2 0xbfffedf8)
Root cause — proven
On-disk evidence. The image is built with mke2fs -d filesystem -b 4096 (CMakeLists.txt:182). mke2fs stores all-zero blocks as sparse holes. debugfs -R "stat <70>" on a pristine (never-booted) rootfs.img:
Inode: 70 Type: regular Mode: 0755 Size: 110596
BLOCKS:
(0-11):2060-2071, (IND):2072, (12-26):2073-2087
TOTAL: 28
110596 bytes need 28 blocks (27 full + a 4-byte tail). Only 27 data blocks exist; block index 27 (the 4-byte all-zero tail) is a hole. debugfs -R "dump <70> ..." confirms the tail bytes are zeros.
Isolated host proof (no MentOS involved):
$ dd 27*4096 bytes of 0xAB + 4 zero bytes -> ext/file.bin # size 110596
$ mke2fs -N 0 -d ext -b 4096 -t ext2 -F img 4M
$ debugfs -R "stat <12>" img
(0-11):74-85, (IND):86, (12-26):87-101 # 27 data blocks, hole at 27
Kernel side.
ext2_read_inode_data (kernel/src/fs/ext2.c:1886) loops block_index = start_block .. end_block with end_block = size / block_size = 27, so it reads the hole block.
- The indirect block entry for index 27 resolves to 0, and
ext2_read_block (ext2.c:1044-1048) rejects block 0 with You are trying to read an invalid block index (0) → the whole read fails.
Linux semantics: a zero block pointer inside i_size is a hole and must read as zeros.
Secondary effect — crash propagation (proven chain)
The failed read happens inside elf_load_file after __load_executable already destroyed the old address space (mm_destroy(task->mm), kernel/src/process/process.c:175). sys_execve then returns an error to a process whose image no longer exists → kernel-mode page fault at a user-stack address → panic. Any exec-time I/O or format error after that point becomes a kernel panic instead of a clean -EIO/-ENOEXEC; this plausibly also explains open issue #121 ("Crashes with some bad executables").
Hypothesis (not proven)
The write side likely has the mirror problem: writing into a hole must allocate a block first, while ext2_write_block currently rejects block index 0 the same way. Untested.
Expected vs actual
- Expected: sparse holes within file size read as zeros;
t_gid loads and runs; failed/short reads during exec return an error to the caller.
- Actual:
ext2_read_inode_data returns -1; ELF load fails; kernel panics; test suite cannot complete (10/39).
Suggested fix direction
- In the inode-data layer (not in
ext2_read_block, where block 0 remains a valid sanity check for metadata): if the resolved block index is 0 and the offset is inside i_size, zero-fill the cache buffer instead of failing.
- Separately,
__load_executable should not destroy mm before the new image is fully loaded (or its error path must not return to userspace).
- Consider a
t_sparse_read regression test: write a file with a zero tail to the image, read it back in-guest.
Severity
High. Any file produced by the standard image build with an all-zero tail is unreadable; the kernel test suite deterministically panics; the panic itself is masked by the test infrastructure (see the companion issue about qemu-test exit codes).
Refs: #56 (original occurrence), #59 (previous partial fix), #121 (likely related crash class).
Summary
make qemu-testpanics on currentmain(verified at62c638a) while loading test 10/39 (t_gid). The root cause is in the ext2 driver: files containing sparse holes (block pointer 0 withini_size) fail to read, even though the on-disk image is valid and Linux-legal.This is a recurrence of #56 (2024,
t_alarm, identical arithmetic: a file whose size is N full blocks + a few trailing bytes). That issue was closed after PR #59 without the sparse-hole root cause being identified, and the maintainer asked for a follow-up if it resurfaced. It resurfaces whenever a file created by the standard image build has an all-zero trailing partial block — currentlyt_gid, tomorrow any other binary.Reproduction
cmake .. && make && make qemu-testRoot cause — proven
On-disk evidence. The image is built with
mke2fs -d filesystem -b 4096(CMakeLists.txt:182).mke2fsstores all-zero blocks as sparse holes.debugfs -R "stat <70>"on a pristine (never-booted)rootfs.img:110596 bytes need 28 blocks (27 full + a 4-byte tail). Only 27 data blocks exist; block index 27 (the 4-byte all-zero tail) is a hole.
debugfs -R "dump <70> ..."confirms the tail bytes are zeros.Isolated host proof (no MentOS involved):
Kernel side.
ext2_read_inode_data(kernel/src/fs/ext2.c:1886) loopsblock_index = start_block .. end_blockwithend_block = size / block_size= 27, so it reads the hole block.ext2_read_block(ext2.c:1044-1048) rejects block 0 withYou are trying to read an invalid block index (0)→ the whole read fails.Linux semantics: a zero block pointer inside
i_sizeis a hole and must read as zeros.Secondary effect — crash propagation (proven chain)
The failed read happens inside
elf_load_fileafter__load_executablealready destroyed the old address space (mm_destroy(task->mm), kernel/src/process/process.c:175).sys_execvethen returns an error to a process whose image no longer exists → kernel-mode page fault at a user-stack address → panic. Any exec-time I/O or format error after that point becomes a kernel panic instead of a clean-EIO/-ENOEXEC; this plausibly also explains open issue #121 ("Crashes with some bad executables").Hypothesis (not proven)
The write side likely has the mirror problem: writing into a hole must allocate a block first, while
ext2_write_blockcurrently rejects block index 0 the same way. Untested.Expected vs actual
t_gidloads and runs; failed/short reads during exec return an error to the caller.ext2_read_inode_datareturns -1; ELF load fails; kernel panics; test suite cannot complete (10/39).Suggested fix direction
ext2_read_block, where block 0 remains a valid sanity check for metadata): if the resolved block index is 0 and the offset is insidei_size, zero-fill the cache buffer instead of failing.__load_executableshould not destroymmbefore the new image is fully loaded (or its error path must not return to userspace).t_sparse_readregression test: write a file with a zero tail to the image, read it back in-guest.Severity
High. Any file produced by the standard image build with an all-zero tail is unreadable; the kernel test suite deterministically panics; the panic itself is masked by the test infrastructure (see the companion issue about
qemu-testexit codes).Refs: #56 (original occurrence), #59 (previous partial fix), #121 (likely related crash class).