Skip to content

A corrupt chunk length in a .beam crashes indexing #3952

Description

@sh41

A truncated .beam is handled. A corrupt one takes down indexing.

Chunk.from reads a chunk's declared length as an unsigned 32-bit value into a Long, then narrows it with length.toInt() and hands it straight to ByteArray(count). That allocation sits outside the try that is supposed to make these reads safe, and nothing bounds the length first. Two shapes, both reproduced:

Declared chunk length Narrowed Int Result
0xFFFFFFFF -1 NegativeArraySizeException
0x7FFFFFFF 2147483647 OutOfMemoryError: Requested array size exceeds VM limit
in between, e.g. 0x40000000 1073741824 allocates 1 GB, or fails — depends on the heap

Neither escapes into a handler. NegativeArraySizeException is not an IOException, so none of the four catch blocks in Beam.from sees it. OutOfMemoryError is an Error, so even a catch (Exception) would miss it. No caller catches RuntimeException either.

Why this matters more than a decompiler glitch

Cache.from(FileContent) is a FileBasedIndexExtension input. So this is not a broken decompiler view that a user can close — a single corrupt .beam anywhere in a dependency fails indexing for the whole project, and nothing tells the user which file did it.

The contrast with the truncation path is the point. A .beam cut short mid-write degrades exactly as intended: reading stops, complete chunks are kept, nothing is logged. That tolerance was added in 80511d6 and has worked since v11.12.0. It just never extended to a length that is present but implausible.

Reproducing it

A twelve-byte input is enough — FOR1, any length, BEAM, then a chunk header of AtU8 and the length above. No real .beam needed, and the OutOfMemoryError case reproduced under a 4 GB test heap, so it is not a small-heap artefact. Note that the test JVM writes a ~365 MB heap dump when it fires.

Notes for whoever fixes it

  • One expression, one file. safeReadBytes has three call sites and the other two pass a literal 4. The narrowing in Chunk.from is the only place under beam/ where a length taken from the file reaches an allocation.
  • A negative check is not sufficient, and neither is a new catch — the middle row above is positive, and catching OutOfMemoryError would still mean having tried to allocate a nonsense array. What is needed is a plausibility bound on the length before the allocation, inside the try, so an implausible length degrades the same way a short read already does.
  • The guard needs a mutation check. Neuter it once written and confirm the malformed cases actually go red. Passing tests against an unbounded allocation prove nothing.
  • SdkBeamParseTest covers 1666 real .beam files and is worth running, but none of them is corrupt, so it can only show the fix broke nothing — it cannot exercise the path being fixed.

Found while reviewing #1101, which reported the truncation message and is genuinely fixed. This is a different failure mode at a different severity, so it is filed on its own rather than reopening that one.

Filed as part of an AI-assisted review of the backlog. A human checked this one before it was filed, but it might still be wrong — if any of it is, please say so here.

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions