Bug 1301293 - Catch duration overflows in mdhd and mehd. r=kinetik
authorGerald Squelart <gsquelart@mozilla.com>
Tue, 13 Sep 2016 22:46:03 +1000
changeset 355475 c94e42d7c9bf0976fecd9ae37acbd121759effd1
parent 355474 27f8a2467b316cfa2ea161f2235abbe074540494
child 355476 1ee1b3c2df133672d920267794eb6ca3f2e07894
push id6570
push userraliiev@mozilla.com
push dateMon, 14 Nov 2016 12:26:13 +0000
treeherdermozilla-beta@f455459b2ae5 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskinetik
bugs1301293
milestone51.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1301293 - Catch duration overflows in mdhd and mehd. r=kinetik A better timescaled units to microseconds routine lessens the likelihood of overflows. MozReview-Commit-ID: J0J8ZTG3Rq
media/libstagefright/frameworks/av/media/libstagefright/MPEG4Extractor.cpp
--- a/media/libstagefright/frameworks/av/media/libstagefright/MPEG4Extractor.cpp
+++ b/media/libstagefright/frameworks/av/media/libstagefright/MPEG4Extractor.cpp
@@ -20,34 +20,61 @@
 #include <utils/Log.h>
 
 #include "include/MPEG4Extractor.h"
 #include "include/SampleTable.h"
 #include "include/ESDS.h"
 
 #include <algorithm>
 #include <ctype.h>
+#include <limits>
 #include <stdint.h>
 #include <stdlib.h>
 #include <string.h>
+#include <type_traits>
 
 #include <media/stagefright/foundation/ABitReader.h>
 #include <media/stagefright/foundation/ABuffer.h>
 #include <media/stagefright/foundation/ADebug.h>
 #include <media/stagefright/foundation/AMessage.h>
 #include <media/stagefright/MediaBuffer.h>
 #include <media/stagefright/MediaDefs.h>
 #include <media/stagefright/MediaSource.h>
 #include <media/stagefright/MetaData.h>
 
 static const uint32_t kMAX_ALLOCATION =
     (SIZE_MAX < INT32_MAX ? SIZE_MAX : INT32_MAX) - 128;
 
 namespace stagefright {
 
+static const int64_t OVERFLOW_ERROR = -INT64_MAX;
+
+// Calculate units*1,000,000/hz, trying to avoid overflow.
+// Return OVERFLOW_ERROR in case of unavoidable overflow.
+int64_t unitsToUs(int64_t units, int64_t hz) {
+    const int64_t MAX_S = INT64_MAX / 1000000;
+    if (std::abs(units) <= MAX_S) {
+        return units * 1000000 / hz;
+    }
+    // Hard case, avoid overflow-inducing 'units*1M' by calculating:
+    // (units / hz) * 1M + ((units % hz) * 1M) / hz.
+    //              ^--  ^--             ^-- overflows still possible
+    int64_t units_div_hz = units / hz;
+    int64_t units_rem_hz = units % hz;
+    if (std::abs(units_div_hz) > MAX_S || std::abs(units_rem_hz) > MAX_S) {
+        return OVERFLOW_ERROR;
+    }
+    int64_t quot_us = units_div_hz * 1000000;
+    int64_t rem_us = (units_rem_hz * 1000000) / hz;
+    if (std::abs(quot_us) > INT64_MAX - std::abs(rem_us)) {
+        return OVERFLOW_ERROR;
+    }
+    return quot_us + rem_us;
+}
+
 class MPEG4Source : public MediaSource {
 public:
     MPEG4Source(const sp<MetaData> &format,
                 uint32_t timeScale,
                 const sp<SampleTable> &sampleTable);
 
     sp<MetaData> getFormat() override;
 
@@ -1058,16 +1085,19 @@ status_t MPEG4Extractor::parseChunk(off6
                     < (ssize_t)sizeof(timescale)) {
                 return ERROR_IO;
             }
 
             if (!mLastTrack) {
               return ERROR_MALFORMED;
             }
             mLastTrack->timescale = ntohl(timescale);
+            if (!mLastTrack->timescale) {
+                return ERROR_MALFORMED;
+            }
 
             // Now that we've parsed the media timescale, we can interpret
             // the edit list data.
             storeEditList();
 
             int64_t duration = 0;
             if (version == 1) {
                 if (mDataSource->readAt(
@@ -1083,21 +1113,24 @@ status_t MPEG4Extractor::parseChunk(off6
                         < (ssize_t)sizeof(duration32)) {
                     return ERROR_IO;
                 }
                 // ffmpeg sets duration to -1, which is incorrect.
                 if (duration32 != 0xffffffff) {
                     duration = ntohl(duration32);
                 }
             }
-            if (!mLastTrack->timescale) {
+            if (duration < 0) {
                 return ERROR_MALFORMED;
             }
-            mLastTrack->meta->setInt64(
-                    kKeyDuration, (duration * 1000000) / mLastTrack->timescale);
+            int64_t duration_us = unitsToUs(duration, mLastTrack->timescale);
+            if (duration_us == OVERFLOW_ERROR) {
+                return ERROR_MALFORMED;
+            }
+            mLastTrack->meta->setInt64(kKeyDuration, duration_us);
 
             uint8_t lang[2];
             off64_t lang_offset;
             if (version == 1) {
                 lang_offset = timescale_offset + 4 + 8;
             } else if (version == 0) {
                 lang_offset = timescale_offset + 4 + 4;
             } else {
@@ -1793,19 +1826,25 @@ status_t MPEG4Extractor::parseChunk(off6
                 uint32_t duration32;
                 if (mDataSource->readAt(
                             data_offset + 4, &duration32, sizeof(duration32))
                         < (ssize_t)sizeof(duration32)) {
                     return ERROR_IO;
                 }
                 duration = ntohl(duration32);
             }
+            if (duration < 0) {
+                return ERROR_MALFORMED;
+            }
+            int64_t duration_us = unitsToUs(duration, mHeaderTimescale);
+            if (duration_us == OVERFLOW_ERROR) {
+                return ERROR_MALFORMED;
+            }
             if (duration && mHeaderTimescale) {
-                mFileMetaData->setInt64(
-                        kKeyMovieDuration, (duration * 1000000) / mHeaderTimescale);
+                mFileMetaData->setInt64(kKeyMovieDuration, duration_us);
             }
 
             *offset += chunk_size;
             break;
         }
 
         case FOURCC('m', 'd', 'a', 't'):
         {
@@ -1960,21 +1999,31 @@ status_t MPEG4Extractor::parseChunk(off6
 void MPEG4Extractor::storeEditList()
 {
   if (mHeaderTimescale == 0 ||
       !mLastTrack ||
       mLastTrack->timescale == 0) {
     return;
   }
 
-  uint64_t segment_duration = (mLastTrack->segment_duration * 1000000) / mHeaderTimescale;
+  if (mLastTrack->segment_duration > uint64_t(INT64_MAX) ||
+      mLastTrack->empty_duration > uint64_t(INT64_MAX)) {
+    return;
+  }
+  uint64_t segment_duration =
+    uint64_t(unitsToUs(mLastTrack->segment_duration, mHeaderTimescale));
   // media_time is measured in media time scale units.
-  int64_t media_time = (mLastTrack->media_time * 1000000) / mLastTrack->timescale;
+  int64_t media_time = unitsToUs(mLastTrack->media_time, mLastTrack->timescale);
   // empty_duration is in the Movie Header Box's timescale.
-  int64_t empty_duration = (mLastTrack->empty_duration * 1000000) / mHeaderTimescale;
+  int64_t empty_duration = unitsToUs(mLastTrack->empty_duration, mHeaderTimescale);
+  if (segment_duration == OVERFLOW_ERROR ||
+      media_time == OVERFLOW_ERROR ||
+      empty_duration == OVERFLOW_ERROR) {
+    return;
+  }
   media_time -= empty_duration;
   mLastTrack->meta->setInt64(kKeyMediaTime, media_time);
 
   int64_t duration;
   int32_t samplerate;
   if (mLastTrack->meta->findInt64(kKeyDuration, &duration) &&
       mLastTrack->meta->findInt32(kKeySampleRate, &samplerate)) {
 
@@ -2065,17 +2114,17 @@ status_t MPEG4Extractor::parseSegmentInd
     offset += 4;
     size -= 4;
     ALOGV("refcount: %d", referenceCount);
 
     if (size < referenceCount * 12) {
         return -EINVAL;
     }
 
-    uint64_t total_duration = 0;
+    int64_t total_duration = 0;
     for (unsigned int i = 0; i < referenceCount; i++) {
         uint32_t d1, d2, d3;
 
         if (!mDataSource->getUInt32(offset, &d1) ||     // size
             !mDataSource->getUInt32(offset + 4, &d2) || // duration
             !mDataSource->getUInt32(offset + 8, &d3)) { // flags
             return ERROR_MALFORMED;
         }
@@ -2088,21 +2137,28 @@ status_t MPEG4Extractor::parseSegmentInd
         if (!sap || saptype > 2) {
             ALOGW("not a stream access point, or unsupported type");
         }
         total_duration += d2;
         offset += 12;
         ALOGV(" item %d, %08x %08x %08x", i, d1, d2, d3);
         SidxEntry se;
         se.mSize = d1 & 0x7fffffff;
-        se.mDurationUs = 1000000LL * d2 / timeScale;
+        int64_t durationUs = unitsToUs(d2, timeScale);
+        if (durationUs == OVERFLOW_ERROR || durationUs > int64_t(UINT32_MAX)) {
+          return ERROR_MALFORMED;
+        }
+        se.mDurationUs = uint32_t(durationUs);
         mSidxEntries.AppendElement(se);
     }
 
-    mSidxDuration = total_duration * 1000000 / timeScale;
+    mSidxDuration = unitsToUs(total_duration, timeScale);
+    if (mSidxDuration == OVERFLOW_ERROR) {
+      return ERROR_MALFORMED;
+    }
     ALOGV("duration: %lld", mSidxDuration);
 
     if (!mLastTrack) {
       return ERROR_MALFORMED;
     }
     int64_t metaDuration;
     if (!mLastTrack->meta->findInt64(kKeyDuration, &metaDuration) || metaDuration == 0) {
         mLastTrack->meta->setInt64(kKeyDuration, mSidxDuration);