Vulnerability: Libsixel prior to v1.8.3 contains a stack buffer overflow in the function gif_process_raster at fromgif.c.
Vulnerability ID: CVE-2020-21050
CWE: CWE-787
Severity score: 2.9
Project: https://github.com/saitoha/libsixel
Published: 2021-09-14
File: src/fromgif.c
Commit message: gif loader: check LZW code size (Issue #75)
Commit author: Hayaki Saito

Vulnerable version (before fix):
 typedef struct    unsigned char suffix; } gif_lzw;  typedef struct {    int w, h;    unsigned char *out;  /* output buffer (always 4 components) */    int flags, bgindex, ratio, transparent, eflags;    unsigned char pal[256][3];    unsigned char lpal[256][3];    gif_lzw codes[4096];    unsigned char *color_table;    int parse, step;    int lflags; gif_process_raster(     signed int codesize, codemask, avail, oldcode, bits, valid_bits, clear;     gif_lzw *p;      lzw_cs = gif_get8(s);     clear = 1 << lzw_cs;     first = 1;     codesize = lzw_cs + 1; gif_process_raster(                     goto end;                 }                 if (oldcode >= 0) {                     if (avail < 4096) {                         p = &g->codes[avail++];                         p->prefix = (signed short) oldcode;                         p->first = g->codes[oldcode].first;

Fixed version (after fix):
 typedef struct    unsigned char suffix; } gif_lzw;  enum {    gif_lzw_max_code_size = 12 };  typedef struct {    int w, h;    unsigned char *out;  /* output buffer (always 4 components) */    int flags, bgindex, ratio, transparent, eflags;    unsigned char pal[256][3];    unsigned char lpal[256][3];    gif_lzw codes[1 << gif_lzw_max_code_size];    unsigned char *color_table;    int parse, step;    int lflags; gif_process_raster(     signed int codesize, codemask, avail, oldcode, bits, valid_bits, clear;     gif_lzw *p;      /* LZW Minimum Code Size */     lzw_cs = gif_get8(s);     if (lzw_cs > gif_lzw_max_code_size) {         sixel_helper_set_additional_message(             "Unsupported GIF (LZW code size)");         status = SIXEL_RUNTIME_ERROR;         goto end;     }      clear = 1 << lzw_cs;     first = 1;     codesize = lzw_cs + 1; gif_process_raster(                     goto end;                 }                 if (oldcode >= 0) {                     if (avail < (1 << gif_lzw_max_code_size)) {                         p = &g->codes[avail++];                         p->prefix = (signed short) oldcode;                         p->first = g->codes[oldcode].first;

Patch:
patch: @@ -58,14 +58,18 @@ typedef struct
    unsigned char suffix;
 } gif_lzw;
 
+enum {
+   gif_lzw_max_code_size = 12
+};
+
 typedef struct
 {
    int w, h;
    unsigned char *out;  /* output buffer (always 4 components) */
    int flags, bgindex, ratio, transparent, eflags;
    unsigned char pal[256][3];
    unsigned char lpal[256][3];
-   gif_lzw codes[4096];
+   gif_lzw codes[1 << gif_lzw_max_code_size];
    unsigned char *color_table;
    int parse, step;
    int lflags;
@@ -299,7 +303,15 @@ gif_process_raster(
     signed int codesize, codemask, avail, oldcode, bits, valid_bits, clear;
     gif_lzw *p;
 
+    /* LZW Minimum Code Size */
     lzw_cs = gif_get8(s);
+    if (lzw_cs > gif_lzw_max_code_size) {
+        sixel_helper_set_additional_message(
+            "Unsupported GIF (LZW code size)");
+        status = SIXEL_RUNTIME_ERROR;
+        goto end;
+    }
+
     clear = 1 << lzw_cs;
     first = 1;
     codesize = lzw_cs + 1;
@@ -353,7 +365,7 @@ gif_process_raster(
                     goto end;
                 }
                 if (oldcode >= 0) {
-                    if (avail < 4096) {
+                    if (avail < (1 << gif_lzw_max_code_size)) {
                         p = &g->codes[avail++];
                         p->prefix = (signed short) oldcode;
                         p->first = g->codes[oldcode].first;
path: src/fromgif.c