Skip to content

iskorotkov/avro: Denial-of-Service Vulnerability in Decoder

High severity GitHub Reviewed Published May 7, 2026 in iskorotkov/avro • Updated May 18, 2026

Package

gomod github.com/iskorotkov/avro/v2 (Go)

Affected versions

< 2.33.0

Patched versions

2.33.0

Description

Memory Exhaustion via Unbounded Map Allocations in Avro Decoder

Summary

The Avro map decoder accepted attacker-controlled block-element counts from the wire format and grew the destination map without enforcing an upper bound. The slice decoder already had Config.MaxSliceAllocSize for the equivalent attack against arrays; the map decoder had no analogous limit, so a producer could declare an arbitrarily large map (in one block, or chunked across many sub-limit blocks) and exhaust process memory until the OOM killer fired.

The fix introduces Config.MaxMapAllocSize with cumulative enforcement across block boundaries. The new limit is opt-in: the field defaults to zero, which preserves the previous unbounded behavior for backward compatibility. Upgrading to v2.33.0 alone does not mitigate the issue — consumers of untrusted Avro data must explicitly set MaxMapAllocSize on their avro.Config.

Description

Avro maps are encoded as a sequence of blocks; each block declares a long element count followed by that many key/value pairs. The decoder uses these counts both to size the destination map and as the loop bound for reading entries.

Pre-fix, the map decoder enforced no upper limit at any layer:

  • No per-block element-count check.
  • No cumulative across-block element-count check.
  • No memory-budget check before make(map[...]..., n) or before growing the map.

The slice decoder had been hardened via Config.MaxSliceAllocSize and tracked cumulatively across blocks; the map decoder was a missing-by-symmetry gap. Even a partial per-block bound on maps would have been insufficient on its own — Avro permits encoding a logical map as many small blocks, so a producer could split a 10 GB map into 10,000 sub-MaxMapAllocSize blocks and still drive total allocation past any single-block threshold. The fix tracks cumulative entry count at block-header boundaries — before the block's entries are decoded into the map — and errors out before allocation when the running total would exceed the configured cap.

Two decoder variants were affected, both in codec_map.go:

  • mapDecoder.Decode — string-keyed maps.
  • mapDecoderUnmarshaler.Decodeencoding.TextUnmarshaler-keyed maps (e.g. map[CustomKey]V where *CustomKey implements UnmarshalText).

Affected components

File Symbol Pre-fix behavior Post-fix behavior
config.go Config.MaxMapAllocSize Field did not exist New int field; default zero means unlimited (back-compat)
codec_map.go mapDecoder.Decode Read block count, grew map unbounded Validates cumulative count against MaxMapAllocSize at each block header
codec_map.go mapDecoderUnmarshaler.Decode Same Same

PR #5 (fix/map-alloc-chunking-bypass) covers both decoders and adds chunking-attack tests for both. The same PR also adds the previously-missing chunking-attack test coverage for the slice path in 534c7518 — the slice logic was already correct, only its test coverage was incomplete.

Technical details

The fix mirrors the slice decoder's pattern:

  1. At each block header, read the element count as int64.
  2. Add it to a running total maintained across the block loop.
  3. If the running total exceeds Config.MaxMapAllocSize (when nonzero), return an error before allocating any of that block's entries.
  4. Otherwise, decode the block's entries into the map.

Per-block enforcement alone would be bypassable by chunking; cumulative tracking closes that. The check sits at the block-header read, before per-entry allocation, so a single oversized block also cannot allocate first and then fail post-hoc.

Config.MaxMapAllocSize semantics match Config.MaxSliceAllocSize: zero means unlimited, any positive value is the cumulative cap on element count (not byte size).

Fixed behavior

v2.33.0 adds the MaxMapAllocSize configuration field and the cumulative-enforcement logic in both map decoders. Both decoders return a descriptive error when the cumulative entry count would exceed the configured cap; no entries are allocated past the limit.

Tests added in PR #5 cover, for both mapDecoder and mapDecoderUnmarshaler:

  • Single-block allocation exceeding the limit (rejected before allocation).
  • Chunking attack: multiple sub-limit blocks whose cumulative count exceeds the limit (rejected at the block-header that crosses the threshold).
  • Multi-block under the limit (decoded normally).

Affected versions

  • github.com/hamba/avro/v2 — all versions up to and including v2.31.0 (repository is read-only upstream).
  • github.com/iskorotkov/avro/v2 — all versions prior to v2.33.0. Note: v2.33.0 and later are vulnerable by default and only protected when MaxMapAllocSize is explicitly configured — see Mitigation.

