shithub: sox

Download patch

ref: e9a8dcf780f1cbb6a6faf63b7ba28591963c1cbd
parent: ff11eb9046ea389f250223302cfc83cde8dbb4e6
author: Eric Wong <normalperson@yhbt.net>
date: Wed May 23 17:52:28 EDT 2012

flac: fix invalid memory access w/ optimize_trim

Reverse the buffer assignment roles and have read_samples() assign a
buffer location for decoder_write_callback().

Based on my analysis of src/libFLAC/stream_decoder.c in flac 1.2.1,
the previous SoX code to use an on-stack buffer was safe only (and
only questionably so) for non-seek decoding.

When seeking, FLAC__stream_decoder_seek_absolute() may call
FLAC__stream_decoder_process_single() internally to get to a target
sample.  In the seeking case, the write_audio_frame_to_client_()
function in libFLAC will use an on-stack array of pointers
("newbuffer", lines 2932-2938 of src/libFLAC/stream_decoder.c).

By stashing the address of newbuffer in p->decoded_wide_samples,
returning from FLAC__stream_decoder_seek_absolute() would leave
p->decoded_wide_samples pointing to the old stack location and
cause an invalid memory access.

ref: Invalid memory access within flac handler after seek - ID: 3476843
ref: https://sourceforge.net/tracker/?func=detail&aid=3476843&group_id=10706&atid=110706

--- a/ChangeLog
+++ b/ChangeLog
@@ -18,6 +18,7 @@
 
   o Fix pipe file-type detection regression. (robs)
   o MAUD write fixes. [3507927] (Carl Eric Codere and Ulrich Klauer)
+  o Fix crash when seeking within a FLAC file. [3476843] (Eric Wong)
 
 Other bug fixes:
 
--- a/src/flac.c
+++ b/src/flac.c
@@ -35,9 +35,10 @@
   uint64_t total_samples;
 
   /* Decode buffer: */
-  FLAC__int32 const * const * decoded_wide_samples;
-  unsigned number_of_wide_samples;
-  unsigned wide_sample_number;
+  sox_sample_t *req_buffer; /* this may be on the stack */
+  size_t number_of_requested_samples;
+  sox_sample_t *leftover_buf; /* heap */
+  unsigned number_of_leftover_samples;
 
   FLAC__StreamDecoder * decoder;
   FLAC__bool eof;
@@ -109,6 +110,11 @@
 {
   sox_format_t * ft = (sox_format_t *) client_data;
   priv_t * p = (priv_t *)ft->priv;
+  sox_sample_t * dst = p->req_buffer;
+  unsigned channel;
+  unsigned nsamples = frame->header.blocksize;
+  unsigned sample = 0;
+  size_t actual = nsamples * p->channels;
 
   (void) flac;
 
@@ -116,11 +122,47 @@
     lsx_fail_errno(ft, SOX_EINVAL, "FLAC ERROR: parameters differ between frame and header");
     return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT;
   }
+  if (dst == NULL) {
+    lsx_warn("FLAC ERROR: entered write callback without a buffer (SoX bug)");
+    return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT;
+  }
 
-  /* FIXME: We're skating on thin ice here: buffer may be on the stack! */
-  p->decoded_wide_samples = buffer;
-  p->number_of_wide_samples = frame->header.blocksize;
-  p->wide_sample_number = 0;
+  /* FLAC may give us too much data, prepare the leftover buffer */
+  if (actual > p->number_of_requested_samples) {
+    size_t to_stash = actual - p->number_of_requested_samples;
+
+    p->leftover_buf = lsx_malloc(to_stash * sizeof(sox_sample_t));
+    p->number_of_leftover_samples = to_stash;
+    nsamples = p->number_of_requested_samples / p->channels;
+
+    p->req_buffer += p->number_of_requested_samples;
+    p->number_of_requested_samples = 0;
+  } else {
+    p->req_buffer += actual;
+    p->number_of_requested_samples -= actual;
+  }
+
+leftover_copy:
+
+  for (; sample < nsamples; sample++) {
+    for (channel = 0; channel < p->channels; channel++) {
+      FLAC__int32 d = buffer[channel][sample];
+      switch (p->bits_per_sample) {
+      case  8: *dst++ = SOX_SIGNED_8BIT_TO_SAMPLE(d,); break;
+      case 16: *dst++ = SOX_SIGNED_16BIT_TO_SAMPLE(d,); break;
+      case 24: *dst++ = SOX_SIGNED_24BIT_TO_SAMPLE(d,); break;
+      case 32: *dst++ = SOX_SIGNED_32BIT_TO_SAMPLE(d,); break;
+      }
+    }
+  }
+
+  /* copy into the leftover buffer if we've prepared it */
+  if (sample < frame->header.blocksize) {
+    nsamples = frame->header.blocksize;
+    dst = p->leftover_buf;
+    goto leftover_copy;
+  }
+
   return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE;
 }
 
@@ -168,35 +210,66 @@
 static size_t read_samples(sox_format_t * const ft, sox_sample_t * sampleBuffer, size_t const requested)
 {
   priv_t * p = (priv_t *)ft->priv;
-  size_t actual = 0;
+  size_t prev_requested;
 
   if (p->seek_pending) {
     p->seek_pending = sox_false; 
-    p->wide_sample_number = p->number_of_wide_samples = 0;
-    if (!FLAC__stream_decoder_seek_absolute(p->decoder, (FLAC__uint64)(p->seek_offset / ft->signal.channels)))
+
+    /* discard leftover decoded data */
+    free(p->leftover_buf);
+    p->leftover_buf = NULL;
+    p->number_of_leftover_samples = 0;
+
+    p->req_buffer = sampleBuffer;
+    p->number_of_requested_samples = requested;
+
+    /* calls decoder_write_callback */
+    if (!FLAC__stream_decoder_seek_absolute(p->decoder, (FLAC__uint64)(p->seek_offset / ft->signal.channels))) {
+      p->req_buffer = NULL;
       return 0;
-  }
-  while (!p->eof && actual < requested) {
-    if (p->wide_sample_number >= p->number_of_wide_samples)
-      FLAC__stream_decoder_process_single(p->decoder);
-    if (p->wide_sample_number >= p->number_of_wide_samples)
-      p->eof = sox_true;
-    else { /* FIXME: this block should really be inside the decode callback */
-      unsigned channel;
+    }
+  } else if (p->number_of_leftover_samples > 0) {
 
-      for (channel = 0; channel < p->channels; channel++, actual++) {
-        FLAC__int32 d = p->decoded_wide_samples[channel][p->wide_sample_number];
-        switch (p->bits_per_sample) {
-        case  8: *sampleBuffer++ = SOX_SIGNED_8BIT_TO_SAMPLE(d,); break;
-        case 16: *sampleBuffer++ = SOX_SIGNED_16BIT_TO_SAMPLE(d,); break;
-        case 24: *sampleBuffer++ = SOX_SIGNED_24BIT_TO_SAMPLE(d,); break;
-        case 32: *sampleBuffer++ = SOX_SIGNED_32BIT_TO_SAMPLE(d,); break;
-        }
-      }
-      ++p->wide_sample_number;
+    /* small request, no need to decode more samples since we have leftovers */
+    if (requested < p->number_of_leftover_samples) {
+      size_t req_bytes = requested * sizeof(sox_sample_t);
+
+      memcpy(sampleBuffer, p->leftover_buf, req_bytes);
+      p->number_of_leftover_samples -= requested;
+      memmove(p->leftover_buf, (char *)p->leftover_buf + req_bytes,
+              (size_t)p->number_of_leftover_samples * sizeof(sox_sample_t));
+      return requested;
     }
+
+    /* first, give them all of our leftover data: */
+    memcpy(sampleBuffer, p->leftover_buf,
+           p->number_of_leftover_samples * sizeof(sox_sample_t));
+
+    p->req_buffer = sampleBuffer + p->number_of_leftover_samples;
+    p->number_of_requested_samples = requested - p->number_of_leftover_samples;
+
+    free(p->leftover_buf);
+    p->leftover_buf = NULL;
+    p->number_of_leftover_samples = 0;
+
+    /* continue invoking decoder below */
+  } else {
+    p->req_buffer = sampleBuffer;
+    p->number_of_requested_samples = requested;
   }
-  return actual;
+
+  /* invoke the decoder, calls decoder_write_callback */
+  while ((prev_requested = p->number_of_requested_samples) && !p->eof) {
+    if (!FLAC__stream_decoder_process_single(p->decoder))
+      break; /* error, but maybe got earlier in the loop, though */
+
+    /* number_of_requested_samples decrements as the decoder progresses */
+    if (p->number_of_requested_samples == prev_requested)
+      p->eof = sox_true;
+  }
+  p->req_buffer = NULL;
+
+  return requested - p->number_of_requested_samples;
 }
 
 
@@ -207,6 +280,10 @@
   if (!FLAC__stream_decoder_finish(p->decoder) && p->eof)
     lsx_warn("decoder MD5 checksum mismatch.");
   FLAC__stream_decoder_delete(p->decoder);
+
+  free(p->leftover_buf);
+  p->leftover_buf = NULL;
+  p->number_of_leftover_samples = 0;
   return SOX_SUCCESS;
 }