diff --git a/debian/changelog b/debian/changelog index 338bc64..3fb8585 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,15 @@ +libsixel (1.10.5-1deepin3) unstable; urgency=medium + + * Backport CVE-2026 security fixes from upstream saitoha/libsixel + - CVE-2026-33023: Integer overflow in DCS parameter scaling + - CVE-2026-33020: Parser cursor overflow in SIXEL decoder + - CVE-2026-33018: Null pointer dereference in palette allocation + - CVE-2026-33021: Integer overflow in highcolor encoder + - CVE-2026-44636: Integer overflows in encoder and quantizer + - CVE-2026-44637: Bad-free in libpng and improper frame lifecycle + + -- lichenggang Thu, 21 May 2026 21:50:00 +0800 + libsixel (1.10.5-1deepin2) unstable; urgency=medium * Fix CVE-2025-9300: heap buffer overflow in encoder.c diff --git a/debian/patches/CVE-2026-33018.patch b/debian/patches/CVE-2026-33018.patch new file mode 100644 index 0000000..0b81269 --- /dev/null +++ b/debian/patches/CVE-2026-33018.patch @@ -0,0 +1,24 @@ +--- a/src/fromsixel.c ++++ b/src/fromsixel.c +@@ -1030,7 +1030,7 @@ sixel_decode_raw( + alloc_size = SIXEL_PALETTE_MAX; + } + *palette = (unsigned char *)sixel_allocator_malloc(allocator, (size_t)(alloc_size * 3)); +- if (palette == NULL) { ++ if (*palette == NULL) { + sixel_allocator_free(allocator, image.data); + sixel_helper_set_additional_message( + "sixel_deocde_raw: sixel_allocator_malloc() failed."); +@@ -1102,10 +1102,10 @@ sixel_decode(unsigned char /* in */ *p, /* sixel bytes */ + + *ncolors = image.ncolors + 1; + *palette = (unsigned char *)sixel_allocator_malloc(allocator, (size_t)(*ncolors * 3)); +- if (palette == NULL) { ++ if (*palette == NULL) { + sixel_allocator_free(allocator, image.data); + sixel_helper_set_additional_message( +- "sixel_deocde_raw: sixel_allocator_malloc() failed."); ++ "sixel_decode: sixel_allocator_malloc() failed."); + status = SIXEL_BAD_ALLOCATION; + goto end; + } diff --git a/debian/patches/CVE-2026-33020.patch b/debian/patches/CVE-2026-33020.patch new file mode 100644 index 0000000..2e66646 --- /dev/null +++ b/debian/patches/CVE-2026-33020.patch @@ -0,0 +1,87 @@ +--- a/src/fromsixel.c ++++ b/src/fromsixel.c +@@ -412,6 +412,50 @@ end: + } + + ++static SIXELSTATUS ++reject_invalid_position(parser_context_t const *context) ++{ ++ SIXELSTATUS status = SIXEL_FALSE; ++ ++ if (context->pos_x < 0 ++ || context->pos_x >= SIXEL_WIDTH_LIMIT ++ || context->pos_y < 0 ++ || context->pos_y >= SIXEL_HEIGHT_LIMIT) { ++ status = SIXEL_BAD_INPUT; ++ sixel_helper_set_additional_message( ++ "reject_invalid_position: cursor position limit exceeded."); ++ goto end; ++ } ++ ++ status = SIXEL_OK; ++ ++end: ++ return status; ++} ++ ++ ++static SIXELSTATUS ++sixel_parser_advance_line(parser_context_t *context) ++{ ++ SIXELSTATUS status = SIXEL_FALSE; ++ ++ if (context->pos_y < 0 ++ || context->pos_y > SIXEL_HEIGHT_LIMIT - 6) { ++ status = SIXEL_BAD_INPUT; ++ sixel_helper_set_additional_message( ++ "sixel_parser_advance_line: vertical position limit exceeded."); ++ goto end; ++ } ++ ++ context->pos_x = 0; ++ context->pos_y += 6; ++ status = SIXEL_OK; ++ ++end: ++ return status; ++} ++ ++ + /* convert sixel data into indexed pixel bytes and palette data */ + SIXELAPI SIXELSTATUS + sixel_decode_raw_impl( +@@ -616,13 +660,20 @@ sixel_decode_raw_impl( + break; + case '-': + /* DECGNL Graphics Next Line */ +- context->pos_x = 0; +- context->pos_y += 6; ++ status = sixel_parser_advance_line(context); ++ if (SIXEL_FAILED(status)) { ++ goto end; ++ } + p++; + break; + default: + if (*p >= '?' && *p <= '~') { /* sixel characters */ + ++ status = reject_invalid_position(context); ++ if (SIXEL_FAILED(status)) { ++ goto end; ++ } ++ + sx = image->width; + while (sx < context->pos_x + context->repeat_count) { + sx *= 2; +@@ -644,10 +695,6 @@ sixel_decode_raw_impl( + image->ncolors = context->color_index; + } + +- if (context->pos_x < 0 || context->pos_y < 0) { +- status = SIXEL_BAD_INPUT; +- goto end; +- } + bits = *p - '?'; + + if (bits == 0) { diff --git a/debian/patches/CVE-2026-33021.patch b/debian/patches/CVE-2026-33021.patch new file mode 100644 index 0000000..4eaa8e8 --- /dev/null +++ b/debian/patches/CVE-2026-33021.patch @@ -0,0 +1,79 @@ +--- a/src/tosixel.c ++++ b/src/tosixel.c +@@ -1425,10 +1425,11 @@ sixel_encode_highcolor( + unsigned char palstate[SIXEL_PALETTE_MAX]; + int output_count; + int const maxcolors = 1 << 15; +- int whole_size = width * height /* for paletted_pixels */ +- + maxcolors /* for rgbhit */ +- + maxcolors /* for rgb2pal */ +- + width * 6; /* for marks */ ++ size_t image_size; ++ size_t marks_size; ++ size_t normalized_size; ++ size_t whole_size; ++ size_t maxcolors_size; + int x, y; + unsigned char *dst; + unsigned char *mptr; +@@ -1440,10 +1441,49 @@ sixel_encode_highcolor( + int orig_height; + unsigned char *pal; + ++ maxcolors_size = (size_t)maxcolors; ++ if ((size_t)height > ((size_t)-1) / (size_t)width) { ++ sixel_helper_set_additional_message( ++ "sixel_encode_highcolor: image size overflow."); ++ status = SIXEL_BAD_INPUT; ++ goto error; ++ } ++ image_size = (size_t)width * (size_t)height; ++ ++ if (image_size > ((size_t)-1) / 3UL) { ++ sixel_helper_set_additional_message( ++ "sixel_encode_highcolor: normalized size overflow."); ++ status = SIXEL_BAD_INPUT; ++ goto error; ++ } ++ normalized_size = image_size * 3UL; ++ ++ if ((size_t)width > ((size_t)-1) / 6UL) { ++ sixel_helper_set_additional_message( ++ "sixel_encode_highcolor: marks size overflow."); ++ status = SIXEL_BAD_INPUT; ++ goto error; ++ } ++ marks_size = (size_t)width * 6UL; ++ ++ if (image_size > (size_t)-1 - maxcolors_size || ++ image_size + maxcolors_size > (size_t)-1 - maxcolors_size || ++ image_size + maxcolors_size + maxcolors_size ++ > (size_t)-1 - marks_size) { ++ sixel_helper_set_additional_message( ++ "sixel_encode_highcolor: whole size overflow."); ++ status = SIXEL_BAD_INPUT; ++ goto error; ++ } ++ whole_size = image_size /* for paletted_pixels */ ++ + maxcolors_size /* for rgbhit */ ++ + maxcolors_size /* for rgb2pal */ ++ + marks_size; /* for marks */ ++ + if (dither->pixelformat != SIXEL_PIXELFORMAT_RGB888) { + /* normalize pixelfromat */ +- normalized_pixels = (unsigned char *)sixel_allocator_malloc(dither->allocator, +- (size_t)(width * height * 3)); ++ normalized_pixels = (unsigned char *)sixel_allocator_malloc( ++ dither->allocator, normalized_size); + if (normalized_pixels == NULL) { + goto error; + } +@@ -1458,7 +1498,7 @@ sixel_encode_highcolor( + pixels = normalized_pixels; + } + paletted_pixels = (sixel_index_t *)sixel_allocator_malloc(dither->allocator, +- (size_t)whole_size); ++ whole_size); + if (paletted_pixels == NULL) { + goto error; + } diff --git a/debian/patches/CVE-2026-33023.patch b/debian/patches/CVE-2026-33023.patch new file mode 100644 index 0000000..376d2c3 --- /dev/null +++ b/debian/patches/CVE-2026-33023.patch @@ -0,0 +1,63 @@ +--- a/src/fromsixel.c ++++ b/src/fromsixel.c +@@ -392,6 +392,26 @@ safe_addition_for_params(parser_context_t *context, unsigned char *p) + } + + ++static SIXELSTATUS ++safe_multiply_by_params_div10(int lhs, int rhs, int *result) ++{ ++ SIXELSTATUS status = SIXEL_FALSE; ++ ++ if (lhs > 0 && rhs > INT_MAX / lhs) { ++ status = SIXEL_BAD_INTEGER_OVERFLOW; ++ sixel_helper_set_additional_message( ++ "safe_multiply_by_params_div10: integer overflow detected."); ++ goto end; ++ } ++ ++ *result = lhs * rhs / 10; ++ status = SIXEL_OK; ++ ++end: ++ return status; ++} ++ ++ + /* convert sixel data into indexed pixel bytes and palette data */ + SIXELAPI SIXELSTATUS + sixel_decode_raw_impl( +@@ -524,11 +544,31 @@ sixel_decode_raw_impl( + + if (context->nparams > 2) { + /* Pn3 */ ++ int scaled_pan; ++ int scaled_pad; ++ + if (context->params[2] == 0) { + context->params[2] = 10; + } +- context->attributed_pan = context->attributed_pan * context->params[2] / 10; +- context->attributed_pad = context->attributed_pad * context->params[2] / 10; ++ ++ status = safe_multiply_by_params_div10( ++ context->attributed_pan, ++ context->params[2], ++ &scaled_pan); ++ if (SIXEL_FAILED(status)) { ++ goto end; ++ } ++ ++ status = safe_multiply_by_params_div10( ++ context->attributed_pad, ++ context->params[2], ++ &scaled_pad); ++ if (SIXEL_FAILED(status)) { ++ goto end; ++ } ++ ++ context->attributed_pan = scaled_pan; ++ context->attributed_pad = scaled_pad; + if (context->attributed_pan <= 0) { + context->attributed_pan = 1; + } diff --git a/debian/patches/CVE-2026-44636.patch b/debian/patches/CVE-2026-44636.patch new file mode 100644 index 0000000..c5b903a --- /dev/null +++ b/debian/patches/CVE-2026-44636.patch @@ -0,0 +1,199 @@ +--- a/src/encoder.c ++++ b/src/encoder.c +@@ -723,6 +723,8 @@ sixel_encoder_output_without_macro( + int dulation; + int delay; + int lag = 0; ++ long long target_usec; ++ long long remaining_usec; + struct timespec tv; + clock_t start; + unsigned char *pixbuf; +@@ -768,17 +770,29 @@ sixel_encoder_output_without_macro( + } + start = clock(); + delay = sixel_frame_get_delay(frame); +- if (delay > 0 && !encoder->fignore_delay) { +- dulation = (int)((clock() - start) * 1000 * 1000 / CLOCKS_PER_SEC) - (int)lag; +- lag = 0; +- if (dulation < 10000 * delay) { +- tv.tv_sec = 0; +- tv.tv_nsec = (long)((10000 * delay - dulation) * 1000); +- nanosleep(&tv, NULL); +- } else { +- lag = (int)(10000 * delay - dulation); ++ if (delay > 0 && !encoder->fignore_delay && !encoder->fstatic) { ++#if HAVE_CLOCK ++ dulation = (int)((clock() - start) * 1000 * 1000 / CLOCKS_PER_SEC) - (int)lag; ++ lag = 0; ++#else ++ dulation = 0; ++#endif ++ target_usec = 10000LL * (long long)delay; ++ remaining_usec = target_usec - (long long)dulation; ++ if (remaining_usec > 0) { ++ tv.tv_sec = (time_t)(remaining_usec / 1000000LL); ++ tv.tv_nsec = (long)((remaining_usec % 1000000LL) * 1000LL); ++ nanosleep(&tv, NULL); ++ } else { ++ if (remaining_usec > INT_MAX) { ++ lag = INT_MAX; ++ } else if (remaining_usec < INT_MIN) { ++ lag = INT_MIN; ++ } else { ++ lag = (int)remaining_usec; ++ } ++ } + } +- } + + pixbuf = sixel_frame_get_pixels(frame); + memcpy(p, pixbuf, (size_t)(width * height * depth)); +@@ -812,6 +826,8 @@ sixel_encoder_output_with_macro( + int nwrite; + int dulation; + int lag = 0; ++ long long target_usec; ++ long long remaining_usec; + struct timespec tv; + clock_t start; + unsigned char *pixbuf; +@@ -870,16 +886,28 @@ sixel_encoder_output_with_macro( + "sixel_encoder_output_with_macro: sixel_write_callback() failed."); + goto end; + } +- delay = sixel_frame_get_delay(frame); +- if (delay > 0 && !encoder->fignore_delay) { ++ delay = sixel_frame_get_delay(frame); ++ if (delay > 0 && !encoder->fignore_delay && !encoder->fstatic) { ++#if HAVE_CLOCK + dulation = (int)((clock() - start) * 1000 * 1000 / CLOCKS_PER_SEC) - (int)lag; + lag = 0; +- if (dulation < 10000 * delay) { +- tv.tv_sec = 0; +- tv.tv_nsec = (long)((10000 * delay - dulation) * 1000); ++#else ++ dulation = 0; ++#endif ++ target_usec = 10000LL * (long long)delay; ++ remaining_usec = target_usec - (long long)dulation; ++ if (remaining_usec > 0) { ++ tv.tv_sec = (time_t)(remaining_usec / 1000000LL); ++ tv.tv_nsec = (long)((remaining_usec % 1000000LL) * 1000LL); + nanosleep(&tv, NULL); + } else { +- lag = (int)(10000 * delay - dulation); ++ if (remaining_usec > INT_MAX) { ++ lag = INT_MAX; ++ } else if (remaining_usec < INT_MIN) { ++ lag = INT_MIN; ++ } else { ++ lag = (int)remaining_usec; ++ } + } + } + } +--- a/src/frame.c ++++ b/src/frame.c +@@ -401,11 +401,26 @@ sixel_frame_convert_to_rgb888(sixel_frame_t /*in */ *frame) + + sixel_frame_ref(frame); + ++ if (frame->height != 0 && frame->width > INT_MAX / frame->height) { ++ sixel_helper_set_additional_message( ++ "sixel_frame_convert_to_rgb888: image dimensions are too huge."); ++ status = SIXEL_BAD_INPUT; ++ goto end; ++ } ++ pixel_count = (size_t)frame->width * (size_t)frame->height; ++ ++ + switch (frame->pixelformat) { + case SIXEL_PIXELFORMAT_PAL1: + case SIXEL_PIXELFORMAT_PAL2: + case SIXEL_PIXELFORMAT_PAL4: +- size = (size_t)(frame->width * frame->height * 4); ++ if (pixel_count > (size_t)INT_MAX / 4u) { ++ sixel_helper_set_additional_message( ++ "sixel_frame_convert_to_rgb888: image dimensions are too huge."); ++ status = SIXEL_BAD_INPUT; ++ goto end; ++ } ++ size = pixel_count * 4u; + normalized_pixels = (unsigned char *)sixel_allocator_malloc(frame->allocator, size); + if (normalized_pixels == NULL) { + sixel_helper_set_additional_message( +@@ -413,7 +428,7 @@ sixel_frame_convert_to_rgb888(sixel_frame_t /*in */ *frame) + status = SIXEL_BAD_ALLOCATION; + goto end; + } +- src = normalized_pixels + frame->width * frame->height * 3; ++ src = normalized_pixels + pixel_count * 3u; + dst = normalized_pixels; + status = sixel_helper_normalize_pixelformat(src, + &frame->pixelformat, +@@ -435,7 +450,13 @@ sixel_frame_convert_to_rgb888(sixel_frame_t /*in */ *frame) + frame->pixelformat = SIXEL_PIXELFORMAT_RGB888; + break; + case SIXEL_PIXELFORMAT_PAL8: +- size = (size_t)(frame->width * frame->height * 3); ++ if (pixel_count > (size_t)INT_MAX / 3u) { ++ sixel_helper_set_additional_message( ++ "sixel_frame_convert_to_rgb888: image dimensions are too huge."); ++ status = SIXEL_BAD_INPUT; ++ goto end; ++ } ++ size = pixel_count * 3u; + normalized_pixels = (unsigned char *)sixel_allocator_malloc(frame->allocator, size); + if (normalized_pixels == NULL) { + sixel_helper_set_additional_message( +@@ -466,7 +487,13 @@ sixel_frame_convert_to_rgb888(sixel_frame_t /*in */ *frame) + case SIXEL_PIXELFORMAT_RGBA8888: + case SIXEL_PIXELFORMAT_ARGB8888: + /* normalize pixelformat */ +- size = (size_t)(frame->width * frame->height * 3); ++ if (pixel_count > (size_t)INT_MAX / 3u) { ++ sixel_helper_set_additional_message( ++ "sixel_frame_convert_to_rgb888: image dimensions are too huge."); ++ status = SIXEL_BAD_INPUT; ++ goto end; ++ } ++ size = pixel_count * 3u; + normalized_pixels = (unsigned char *)sixel_allocator_malloc(frame->allocator, size); + if (normalized_pixels == NULL) { + sixel_helper_set_additional_message( +--- a/src/quant.c ++++ b/src/quant.c +@@ -1259,8 +1259,11 @@ sixel_quant_apply_palette( + { + typedef int component_t; + enum { max_depth = 4 }; ++ enum { max_channel_diff_sq = 255 * 255 }; + SIXELSTATUS status = SIXEL_FALSE; + int pos, n, x, y, sum1, sum2; ++ int non_weighted_components; ++ long long max_complexion; + component_t offset; + int color_index; + unsigned short *indextable; +@@ -1359,6 +1362,20 @@ sixel_quant_apply_palette( + } + } + ++ ++ if ((f_lookup == lookup_fast || f_lookup == lookup_normal) && complexion > 1) { ++ non_weighted_components = depth > 1 ? depth - 1 : 0; ++ max_complexion = (INT_MAX - (long long)max_channel_diff_sq ++ * (long long)non_weighted_components) ++ / (long long)max_channel_diff_sq; ++ if ((long long)complexion > max_complexion) { ++ status = SIXEL_BAD_ARGUMENT; ++ sixel_helper_set_additional_message( ++ "sixel_quant_apply_palette: complexion parameter is too large."); ++ goto end; ++ } ++ } ++ + if (foptimize_palette) { + *ncolors = 0; + diff --git a/debian/patches/CVE-2026-44637.patch b/debian/patches/CVE-2026-44637.patch new file mode 100644 index 0000000..403c926 --- /dev/null +++ b/debian/patches/CVE-2026-44637.patch @@ -0,0 +1,136 @@ +--- a/src/encoder.c ++++ b/src/encoder.c +@@ -655,19 +655,14 @@ sixel_encoder_do_clip( + 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 { ++ 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; + } + } +--- a/src/fromgif.c ++++ b/src/fromgif.c +@@ -617,8 +617,12 @@ load_gif( + char message[256]; + + fnp.p = fn_load; ++ int frame_no; ++ int loop_no; + +- status = sixel_frame_new(&frame, allocator); ++ frame = NULL; ++ ++// sixel_frame_new moved inside loop + if (SIXEL_FAILED(status)) { + goto end; + } +@@ -642,11 +646,11 @@ load_gif( + } + memset(g.out, 0, bytes); + +- frame->loop_count = 0; ++ loop_no = 0; + + for (;;) { /* per loop */ + +- frame->frame_no = 0; ++ frame_no = 0; + + s.img_buffer = s.img_buffer_original; + status = gif_load_header(&s, &g); +@@ -665,6 +669,12 @@ load_gif( + break; + } + ++ status = sixel_frame_new(&frame, allocator); ++ if (SIXEL_FAILED(status)) { ++ goto end; ++ } ++ frame->loop_count = loop_no; ++ frame->frame_no = frame_no; + frame->width = g.actual_width; + frame->height = g.actual_height; + status = gif_init_frame(frame, &g, bgcolor, reqcolors, fuse_palette); +@@ -676,31 +686,37 @@ load_gif( + if (status != SIXEL_OK) { + goto end; + } ++ sixel_frame_unref(frame); ++ frame = NULL; + + if (fstatic) { + goto end; + } +- ++frame->frame_no; ++ ++frame_no; + } + +- ++frame->loop_count; ++ ++loop_no; + + if (g.loop_count < 0) { + break; + } +- if (loop_control == SIXEL_LOOP_DISABLE || frame->frame_no == 1) { ++ if (loop_control == SIXEL_LOOP_DISABLE || frame_no == 1) { + break; + } + if (loop_control == SIXEL_LOOP_AUTO) { +- if (frame->loop_count == g.loop_count) { ++ if (loop_no == g.loop_count) { + break; + } + } + } + + end: +- sixel_allocator_free(frame->allocator, g.out); +- sixel_frame_unref(frame); ++ sixel_allocator_free(allocator, g.out); ++ sixel_allocator_free(allocator, g.prev_out); ++ sixel_allocator_free(allocator, g.history); ++ if (frame != NULL) { ++ sixel_frame_unref(frame); ++ } + + return status; + } +--- a/src/loader.c ++++ b/src/loader.c +@@ -285,6 +285,8 @@ load_png(unsigned char /* out */ **result, + status = SIXEL_FALSE; + *result = NULL; + ++ png_ptr = NULL; ++ info_ptr = NULL; + png_ptr = png_create_read_struct( + PNG_LIBPNG_VER_STRING, NULL, &png_error_callback, NULL); + if (!png_ptr) { +@@ -589,7 +591,9 @@ load_png(unsigned char /* out */ **result, + status = SIXEL_OK; + + cleanup: ++ if (png_ptr != NULL) { + png_destroy_read_struct(&png_ptr, &info_ptr,(png_infopp)0); ++ } + + if (rows != NULL) { + sixel_allocator_free(allocator, rows); diff --git a/debian/patches/series b/debian/patches/series index 7ce9e10..cc9274a 100644 --- a/debian/patches/series +++ b/debian/patches/series @@ -8,3 +8,9 @@ # 0008-check-number-of-repeat_count.patch CVE-2025-61146.patch CVE-2025-9300.patch +CVE-2026-33023.patch +CVE-2026-33020.patch +CVE-2026-33018.patch +CVE-2026-33021.patch +CVE-2026-44636.patch +CVE-2026-44637.patch