Skip to content

ext2: sparse file holes fail to read ("invalid block index (0)") — qemu-test panics loading t_gid #192

Description

@Galfurian

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

  1. cmake .. && make && make qemu-test
  2. 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).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions