Skip to content

Fix/string table oom on malformed apk#6

Merged
MiroslavDrbal merged 2 commits intomasterfrom
fix/string-table-oom-on-malformed-apk
Apr 10, 2026
Merged

Fix/string table oom on malformed apk#6
MiroslavDrbal merged 2 commits intomasterfrom
fix/string-table-oom-on-malformed-apk

Conversation

@MiroslavDrbal
Copy link
Copy Markdown
Collaborator

No description provided.

Two allocation vulnerabilities in string table parsing allow a crafted
resources.arsc to cause multi-GiB heap allocations:

parseStringTable — res.data = make([]byte, r.N)
  r.N is derived from the chunk's totalLen field, a raw uint32 that a
  malformed APK can set to ~4 GiB. make() commits the memory immediately;
  io.ReadFull then fails (if the file is smaller), but only after the 4 GiB
  block is live on the heap. The existing stringCnt >= 2M guard does not
  bound the data section size. Guard with a 50 MiB limit before the make.

parseString16 — buf = make([]uint16, strCharacters)
  The extended 31-bit length encoding (high bit of the first uint16 set)
  allows strCharacters up to 0x7FFF_FFFF (4 GiB as []uint16) per call.
  r is bytes.NewReader(t.data[offset:]), so binary.Read fails immediately
  when the actual data is smaller -- but only after the allocation lands.
  Fix with two guards in order:
  1. Validate strCharacters against br.Len(): a correctly-formed string
     cannot claim more chars than remaining_bytes/2. Catches both crafted
     fields and any future decoding misalignment.
  2. Secondary cap at 64 KiB: no legitimate resource string exceeds this.

Also fix &buf -> buf in binary.Read calls (parseString16, parseString8):
  Passing *[]uint16 / *[]uint8 instead of the slice value itself bypasses
  binary.Read's intDataSize fast-path switch (which handles []uint16 /
  []uint8 but not pointer-to-slice). The fallback reflection path allocates
  an extra []byte temp buffer of the same size and calls reflect.New per
  element -- visible as ~3 GiB flat in encoding/binary.Read and ~378 MB
  in reflect.New in heap profiles of affected servers. Passing the slice
  directly uses the fast path, is functionally identical, and is zero-
  reflection.
@MiroslavDrbal MiroslavDrbal requested a review from Tasssadar April 9, 2026 14:20
@MiroslavDrbal MiroslavDrbal force-pushed the fix/string-table-oom-on-malformed-apk branch 2 times, most recently from 0911a35 to 86806db Compare April 9, 2026 14:26
Add hierarchical size validation to the binary resource parser.
Every size field parsed from the file is now validated against the
bytes actually available from the parent before it can drive an
allocation or loop.

common.go:
- parseChunkHeader enforces headerLen >= chunkHeaderSize and
  totalLen >= headerLen, rejecting structurally invalid chunks.

binxml.go:
- ParseXml: replaced uint32 totalLen subtraction with int64 dataLen
  to eliminate underflow that could create ~4 GiB loop bounds.
  Loop position tracking changed to int64. Inner chunks validated
  against remaining parent data. Magic 2*4 replaced with
  chunkHeaderSize constant.
- parseResourceIds: capped at 1M entries (~4 MB).

resources.go:
- ParseResourceTable: same int64 arithmetic and inner chunk bounds
  check as ParseXml.
- parseTypeSpec: signature tightened to *io.LimitedReader, entryCount
  validated against r.N before the append loop.

stringtable.go:
- parseStringTable: 4*stringCnt validated against r.N before
  allocating the string offsets buffer.
- parseStringTableWithChunk: uint32 subtraction moved to int64 space.
@MiroslavDrbal MiroslavDrbal force-pushed the fix/string-table-oom-on-malformed-apk branch from 86806db to 997da5a Compare April 9, 2026 18:12
@MiroslavDrbal MiroslavDrbal merged commit 7171baf into master Apr 10, 2026
3 of 7 checks passed
@MiroslavDrbal MiroslavDrbal deleted the fix/string-table-oom-on-malformed-apk branch April 10, 2026 09:29
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.

2 participants