Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
192 changes: 192 additions & 0 deletions debian/patches/0001-fix-CVE-2026-33021.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
From: Hayaki Saito <saitoha@me.com>
Date: Mon, 22 May 2026 09:00:00 +0800
Subject: fix(libsixel): CVE-2026-33021 - Use-after-free in sixel_encoder_encode_bytes()

Based on the patch provided by @curious-rabbit.
Fixes: GHSA-hx93-w8p2-ffh5
Upstream: https://github.com/saitoha/libsixel/commit/bc46b293

---
src/encoder.c | 180 +++++++++++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 178 insertions(+), 2 deletions(-)

diff --git a/src/encoder.c b/src/encoder.c
index 4a6a038..e871aa6 100644
--- a/src/encoder.c
+++ b/src/encoder.c
@@ -654,20 +654,18 @@ sixel_encoder_do_clip(
clip_w = encoder->clipwidth;
clip_h = encoder->clipheight;

- /* adjust clipping width with comparing it to frame width */
- if (clip_w + clip_x > src_width) {
- if (clip_x > src_width) {
- clip_w = 0;
- } else {
+ /*
+ * Keep clipping math overflow-safe by comparing against
+ * (dimension - offset) instead of evaluating (size + offset).
+ */
+ if (clip_x >= src_width || clip_y >= src_height) {
+ clip_w = 0;
+ clip_h = 0;
+ } else {
+ if (clip_w > src_width - clip_x) {
clip_w = src_width - clip_x;
}
- }
-
- /* adjust clipping height with comparing it to frame height */
- if (clip_h + clip_y > src_height) {
- if (clip_y > src_height) {
- clip_h = 0;
- } else {
+ if (clip_h > src_height - clip_y) {
clip_h = src_height - clip_y;
}
}
@@ -1748,23 +1746,90 @@ sixel_encoder_encode_bytes(
int /* in */ ncolors)
{
SIXELSTATUS status = SIXEL_FALSE;
- sixel_frame_t *frame;
+ sixel_frame_t *frame = NULL;
+ unsigned char *owned_pixels = NULL;
+ unsigned char *owned_palette = NULL;
+ size_t pixel_bytes;
+ size_t pixel_total;
+ size_t palette_bytes;

if (encoder == NULL || bytes == NULL) {
status = SIXEL_BAD_ARGUMENT;
goto end;
}

+ pixel_total = (size_t)width * (size_t)height;
+ if (width <= 0 || height <= 0 ||
+ pixel_total / (size_t)width != (size_t)height) {
+ sixel_helper_set_additional_message(
+ "sixel_encoder_encode_bytes: invalid frame dimensions.");
+ status = SIXEL_BAD_INPUT;
+ goto end;
+ }
+ if (width > SIXEL_WIDTH_LIMIT || height > SIXEL_HEIGHT_LIMIT) {
+ sixel_helper_set_additional_message(
+ "sixel_encoder_encode_bytes: frame dimensions exceed limits.");
+ status = SIXEL_BAD_INPUT;
+ goto end;
+ }
+ pixel_bytes = sixel_encoder_compute_frame_size(pixelformat,
+ width,
+ height);
+ if (pixel_bytes == 0) {
+ sixel_helper_set_additional_message(
+ "sixel_encoder_encode_bytes: buffer size overflow.");
+ status = SIXEL_BAD_INPUT;
+ goto end;
+ }
+ owned_pixels = (unsigned char *)sixel_allocator_malloc(
+ encoder->allocator, pixel_bytes);
+ if (owned_pixels == NULL) {
+ sixel_helper_set_additional_message(
+ "sixel_encoder_encode_bytes: sixel_allocator_malloc() failed.");
+ status = SIXEL_BAD_ALLOCATION;
+ goto end;
+ }
+ memcpy(owned_pixels, bytes, pixel_bytes);
+
+ palette_bytes = 0u;
+ if (pixelformat & SIXEL_FORMATTYPE_PALETTE) {
+ if (palette == NULL || ncolors <= 0) {
+ sixel_helper_set_additional_message(
+ "sixel_encoder_encode_bytes: missing palette data.");
+ status = SIXEL_BAD_INPUT;
+ goto end;
+ }
+ palette_bytes = (size_t)ncolors * 3u;
+ if (palette_bytes / 3u != (size_t)ncolors) {
+ sixel_helper_set_additional_message(
+ "sixel_encoder_encode_bytes: palette size overflow.");
+ status = SIXEL_BAD_INPUT;
+ goto end;
+ }
+ owned_palette = (unsigned char *)sixel_allocator_malloc(
+ encoder->allocator, palette_bytes);
+ if (owned_palette == NULL) {
+ sixel_helper_set_additional_message(
+ "sixel_encoder_encode_bytes: "
+ "sixel_allocator_malloc() failed.");
+ status = SIXEL_BAD_ALLOCATION;
+ goto end;
+ }
+ memcpy(owned_palette, palette, palette_bytes);
+ }
+
status = sixel_frame_new(&frame, encoder->allocator);
if (SIXEL_FAILED(status)) {
goto end;
}

- status = sixel_frame_init(frame, bytes, width, height,
- pixelformat, palette, ncolors);
+ status = sixel_frame_init(frame, owned_pixels, width, height,
+ pixelformat, owned_palette, ncolors);
if (SIXEL_FAILED(status)) {
goto end;
}
+ owned_pixels = NULL;
+ owned_palette = NULL;

status = sixel_encoder_encode_frame(encoder, frame, NULL);
if (SIXEL_FAILED(status)) {
@@ -1802,6 +1867,50 @@ error:
}


+
+/* Compute raw byte size of one frame by pixelformat and geometry.
+ Packed formats (1/2/4bpp) require ceil(width * bpp / 8) bytes per row. */
+static size_t
+sixel_encoder_compute_frame_size(
+ int pixelformat,
+ int width,
+ int height)
+{
+ size_t size = 0;
+ int bpp;
+ int depth;
+
+ if (width <= 0 || height <= 0) {
+ goto end;
+ }
+
+ switch (pixelformat) {
+ case SIXEL_PIXELFORMAT_PAL1:
+ case SIXEL_PIXELFORMAT_G1:
+ bpp = 1;
+ break;
+ case SIXEL_PIXELFORMAT_PAL2:
+ case SIXEL_PIXELFORMAT_G2:
+ bpp = 2;
+ break;
+ case SIXEL_PIXELFORMAT_PAL4:
+ case SIXEL_PIXELFORMAT_G4:
+ bpp = 4;
+ break;
+ default:
+ depth = sixel_helper_compute_depth(pixelformat);
+ if (depth <= 0) {
+ goto end;
+ }
+ size = (size_t)width * (size_t)height * (size_t)depth;
+ goto end;
+ }
+
+ size = (((size_t)width * (size_t)bpp + 7UL) / 8UL) * (size_t)height;
+
+end:
+ return size;
+}
static int
test2(void)
{
114 changes: 114 additions & 0 deletions debian/patches/0003-fix-CVE-2026-33018.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
From: Hayaki Saito <saitoha@me.com>
Date: Mon, 22 May 2026 09:00:00 +0800
Subject: fix(libsixel): CVE-2026-33018 - Use-After-Free in load_gif()

Based on the patch provided by @curious-rabbit.
Fixes: GHSA-w46f-jr9f-rgvp
Upstream: https://github.com/saitoha/libsixel/commit/dec72ece

---
src/fromgif.c | 85 +++++++++++++++++++++++++--------------------------------
1 file changed, 35 insertions(+), 50 deletions(-)

diff --git a/src/fromgif.c b/src/fromgif.c
index ded5ffd..d0e1d5f 100644
--- a/src/fromgif.c
+++ b/src/fromgif.c
@@ -180,81 +180,29 @@ gif_init_frame(
int /* in */ fuse_palette)
{
SIXELSTATUS status = SIXEL_OK;
- int i;
- int ncolors;
- size_t palette_size, frame_size;
+ size_t frame_size;
+
+ (void)bgcolor;
+ (void)reqcolors;
+ (void)fuse_palette;

frame->delay = pg->delay;
- ncolors = 2 << (((pg->lflags & 0x80) ? pg->lflags : pg->flags) & 7);
- palette_size = (size_t)ncolors * 3;
- if (frame->palette == NULL) {
- frame->palette = (unsigned char *)sixel_allocator_malloc(frame->allocator, palette_size);
- } else if (frame->ncolors < ncolors) {
- sixel_allocator_free(frame->allocator, frame->palette);
- frame->palette = (unsigned char *)sixel_allocator_malloc(frame->allocator, palette_size);
- }
- if (frame->palette == NULL) {
+ frame->pixelformat = SIXEL_PIXELFORMAT_RGB888;
+ sixel_allocator_free(frame->allocator, frame->pixels);
+ frame_size = (size_t)frame->width * (size_t)frame->height * 3;
+ frame->pixels = (unsigned char *)sixel_allocator_malloc(frame->allocator, frame_size);
+ if (frame->pixels == NULL) {
sixel_helper_set_additional_message(
- "gif_init_frame: sixel_allocator_malloc() failed.");
+ "sixel_allocator_malloc() failed in gif_init_frame().");
status = SIXEL_BAD_ALLOCATION;
goto end;
}
- frame->ncolors = ncolors;
- if (frame->ncolors <= reqcolors && fuse_palette) {
- frame->pixelformat = SIXEL_PIXELFORMAT_PAL8;
- sixel_allocator_free(frame->allocator, frame->pixels);
- frame_size = (size_t)frame->width * (size_t)frame->height;
- frame->pixels = (unsigned char *)sixel_allocator_malloc(frame->allocator, frame_size);
- if (frame->pixels == NULL) {
- sixel_helper_set_additional_message(
- "sixel_allocator_malloc() failed in gif_init_frame().");
- status = SIXEL_BAD_ALLOCATION;
- goto end;
- }
- memcpy(frame->pixels, pg->out, frame_size);

- for (i = 0; i < frame->ncolors; ++i) {
- frame->palette[i * 3 + 0] = pg->color_table[i * 3 + 2];
- frame->palette[i * 3 + 1] = pg->color_table[i * 3 + 1];
- frame->palette[i * 3 + 2] = pg->color_table[i * 3 + 0];
- }
- if (pg->lflags & 0x80) {
- if (pg->eflags & 0x01) {
- if (bgcolor) {
- frame->palette[pg->transparent * 3 + 0] = bgcolor[0];
- frame->palette[pg->transparent * 3 + 1] = bgcolor[1];
- frame->palette[pg->transparent * 3 + 2] = bgcolor[2];
- } else {
- frame->transparent = pg->transparent;
- }
- }
- } else if (pg->flags & 0x80) {
- if (pg->eflags & 0x01) {
- if (bgcolor) {
- frame->palette[pg->transparent * 3 + 0] = bgcolor[0];
- frame->palette[pg->transparent * 3 + 1] = bgcolor[1];
- frame->palette[pg->transparent * 3 + 2] = bgcolor[2];
- } else {
- frame->transparent = pg->transparent;
- }
- }
- }
- } else {
- frame->pixelformat = SIXEL_PIXELFORMAT_RGB888;
- frame_size = (size_t)pg->w * (size_t)pg->h * 3;
- frame->pixels = (unsigned char *)sixel_allocator_malloc(frame->allocator, frame_size);
- if (frame->pixels == NULL) {
- sixel_helper_set_additional_message(
- "sixel_allocator_malloc() failed in gif_init_frame().");
- status = SIXEL_BAD_ALLOCATION;
- goto end;
- }
- for (i = 0; i < pg->w * pg->h; ++i) {
- frame->pixels[i * 3 + 0] = pg->color_table[pg->out[i] * 3 + 2];
- frame->pixels[i * 3 + 1] = pg->color_table[pg->out[i] * 3 + 1];
- frame->pixels[i * 3 + 2] = pg->color_table[pg->out[i] * 3 + 0];
- }
- }
+ /*
+ * The canvas is already composited in RGB888, so each frame can be
+ * exported directly without reinterpretation through a per-frame palette.
+ */
+ memcpy(frame->pixels, pg->out, frame_size);
frame->multiframe = (pg->loop_count != (-1));

status = SIXEL_OK;
12 changes: 2 additions & 10 deletions debian/patches/series
Original file line number Diff line number Diff line change
@@ -1,10 +1,2 @@
# 0001-Add-malloc-size-check.patch
# 0002-assign-default-error-message.patch
# 0003-add-limitation-to-width-and-height.patch
# 0004-position-error-check.patch
# 0005-size-check.patch
# 0006-prevent-to-access-heap-overflow.patch
# 0007-check-error-for-jpeg_read_scanlines.patch
# 0008-check-number-of-repeat_count.patch
CVE-2025-61146.patch
CVE-2025-9300.patch
0001-fix-CVE-2026-33021.patch
0003-fix-CVE-2026-33018.patch
Loading