Bug 1378518 - Use offset-to-block utilities to check MediaCache offset ranges - r?jwwang draft
authorGerald Squelart <gsquelart@mozilla.com>
Thu, 06 Jul 2017 15:42:56 +1200
changeset 605133 f923dd444a34781a30e3f830d82cc3814a225bba
parent 605132 26e925be52f9eb76219ffc15fa7049d6db50e403
child 636403 792e91adf54cdc88731c414501c7609d4fd2ee32
push id67311
push usergsquelart@mozilla.com
push dateFri, 07 Jul 2017 04:13:16 +0000
reviewersjwwang
bugs1378518
milestone56.0a1
Bug 1378518 - Use offset-to-block utilities to check MediaCache offset ranges - r?jwwang MozReview-Commit-ID: 8IsctoABVrb
dom/media/MediaCache.cpp
--- a/dom/media/MediaCache.cpp
+++ b/dom/media/MediaCache.cpp
@@ -757,24 +757,65 @@ static int32_t GetMaxBlocks()
   // but the latter formula avoids a potential overflow from `* 1024`.
   // And because BLOCK_SIZE/1024 is at least 2, the maximum cache size
   // INT32_MAX*2 will give a maxBlocks that can fit in an int32_t.
   constexpr uint32_t blockSizeKb = uint32_t(MediaCache::BLOCK_SIZE / 1024);
   const int32_t maxBlocks = int32_t(cacheSizeKb / blockSizeKb);
   return std::max(maxBlocks, int32_t(1));
 }
 
+// Allowed range is whatever can be accessed with an int32_t block index.
+static bool
+IsOffsetAllowed(int64_t aOffset)
+{
+  return aOffset < (int64_t(INT32_MAX) + 1) * MediaCache::BLOCK_SIZE &&
+         aOffset >= 0;
+}
+
+// Convert 64-bit offset to 32-bit block index.
+// Assumes offset range-check was already done.
+static int32_t
+OffsetToBlockIndexUnchecked(int64_t aOffset)
+{
+  // Still check for allowed range in debug builds, to catch out-of-range
+  // issues early during development.
+  MOZ_ASSERT(IsOffsetAllowed(aOffset));
+  return int32_t(aOffset / MediaCache::BLOCK_SIZE);
+}
+
+// Convert 64-bit offset to 32-bit block index. -1 if out of allowed range.
+static int32_t
+OffsetToBlockIndex(int64_t aOffset)
+{
+  return IsOffsetAllowed(aOffset) ? OffsetToBlockIndexUnchecked(aOffset) : -1;
+}
+
+// Convert 64-bit offset to 32-bit offset inside a block.
+// Will not fail (even if offset is outside allowed range), so there is no
+// need to check for errors.
+static int32_t
+OffsetInBlock(int64_t aOffset)
+{
+  // Still check for allowed range in debug builds, to catch out-of-range
+  // issues early during development.
+  MOZ_ASSERT(IsOffsetAllowed(aOffset));
+  return int32_t(aOffset % MediaCache::BLOCK_SIZE);
+}
+
 int32_t
 MediaCache::FindBlockForIncomingData(TimeStamp aNow,
                                        MediaCacheStream* aStream)
 {
   mReentrantMonitor.AssertCurrentThreadIn();
 
-  int32_t blockIndex = FindReusableBlock(aNow, aStream,
-      aStream->mChannelOffset/BLOCK_SIZE, INT32_MAX);
+  int32_t blockIndex =
+    FindReusableBlock(aNow,
+                      aStream,
+                      OffsetToBlockIndexUnchecked(aStream->mChannelOffset),
+                      INT32_MAX);
 
   if (blockIndex < 0 || !IsBlockFree(blockIndex)) {
     // The block returned is already allocated.
     // Don't reuse it if a) there's room to expand the cache or
     // b) the data we're going to store in the free block is not higher
     // priority than the data already stored in the free block.
     // The latter can lead us to go over the cache limit a bit.
     if ((mIndex.Length() < uint32_t(GetMaxBlocks()) || blockIndex < 0 ||
@@ -793,17 +834,18 @@ MediaCache::FindBlockForIncomingData(Tim
 
 bool
 MediaCache::BlockIsReusable(int32_t aBlockIndex)
 {
   Block* block = &mIndex[aBlockIndex];
   for (uint32_t i = 0; i < block->mOwners.Length(); ++i) {
     MediaCacheStream* stream = block->mOwners[i].mStream;
     if (stream->mPinCount > 0 ||
-        stream->mStreamOffset/BLOCK_SIZE == block->mOwners[i].mStreamBlock) {
+        uint32_t(OffsetToBlockIndex(stream->mStreamOffset)) ==
+          block->mOwners[i].mStreamBlock) {
       return false;
     }
   }
   return true;
 }
 
 void
 MediaCache::AppendMostReusableBlock(BlockList* aBlockList,
@@ -1336,37 +1378,41 @@ MediaCache::Update()
               stream, predictedNewDataUse.ToSeconds(), latestNextUse.ToSeconds());
           enableReading = predictedNewDataUse < latestNextUse;
         }
       }
 
       if (enableReading) {
         for (uint32_t j = 0; j < i; ++j) {
           MediaCacheStream* other = mStreams[j];
-          if (other->mResourceID == stream->mResourceID &&
-              !other->mClosed && !other->mClient->IsSuspended() &&
-              other->mChannelOffset/BLOCK_SIZE == desiredOffset/BLOCK_SIZE) {
+          if (other->mResourceID == stream->mResourceID && !other->mClosed &&
+              !other->mClient->IsSuspended() &&
+              OffsetToBlockIndexUnchecked(other->mChannelOffset) ==
+                OffsetToBlockIndexUnchecked(desiredOffset)) {
             // This block is already going to be read by the other stream.
             // So don't try to read it from this stream as well.
             enableReading = false;
-            LOG("Stream %p waiting on same block (%" PRId64 ") from stream %p",
-                stream, desiredOffset/BLOCK_SIZE, other);
+            LOG("Stream %p waiting on same block (%" PRId32 ") from stream %p",
+                stream,
+                OffsetToBlockIndexUnchecked(desiredOffset),
+                other);
             break;
           }
         }
       }
 
       if (stream->mChannelOffset != desiredOffset && enableReading) {
         // We need to seek now.
         NS_ASSERTION(stream->mIsTransportSeekable || desiredOffset == 0,
                      "Trying to seek in a non-seekable stream!");
         // Round seek offset down to the start of the block. This is essential
         // because we don't want to think we have part of a block already
         // in mPartialBlockBuffer.
-        stream->mChannelOffset = (desiredOffset/BLOCK_SIZE)*BLOCK_SIZE;
+        stream->mChannelOffset =
+          OffsetToBlockIndexUnchecked(desiredOffset) * BLOCK_SIZE;
         actions[i] = stream->mCacheSuspended ? SEEK_AND_RESUME : SEEK;
       } else if (enableReading && stream->mCacheSuspended) {
         actions[i] = RESUME;
       } else if (!enableReading && !stream->mCacheSuspended) {
         actions[i] = SUSPEND;
       }
     }
 #ifdef DEBUG
@@ -1559,17 +1605,18 @@ MediaCache::InsertReadaheadBlock(BlockOw
 
 void
 MediaCache::AllocateAndWriteBlock(
   MediaCacheStream* aStream, MediaCacheStream::ReadMode aMode,
   Span<const uint8_t> aData1, Span<const uint8_t> aData2)
 {
   mReentrantMonitor.AssertCurrentThreadIn();
 
-  int32_t streamBlockIndex = aStream->mChannelOffset/BLOCK_SIZE;
+  int32_t streamBlockIndex =
+    OffsetToBlockIndexUnchecked(aStream->mChannelOffset);
 
   // Remove all cached copies of this block
   ResourceStreamIterator iter(this, aStream->mResourceID);
   while (MediaCacheStream* stream = iter.Next()) {
     while (streamBlockIndex >= int32_t(stream->mBlocks.Length())) {
       stream->mBlocks.AppendElement(-1);
     }
     if (stream->mBlocks[streamBlockIndex] >= 0) {
@@ -1758,40 +1805,52 @@ void
 MediaCache::NoteSeek(MediaCacheStream* aStream, int64_t aOldOffset)
 {
   mReentrantMonitor.AssertCurrentThreadIn();
 
   if (aOldOffset < aStream->mStreamOffset) {
     // We seeked forward. Convert blocks from readahead to played.
     // Any readahead block that intersects the seeked-over range must
     // be converted.
-    int32_t blockIndex = aOldOffset/BLOCK_SIZE;
+    int32_t blockIndex = OffsetToBlockIndex(aOldOffset);
+    if (blockIndex < 0) {
+      return;
+    }
     int32_t endIndex =
-      std::min<int64_t>((aStream->mStreamOffset + BLOCK_SIZE - 1)/BLOCK_SIZE,
-             aStream->mBlocks.Length());
+      std::min(OffsetToBlockIndex(aStream->mStreamOffset + (BLOCK_SIZE - 1)),
+               int32_t(aStream->mBlocks.Length()));
+    if (endIndex < 0) {
+      return;
+    }
     TimeStamp now = TimeStamp::Now();
     while (blockIndex < endIndex) {
       int32_t cacheBlockIndex = aStream->mBlocks[blockIndex];
       if (cacheBlockIndex >= 0) {
         // Marking the block used may not be exactly what we want but
         // it's simple
         NoteBlockUsage(aStream, cacheBlockIndex, aStream->mStreamOffset,
                        MediaCacheStream::MODE_PLAYBACK, now);
       }
       ++blockIndex;
     }
   } else {
     // We seeked backward. Convert from played to readahead.
     // Any played block that is entirely after the start of the seeked-over
     // range must be converted.
     int32_t blockIndex =
-      (aStream->mStreamOffset + BLOCK_SIZE - 1)/BLOCK_SIZE;
+      OffsetToBlockIndex(aStream->mStreamOffset + (BLOCK_SIZE - 1));
+    if (blockIndex < 0) {
+      return;
+    }
     int32_t endIndex =
-      std::min<int64_t>((aOldOffset + BLOCK_SIZE - 1)/BLOCK_SIZE,
-             aStream->mBlocks.Length());
+      std::min(OffsetToBlockIndex(aOldOffset + (BLOCK_SIZE - 1)),
+               int32_t(aStream->mBlocks.Length()));
+    if (endIndex < 0) {
+      return;
+    }
     while (blockIndex < endIndex) {
       MOZ_ASSERT(endIndex > 0);
       int32_t cacheBlockIndex = aStream->mBlocks[endIndex - 1];
       if (cacheBlockIndex >= 0) {
         BlockOwner* bo = GetBlockOwner(cacheBlockIndex, aStream);
         NS_ASSERTION(bo, "Stream doesn't own its blocks?");
         if (bo->mClass == PLAYED_BLOCK) {
           aStream->mPlayedBlocks.RemoveBlock(cacheBlockIndex);
@@ -1869,17 +1928,17 @@ MediaCacheStream::NotifyDataReceived(int
   int64_t size = aSize;
   const char* data = aData;
 
   LOG("Stream %p DataReceived at %" PRId64 " count=%" PRId64,
       this, mChannelOffset, aSize);
 
   // We process the data one block (or part of a block) at a time
   while (size > 0) {
-    uint32_t blockIndex = mChannelOffset/BLOCK_SIZE;
+    uint32_t blockIndex = OffsetToBlockIndexUnchecked(mChannelOffset);
     int32_t blockOffset = int32_t(mChannelOffset - blockIndex*BLOCK_SIZE);
     int32_t chunkSize = std::min<int64_t>(BLOCK_SIZE - blockOffset, size);
 
     if (blockOffset == 0) {
       // We've just started filling this buffer so now is a good time
       // to clear this flag.
       mMetadataInPartialBlockBuffer = false;
     }
@@ -1919,17 +1978,17 @@ MediaCacheStream::NotifyDataReceived(int
 }
 
 void
 MediaCacheStream::FlushPartialBlockInternal(bool aNotifyAll,
                                             ReentrantMonitorAutoEnter& aReentrantMonitor)
 {
   NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
 
-  int32_t blockOffset = int32_t(mChannelOffset%BLOCK_SIZE);
+  int32_t blockOffset = OffsetInBlock(mChannelOffset);
   if (blockOffset > 0) {
     LOG("Stream %p writing partial block: [%d] bytes; "
         "mStreamOffset [%" PRId64 "] mChannelOffset[%"
         PRId64 "] mStreamLength [%" PRId64 "] notifying: [%s]",
         this, blockOffset, mStreamOffset, mChannelOffset, mStreamLength,
         aNotifyAll ? "yes" : "no");
 
     // Write back the partial block
@@ -2164,23 +2223,25 @@ MediaCacheStream::IsDataCachedToEndOfStr
     return false;
   return GetCachedDataEndInternal(aOffset) >= mStreamLength;
 }
 
 int64_t
 MediaCacheStream::GetCachedDataEndInternal(int64_t aOffset)
 {
   mMediaCache->GetReentrantMonitor().AssertCurrentThreadIn();
-  uint32_t startBlockIndex = aOffset/BLOCK_SIZE;
-  uint32_t blockIndex = startBlockIndex;
-  while (blockIndex < mBlocks.Length() && mBlocks[blockIndex] != -1) {
+  int32_t blockIndex = OffsetToBlockIndex(aOffset);
+  if (blockIndex < 0) {
+    return aOffset;
+  }
+  while (size_t(blockIndex) < mBlocks.Length() && mBlocks[blockIndex] != -1) {
     ++blockIndex;
   }
   int64_t result = blockIndex*BLOCK_SIZE;
-  if (blockIndex == mChannelOffset/BLOCK_SIZE) {
+  if (blockIndex == OffsetToBlockIndexUnchecked(mChannelOffset)) {
     // The block containing mChannelOffset may be partially read but not
     // yet committed to the main cache
     result = mChannelOffset;
   }
   if (mStreamLength >= 0) {
     // The last block in the cache may only be partially valid, so limit
     // the cached range to the stream length
     result = std::min(result, mStreamLength);
@@ -2190,47 +2251,50 @@ MediaCacheStream::GetCachedDataEndIntern
 
 int64_t
 MediaCacheStream::GetNextCachedDataInternal(int64_t aOffset)
 {
   mMediaCache->GetReentrantMonitor().AssertCurrentThreadIn();
   if (aOffset == mStreamLength)
     return -1;
 
-  uint32_t startBlockIndex = aOffset/BLOCK_SIZE;
-  uint32_t channelBlockIndex = mChannelOffset/BLOCK_SIZE;
+  int32_t startBlockIndex = OffsetToBlockIndex(aOffset);
+  if (startBlockIndex < 0) {
+    return -1;
+  }
+  int32_t channelBlockIndex = OffsetToBlockIndexUnchecked(mChannelOffset);
 
   if (startBlockIndex == channelBlockIndex &&
       aOffset < mChannelOffset) {
     // The block containing mChannelOffset is partially read, but not
     // yet committed to the main cache. aOffset lies in the partially
     // read portion, thus it is effectively cached.
     return aOffset;
   }
 
-  if (startBlockIndex >= mBlocks.Length())
+  if (size_t(startBlockIndex) >= mBlocks.Length())
     return -1;
 
   // Is the current block cached?
   if (mBlocks[startBlockIndex] != -1)
     return aOffset;
 
   // Count the number of uncached blocks
-  bool hasPartialBlock = (mChannelOffset % BLOCK_SIZE) != 0;
-  uint32_t blockIndex = startBlockIndex + 1;
+  bool hasPartialBlock = OffsetInBlock(mChannelOffset) != 0;
+  int32_t blockIndex = startBlockIndex + 1;
   while (true) {
     if ((hasPartialBlock && blockIndex == channelBlockIndex) ||
-        (blockIndex < mBlocks.Length() && mBlocks[blockIndex] != -1)) {
+        (size_t(blockIndex) < mBlocks.Length() && mBlocks[blockIndex] != -1)) {
       // We at the incoming channel block, which has has data in it,
       // or are we at a cached block. Return index of block start.
       return blockIndex * BLOCK_SIZE;
     }
 
     // No more cached blocks?
-    if (blockIndex >= mBlocks.Length())
+    if (size_t(blockIndex) >= mBlocks.Length())
       return -1;
 
     ++blockIndex;
   }
 
   NS_NOTREACHED("Should return in loop");
   return -1;
 }
@@ -2279,18 +2343,19 @@ MediaCacheStream::Seek(int32_t aWhence, 
   case PR_SEEK_SET:
     newOffset = aOffset;
     break;
   default:
     NS_ERROR("Unknown whence");
     return NS_ERROR_FAILURE;
   }
 
-  if (newOffset < 0)
+  if (!IsOffsetAllowed(newOffset)) {
     return NS_ERROR_FAILURE;
+  }
   mStreamOffset = newOffset;
 
   LOG("Stream %p Seek to %" PRId64, this, mStreamOffset);
   mMediaCache->NoteSeek(this, oldOffset);
 
   mMediaCache->QueueUpdate();
   return NS_OK;
 }
@@ -2325,50 +2390,55 @@ MediaCacheStream::Read(char* aBuffer, ui
 
   // Cache the offset in case it is changed again when we are waiting for the
   // monitor to be notified to avoid reading at the wrong position.
   auto streamOffset = mStreamOffset;
 
   uint32_t count = 0;
   // Read one block (or part of a block) at a time
   while (count < aCount) {
-    uint32_t streamBlock = uint32_t(streamOffset/BLOCK_SIZE);
+    int32_t streamBlock = OffsetToBlockIndex(streamOffset);
+    if (streamBlock < 0) {
+      break;
+    }
     uint32_t offsetInStreamBlock = uint32_t(streamOffset - streamBlock*BLOCK_SIZE);
     int64_t size = std::min<int64_t>(aCount - count, BLOCK_SIZE - offsetInStreamBlock);
 
     if (mStreamLength >= 0) {
       // Don't try to read beyond the end of the stream
       int64_t bytesRemaining = mStreamLength - streamOffset;
       if (bytesRemaining <= 0) {
         // Get out of here and return NS_OK
         break;
       }
       size = std::min(size, bytesRemaining);
       // Clamp size until 64-bit file size issues are fixed.
       size = std::min(size, int64_t(INT32_MAX));
     }
 
-    int32_t cacheBlock = streamBlock < mBlocks.Length() ? mBlocks[streamBlock] : -1;
+    int32_t cacheBlock =
+      size_t(streamBlock) < mBlocks.Length() ? mBlocks[streamBlock] : -1;
     if (cacheBlock < 0) {
       // We don't have a complete cached block here.
 
       if (count > 0) {
         // Some data has been read, so return what we've got instead of
         // blocking or trying to find a stream with a partial block.
         break;
       }
 
       // See if the data is available in the partial cache block of any
       // stream reading this resource. We need to do this in case there is
       // another stream with this resource that has all the data to the end of
       // the stream but the data doesn't end on a block boundary.
       MediaCacheStream* streamWithPartialBlock = nullptr;
       MediaCache::ResourceStreamIterator iter(mMediaCache, mResourceID);
       while (MediaCacheStream* stream = iter.Next()) {
-        if (uint32_t(stream->mChannelOffset/BLOCK_SIZE) == streamBlock &&
+        if (OffsetToBlockIndexUnchecked(stream->mChannelOffset) ==
+              streamBlock &&
             streamOffset < stream->mChannelOffset) {
           streamWithPartialBlock = stream;
           break;
         }
       }
       if (streamWithPartialBlock) {
         // We can just use the data in mPartialBlockBuffer. In fact we should
         // use it rather than waiting for the block to fill and land in
@@ -2447,35 +2517,39 @@ MediaCacheStream::ReadFromCache(char* aB
   uint32_t count = 0;
   int64_t streamOffset = aOffset;
   while (count < aCount) {
     if (mClosed) {
       // We need to check |mClosed| in each iteration which might be changed
       // after calling |mMediaCache->ReadCacheFile|.
       return NS_ERROR_FAILURE;
     }
-    uint32_t streamBlock = uint32_t(streamOffset/BLOCK_SIZE);
+    int32_t streamBlock = OffsetToBlockIndex(streamOffset);
+    if (streamBlock < 0) {
+      break;
+    }
     uint32_t offsetInStreamBlock =
       uint32_t(streamOffset - streamBlock*BLOCK_SIZE);
     int64_t size = std::min<int64_t>(aCount - count, BLOCK_SIZE - offsetInStreamBlock);
 
     if (mStreamLength >= 0) {
       // Don't try to read beyond the end of the stream
       int64_t bytesRemaining = mStreamLength - streamOffset;
       if (bytesRemaining <= 0) {
         return NS_ERROR_FAILURE;
       }
       size = std::min(size, bytesRemaining);
       // Clamp size until 64-bit file size issues are fixed.
       size = std::min(size, int64_t(INT32_MAX));
     }
 
     int32_t bytes;
-    uint32_t channelBlock = uint32_t(mChannelOffset/BLOCK_SIZE);
-    int32_t cacheBlock = streamBlock < mBlocks.Length() ? mBlocks[streamBlock] : -1;
+    int32_t channelBlock = OffsetToBlockIndexUnchecked(mChannelOffset);
+    int32_t cacheBlock =
+      size_t(streamBlock) < mBlocks.Length() ? mBlocks[streamBlock] : -1;
     if (channelBlock == streamBlock && streamOffset < mChannelOffset) {
       // We can just use the data in mPartialBlockBuffer. In fact we should
       // use it rather than waiting for the block to fill and land in
       // the cache.
       // Clamp bytes until 64-bit file size issues are fixed.
       int64_t toCopy = std::min<int64_t>(size, mChannelOffset - streamOffset);
       bytes = std::min(toCopy, int64_t(INT32_MAX));
       MOZ_ASSERT(bytes >= 0 && bytes <= toCopy, "Bytes out of range.");