From 64e5f655526b96be0c452bfa2900d70530cfee15 Mon Sep 17 00:00:00 2001 From: deepin-ci-robot Date: Fri, 22 May 2026 15:32:56 +0800 Subject: [PATCH] fix(libsixel): Fix CVE-2026-33021, CVE-2026-33019, CVE-2026-33018 - Fix CVE-2026-33021: Use-after-free in sixel_encoder_encode_bytes() - Fix CVE-2026-33019: integer overflow lead to OOB Read in img2sixel - Fix CVE-2026-33018: Use-After-Free in load_gif() Remove redundant CVE-2025-61146.patch and CVE-2025-9300.patch. Fix 0003-fix-CVE-2026-33018.patch corruption and context mismatch. Based on patches provided by @curious-rabbit. Upstream: https://github.com/saitoha/libsixel/security/advisories Generated-By: glm-5.1 Co-Authored-By: hudeng --- debian/patches/0001-fix-CVE-2026-33021.patch | 192 +++++++++++++++++++ debian/patches/0003-fix-CVE-2026-33018.patch | 114 +++++++++++ debian/patches/series | 12 +- src/encoder.c | 139 ++++++++++++-- src/fromgif.c | 84 ++------ 5 files changed, 448 insertions(+), 93 deletions(-) create mode 100644 debian/patches/0001-fix-CVE-2026-33021.patch create mode 100644 debian/patches/0003-fix-CVE-2026-33018.patch diff --git a/debian/patches/0001-fix-CVE-2026-33021.patch b/debian/patches/0001-fix-CVE-2026-33021.patch new file mode 100644 index 0000000..357da0d --- /dev/null +++ b/debian/patches/0001-fix-CVE-2026-33021.patch @@ -0,0 +1,192 @@ +From: Hayaki Saito +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) + { diff --git a/debian/patches/0003-fix-CVE-2026-33018.patch b/debian/patches/0003-fix-CVE-2026-33018.patch new file mode 100644 index 0000000..b3eb4df --- /dev/null +++ b/debian/patches/0003-fix-CVE-2026-33018.patch @@ -0,0 +1,114 @@ +From: Hayaki Saito +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; diff --git a/debian/patches/series b/debian/patches/series index 7ce9e10..5f367dd 100644 --- a/debian/patches/series +++ b/debian/patches/series @@ -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 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 @@ test1(void) } + +/* 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) { 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;