Skip to content

readBend/readTremoloBarEffect OOM-crash on unbounded count fields (DoS) #2677

@kaizenman

Description

@kaizenman

Is there an existing issue for this?

  • I have searched the existing issues

Current Behavior

Gp3To5Importer.readBend and readTremoloBarEffect read a pointCount: int32 from the input stream and then loop pointCount times, reading 9 bytes per iteration. Neither validates that pointCount * 9 fits in the remaining stream:

const pointCount: number = IOHelper.readInt32LE(this.data);
if (pointCount > 0) {
    for (let i: number = 0; i < pointCount; i++) {
        IOHelper.readInt32LE(this.data);
        IOHelper.readInt32LE(this.data);
        GpBinaryHelpers.gpReadBool(this.data);
        // ...allocates a BendPoint per iteration
    }
}

When the parser becomes misaligned mid-stream (e.g. on a corrupted file where an earlier field shifts the cursor onto random bytes), pointCount is read from those random bytes and can be up to ~2^31. The loop then iterates hundreds of millions of times. ByteBuffer.readByte() returns -1 silently at EOF rather than throwing, so the loop never terminates from the input side; meanwhile each iteration allocates a BendPoint object — V8 heap grows until OOM-crash.

This is a practical DoS vector against any browser page that runs AlphaTab on user-supplied input: a single ~3 KB corrupt .gp5 can OOM-crash the tab in 10–30 seconds, taking down all sibling tabs in the same Chromium renderer process.

Expected Behavior

The parser should bail out with a typed UnsupportedFormatError when an input field is impossible by conservation of bytes (count × bytes-per-iteration exceeds remaining stream). Real bends have ≤ ~30 points (270 bytes); anything larger than the remaining stream is malformed input and should never enter the loop.

Steps To Reproduce

  1. Obtain the reference fixture: test-data/guitarpro5/corrupted-bend-point-count.gp5 (a 3.8 KB GP5 file). The file's readBeatEffects flag byte mid-stream causes readTremoloBarEffect to be invoked on garbage, where pointCount reads as 587,530,544 against 1,413 remaining bytes — a 6-orders-of-magnitude mismatch. Fixture is committed in PR fix(importer): bound runaway loops on corrupt count fields in GP3-5 binary parser #2670.
  2. Run ScoreLoader.loadScoreFromBytes(bytes) in Node with --max-old-space-size=512.
  3. Observe OOM-crash after ~10 seconds (FATAL ERROR: Reached heap limit Allocation failed).

Link to jsFiddle, CodePen, Project

No response

Version and Environment

alphaTab: 1.9.0 (commit f8ef24f, current HEAD of `develop`)
Node.js: v22.14.0
OS: Ubuntu 22.04.3 LTS on WSL2 (kernel 6.6.87.2-microsoft-standard-WSL2)

Platform

Web

Anything else?

PyGuitarPro rejects the same fixture with a typed GPException because Python's struct.unpack raises on insufficient bytes — AlphaTab's ByteBuffer.readByte() → -1-on-EOF lacks the equivalent safety net. Fix proposed in PR #2670 (targeted bounds check in the two empirically-observed loops).

Metadata

Metadata

Assignees

Labels

area-file-formatsRelated to supported file formatsplatform-allAffects all platformsstate-acceptedThis is a valid topic to work on.

Type

Projects

Status

No status

Relationships

None yet

Development

No branches or pull requests

Issue actions