* [SECURITY] e2fsprogs v1.47.4 Vulnerabilities — Orphan File & Extent Handling
@ 2026-04-03 11:29 4fqr
2026-04-03 13:48 ` Theodore Tso
2026-04-03 16:17 ` Andreas Dilger
0 siblings, 2 replies; 5+ messages in thread
From: 4fqr @ 2026-04-03 11:29 UTC (permalink / raw)
To: linux-ext4@vger.kernel.org
[-- Attachment #1.1: Type: text/plain, Size: 1349 bytes --]
linux-ext4@vger.kernel.org,
I'm disclosing three security vulnerabilities in e2fsprogs v1.47.4 affecting orphan file inode processing and extent tree validation. This follows responsible disclosure notification to the maintainer (Theodore Ts'o).
**Vulnerability Overview:**
F1 (CRITICAL): process_orphan_block() lacks inode range validation before calling release_orphan_inode(), allowing arbitrary inode destruction via crafted orphan file blocks.
F2 (HIGH): pass1 dispatch chain aliases s_orphan_file_inum with reserved inodes (5–10), bypassing reserved inode guards and enabling mode corruption on critical system inodes like the resize inode.
F3 (MEDIUM): ext2fs_extent_fix_parents() contains an unsigned underflow where blk64_t subtraction truncates to __u32, corrupting parent extent length metadata.
**Technical Details:**
All three are exploitable via crafted .img files processed by e2fsck -y with no special privileges. Detailed technical report with code locations, attack scenarios, and exact fixes is attached.
**Timeline:**
- Primary maintainer contact: Theodore Ts'o (tytso@mit.edu)
- 90-day embargo from maintainer acknowledgment
- Kernel security team notified concurrently
Patches and coordination discussion will follow once the maintainer has reviewed.
Thanks,
4fqr
4fqr@proton.me
Attachment: e2fsprogs_audit_4fqr.txt
[-- Attachment #1.2: Type: text/html, Size: 2449 bytes --]
[-- Attachment #2: e2fsprogs_audit_4fqr.txt --]
[-- Type: text/plain, Size: 30030 bytes --]
================================================================================
e2fsprogs â SECURITY AUDIT REPORT
Target: v1.47.4 | Date: 2026-04-03
================================================================================
Auditor : 4fqr
Scope : Full source tree â emphasis on e2fsck, libext2fs, orphan handling,
extent tree code, and any path reachable by a malicious disk image.
Method : Static analysis + manual code review. No fuzzing performed.
Threat : Attacker supplies a crafted ext4 filesystem image.
Victim runs e2fsck -y on it (USB attach, cloud disk, VM image).
================================================================================
EXECUTIVE SUMMARY
================================================================================
Three confirmed vulnerabilities were found. Two are directly triggerable by
a crafted filesystem image processed by e2fsck. One creates a destructive
chain attack when combined with the other.
The root cause in each case is the same class of mistake: on-disk values are
trusted as valid indices or inode numbers without the same range-checks that
are applied in older, parallel code paths.
None of these require kernel exploitation, memory corruption primitives, or
special privileges. A crafted .img file + "e2fsck -y image.img" is enough.
Severity summary:
ââââââ¬ââââââââââââââââââââââââââââââââââââââââââââââââââââââââ¬âââââââââââ
â # â Title â Severity â
ââââââ¼ââââââââââââââââââââââââââââââââââââââââââââââââââââââââ¼âââââââââââ¤
â F1 â Orphan file blocks: no inode range check before â CRITICAL â
â â release_orphan_inode() â â
ââââââ¼ââââââââââââââââââââââââââââââââââââââââââââââââââââââââ¼âââââââââââ¤
â F2 â s_orphan_file_inum aliases reserved inodes in pass1 â HIGH â
â â dispatch; triggers destructive chain via resize inode â â
ââââââ¼ââââââââââââââââââââââââââââââââââââââââââââââââââââââââ¼âââââââââââ¤
â F3 â ext2fs_extent_fix_parents(): __u32 += blk64_t â MEDIUM â
â â unsigned underflow corrupts parent extent length â â
ââââââ´ââââââââââââââââââââââââââââââââââââââââââââââââââââââââ´âââââââââââ
================================================================================
BACKGROUND â HOW ORPHAN PROCESSING WORKS
================================================================================
When ext4 needs to track inodes that must be cleaned up on next mount/fsck
(e.g. unlinked files still open), it uses one of two mechanisms:
LEGACY â a singly-linked list threaded through s_last_orphan â
inode.i_dtime â inode.i_dtime â ... â 0
NEW â an "orphan file": a dedicated hidden inode (s_orphan_file_inum)
whose data blocks each contain an array of u32 inode numbers.
Indicated by feature flags orphan_file + orphan_present.
e2fsck processes both at startup in release_orphan_inodes() (super.c:509),
before Pass 1 runs â meaning before any inode bitmap, block bitmap, or inode
table has been validated against the actual filesystem state.
The function that does the actual work for both paths is:
release_orphan_inode(ctx, &ino, block_buf) [super.c:317]
It reads the inode, frees its blocks, and â if i_links_count == 0 â marks
the inode itself as free in the inode allocation bitmap.
================================================================================
FINDING F1 â CRITICAL
Missing inode range validation in process_orphan_block()
================================================================================
File : e2fsck/super.c
Lines : 420 â 427 (vulnerable path)
548 â 552 (the guard that exists for the legacy path, but NOT here)
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
â VULNERABLE CODE â
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
super.c:420
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
bdata = (__u32 *)pd->buf;
for (j = 0; j < inodes_per_ob; j++) {
if (!bdata[j])
continue;
ino = ext2fs_le32_to_cpu(bdata[j]); /* raw disk value */
if (release_orphan_inode(ctx, &ino, pd->block_buf)) /* NO CHECK */
goto return_abort;
bdata[j] = 0;
}
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
â THE GUARD THAT PROTECTS THE LEGACY PATH (but is absent here) â
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
super.c:548
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
/* Traditional orphan list: head inode is validated */
if (ino && ((ino < EXT2_FIRST_INODE(fs->super)) ||
(ino > fs->super->s_inodes_count))) {
fix_problem(ctx, PR_0_ORPHAN_ILLEGAL_HEAD_INODE, &pctx);
goto err_qctx;
}
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
â WHAT release_orphan_inode() DOES WITHOUT A VALID INODE â
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
super.c:317
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
static int release_orphan_inode(e2fsck_t ctx, ext2_ino_t *ino, ...)
{
e2fsck_read_inode_full(ctx, *ino, ...); /* reads inode from disk */
next_ino = inode.i_dtime;
if (next_ino &&
((next_ino < EXT2_FIRST_INODE(fs->super)) || ...)) /* NEXT is
{ return 1; } checked,
not *ino */
if (release_inode_blocks(ctx, *ino, &inode, ...)) /* frees */
return 1; /* blocks */
if (!inode.i_links_count) {
ext2fs_inode_alloc_stats2(fs, *ino, -1, ...); /* marks inode */
ctx->free_inodes++; /* as FREE in */
ext2fs_set_dtime(fs, ...); /* the bitmap */
}
e2fsck_write_inode_full(ctx, *ino, ...); /* writes back */
}
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
Note carefully: inside release_orphan_inode, the range check at lines
334â339 validates NEXT (i_dtime chain), not *ino itself. The current
inode is never validated.
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
â ATTACK SCENARIO â
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
Craft an ext4 image with:
1. Feature flags: orphan_file + orphan_present set in the superblock.
Do NOT set metadata_csum â the block checksum is then skipped.
(If metadata_csum is desired, set s_checksum_seed to any known value
and compute the matching CRC32c â the attacker controls the seed.)
2. A valid inode for s_orphan_file_inum (any regular non-reserved inode).
Its data block(s) are attacker-controlled.
3. The data block(s) filled with target inode numbers as little-endian
u32 values. Useful targets with i_links_count == 0:
- inode 7 (EXT2_RESIZE_INO) â journal/block bitmaps freed
- inode 9 (exclude bitmap inode)
- inode 10 (journal backup inode)
Useful targets with i_links_count > 0 (blocks released, inode kept):
- Any inode whose blocks you want freed (data destruction)
4. Do NOT set EXT2_ERROR_FS in s_state â that flag causes
release_orphan_inodes() to short-circuit before reaching this code.
Result: e2fsck -y reads the image, enters release_orphan_inodes(), calls
process_orphan_file(), reaches process_orphan_block(), and invokes
release_inode_blocks() + ext2fs_inode_alloc_stats2() on each target inode
â all before Pass 1 has validated a single bitmap.
Impact: targeted inodes have their blocks freed and are marked available
for reuse. The filesystem is silently corrupted. With EXT2_RESIZE_INO as
a target, the block group descriptor tables are freed.
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
â FIX â
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
Add the same guard that the legacy path has, immediately before the call
to release_orphan_inode() in process_orphan_block():
ino = ext2fs_le32_to_cpu(bdata[j]);
+ if (ino < EXT2_FIRST_INODE(fs->super) ||
+ ino > fs->super->s_inodes_count) {
+ fix_problem(ctx, PR_0_ORPHAN_ILLEGAL_INODE, &pctx);
+ goto return_abort;
+ }
if (release_orphan_inode(ctx, &ino, pd->block_buf))
================================================================================
FINDING F2 â HIGH
s_orphan_file_inum aliases reserved inodes in pass1 dispatch chain
================================================================================
File : e2fsck/pass1.c
Lines : 1725 â 1879 (the dispatch chain)
1853 â 1866 (the orphan-file branch â fires too early)
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
â THE DISPATCH CHAIN IN PASS 1 â
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
pass1.c evaluates the following else-if ladder for every inode:
1725: if (ino == EXT2_BAD_INO) â inode 1 â protected
1773: elif (ino == EXT2_ROOT_INO) â inode 2 â protected
1800: elif (ino == EXT2_JOURNAL_INO) â inode 8 â protected
1826: elif (quota_inum_is_reserved(fs, ino)) â inodes 3, 4 â protected
1853: elif (ino == s_orphan_file_inum) â ORPHAN FILE BRANCH
1879: elif (ino < EXT2_FIRST_INODE(fs->super))â catches 5,6,7,9,10
If s_orphan_file_inum is set to 5, 6, 7, 9, or 10 in the superblock,
the orphan-file branch at line 1853 fires BEFORE the generic reserved-inode
guard at line 1879. Those reserved inodes are never properly handled.
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
â VULNERABLE CODE â
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
pass1.c:1853
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
} else if (ino == fs->super->s_orphan_file_inum) {
ext2fs_mark_inode_bitmap2(ctx->inode_used_map, ino);
if (ext2fs_has_feature_orphan_file(fs->super)) {
if (!LINUX_S_ISREG(inode->i_mode) && /* mode check */
fix_problem(ctx, PR_1_ORPHAN_FILE_BAD_MODE, &pctx)) {
inode->i_mode = LINUX_S_IFREG; /* WRITES BACK */
e2fsck_write_inode(ctx, ino, inode, "pass1");
}
check_blocks(ctx, &pctx, block_buf, NULL);
FINISH_INODE_LOOP(ctx, ino, &pctx, failed_csum);
continue; /* skips all reserved-inode validation */
}
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
With -y, fix_problem() returns 1 unconditionally. For any reserved inode
that is not a regular file (resize inode has S_IFREG, others have mode=0),
e2fsck would write LINUX_S_IFREG into that inode's i_mode field on disk.
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
â THE DESTRUCTIVE CHAIN: s_orphan_file_inum = 7 (EXT2_RESIZE_INO) â
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
The resize inode (inode 7) is used for online filesystem expansion. Its
data blocks are the per-group BLOCK BITMAP IMAGES â one block per group,
containing bit-for-bit maps of allocated blocks.
When process_orphan_file() runs on inode 7 (in release_orphan_inodes,
before Pass 1), it iterates every data block of inode 7 and calls
process_orphan_block() on each:
/* process_orphan_block reads the block as an array of u32 inode#s */
bdata = (__u32 *)pd->buf; /* resize inode block data */
for (j = 0; j < inodes_per_ob; j++) {
ino = ext2fs_le32_to_cpu(bdata[j]); /* bitmap words as inode#s */
release_orphan_inode(ctx, &ino, ...);
}
A per-group block bitmap for a dense filesystem is full of 0xFFFFFFFF
words â inode# 4294967295 > s_inodes_count, harmless. But for a filesystem
that is moderately allocated, the bitmaps contain words like 0x0000003F or
0x000003FF â small inode numbers that ARE valid inodes. Those inodes get
their blocks released and are marked free.
On a semi-sparse crafted filesystem, the attacker can arrange exactly which
words appear in the resize inode's blocks by controlling block allocation
density, giving precise control over which inodes get wiped.
This chain requires BOTH F1 and F2:
F2 â process_orphan_file() is called on the resize inode's blocks
F1 â each word in those blocks passes unchecked into release_orphan_inode
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
â FIX â
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
Validate s_orphan_file_inum at superblock-check time and in the pass1
dispatch chain. Two changes:
(a) In e2fsck/super.c, when opening the orphan file, guard the inode number:
orphan_inum = fs->super->s_orphan_file_inum;
+ if (orphan_inum < EXT2_FIRST_INODE(fs->super) ||
+ orphan_inum > fs->super->s_inodes_count) {
+ /* fix_problem / clear orphan_file feature */
+ }
(b) In e2fsck/pass1.c, move the orphan-file else-if AFTER the
ino < EXT2_FIRST_INODE guard (line 1879), or add an explicit
lower-bound check:
} else if (ino == fs->super->s_orphan_file_inum
+ && ino >= EXT2_FIRST_INODE(fs->super)) {
================================================================================
FINDING F3 â MEDIUM
ext2fs_extent_fix_parents(): __u32 += blk64_t unsigned underflow
================================================================================
File : lib/ext2fs/extent.c
Line : 816
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
â VULNERABLE CODE â
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
extent.c:800
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
/* modified node's start block */
start = extent.e_lblk; /* blk64_t: leaf's logical block */
...
while (handle->level > 0 &&
(path->left == path->entries - 1)) {
ext2fs_extent_get(handle, EXT2_EXTENT_UP, &extent); /* go to parent*/
if (extent.e_lblk == start)
break;
path = handle->path + handle->level;
extent.e_len += (extent.e_lblk - start); /* LINE 816 */
extent.e_lblk = start;
ext2fs_extent_replace(handle, 0, &extent);
}
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
Type breakdown:
extent.e_len is __u32 (32-bit unsigned)
extent.e_lblk is blk64_t (64-bit unsigned)
start is blk64_t (64-bit unsigned)
The function is designed for the case where a leaf's start block was moved
EARLIER (smaller) â the parent index entry must extend to cover it:
extent.e_lblk > start â subtraction positive â e_len grows. Correct.
But if a crafted extent tree has a parent index entry whose e_lblk is
LESS than the current leaf's e_lblk (a B-tree invariant violation that is
not rejected on read), then:
extent.e_lblk < start
â (extent.e_lblk - start) as blk64_t wraps to ~0ULL - delta + 1
â += on __u32 truncates that to a small garbage value
â extent_replace() writes the corrupted length back to disk
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
â REACHABILITY â
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
ext2fs_extent_fix_parents() is called by:
- ext2fs_extent_insert() used throughout e2fsck and libext2fs
- ext2fs_extent_set_bmap() used during extent tree rebuild
- rewrite_extent_replay() e2fsck/extents.c â called in Pass 1E
on every inode flagged for rebuild
A crafted inode with an extent tree where a leaf's e_lblk < its parent
index's e_lblk will reach this path during Pass 1E. The attacker does not
need to corrupt the leaf-writing path â the malformed parent-child
relationship in the on-disk image is sufficient.
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
â FIX â
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
Guard against the underflow with an explicit check before the arithmetic:
if (extent.e_lblk == start)
break;
path = handle->path + handle->level;
+ if (extent.e_lblk < start) {
+ /* parent starts after child â malformed tree; bail */
+ retval = EXT2_ET_EXTENT_INVALID_LENGTH;
+ goto done;
+ }
extent.e_len += (extent.e_lblk - start);
================================================================================
FINDINGS REVIEW
================================================================================
The preliminary scan raised several additional issues. After careful review
of the actual source, here is the verdict on each:
ââââââââââââââââââââââââââââââââââââ¬âââââââââââââââââââââââââââââââââââââââ
â Claim â Verdict â
ââââââââââââââââââââââââââââââââââââ¼âââââââââââââââââââââââââââââââââââââââ¤
â extents.c:96 â blk64_t overflow â NOT A BUG. Both __u32 values are â
â in extent merge accumulation â promoted to blk64_t before addition. â
â â The (1ULL<<32) check is correct. â
ââââââââââââââââââââââââââââââââââââ¼âââââââââââââââââââââââââââââââââââââââ¤
â icount.c:220 â sprintf overflow â NOT A BUG. Allocation at line 216 â
â with long tdb_dir path â is strlen(tdb_dir)+64; format string â
â â produces at most +53 bytes. Safe. â
ââââââââââââââââââââââââââââââââââââ¼âââââââââââââââââââââââââââââââââââââââ¤
â dir_iterate.c â infinite loop â NOT EXPLOITABLE. rec_len < 8 check â
â on rec_len = 0 or 1 â at line 84 returns 0 immediately. â
â â Zero-length entries abort cleanly. â
ââââââââââââââââââââââââââââââââââââ¼âââââââââââââââââââââââââââââââââââââââ¤
â extents.c:241 â loop re-entry â BENIGN by design. The ex--; i-- â
â use-after-free in UNINIT split â pattern re-processes the same array â
â â slot (e_len reduced each pass). The â
â â e_len==0 guard at line 232 exits. â
ââââââââââââââââââââââââââââââââââââ¼âââââââââââââââââââââââââââââââââââââââ¤
â extent.c:1607 â save_length â NEEDS FUZZING. Context unclear from â
â minus underflow in split_node â static analysis alone. Recommend â
â â targeted AFL++ run on this function. â
ââââââââââââââââââââââââââââââââââââ´âââââââââââââââââââââââââââââââââââââââ
================================================================================
ATTACK SURFACE NOTES
================================================================================
WHY THIS MATTERS MORE THAN TYPICAL PARSER BUGS
e2fsck is routinely run automatically:
- systemd-fsck triggers it on dirty ext4 partitions at boot
- Desktop OS automounters call it on USB drives
- Cloud providers run it during disk attach / snapshot restore
- CI pipelines often call "e2fsck -y" to clean up test images
In all of these contexts, the filesystem image is the attack input and
e2fsck runs as root. Filesystem-level bugs here can silently destroy
data, corrupt kernel metadata, or (with further chaining) achieve
privilege escalation via inode bitmap manipulation.
THE TIMING OF ORPHAN PROCESSING IS CRITICAL
release_orphan_inodes() is called at the very start of e2fsck, from
check_super_block() â before Pass 1, before bitmaps are validated,
before any consistency has been established. The attacker's inode
destructions happen against an unvalidated filesystem state.
CHECKSUMS DO NOT PROTECT YOU HERE
Finding F1 is exploitable both with and without metadata_csum:
- Without: checksum check is skipped entirely.
- With: attacker sets s_checksum_seed to any value, computes
CRC32c(seed || ino || gen || blk || buf) correctly.
The seed lives in the same superblock the attacker crafts.
Checksums here protect against accidental corruption, not adversarial input.
================================================================================
QUICK REFERENCE â EXACT DIFF LOCATIONS
================================================================================
F1 e2fsck/super.c line 424 add range check before
release_orphan_inode() call
F2a e2fsck/super.c ~line 446 validate s_orphan_file_inum
>= EXT2_FIRST_INODE on entry
to process_orphan_file()
F2b e2fsck/pass1.c line 1853 add lower-bound guard to the
s_orphan_file_inum elif branch
F3 lib/ext2fs/extent.c line 816 guard against extent.e_lblk < start
before the += arithmetic
================================================================================
END OF REPORT
================================================================================
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [SECURITY] e2fsprogs v1.47.4 Vulnerabilities — Orphan File & Extent Handling
2026-04-03 11:29 [SECURITY] e2fsprogs v1.47.4 Vulnerabilities — Orphan File & Extent Handling 4fqr
@ 2026-04-03 13:48 ` Theodore Tso
2026-04-03 16:17 ` Andreas Dilger
1 sibling, 0 replies; 5+ messages in thread
From: Theodore Tso @ 2026-04-03 13:48 UTC (permalink / raw)
To: 4fqr; +Cc: linux-ext4@vger.kernel.org
On Fri, Apr 03, 2026 at 11:29:55AM +0000, 4fqr wrote:
>
> I'm disclosing three security vulnerabilities in e2fsprogs v1.47.4
> affecting orphan file inode processing and extent tree
> validation. This follows responsible disclosure notification to the
> maintainer (Theodore Ts'o).
You notified me 45 seconds before sending this e-mail to linux-ext4,
which is a public mailing list. (For future reference, essentially
all lists @vger.kernel.org are public, with the contents available at
https://lore.kernel.org.)
> Patches and coordination discussion will follow once the maintainer
> has reviewed.
Coordination discussion is moot at this point, because you've already
made your findings public. I'll review them in detail in the next few
days, but it does appear that we are missing some checks in the
orphan_file handling, which whether or not they are exploitable by a
malicious attacker, are real bugs that should be fixed.
Cheers,
- Ted
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [SECURITY] e2fsprogs v1.47.4 Vulnerabilities — Orphan File & Extent Handling
2026-04-03 11:29 [SECURITY] e2fsprogs v1.47.4 Vulnerabilities — Orphan File & Extent Handling 4fqr
2026-04-03 13:48 ` Theodore Tso
@ 2026-04-03 16:17 ` Andreas Dilger
2026-04-03 18:34 ` Theodore Tso
2026-04-03 23:11 ` Theodore Tso
1 sibling, 2 replies; 5+ messages in thread
From: Andreas Dilger @ 2026-04-03 16:17 UTC (permalink / raw)
To: 4fqr; +Cc: linux-ext4@vger.kernel.org
On Apr 3, 2026, at 05:29, 4fqr <4fqr@proton.me> wrote:
>
> linux-ext4@vger.kernel.org,
>
> I'm disclosing three security vulnerabilities in e2fsprogs v1.47.4 affecting orphan file inode processing and extent tree validation. This follows responsible disclosure notification to the maintainer (Theodore Ts'o).
>
> **Vulnerability Overview:**
>
> F1 (CRITICAL): process_orphan_block() lacks inode range validation before calling release_orphan_inode(), allowing arbitrary inode destruction via crafted orphan file blocks.
>
> F2 (HIGH): pass1 dispatch chain aliases s_orphan_file_inum with reserved inodes (5–10), bypassing reserved inode guards and enabling mode corruption on critical system inodes like the resize inode.
>
> F3 (MEDIUM): ext2fs_extent_fix_parents() contains an unsigned underflow where blk64_t subtraction truncates to __u32, corrupting parent extent length metadata.
I don't see how this exposes any kind of security vulnerability if it
requires that the image be specially modified in the first place?
At that point the "attacker" can directly modify the image in any way
they want, regardless of how e2fsprogs behaves.
It's like saying "the curatins on the inside of my windows do not
protect my privacy if I'm inside the house and I open them up...
Cheers, Andreas
>
> **Technical Details:**
>
> All three are exploitable via crafted .img files processed by e2fsck -y with no special privileges. Detailed technical report with code locations, attack scenarios, and exact fixes is attached.
>
> **Timeline:**
> - Primary maintainer contact: Theodore Ts'o (tytso@mit.edu)
> - 90-day embargo from maintainer acknowledgment
> - Kernel security team notified concurrently
>
> Patches and coordination discussion will follow once the maintainer has reviewed.
>
> Thanks,
> 4fqr
> 4fqr@proton.me
>
> Attachment: e2fsprogs_audit_4fqr.txt
> <e2fsprogs_audit_4fqr.txt>
Cheers, Andreas
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [SECURITY] e2fsprogs v1.47.4 Vulnerabilities — Orphan File & Extent Handling
2026-04-03 16:17 ` Andreas Dilger
@ 2026-04-03 18:34 ` Theodore Tso
2026-04-03 23:11 ` Theodore Tso
1 sibling, 0 replies; 5+ messages in thread
From: Theodore Tso @ 2026-04-03 18:34 UTC (permalink / raw)
To: Andreas Dilger; +Cc: 4fqr, linux-ext4@vger.kernel.org
On Fri, Apr 03, 2026 at 10:17:38AM -0600, Andreas Dilger wrote:
>
> I don't see how this exposes any kind of security vulnerability if it
> requires that the image be specially modified in the first place?
> At that point the "attacker" can directly modify the image in any way
> they want, regardless of how e2fsprogs behaves.
We can debate whther or not it is a security vulnerability, and if so,
whether it deserves a CVSS corresponding to a low, medium, or high
severity since that's what people who are worrying about FEDRAMP
compliance need to worry about in terms of time to mitigation.
The reason why it's of interest is because we tell people that if they
have a potentially untrustwothy file system image (say, they if they
pick up a USB stick that was thoughtfully dropped off in the parking
lot by the Chinese MSS or Russian KGB agent), they should run e2fsck
on said file system image before they try mounting it. Asking them to
run e2fsck on the image is only a little bit more likely to work than
telling them to just not pick up the USB stick, or DESTROY IT BY FIRE,
and so the reality is that 99% of all uesrs will just mount the file
system without running fsck --- at which point, when their system is
compromised, we call it a "problem exists between chair and keyboard"
situation.
I don't really care whether we call it a security bug, or a quality of
implementation bug, but that's because I'm not the person responsible
for FEDRAMP compliance at $WORK, and I'm not someone who hands out bug
bounties, or who is trying to keep score of how many security
vulnerabilities that I found in order to demonstrate real world impact
as part of an academic tenure-track evaluation. :-)
As far as I'm concerned, if e2fsck doesn't (a) detect a file system
corruption, or (b) doesn't fix it after a single e2fsck -y run, or
(c) crashes or results in running a malicious payload as a result of a
specially crafted payload, it's a bug, and bugs should get fixed.
Cheers,
- Ted
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [SECURITY] e2fsprogs v1.47.4 Vulnerabilities — Orphan File & Extent Handling
2026-04-03 16:17 ` Andreas Dilger
2026-04-03 18:34 ` Theodore Tso
@ 2026-04-03 23:11 ` Theodore Tso
1 sibling, 0 replies; 5+ messages in thread
From: Theodore Tso @ 2026-04-03 23:11 UTC (permalink / raw)
To: Andreas Dilger; +Cc: 4fqr, linux-ext4@vger.kernel.org
On Fri, Apr 03, 2026 at 10:17:38AM -0600, Andreas Dilger wrote:
>
> I don't see how this exposes any kind of security vulnerability if it
> requires that the image be specially modified in the first place?
> At that point the "attacker" can directly modify the image in any way
> they want, regardless of how e2fsprogs behaves.
After taking a closer look the "vulnerabilities", I understand where
Andreas is coming from. If the threat model is "the attacker can
modify the file system, then the fact that you can craft the orphan to
"force" the the file system to release an inode isn't interesting ---
because the attacker could just simply overwrite the inode and mark
the blocks as not in use in the block allocation directly.
If there was a way an attempt to pass an inode number which is very
large to release_orphan_inode() could result in a buffer overrun, then
that might be interesting. But the oh, nooes! You might be able to
force the file system to release the resize inode is not interesting;
the attacker could just stomp on the resize inode directly.
Should we add some better bounds checking? Sure, so that we can give
a more user-friendly error message, and reduce the chance of
accidentally making things worse if the file system is corrupted by
chance and metadata checksum is not enabled. But is it a "security
vulnerability"? No.
No, if you could actually force a malicious payload to run due to a
stack overrun attack, that would be interesting. And I was expecting
to find *something* like that in your analysis, only to be
disappointed.
Cheers,
- Ted
^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2026-04-03 23:11 UTC | newest]
Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-04-03 11:29 [SECURITY] e2fsprogs v1.47.4 Vulnerabilities — Orphan File & Extent Handling 4fqr
2026-04-03 13:48 ` Theodore Tso
2026-04-03 16:17 ` Andreas Dilger
2026-04-03 18:34 ` Theodore Tso
2026-04-03 23:11 ` Theodore Tso
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox