CVE-2019-13221: Out-of-bounds Write (CWE-787)
value: A stack buffer overflow in the compute_codewords 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.
CWE description: The product writes data past the end, or before the beginning, of the intended buffer.
CVSS v3 base score: 7.8
Published: 2019-08-15T17:15Z
Repository: https://github.com/nothings/stb
Language: C
Files changed: stb_vorbis.c
Commit date: 2019-08-09 04:05:22 -0700
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()

Diff: diff --git a/stb_vorbis.c b/stb_vorbis.c
index ff117ab..aa48137 100644
--- a/stb_vorbis.c
+++ b/stb_vorbis.c
@@ -31,10 +31,11 @@
 //    Phillip Bennefall  Rohit               Thiago Goulart
 //    manxorist@github   saga musix          github:infatum
 //    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
 //    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
@@ -1200,12 +1201,14 @@ static int vorbis_validate(uint8 *data)
 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;
 }
 
 // called twice per file
 static void compute_twiddle_factors(int n, float *A, float *B, float *C)
@@ -2011,19 +2014,19 @@ static __forceinline void draw_line(float *output, int x0, int y0, int x1, int y
       sy = base+1;
 #endif
    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]);
       }
    }
 }
 
 static int residue_decode(vorb *f, Codebook *book, float *target, int offset, int n, int rtype)
@@ -3046,11 +3049,10 @@ void inverse_mdct_naive(float *buffer, int n)
 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;
 }
 
 #ifndef STB_VORBIS_NO_DEFER_FLOOR
 typedef int16 YTYPE;
@@ -3452,10 +3454,11 @@ static int vorbis_finish_frame(stb_vorbis *f, int len, int left, int right)
 
    // mixin from previous window
    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] =
                f->channel_buffers[i][left+j]*w[    j] +
                f->previous_window[i][     j]*w[n-1-j];
@@ -3693,10 +3696,11 @@ static int start_decoder(vorb *f)
          int current_entry = 0;
          int current_length = get_bits(f,5) + 1;
          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;
             ++current_length;
          }
@@ -3796,11 +3800,13 @@ static int start_decoder(vorb *f)
          c->minimum_value = float32_unpack(get_bits(f, 32));
          c->delta_value = float32_unpack(get_bits(f, 32));
          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;
          }
          if (c->lookup_values == 0) return error(f, VORBIS_invalid_setup);
          mults = (uint16 *) setup_temp_malloc(f, sizeof(mults[0]) * c->lookup_values);
@@ -3932,10 +3938,13 @@ static int start_decoder(vorb *f)
          for (j=0; j < g->values; ++j) {
             p[j].x = g->Xlist[j];
             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
          for (j=2; j < g->values; ++j) {
             int low,hi;
@@ -4018,10 +4027,11 @@ static int start_decoder(vorb *f)
          m->submaps = 1;
       if (m->submaps > max_submaps)
          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));
             if (m->chan[k].magnitude >= f->channels)        return error(f, VORBIS_invalid_setup);
             if (m->chan[k].angle     >= f->channels)        return error(f, VORBIS_invalid_setup);
@@ -5384,10 +5394,16 @@ int stb_vorbis_get_samples_float(stb_vorbis *f, int channels, float **buffer, in
    return n;
 }
 #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
     1.09    - 2016-04-04 - back out 'avoid discarding last frame' fix from previous version
     1.08    - 2016-04-02 - fixed multiple warnings; fix setup memory leaks;

Vulnerable code:    assert(pow((float) r+1, dim) > entries);
   assert((int) floor(pow((float) r, dim)) <= entries); // (int),floor() as above
      LINE_OP(output[x], inverse_db_table[y]);
         LINE_OP(output[x], inverse_db_table[y]);
   assert(0);
            c->lookup_values = lookup1_values(c->entries, c->dimensions);
Fixed code: //    1.17    - 2019-07-08 - fix CVE-2019-13217..CVE-2019-13223 (by ForAllSecure)
   if (pow((float) r+1, dim) <= entries)
      return -1;
   if ((int) floor(pow((float) r, dim)) > entries)
      return -1;
      LINE_OP(output[x], inverse_db_table[y&255]);
         LINE_OP(output[x], inverse_db_table[y&255]);
      if (w == NULL) return 0;
            if (current_length >= 32) return error(f, VORBIS_invalid_setup);
            int values = lookup1_values(c->entries, c->dimensions);
            if (values < 0) return error(f, VORBIS_invalid_setup);
            c->lookup_values = (uint32) values;
         for (j=0; j < g->values-1; ++j)
            if (p[j].x == p[j+1].x)
               return error(f, VORBIS_invalid_setup);
         if (m->coupling_steps > f->channels) return error(f, VORBIS_invalid_setup);
    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)