tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

RLBoxWOFF2Host.cpp (9225B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
      3 /* This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #include "RLBoxWOFF2Host.h"
      8 #include "nsPrintfCString.h"
      9 #include "nsThreadUtils.h"
     10 #include "mozilla/ClearOnShutdown.h"
     11 #include "mozilla/RLBoxUtils.h"
     12 #include "mozilla/ScopeExit.h"
     13 #include "opentype-sanitiser.h"  // For ots_ntohl
     14 
     15 using namespace rlbox;
     16 using namespace mozilla;
     17 
     18 tainted_woff2<BrotliDecoderResult> RLBoxBrotliDecoderDecompressCallback(
     19    rlbox_sandbox_woff2& aSandbox, tainted_woff2<unsigned long> aEncodedSize,
     20    tainted_woff2<const char*> aEncodedBuffer,
     21    tainted_woff2<unsigned long*> aDecodedSize,
     22    tainted_woff2<char*> aDecodedBuffer) {
     23  if (!aEncodedBuffer || !aDecodedSize || !aDecodedBuffer) {
     24    return BROTLI_DECODER_RESULT_ERROR;
     25  }
     26 
     27  // We don't create temporary buffers for brotli to operate on. Instead we
     28  // pass a pointer to the in (encoded) and out (decoded) buffers. We check
     29  // (specifically, unverified_safe_pointer checks) that the buffers are within
     30  // the sandbox boundary (for the given sizes).
     31 
     32  size_t encodedSize =
     33      aEncodedSize.unverified_safe_because("Any size within sandbox is ok.");
     34  const uint8_t* encodedBuffer = reinterpret_cast<const uint8_t*>(
     35      aEncodedBuffer.unverified_safe_pointer_because(
     36          encodedSize, "Pointer fits within sandbox"));
     37 
     38  size_t decodedSize =
     39      (*aDecodedSize).unverified_safe_because("Any size within sandbox is ok.");
     40  uint8_t* decodedBuffer =
     41      reinterpret_cast<uint8_t*>(aDecodedBuffer.unverified_safe_pointer_because(
     42          decodedSize, "Pointer fits within sandbox"));
     43 
     44  BrotliDecoderResult res = BrotliDecoderDecompress(
     45      encodedSize, encodedBuffer, &decodedSize, decodedBuffer);
     46 
     47  *aDecodedSize = decodedSize;
     48 
     49  return res;
     50 }
     51 
     52 UniquePtr<RLBoxSandboxDataBase> RLBoxWOFF2SandboxPool::CreateSandboxData(
     53    uint64_t aSize) {
     54  // Create woff2 sandbox
     55  auto sandbox = MakeUnique<rlbox_sandbox_woff2>();
     56 
     57 #if defined(MOZ_WASM_SANDBOXING_WOFF2)
     58  const w2c_mem_capacity capacity =
     59      get_valid_wasm2c_memory_capacity(aSize, true /* 32-bit wasm memory*/);
     60  bool createOK = sandbox->create_sandbox(/* shouldAbortOnFailure = */ false,
     61                                          &capacity, "rlbox_wasm2c_woff2");
     62 #else
     63  bool createOK = sandbox->create_sandbox();
     64 #endif
     65  NS_ENSURE_TRUE(createOK, nullptr);
     66 
     67  UniquePtr<RLBoxWOFF2SandboxData> sbxData =
     68      MakeUnique<RLBoxWOFF2SandboxData>(aSize, std::move(sandbox));
     69 
     70  // Register brotli callback
     71  sbxData->mDecompressCallback = sbxData->Sandbox()->register_callback(
     72      RLBoxBrotliDecoderDecompressCallback);
     73  sbxData->Sandbox()->invoke_sandbox_function(RegisterWOFF2Callback,
     74                                              sbxData->mDecompressCallback);
     75 
     76  return sbxData;
     77 }
     78 
     79 StaticRefPtr<RLBoxWOFF2SandboxPool> RLBoxWOFF2SandboxPool::sSingleton;
     80 
     81 void RLBoxWOFF2SandboxPool::Initalize(size_t aDelaySeconds) {
     82  AssertIsOnMainThread();
     83  RLBoxWOFF2SandboxPool::sSingleton = new RLBoxWOFF2SandboxPool(aDelaySeconds);
     84  ClearOnShutdown(&RLBoxWOFF2SandboxPool::sSingleton);
     85 }
     86 
     87 RLBoxWOFF2SandboxData::RLBoxWOFF2SandboxData(
     88    uint64_t aSize, mozilla::UniquePtr<rlbox_sandbox_woff2> aSandbox)
     89    : mozilla::RLBoxSandboxDataBase(aSize), mSandbox(std::move(aSandbox)) {
     90  MOZ_COUNT_CTOR(RLBoxWOFF2SandboxData);
     91 }
     92 
     93 RLBoxWOFF2SandboxData::~RLBoxWOFF2SandboxData() {
     94  MOZ_ASSERT(mSandbox);
     95  mDecompressCallback.unregister();
     96  mSandbox->destroy_sandbox();
     97  MOZ_COUNT_DTOR(RLBoxWOFF2SandboxData);
     98 }
     99 
    100 static bool Woff2SizeValidator(size_t aLength, size_t aSize, size_t aLimit) {
    101  if (aSize < aLength) {
    102    NS_WARNING("Size of decompressed WOFF 2.0 is less than compressed size");
    103    return false;
    104  } else if (aSize == 0) {
    105    NS_WARNING("Size of decompressed WOFF 2.0 is set to 0");
    106    return false;
    107  } else if (aSize > aLimit) {
    108    NS_WARNING(
    109        nsPrintfCString("Size of decompressed WOFF 2.0 font exceeds %gMB",
    110                        aLimit / (1024.0 * 1024.0))
    111            .get());
    112    return false;
    113  }
    114  return true;
    115 }
    116 
    117 // Code replicated from modules/woff2/src/woff2_dec.cc
    118 // This is used both to compute the expected size of the Woff2 RLBox sandbox
    119 // as well as internally by WOFF2 as a performance hint
    120 static uint32_t ComputeWOFF2FinalSize(const uint8_t* aData, size_t aLength,
    121                                      size_t aLimit) {
    122  // Expected size is stored as a 4 byte value starting from the 17th byte
    123  if (aLength < 20) {
    124    return 0;
    125  }
    126 
    127  uint32_t decompressedSize = 0;
    128  const void* location = &(aData[16]);
    129  std::memcpy(&decompressedSize, location, sizeof(decompressedSize));
    130  decompressedSize = ots_ntohl(decompressedSize);
    131 
    132  // We bump the decompressedSize slightly because it seems that some fonts
    133  // have an incorrectly-set value that results in decompression failure.
    134  // (See https://bugzilla.mozilla.org/show_bug.cgi?id=1934606, and original
    135  // discussion in https://github.com/harfbuzz/harfbuzz/issues/4962.)
    136  decompressedSize =
    137      std::max(decompressedSize, decompressedSize + (decompressedSize >> 4));
    138 
    139  if (!Woff2SizeValidator(aLength, decompressedSize, aLimit)) {
    140    return 0;
    141  }
    142 
    143  return decompressedSize;
    144 }
    145 
    146 template <typename T>
    147 using TransferBufferToWOFF2 =
    148    mozilla::RLBoxTransferBufferToSandbox<T, rlbox_woff2_sandbox_type>;
    149 template <typename T>
    150 using WOFF2Alloc = mozilla::RLBoxAllocateInSandbox<T, rlbox_woff2_sandbox_type>;
    151 
    152 bool RLBoxProcessWOFF2(ots::FontFile* aHeader, ots::OTSStream* aOutput,
    153                       const uint8_t* aData, size_t aLength, uint32_t aIndex,
    154                       ProcessTTCFunc* aProcessTTC,
    155                       ProcessTTFFunc* aProcessTTF) {
    156  MOZ_ASSERT(aProcessTTC);
    157  MOZ_ASSERT(aProcessTTF);
    158 
    159  // We index into aData before processing it (very end of this function). Our
    160  // validator ensures that the untrusted size is greater than aLength, so we
    161  // just need to conservatively ensure that aLength is greater than the highest
    162  // index (7).
    163  NS_ENSURE_TRUE(aLength >= 8, false);
    164 
    165  size_t limit =
    166      std::min(size_t(OTS_MAX_DECOMPRESSED_FILE_SIZE), aOutput->size());
    167  uint32_t expectedSize = ComputeWOFF2FinalSize(aData, aLength, limit);
    168  NS_ENSURE_TRUE(expectedSize > 0, false);
    169 
    170  // The sandbox should have space for the input, output and misc allocations
    171  // To account for misc allocations, we'll set the sandbox size to:
    172  // twice the size of (input + output)
    173 
    174  const uint64_t expectedSandboxSize =
    175      static_cast<uint64_t>(2 * (aLength + expectedSize));
    176  auto sandboxPoolData =
    177      RLBoxWOFF2SandboxPool::sSingleton->PopOrCreate(expectedSandboxSize);
    178  NS_ENSURE_TRUE(sandboxPoolData, false);
    179 
    180  const auto* sandboxData =
    181      static_cast<const RLBoxWOFF2SandboxData*>(sandboxPoolData->SandboxData());
    182  MOZ_ASSERT(sandboxData);
    183 
    184  auto* sandbox = sandboxData->Sandbox();
    185 
    186  // Transfer aData into the sandbox.
    187 
    188  auto data = TransferBufferToWOFF2<char>(
    189      sandbox, reinterpret_cast<const char*>(aData), aLength);
    190  NS_ENSURE_TRUE(*data, false);
    191 
    192  // Perform the actual conversion to TTF.
    193 
    194  auto sizep = WOFF2Alloc<unsigned long>(sandbox);
    195  auto bufp = WOFF2Alloc<char*>(sandbox);
    196  auto bufOwnerString =
    197      WOFF2Alloc<void*>(sandbox);  // pointer to string that owns the bufer
    198 
    199  if (!sandbox
    200           ->invoke_sandbox_function(RLBoxConvertWOFF2ToTTF, *data, aLength,
    201                                     expectedSize, sizep.get(),
    202                                     bufOwnerString.get(), bufp.get())
    203           .unverified_safe_because(
    204               "The ProcessTT* functions validate the decompressed data.")) {
    205    return false;
    206  }
    207 
    208  auto bufCleanup = mozilla::MakeScopeExit([&sandbox, &bufOwnerString] {
    209    // Delete the string created by RLBoxConvertWOFF2ToTTF.
    210    sandbox->invoke_sandbox_function(RLBoxDeleteWOFF2String,
    211                                     bufOwnerString.get());
    212  });
    213 
    214  // Get the actual decompression size and validate it.
    215  // We need to validate the size again. RLBoxConvertWOFF2ToTTF works even if
    216  // the computed size (with ComputeWOFF2FinalSize) is wrong, so we can't
    217  // trust the expectedSize to be the same as size sizep.
    218  bool validateOK = false;
    219  unsigned long actualSize =
    220      (*sizep.get()).copy_and_verify([&](unsigned long val) {
    221        validateOK = Woff2SizeValidator(aLength, val, limit);
    222        return val;
    223      });
    224 
    225  NS_ENSURE_TRUE(validateOK, false);
    226 
    227  const uint8_t* decompressed = reinterpret_cast<const uint8_t*>(
    228      (*bufp.get())
    229          .unverified_safe_pointer_because(
    230              actualSize,
    231              "Only care that the buffer is within sandbox boundary."));
    232 
    233  // Since ProcessTT* memcpy from the buffer, make sure it's not null.
    234  NS_ENSURE_TRUE(decompressed, false);
    235 
    236  if (aData[4] == 't' && aData[5] == 't' && aData[6] == 'c' &&
    237      aData[7] == 'f') {
    238    return aProcessTTC(aHeader, aOutput, decompressed, actualSize, aIndex);
    239  }
    240  ots::Font font(aHeader);
    241  return aProcessTTF(aHeader, &font, aOutput, decompressed, actualSize, 0);
    242 }