Skip to content

EXT4: guard extent-tree parsing against out-of-bounds reads on malformed images - #849

Open
iabdullah215 wants to merge 1 commit into
apple:mainfrom
iabdullah215:harden-ext4-extent-bounds
Open

EXT4: guard extent-tree parsing against out-of-bounds reads on malformed images#849
iabdullah215 wants to merge 1 commit into
apple:mainfrom
iabdullah215:harden-ext4-extent-bounds

Conversation

@iabdullah215

Copy link
Copy Markdown

What

Bounds-check the extent-tree decode in EXT4.EXT4Reader so a malformed inode can no longer drive Data.subdata past the inode's block region. The decode is extracted into an internal decodeExtents(inodeBlock:) helper so it can be unit-tested directly.

Why

An ext4 image is untrusted input. getExtents iterated 0..<entries reading fixed-size records (12 bytes each) with subdata, trusting the entry counts from the on-disk extent headers without checking them against the bytes available:

  • The inode block region is only 60 bytes (header + up to four 12-byte records). A depth-0 header claiming >4 leaves, or a depth-1 header claiming >4 indices, makes a read run past the block.
  • In the depth-1 path, each leaf block trusts its own header's entries the same way, past the end of the block buffer.

Because Data.subdata(in:) has a precondition on its range, either case traps the process (SIGTRAP) instead of returning an error, a denial of service for any caller that reads a file backed by a crafted inode.

Fix

  • Guard every fixed-size read with offset + size <= buffer.count (throwing EXT4.Error.invalidExtents when a header claims more records than fit, i.e. surfacing the corruption rather than silently truncating).
  • Fix a latent reversed range when reading a leaf block's header: blockOffset..<extentHeaderSizeblockOffset..<blockOffset + extentHeaderSize (previously benign only because blockOffset == 0 there).

Well-formed images are unaffected: their headers never claim more records than the block holds.

Testing

New Tests/ContainerizationEXT4Tests/TestEXT4Reader+ExtentBounds.swift:

  • wellFormedDepth0ExtentsParse — regression: valid depth-0 extents still parse.
  • tooManyDepth0EntriesDoesNotTrap — a header claiming 5 leaves in a 60-byte block is rejected (crashes the reader without this change).
  • shortInodeBlockReturnsEmpty / nonExtentInodeReturnsEmpty, no-extent cases.

All existing EXT4PathIOTests (which read real files through getExtents) still pass.

Related

…med images

EXT4.EXT4Reader.getExtents decodes the extent tree from an inode's block
region, which is untrusted input. Both the depth-0 and depth-1 loops iterated
0..<entries reading fixed-size records via subdata without checking the
attacker-controlled entry count against the bytes available:

- the inode block is only 60 bytes, so a header claiming more than four
  depth-0 leaves (or depth-1 indices) reads past it; and
- a depth-1 leaf block trusts its own header's entries the same way.

A malformed inode therefore traps the reader (Data.subdata precondition
failure, SIGTRAP), a denial of service. Move the decode into an internal
decodeExtents(inodeBlock:) helper, bounds-check every fixed-size read, and fix
a latent reversed range when reading a leaf block's header
(blockOffset..<extentHeaderSize -> blockOffset..<blockOffset + extentHeaderSize).
Add regression tests for the depth-0 overflow and the valid/short/non-extent
cases (the overflow crashes the reader without the fix; existing EXT4 reader
tests still pass).
@iabdullah215
iabdullah215 force-pushed the harden-ext4-extent-bounds branch from 214abeb to 2e2a771 Compare August 24, 2026 07:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant