Vulnerability: A heap buffer overflow in the start_decoder function in stb_vorbis through 2019-03-04 allows an attacker to cause a denial of service or execute arbitrary code by opening a crafted Ogg Vorbis file.
Vulnerability ID: CVE-2019-13217
CWE: CWE-787
Severity score: 6.4
Project: https://github.com/nothings/stb
Published: 2019-08-15
File: stb_vorbis.c
Commit message: Fix seven bugs discovered and fixed by ForAllSecure:

CVE-2019-13217: heap buffer overflow in start_decoder()
CVE-2019-13218: stack buffer overflow in compute_codewords()
CVE-2019-13219: uninitialized memory in vorbis_decode_packet_rest()
CVE-2019-13220: out-of-range read in draw_line()
CVE-2019-13221: issue with large 1D codebooks in lookup1_values()
CVE-2019-13222: unchecked NULL returned by get_window()
CVE-2019-13223: division by zero in predict_point()
Commit author: Sean Barrett

Vulnerable version (before fix):
 //    Timur Gagiev // // Partial history: //    1.16    - 2019-03-04 - fix warnings //    1.15    - 2019-02-07 - explicit failure if Ogg Skeleton data is found //    1.14    - 2018-02-11 - delete bogus dealloca usage static int lookup1_values(int entries, int dim)    int r = (int) floor(exp((float) log((float) entries) / dim));    if ((int) floor(pow((float) r+1, dim)) <= entries)   // (int) cast for MinGW warning;       ++r;                                              // floor() to avoid _ftol() when non-CRT    assert(pow((float) r+1, dim) > entries);    assert((int) floor(pow((float) r, dim)) <= entries); // (int),floor() as above    return r; }  static __forceinline void draw_line(float *output, int x0, int y0, int x1, int y    ady -= abs(base) * adx;    if (x1 > n) x1 = n;    if (x < x1) {       LINE_OP(output[x], inverse_db_table[y]);       for (++x; x < x1; ++x) {          err += ady;          if (err >= adx) {             err -= adx;             y += sy;          } else             y += base;          LINE_OP(output[x], inverse_db_table[y]);       }    } } static float *get_window(vorb *f, int len)    len <<= 1;    if (len == f->blocksize_0) return f->window[0];    if (len == f->blocksize_1) return f->window[1];    assert(0);    return NULL; }  static int vorbis_finish_frame(stb_vorbis *f, int len, int left, int right)    if (f->previous_length) {       int i,j, n = f->previous_length;       float *w = get_window(f, n);       for (i=0; i < f->channels; ++i) {          for (j=0; j < n; ++j)             f->channel_buffers[i][left+j] = static int start_decoder(vorb *f)          while (current_entry < c->entries) {             int limit = c->entries - current_entry;             int n = get_bits(f, ilog(limit));             if (current_entry + n > (int) c->entries) { return error(f, VORBIS_invalid_setup); }             memset(lengths + current_entry, current_length, n);             current_entry += n; static int start_decoder(vorb *f)          c->value_bits = get_bits(f, 4)+1;          c->sequence_p = get_bits(f,1);          if (c->lookup_type == 1) {             c->lookup_values = lookup1_values(c->entries, c->dimensions);          } else {             c->lookup_values = c->entries * c->dimensions;          } static int start_decoder(vorb *f)             p[j].id = j;          }          qsort(p, g->values, sizeof(p[0]), point_compare);          for (j=0; j < g->values; ++j)             g->sorted_order[j] = (uint8) p[j].id;          // precompute the neighbors static int start_decoder(vorb *f)          max_submaps = m->submaps;       if (get_bits(f,1)) {          m->coupling_steps = get_bits(f,8)+1;          for (k=0; k < m->coupling_steps; ++k) {             m->chan[k].magnitude = get_bits(f, ilog(f->channels-1));             m->chan[k].angle = get_bits(f, ilog(f->channels-1)); int stb_vorbis_get_samples_float(stb_vorbis *f, int channels, float **buffer, in #endif // STB_VORBIS_NO_PULLDATA_API  /* Version history     1.12    - 2017-11-21 - limit residue begin/end to blocksize/2 to avoid large temp allocs in bad/corrupt files     1.11    - 2017-07-23 - fix MinGW compilation      1.10    - 2017-03-03 - more robust seeking; fix negative ilog(); clear error in open_memory

Fixed version (after fix):
 //    Timur Gagiev // // Partial history: //    1.17    - 2019-07-08 - fix CVE-2019-13217..CVE-2019-13223 (by ForAllSecure) //    1.16    - 2019-03-04 - fix warnings //    1.15    - 2019-02-07 - explicit failure if Ogg Skeleton data is found //    1.14    - 2018-02-11 - delete bogus dealloca usage static int lookup1_values(int entries, int dim)    int r = (int) floor(exp((float) log((float) entries) / dim));    if ((int) floor(pow((float) r+1, dim)) <= entries)   // (int) cast for MinGW warning;       ++r;                                              // floor() to avoid _ftol() when non-CRT    if (pow((float) r+1, dim) <= entries)       return -1;    if ((int) floor(pow((float) r, dim)) > entries)       return -1;    return r; }  static __forceinline void draw_line(float *output, int x0, int y0, int x1, int y    ady -= abs(base) * adx;    if (x1 > n) x1 = n;    if (x < x1) {       LINE_OP(output[x], inverse_db_table[y&255]);       for (++x; x < x1; ++x) {          err += ady;          if (err >= adx) {             err -= adx;             y += sy;          } else             y += base;          LINE_OP(output[x], inverse_db_table[y&255]);       }    } } static float *get_window(vorb *f, int len)    len <<= 1;    if (len == f->blocksize_0) return f->window[0];    if (len == f->blocksize_1) return f->window[1];    return NULL; }  static int vorbis_finish_frame(stb_vorbis *f, int len, int left, int right)    if (f->previous_length) {       int i,j, n = f->previous_length;       float *w = get_window(f, n);       if (w == NULL) return 0;       for (i=0; i < f->channels; ++i) {          for (j=0; j < n; ++j)             f->channel_buffers[i][left+j] = static int start_decoder(vorb *f)          while (current_entry < c->entries) {             int limit = c->entries - current_entry;             int n = get_bits(f, ilog(limit));             if (current_length >= 32) return error(f, VORBIS_invalid_setup);             if (current_entry + n > (int) c->entries) { return error(f, VORBIS_invalid_setup); }             memset(lengths + current_entry, current_length, n);             current_entry += n; static int start_decoder(vorb *f)          c->value_bits = get_bits(f, 4)+1;          c->sequence_p = get_bits(f,1);          if (c->lookup_type == 1) {             int values = lookup1_values(c->entries, c->dimensions);             if (values < 0) return error(f, VORBIS_invalid_setup);             c->lookup_values = (uint32) values;          } else {             c->lookup_values = c->entries * c->dimensions;          } static int start_decoder(vorb *f)             p[j].id = j;          }          qsort(p, g->values, sizeof(p[0]), point_compare);          for (j=0; j < g->values-1; ++j)             if (p[j].x == p[j+1].x)                return error(f, VORBIS_invalid_setup);          for (j=0; j < g->values; ++j)             g->sorted_order[j] = (uint8) p[j].id;          // precompute the neighbors static int start_decoder(vorb *f)          max_submaps = m->submaps;       if (get_bits(f,1)) {          m->coupling_steps = get_bits(f,8)+1;          if (m->coupling_steps > f->channels) return error(f, VORBIS_invalid_setup);          for (k=0; k < m->coupling_steps; ++k) {             m->chan[k].magnitude = get_bits(f, ilog(f->channels-1));             m->chan[k].angle = get_bits(f, ilog(f->channels-1)); int stb_vorbis_get_samples_float(stb_vorbis *f, int channels, float **buffer, in #endif // STB_VORBIS_NO_PULLDATA_API  /* Version history     1.17    - 2019-07-08 - fix CVE-2019-13217, -13218, -13219, -13220, -13221, -13223, -13223                            found with Mayhem by ForAllSecure     1.16    - 2019-03-04 - fix warnings     1.15    - 2019-02-07 - explicit failure if Ogg Skeleton data is found     1.14    - 2018-02-11 - delete bogus dealloca usage     1.13    - 2018-01-29 - fix truncation of last frame (hopefully)     1.12    - 2017-11-21 - limit residue begin/end to blocksize/2 to avoid large temp allocs in bad/corrupt files     1.11    - 2017-07-23 - fix MinGW compilation      1.10    - 2017-03-03 - more robust seeking; fix negative ilog(); clear error in open_memory

Patch:
patch: @@ -33,6 +33,7 @@
 //    Timur Gagiev
 //
 // Partial history:
+//    1.17    - 2019-07-08 - fix CVE-2019-13217..CVE-2019-13223 (by ForAllSecure)
 //    1.16    - 2019-03-04 - fix warnings
 //    1.15    - 2019-02-07 - explicit failure if Ogg Skeleton data is found
 //    1.14    - 2018-02-11 - delete bogus dealloca usage
@@ -1202,8 +1203,10 @@ static int lookup1_values(int entries, int dim)
    int r = (int) floor(exp((float) log((float) entries) / dim));
    if ((int) floor(pow((float) r+1, dim)) <= entries)   // (int) cast for MinGW warning;
       ++r;                                              // floor() to avoid _ftol() when non-CRT
-   assert(pow((float) r+1, dim) > entries);
-   assert((int) floor(pow((float) r, dim)) <= entries); // (int),floor() as above
+   if (pow((float) r+1, dim) <= entries)
+      return -1;
+   if ((int) floor(pow((float) r, dim)) > entries)
+      return -1;
    return r;
 }
 
@@ -2013,15 +2016,15 @@ static __forceinline void draw_line(float *output, int x0, int y0, int x1, int y
    ady -= abs(base) * adx;
    if (x1 > n) x1 = n;
    if (x < x1) {
-      LINE_OP(output[x], inverse_db_table[y]);
+      LINE_OP(output[x], inverse_db_table[y&255]);
       for (++x; x < x1; ++x) {
          err += ady;
          if (err >= adx) {
             err -= adx;
             y += sy;
          } else
             y += base;
-         LINE_OP(output[x], inverse_db_table[y]);
+         LINE_OP(output[x], inverse_db_table[y&255]);
       }
    }
 }
@@ -3048,7 +3051,6 @@ static float *get_window(vorb *f, int len)
    len <<= 1;
    if (len == f->blocksize_0) return f->window[0];
    if (len == f->blocksize_1) return f->window[1];
-   assert(0);
    return NULL;
 }
 
@@ -3454,6 +3456,7 @@ static int vorbis_finish_frame(stb_vorbis *f, int len, int left, int right)
    if (f->previous_length) {
       int i,j, n = f->previous_length;
       float *w = get_window(f, n);
+      if (w == NULL) return 0;
       for (i=0; i < f->channels; ++i) {
          for (j=0; j < n; ++j)
             f->channel_buffers[i][left+j] =
@@ -3695,6 +3698,7 @@ static int start_decoder(vorb *f)
          while (current_entry < c->entries) {
             int limit = c->entries - current_entry;
             int n = get_bits(f, ilog(limit));
+            if (current_length >= 32) return error(f, VORBIS_invalid_setup);
             if (current_entry + n > (int) c->entries) { return error(f, VORBIS_invalid_setup); }
             memset(lengths + current_entry, current_length, n);
             current_entry += n;
@@ -3798,7 +3802,9 @@ static int start_decoder(vorb *f)
          c->value_bits = get_bits(f, 4)+1;
          c->sequence_p = get_bits(f,1);
          if (c->lookup_type == 1) {
-            c->lookup_values = lookup1_values(c->entries, c->dimensions);
+            int values = lookup1_values(c->entries, c->dimensions);
+            if (values < 0) return error(f, VORBIS_invalid_setup);
+            c->lookup_values = (uint32) values;
          } else {
             c->lookup_values = c->entries * c->dimensions;
          }
@@ -3934,6 +3940,9 @@ static int start_decoder(vorb *f)
             p[j].id = j;
          }
          qsort(p, g->values, sizeof(p[0]), point_compare);
+         for (j=0; j < g->values-1; ++j)
+            if (p[j].x == p[j+1].x)
+               return error(f, VORBIS_invalid_setup);
          for (j=0; j < g->values; ++j)
             g->sorted_order[j] = (uint8) p[j].id;
          // precompute the neighbors
@@ -4020,6 +4029,7 @@ static int start_decoder(vorb *f)
          max_submaps = m->submaps;
       if (get_bits(f,1)) {
          m->coupling_steps = get_bits(f,8)+1;
+         if (m->coupling_steps > f->channels) return error(f, VORBIS_invalid_setup);
          for (k=0; k < m->coupling_steps; ++k) {
             m->chan[k].magnitude = get_bits(f, ilog(f->channels-1));
             m->chan[k].angle = get_bits(f, ilog(f->channels-1));
@@ -5386,6 +5396,12 @@ int stb_vorbis_get_samples_float(stb_vorbis *f, int channels, float **buffer, in
 #endif // STB_VORBIS_NO_PULLDATA_API
 
 /* Version history
+    1.17    - 2019-07-08 - fix CVE-2019-13217, -13218, -13219, -13220, -13221, -13223, -13223
+                           found with Mayhem by ForAllSecure
+    1.16    - 2019-03-04 - fix warnings
+    1.15    - 2019-02-07 - explicit failure if Ogg Skeleton data is found
+    1.14    - 2018-02-11 - delete bogus dealloca usage
+    1.13    - 2018-01-29 - fix truncation of last frame (hopefully)
     1.12    - 2017-11-21 - limit residue begin/end to blocksize/2 to avoid large temp allocs in bad/corrupt files
     1.11    - 2017-07-23 - fix MinGW compilation 
     1.10    - 2017-03-03 - more robust seeking; fix negative ilog(); clear error in open_memory
path: stb_vorbis.c