Skip to content

Bug Report: length fields from replication input size allocations before validation (GTID set intervals, compressed transaction payload events) #20945

Description

@tzh476

Overview

Two decoders size an allocation from a length field taken straight off the wire, without checking it against the bytes actually present. Both are reachable from replication traffic, and in both cases a short input can make the process reserve far more memory than the input could ever justify.

Filing this as the tracking issue for #20932 and #20933 (the NeedsIssue label on both).

1. mysql56_gtid_set.go — interval count from a Position string

ParseMysql56GTIDSet counts the : separators in the input to pre-size the interval slice. The count is attacker-controlled and is taken before any interval is validated, so a short, wholly invalid value reserves a large buffer that is then discarded on the first rejected element.

Measured (go test -bench, on a value that is rejected immediately):

before: 19,963,872 B/op
after:  11,036,616 B/op   (-44.7%)

A legitimate single-interval set is unchanged, which matters because that is the common case.

2. binlog_event_compression.go — event length inside a compressed transaction payload

GetNextEvent reads a 4-byte event length from the decompressed stream and allocates it directly. Nothing ties that number to the amount of data behind it, and in streaming mode uncompressedSize is itself unverified, so it cannot serve as the bound either.

Measured on this path:

a 26-byte zstd frame claiming a 1 GiB event inside a 2 GiB payload
    allocated 1,073,746,464 bytes

a 145-byte frame delivering 1,114,112 bytes while claiming 4,294,967,295
    allocated 4,297,361,456 bytes

Both return an error eventually — the allocation happens first.

Why a fixed ceiling is the wrong fix

Worth recording, because it was my first attempt: the recorded fixture large_compressed_trx_payload.bin declares an uncompressed size of 173,120,239 bytes and its events sum to exactly that (78 + 68 + 173,120,066 + 27). A 173 MB row event decompressing from ~16 KB is therefore legitimate traffic, and a 64 MiB cap rejects it. Any bound here has to stay proportional to the data that has actually arrived rather than clamp to a constant.

Reproduction

Both cases are covered by tests in the linked PRs, asserting on allocated bytes rather than on whether an error is returned — an error is returned either way, so an error-only assertion passes before and after the fix and guards nothing. Removing either bound reproduces the figures above.

Environment

main at e5b3f9a, go test on darwin/arm64. The numbers are from runs on the PR branches, not estimates.


Disclosure: this issue and the linked PRs were prepared by a human working with an LLM assistant.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions