Bug 1639563 - sanitize WR fonts. r=jrmuizel
☠☠ backed out by 3d2f5e8bb67e ☠ ☠
authorLee Salzman <lsalzman@mozilla.com>
Thu, 21 May 2020 23:30:02 +0000
changeset 531551 b2c8de06588675f9afe0d580e118a6f8591dc8c2
parent 531550 43abf0a9602a925e6a96b6510e8cb6197c32f5a2
child 531552 a98fde98a836ac69aa7b1fedf3616ab969c3b8a0
push id37440
push userabutkovits@mozilla.com
push dateFri, 22 May 2020 09:43:16 +0000
treeherdermozilla-central@fbf71e4d2e21 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjrmuizel
bugs1639563
milestone78.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 1639563 - sanitize WR fonts. r=jrmuizel Differential Revision: https://phabricator.services.mozilla.com/D76360
gfx/layers/wr/IpcResourceUpdateQueue.cpp
gfx/layers/wr/IpcResourceUpdateQueue.h
gfx/layers/wr/WebRenderBridgeParent.cpp
gfx/thebes/gfxOTSUtils.h
gfx/webrender_bindings/WebRenderTypes.h
gfx/webrender_bindings/src/bindings.rs
--- a/gfx/layers/wr/IpcResourceUpdateQueue.cpp
+++ b/gfx/layers/wr/IpcResourceUpdateQueue.cpp
@@ -225,34 +225,84 @@ bool ShmSegmentsReader::Read(const layer
 
   if (aRange.start() + aRange.length() > mChunkSize * mSmallAllocs.Length()) {
     return false;
   }
 
   size_t initialLength = aInto.Length();
 
   size_t srcCursor = aRange.start();
-  int remainingBytesToCopy = aRange.length();
+  size_t remainingBytesToCopy = aRange.length();
   while (remainingBytesToCopy > 0) {
     const size_t shm_idx = srcCursor / mChunkSize;
     const size_t ptrOffset = srcCursor % mChunkSize;
+    if (ptrOffset >= mChunkSize) {
+      break;
+    }
     const size_t copyRange =
-        std::min<int>(remainingBytesToCopy, mChunkSize - ptrOffset);
+        std::min(remainingBytesToCopy, mChunkSize - ptrOffset);
     uint8_t* srcPtr =
         RefCountedShm::GetBytes(mSmallAllocs[shm_idx]) + ptrOffset;
 
     aInto.PushBytes(Range<uint8_t>(srcPtr, copyRange));
 
     srcCursor += copyRange;
     remainingBytesToCopy -= copyRange;
   }
 
   return aInto.Length() - initialLength == aRange.length();
 }
 
+Maybe<Range<uint8_t>> ShmSegmentsReader::GetReadPointerLarge(
+    const layers::OffsetRange& aRange) {
+  // source = zero is for small allocs.
+  MOZ_RELEASE_ASSERT(aRange.source() != 0);
+  if (aRange.source() > mLargeAllocs.Length()) {
+    return Nothing();
+  }
+  size_t id = aRange.source() - 1;
+  const ipc::Shmem& shm = mLargeAllocs[id];
+  if (shm.Size<uint8_t>() < aRange.length()) {
+    return Nothing();
+  }
+
+  uint8_t* srcPtr = shm.get<uint8_t>();
+  return Some(Range<uint8_t>(srcPtr, aRange.length()));
+}
+
+Maybe<Range<uint8_t>> ShmSegmentsReader::GetReadPointer(
+    const layers::OffsetRange& aRange) {
+  if (aRange.length() == 0) {
+    return Some(Range<uint8_t>());
+  }
+
+  if (aRange.source() != 0) {
+    return GetReadPointerLarge(aRange);
+  }
+
+  if (mChunkSize == 0 ||
+      aRange.start() + aRange.length() > mChunkSize * mSmallAllocs.Length()) {
+    return Nothing();
+  }
+
+  size_t srcCursor = aRange.start();
+  size_t remainingBytesToCopy = aRange.length();
+  const size_t shm_idx = srcCursor / mChunkSize;
+  const size_t ptrOffset = srcCursor % mChunkSize;
+  // Return nothing if we can't return a pointer to the full range
+  if (ptrOffset >= mChunkSize ||
+      mChunkSize - ptrOffset < remainingBytesToCopy) {
+    return Nothing();
+  }
+  const size_t copyRange =
+      std::min(remainingBytesToCopy, mChunkSize - ptrOffset);
+  uint8_t* srcPtr = RefCountedShm::GetBytes(mSmallAllocs[shm_idx]) + ptrOffset;
+  return Some(Range<uint8_t>(srcPtr, copyRange));
+}
+
 IpcResourceUpdateQueue::IpcResourceUpdateQueue(
     layers::WebRenderBridgeChild* aAllocator, size_t aChunkSize)
     : mWriter(aAllocator, aChunkSize) {}
 
 IpcResourceUpdateQueue::IpcResourceUpdateQueue(
     IpcResourceUpdateQueue&& aOther) noexcept
     : mWriter(std::move(aOther.mWriter)),
       mUpdates(std::move(aOther.mUpdates)) {}