Fixed versions

github.com/iskorotkov/avro/v2 v2.33.0 and later, with Config.MaxMapAllocSize explicitly set to a non-zero value.

A bare upgrade to v2.33.0 without setting MaxMapAllocSize leaves the decoder in the same unbounded state as v2.32.0. This is a backward-compatibility choice; a future major version may flip the default. Until then, treat this advisory as requiring both an upgrade and a configuration change.

There is no upstream fix for github.com/hamba/avro/v2 — module path is archived. Migrate to the fork as described under Mitigation.

Mitigation

Migrate from github.com/hamba/avro/v2 to github.com/iskorotkov/avro/v2 >= v2.33.0 and configure an allocation cap appropriate for your schema. The recommended approach for processes that decode untrusted input is a dedicated frozen config, used at every relevant call site, rather than mutating avro.DefaultConfig:

cfg := avro.Config{
    MaxByteSliceSize:  102_400,
    MaxSliceAllocSize: 10_000,
    MaxMapAllocSize:   10_000,
}.Freeze()

decoder := cfg.NewDecoder(schema, reader)

Choose the values based on the largest legitimate map your schema produces; a value 2–10× that ceiling provides headroom for benign variance while still bounding worst-case memory.

For consumers that prefer the original import path, a replace directive in go.mod is supported:

replace github.com/hamba/avro/v2 => github.com/iskorotkov/avro/v2 v2.33.0

replace is honoured only for the main module of a build — transitive consumers must add their own replace, or migrate the import path directly.

If you cannot upgrade immediately, the only structural workarounds are out-of-band: run decoders in memory-constrained child processes or cgroups so an OOM is contained, reject inputs from sources without resource controls, and apply per-request decode deadlines so a runaway decode at least times out before the OOM killer fires.

Proof-of-concept input

Two attack shapes, both targeting map[string]int:

Single-block, oversize block count. Emit one block header declaring n = 2³¹ − 1 (or any value whose n × averageEntrySize exceeds available memory) followed by truncated entries. Pre-fix, the decoder pre-allocates make(map[string]int, n), which fails or stalls long before EOF is reached.

Chunking bypass. Emit k blocks each declaring n / k elements, with n / k below any plausible per-block threshold but n itself well into the GB range. Pre-fix, the decoder happily grows the map block-by-block until the OS kills the process. Post-fix with MaxMapAllocSize = 10_000, the decoder rejects whichever block-header read pushes cumulative count past 10,000.

Either shape can be produced by hand-crafting the wire bytes; no iskorotkov/avro writer is needed to generate them.

References

