Skip to content

EXT4: guard directory-entry parsing against out-of-bounds reads on malformed images - #848

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

EXT4: guard directory-entry parsing against out-of-bounds reads on malformed images#848
iabdullah215 wants to merge 1 commit into
apple:mainfrom
iabdullah215:harden-ext4-direntry-bounds

Conversation

@iabdullah215

Copy link
Copy Markdown

What

Add bounds checks to EXT4.EXT4Reader.getDirEntries (in Sources/ContainerizationEXT4/EXT4+Reader.swift) so that a malformed ext4 directory block can no longer drive Data.subdata past the end of the block buffer.

Why

An ext4 image is untrusted input (a distributed / user-supplied rootfs block), so the reader must tolerate hostile on-disk structures. The directory-entry parse loop performed two reads at attacker-controlled offsets without bounds checks:

  • The fixed 8-byte entry header was read whenever offset < dirTree.count. A crafted recordLength that advances offset to within 1–7 bytes of the end makes the header read (offset ..< offset + entrySize) run past the block.
  • The entry name was read as offset + 8 ..< offset + 8 + nameLength, but nameLength (a UInt8 taken straight from the image) was never validated against the block length, and recordLength was only checked >= entrySize, not >= entrySize + nameLength.

Because Data.subdata(in:) has a precondition on its range, either case traps the process (SIGTRAP) rather than returning an error, i.e. a malformed image is a denial of service against any caller that lists a directory.

Fix

Two guards inside the loop, consistent with the existing recordLength >= entrySize guard (stop parsing the block instead of trapping):

  1. offset + entrySize <= dirTree.count before loading the fixed header.
  2. recordLength >= entrySize + nameLength and nameStart + nameLength <= dirTree.count before reading the name.

Well-formed images are unaffected: their entries tile the block exactly and always satisfy recordLength >= 8 + nameLength with the name inside the block.

Testing

New Tests/ContainerizationEXT4Tests/TestEXT4Reader+DirEntryBounds.swift:

  • wellFormedBlockParsesAllEntries — regression: the guards don't reject valid entries.
  • shortHeaderTailDoesNotTrap — header-bounds guard.
  • nameLongerThanRecordDoesNotTrap — record-consistency guard.
  • nameRunningPastBlockDoesNotTrap — block-bounds guard.

Each of the three malformed-block tests crashes the reader (SIGTRAP) without this change and passes with it; all existing EXT4PathIOTests still pass.

Notes

  • getDirEntries was private; it's changed to internal so the test can feed it crafted directory blocks directly. No public API change. Happy to keep it private and instead test via a corrupted on-disk image if preferred.
  • The new guards break (lenient, matching the adjacent recordLength guard). If you'd rather surface corruption, they could throw instead, let me know your preference.

Related

…lformed images

EXT4.EXT4Reader.getDirEntries parses a directory block from an ext4 image,
which is untrusted input. Two subdata reads used attacker-controlled offsets
without bounds checks:

- the fixed 8-byte entry header was read whenever offset < dirTree.count, so a
  recordLength that leaves a short tail runs past the block; and
- the entry name was read as offset+8 ..< offset+8+nameLength without validating
  nameLength against the block length or the record length.

A malformed directory block therefore traps the reader (Data.subdata
precondition failure, SIGTRAP), a denial of service. Add bounds guards
mirroring the existing recordLength check so parsing stops instead of trapping,
and add regression tests for the header, record-consistency, and block-bounds
cases (each crashes the reader without the fix; existing EXT4 reader tests still
pass).
@iabdullah215
iabdullah215 force-pushed the harden-ext4-direntry-bounds branch from 751d54d to 2a7fea6 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