--- a/gfx/layers/wr/IpcResourceUpdateQueue.h
+++ b/gfx/layers/wr/IpcResourceUpdateQueue.h
@@ -66,35 +66,59 @@ class ShmSegmentsWriter {
 
 class ShmSegmentsReader {
  public:
   ShmSegmentsReader(const nsTArray<layers::RefCountedShmem>& aSmallShmems,
                     const nsTArray<mozilla::ipc::Shmem>& aLargeShmems);
 
   bool Read(const layers::OffsetRange& aRange, wr::Vec<uint8_t>& aInto);
 
+  // Get a read pointer, if possible, directly into the shm. If the range has
+  // been broken up into multiple chunks that can't be represented by a single
+  // range, nothing will be returned to indicate failure.
+  Maybe<Range<uint8_t>> GetReadPointer(const layers::OffsetRange& aRange);
+
+  // Get a read pointer, if possible, directly into the shm. Otherwise, copy
+  // it into the Vec and return a pointer to that contiguous memory instead.
+  // If all fails, return nothing.
+  Maybe<Range<uint8_t>> GetReadPointerOrCopy(const layers::OffsetRange& aRange,
+                                             wr::Vec<uint8_t>& aInto) {
+    if (Maybe<Range<uint8_t>> ptr = GetReadPointer(aRange)) {
+      return ptr;
+    } else {
+      size_t initialLength = aInto.Length();
+      if (Read(aRange, aInto)) {
+        return Some(Range<uint8_t>(aInto.Data() + initialLength,
+                                   aInto.Length() - initialLength));
+      } else {
+        return Nothing();
+      }
+    }
+  }
+
  protected:
   bool ReadLarge(const layers::OffsetRange& aRange, wr::Vec<uint8_t>& aInto);
 
+  Maybe<Range<uint8_t>> GetReadPointerLarge(const layers::OffsetRange& aRange);
+
   const nsTArray<layers::RefCountedShmem>& mSmallAllocs;
   const nsTArray<mozilla::ipc::Shmem>& mLargeAllocs;
   size_t mChunkSize;
 };
 
 class IpcResourceUpdateQueue {
  public:
   // Because we are using shmems, the size should be a multiple of the page
   // size. Each shmem has two guard pages, and the minimum shmem size (at least
   // one Windows) is 64k which is already quite large for a lot of the resources
   // we use here. The RefCountedShmem type used to allocate the chunks keeps a
   // 16 bytes header in the buffer which we account for here as well. So we pick
   // 64k - 2 * 4k - 16 = 57328 bytes as the default alloc size.
-  explicit IpcResourceUpdateQueue(
-      layers::WebRenderBridgeChild* aAllocator,
-      size_t aChunkSize = 57328);
+  explicit IpcResourceUpdateQueue(layers::WebRenderBridgeChild* aAllocator,
+                                  size_t aChunkSize = 57328);
 
   IpcResourceUpdateQueue(IpcResourceUpdateQueue&& aOther) noexcept;
   IpcResourceUpdateQueue& operator=(IpcResourceUpdateQueue&& aOther) noexcept;
 
   IpcResourceUpdateQueue(const IpcResourceUpdateQueue& aOther) = delete;
   IpcResourceUpdateQueue& operator=(const IpcResourceUpdateQueue& aOther) =
       delete;
 
--- a/gfx/layers/wr/WebRenderBridgeParent.cpp
+++ b/gfx/layers/wr/WebRenderBridgeParent.cpp
@@ -3,17 +3,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/layers/WebRenderBridgeParent.h"
 
 #include "CompositableHost.h"
 #include "gfxEnv.h"
-#include "gfxEnv.h"
+#include "gfxOTSUtils.h"
 #include "GeckoProfiler.h"
 #include "GLContext.h"
 #include "GLContextProvider.h"
 #include "nsExceptionHandler.h"
 #include "mozilla/Range.h"
 #include "mozilla/StaticPrefs_gfx.h"
 #include "mozilla/UniquePtr.h"
 #include "mozilla/gfx/gfxVars.h"
@@ -425,16 +425,58 @@ mozilla::ipc::IPCResult WebRenderBridgeP
 void WebRenderBridgeParent::Destroy() {
   if (mDestroyed) {
     return;
   }
   mDestroyed = true;
   ClearResources();
 }
 
+struct WROTSAlloc {
+  wr::Vec<uint8_t> mVec;
+
+  void* Grow(void* aPtr, size_t aLength) {
+    if (aLength > mVec.Length()) {
+      mVec.Reserve(aLength - mVec.Length());
+    }
+    return mVec.inner.data;
+  }
+  wr::Vec<uint8_t> ShrinkToFit(void* aPtr, size_t aLength) {
+    wr::Vec<uint8_t> result(std::move(mVec));
+    result.inner.length = aLength;
+    return result;
+  }
+  void Free(void* aPtr) {}
+};
+
+static bool ReadRawFont(const OpAddRawFont& aOp, wr::ShmSegmentsReader& aReader,
+                        wr::TransactionBuilder& aUpdates) {
+  wr::Vec<uint8_t> sourceBytes;
+  Maybe<Range<uint8_t>> ptr =
+      aReader.GetReadPointerOrCopy(aOp.bytes(), sourceBytes);
+  if (ptr.isNothing()) {
+    return false;
+  }
+  Range<uint8_t>& source = ptr.ref();
+  // Attempt to sanitize the font before passing it along for updating
+  size_t lengthHint = gfxOTSContext::GuessSanitizedFontSize(
+      source.begin().get(), source.length());
+  if (!lengthHint) {
+    return false;
+  }
+  gfxOTSExpandingMemoryStream<WROTSAlloc> output(lengthHint);
+  gfxOTSContext otsContext;
+  if (!otsContext.Process(&output, source.begin().get(), source.length())) {
+    return false;
+  }
+  wr::Vec<uint8_t> bytes = output.forget();
+  aUpdates.AddRawFont(aOp.key(), bytes, aOp.fontIndex());
+  return true;
+}
+
 bool WebRenderBridgeParent::UpdateResources(
     const nsTArray<OpUpdateResource>& aResourceUpdates,
     const nsTArray<RefCountedShmem>& aSmallShmems,
     const nsTArray<ipc::Shmem>& aLargeShmems,
     wr::TransactionBuilder& aUpdates) {
   wr::ShmSegmentsReader reader(aSmallShmems, aLargeShmems);
   UniquePtr<ScheduleSharedSurfaceRelease> scheduleRelease;
 
@@ -524,22 +566,19 @@ bool WebRenderBridgeParent::UpdateResour
         if (!UpdateSharedExternalImage(op.externalImageId(), op.key(),
                                        op.dirtyRect(), aUpdates,
                                        scheduleRelease)) {
           return false;
         }
         break;
       }
       case OpUpdateResource::TOpAddRawFont: {
-        const auto& op = cmd.get_OpAddRawFont();
-        wr::Vec<uint8_t> bytes;
-        if (!reader.Read(op.bytes(), bytes)) {
+        if (!ReadRawFont(cmd.get_OpAddRawFont(), reader, aUpdates)) {
           return false;
         }
-        aUpdates.AddRawFont(op.key(), bytes, op.fontIndex());
         break;
       }
       case OpUpdateResource::TOpAddFontDescriptor: {
         const auto& op = cmd.get_OpAddFontDescriptor();
         wr::Vec<uint8_t> bytes;
         if (!reader.Read(op.bytes(), bytes)) {
           return false;
         }
--- a/gfx/thebes/gfxOTSUtils.h
+++ b/gfx/thebes/gfxOTSUtils.h
@@ -22,17 +22,18 @@ struct gfxOTSMozAlloc {
 // adapted to use Mozilla allocators and to allow the final
 // memory buffer to be adopted by the client.
 template <typename AllocT = gfxOTSMozAlloc>
 class gfxOTSExpandingMemoryStream : public ots::OTSStream {
  public:
   // limit output/expansion to 256MB by default
   enum { DEFAULT_LIMIT = 256 * 1024 * 1024 };
 
-  gfxOTSExpandingMemoryStream(size_t initial, size_t limit = DEFAULT_LIMIT)
+  explicit gfxOTSExpandingMemoryStream(size_t initial,
+                                       size_t limit = DEFAULT_LIMIT)
       : mLength(initial), mLimit(limit), mOff(0) {
     mPtr = mAlloc.Grow(nullptr, mLength);
   }
 
   ~gfxOTSExpandingMemoryStream() { mAlloc.Free(mPtr); }
 
   // Return the buffer, resized to fit its contents (as it may have been
   // over-allocated during growth), and give up ownership of it so the
--- a/gfx/webrender_bindings/WebRenderTypes.h
+++ b/gfx/webrender_bindings/WebRenderTypes.h
@@ -647,22 +647,28 @@ struct Vec<uint8_t> final {
   }
 
   void SetEmpty() {
     inner.data = (uint8_t*)1;
     inner.capacity = 0;
     inner.length = 0;
   }
 
+  uint8_t* Data() { return inner.data; }
+
   size_t Length() { return inner.length; }
 
+  Range<uint8_t> GetRange() { return Range<uint8_t>(Data(), Length()); }
+
   void PushBytes(Range<uint8_t> aBytes) {
     wr_vec_u8_push_bytes(&inner, RangeToByteSlice(aBytes));
   }
 
+  void Reserve(size_t aLength) { wr_vec_u8_reserve(&inner, aLength); }
+
   ~Vec() {
     if (inner.data) {
       wr_vec_u8_free(inner);
     }
   }
 };
 
 struct ByteBuffer {
--- a/gfx/webrender_bindings/src/bindings.rs
+++ b/gfx/webrender_bindings/src/bindings.rs
@@ -264,29 +264,41 @@ impl WrVecU8 {
             data: v.as_mut_ptr(),
             length: v.len(),
             capacity: v.capacity(),
         };
         mem::forget(v);
         w
     }
 
+    fn reserve(&mut self, len: usize) {
+        let mut vec = self.flush_into_vec();
+        vec.reserve(len);
+        *self = Self::from_vec(vec);
+    }
+
     fn push_bytes(&mut self, bytes: &[u8]) {
         let mut vec = self.flush_into_vec();
         vec.extend_from_slice(bytes);
         *self = Self::from_vec(vec);
     }
+
 }
 
 #[no_mangle]
 pub extern "C" fn wr_vec_u8_push_bytes(v: &mut WrVecU8, bytes: ByteSlice) {
     v.push_bytes(bytes.as_slice());
 }
 
 #[no_mangle]
+pub extern "C" fn wr_vec_u8_reserve(v: &mut WrVecU8, len: usize) {
+    v.reserve(len);
+}
+
+#[no_mangle]
 pub extern "C" fn wr_vec_u8_free(v: WrVecU8) {
     v.to_vec();
 }
 
 #[repr(C)]
 pub struct ByteSlice<'a> {
     buffer: *const u8,
     len: usize,