Credits

  • Fix author (commit 5192df9, PR #5 — MaxMapAllocSize config field, cumulative enforcement in both map decoders, chunking-attack tests for slices and maps): Ivan Korotkov (@iskorotkov)
  • Review (commit a5fbddcb, "address review comments"): Daniel Błażewicz (@klajok)

Timeline

  • 2026-04-30MaxMapAllocSize introduced (5192df9); chunking-attack test coverage for slices added (534c7518).
  • 2026-05-01 — PR #5 merged into main.
  • 2026-05-06v2.33.0 tagged and released.
  • 2026-05-07 — Advisory published.
  • 2026-05-15 — Advisory revised.

References

@iskorotkov iskorotkov published to iskorotkov/avro May 7, 2026
Published to the GitHub Advisory Database May 18, 2026
Reviewed May 18, 2026
Last updated May 18, 2026

Severity

High

CVSS overall score

This score calculates overall vulnerability severity from 0 to 10 and is based on the Common Vulnerability Scoring System (CVSS).
/ 10

CVSS v4 base metrics

Exploitability Metrics
Attack Vector Network
Attack Complexity Low
Attack Requirements None
Privileges Required None
User interaction None
Vulnerable System Impact Metrics
Confidentiality None
Integrity None
Availability High
Subsequent System Impact Metrics
Confidentiality None
Integrity None
Availability None

CVSS v4 base metrics

Exploitability Metrics
Attack Vector: This metric reflects the context by which vulnerability exploitation is possible. This metric value (and consequently the resulting severity) will be larger the more remote (logically, and physically) an attacker can be in order to exploit the vulnerable system. The assumption is that the number of potential attackers for a vulnerability that could be exploited from across a network is larger than the number of potential attackers that could exploit a vulnerability requiring physical access to a device, and therefore warrants a greater severity.
Attack Complexity: This metric captures measurable actions that must be taken by the attacker to actively evade or circumvent existing built-in security-enhancing conditions in order to obtain a working exploit. These are conditions whose primary purpose is to increase security and/or increase exploit engineering complexity. A vulnerability exploitable without a target-specific variable has a lower complexity than a vulnerability that would require non-trivial customization. This metric is meant to capture security mechanisms utilized by the vulnerable system.
Attack Requirements: This metric captures the prerequisite deployment and execution conditions or variables of the vulnerable system that enable the attack. These differ from security-enhancing techniques/technologies (ref Attack Complexity) as the primary purpose of these conditions is not to explicitly mitigate attacks, but rather, emerge naturally as a consequence of the deployment and execution of the vulnerable system.
Privileges Required: This metric describes the level of privileges an attacker must possess prior to successfully exploiting the vulnerability. The method by which the attacker obtains privileged credentials prior to the attack (e.g., free trial accounts), is outside the scope of this metric. Generally, self-service provisioned accounts do not constitute a privilege requirement if the attacker can grant themselves privileges as part of the attack.
User interaction: This metric captures the requirement for a human user, other than the attacker, to participate in the successful compromise of the vulnerable system. This metric determines whether the vulnerability can be exploited solely at the will of the attacker, or whether a separate user (or user-initiated process) must participate in some manner.
Vulnerable System Impact Metrics
Confidentiality: This metric measures the impact to the confidentiality of the information managed by the VULNERABLE SYSTEM due to a successfully exploited vulnerability. Confidentiality refers to limiting information access and disclosure to only authorized users, as well as preventing access by, or disclosure to, unauthorized ones.
Integrity: This metric measures the impact to integrity of a successfully exploited vulnerability. Integrity refers to the trustworthiness and veracity of information. Integrity of the VULNERABLE SYSTEM is impacted when an attacker makes unauthorized modification of system data. Integrity is also impacted when a system user can repudiate critical actions taken in the context of the system (e.g. due to insufficient logging).
Availability: This metric measures the impact to the availability of the VULNERABLE SYSTEM resulting from a successfully exploited vulnerability. While the Confidentiality and Integrity impact metrics apply to the loss of confidentiality or integrity of data (e.g., information, files) used by the system, this metric refers to the loss of availability of the impacted system itself, such as a networked service (e.g., web, database, email). Since availability refers to the accessibility of information resources, attacks that consume network bandwidth, processor cycles, or disk space all impact the availability of a system.
Subsequent System Impact Metrics
Confidentiality: This metric measures the impact to the confidentiality of the information managed by the SUBSEQUENT SYSTEM due to a successfully exploited vulnerability. Confidentiality refers to limiting information access and disclosure to only authorized users, as well as preventing access by, or disclosure to, unauthorized ones.
Integrity: This metric measures the impact to integrity of a successfully exploited vulnerability. Integrity refers to the trustworthiness and veracity of information. Integrity of the SUBSEQUENT SYSTEM is impacted when an attacker makes unauthorized modification of system data. Integrity is also impacted when a system user can repudiate critical actions taken in the context of the system (e.g. due to insufficient logging).
Availability: This metric measures the impact to the availability of the SUBSEQUENT SYSTEM resulting from a successfully exploited vulnerability. While the Confidentiality and Integrity impact metrics apply to the loss of confidentiality or integrity of data (e.g., information, files) used by the system, this metric refers to the loss of availability of the impacted system itself, such as a networked service (e.g., web, database, email). Since availability refers to the accessibility of information resources, attacks that consume network bandwidth, processor cycles, or disk space all impact the availability of a system.
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N

EPSS score

Weaknesses

Uncontrolled Resource Consumption

The product does not properly control the allocation and maintenance of a limited resource. Learn more on MITRE.

Allocation of Resources Without Limits or Throttling

The product allocates a reusable resource or group of resources on behalf of an actor without imposing any intended restrictions on the size or number of resources that can be allocated. Learn more on MITRE.

Memory Allocation with Excessive Size Value

The product allocates memory based on an untrusted, large size value, but it does not ensure that the size is within expected limits, allowing arbitrary amounts of memory to be allocated. Learn more on MITRE.

Improper Validation of Specified Quantity in Input

The product receives input that is expected to specify a quantity (such as size or length), but it does not validate or incorrectly validates that the quantity has the required properties. Learn more on MITRE.

CVE ID

No known CVE

GHSA ID

GHSA-mx64-mj3q-7prj

Source code

Credits

Loading Checking history
See something to contribute? Suggest improvements for this vulnerability.