s_desc_size unvalidated without the 64BIT feature β ext2_gd_csum() over-reads the group-descriptor heap allocation; console-printed crc is a kernel-heap disclosure oracle
| Field | Value |
|---|---|
| ID | DF-3049 |
| Status | new |
| Severity | Medium |
| CVSS 3.1 | CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:L/I:N/A:N |
| CWE | CWE-125 / CWE-909 |
| File | sys/vfs/ext2fs/ext2_vfsops.c |
| Lines | 549-554 (sink: ext2_csum.c:684-686) |
| Area | vfs/ext2fs |
| Confidence | certain |
| Discovered | 2026-09-02 |
| Pass | 2 (GLM 5.3 second pass) |
| Bucket | kernleak |
| Reported | pending |
| Known CVE | none |
| CVE match | novel |
Summary
The desc_size check applies only inside the 64BIT branch. Without 64BIT, ext2_gd_csum() uses s_desc_size as a crc32c read length past &fs->e2fs_gd[i]+32 for every group (verify at mount, recompute on every cgupdate) β up to ~64KB past the allocation for the last groups. The truncated 16-bit crc is printed to the console on verify failure and written back into the group descriptors on rw sync/unmount. Crafted metadata_csum image (no 64BIT) with desc_size>64: VERIFIED an OOB-read oracle β kernel 'expected' crc values 0x7c2e/0x3c11 varied with heap churn and never matched the exact in-bounds computation (0x04e5), proving live kernel heap bytes enter the crc. Varying desc_size across mounts shifts the (GF(2)-linear, invertible) crc window so adjacent-heap bytes can be solved at ~2 observations per byte; rw unmount exposes the full 32-bit value on the attacker's own device. Fix validated (reject desc_size>64 at mount): EINVAL, oracle gone.
Timeline
- 2026-09-02 Discovered during pass-2 audit of ext2_vfsops.c (GLM 5.3); heap-byte oracle demonstrated + fix validated.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3049 Β· 9 files| File | Type | Description | Size | |
|---|---|---|---|---|
| craft.py | β | 4.1 KB | view raw | |
| vntool.c | β | 1.1 KB | view raw | |
| run.sh | β | 601 B | view raw | |
| build.sh | β | 156 B | view raw | |
| run.log | β | 969 B | view raw | |
| console_lines.txt | β | 780 B | view raw | |
| fix_run.txt | β | 386 B | view raw | |
| env.txt | β | 461 B | view raw | |
| verdict.json | β | 3.0 KB | view raw |
Fix verification
fixedfixed module rejects the image with EINVAL at the desc_size bound before any gd checksum work; console shows no 'bad gd' line (no OOB read, no oracle).
findings/poc/DF-3049/fix_run.txt; findings/poc/DF-3047/fix.diff (hunk 1)
Confirmed kernel references
Detail
Exploit chain
crafted metadata_csum (no 64BIT) image, desc_size=512, gds 0..62 re-checksummed offline, gd63 wrong -> mount -> ext2_gd_csum_verify prints expected=0xNNNN computed over 448 OOB heap bytes -> repeat with varying desc_size / heap churn -> solve crc32c linear system for adjacent-heap bytes (KASLR-defeat / heap disclosure, ~N/2 mounts for N bytes)
Evidence (decisive lines)
console_lines.txt (3 mounts, varying expected), run.log, craft.py output (offline in-bounds csum 0x04e5 == stored), fix_run.txt (fixed: EINVAL, no bad-gd print)
PoC changes
crafted from scratch: 64-group 1K-block metadata_csum fs (512MB sparse), desc_size=512, sb crc32c recomputed, gd csums backward-solved for desc_size=512 semantics so the kernel reaches group 57/63 before failing; crc32c implemented to match calculate_crc32c (no inversions).
Verified recommended fix
validate s_desc_size <= sizeof(struct ext2_gd) (E2FS_64BIT_GD_SIZE) in ext2_compute_sb_data() regardless of feature bits
Verdict
s_desc_size is only validated when the 64BIT feature is set; ext2_gd_csum() otherwise reads s_desc_size-32 bytes from each fs->e2fs_gd[i], past the allocation. Demonstrated via the console-printed crc: kernel 'expected' values (0x7c2e, 0x3c11) vary with heap state and differ from the exact in-bounds computation (0x04e5) -> live kernel heap bytes entered the crc. 16-bit-per-mount oracle; rw unmount writes the crc to the attacker device.
No comments yet.