diff options
Diffstat (limited to '')
-rw-r--r-- | gfx/ots/RLBoxWOFF2Host.cpp | 234 |
1 files changed, 234 insertions, 0 deletions
diff --git a/gfx/ots/RLBoxWOFF2Host.cpp b/gfx/ots/RLBoxWOFF2Host.cpp new file mode 100644 index 0000000000..172300f8dd --- /dev/null +++ b/gfx/ots/RLBoxWOFF2Host.cpp @@ -0,0 +1,234 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 "RLBoxWOFF2Host.h" +#include "nsPrintfCString.h" +#include "nsThreadUtils.h" +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/RLBoxUtils.h" +#include "mozilla/ScopeExit.h" +#include "opentype-sanitiser.h" // For ots_ntohl + +using namespace rlbox; +using namespace mozilla; + +tainted_woff2<BrotliDecoderResult> RLBoxBrotliDecoderDecompressCallback( + rlbox_sandbox_woff2& aSandbox, tainted_woff2<unsigned long> aEncodedSize, + tainted_woff2<const char*> aEncodedBuffer, + tainted_woff2<unsigned long*> aDecodedSize, + tainted_woff2<char*> aDecodedBuffer) { + if (!aEncodedBuffer || !aDecodedSize || !aDecodedBuffer) { + return BROTLI_DECODER_RESULT_ERROR; + } + + // We don't create temporary buffers for brotli to operate on. Instead we + // pass a pointer to the in (encoded) and out (decoded) buffers. We check + // (specifically, unverified_safe_pointer checks) that the buffers are within + // the sandbox boundary (for the given sizes). + + size_t encodedSize = + aEncodedSize.unverified_safe_because("Any size within sandbox is ok."); + const uint8_t* encodedBuffer = reinterpret_cast<const uint8_t*>( + aEncodedBuffer.unverified_safe_pointer_because( + encodedSize, "Pointer fits within sandbox")); + + size_t decodedSize = + (*aDecodedSize).unverified_safe_because("Any size within sandbox is ok."); + uint8_t* decodedBuffer = + reinterpret_cast<uint8_t*>(aDecodedBuffer.unverified_safe_pointer_because( + decodedSize, "Pointer fits within sandbox")); + + BrotliDecoderResult res = BrotliDecoderDecompress( + encodedSize, encodedBuffer, &decodedSize, decodedBuffer); + + *aDecodedSize = decodedSize; + + return res; +} + +UniquePtr<RLBoxSandboxDataBase> RLBoxWOFF2SandboxPool::CreateSandboxData( + uint64_t aSize) { + // Create woff2 sandbox + auto sandbox = MakeUnique<rlbox_sandbox_woff2>(); + +#if defined(MOZ_WASM_SANDBOXING_WOFF2) + const w2c_mem_capacity capacity = + get_valid_wasm2c_memory_capacity(aSize, true /* 32-bit wasm memory*/); + bool createOK = sandbox->create_sandbox(/* infallible = */ false, &capacity); +#else + bool createOK = sandbox->create_sandbox(); +#endif + NS_ENSURE_TRUE(createOK, nullptr); + + UniquePtr<RLBoxWOFF2SandboxData> sbxData = + MakeUnique<RLBoxWOFF2SandboxData>(aSize, std::move(sandbox)); + + // Register brotli callback + sbxData->mDecompressCallback = sbxData->Sandbox()->register_callback( + RLBoxBrotliDecoderDecompressCallback); + sbxData->Sandbox()->invoke_sandbox_function(RegisterWOFF2Callback, + sbxData->mDecompressCallback); + + return sbxData; +} + +StaticRefPtr<RLBoxWOFF2SandboxPool> RLBoxWOFF2SandboxPool::sSingleton; + +void RLBoxWOFF2SandboxPool::Initalize(size_t aDelaySeconds) { + AssertIsOnMainThread(); + RLBoxWOFF2SandboxPool::sSingleton = new RLBoxWOFF2SandboxPool(aDelaySeconds); + ClearOnShutdown(&RLBoxWOFF2SandboxPool::sSingleton); +} + +RLBoxWOFF2SandboxData::RLBoxWOFF2SandboxData( + uint64_t aSize, mozilla::UniquePtr<rlbox_sandbox_woff2> aSandbox) + : mozilla::RLBoxSandboxDataBase(aSize), mSandbox(std::move(aSandbox)) { + MOZ_COUNT_CTOR(RLBoxWOFF2SandboxData); +} + +RLBoxWOFF2SandboxData::~RLBoxWOFF2SandboxData() { + MOZ_ASSERT(mSandbox); + mDecompressCallback.unregister(); + mSandbox->destroy_sandbox(); + MOZ_COUNT_DTOR(RLBoxWOFF2SandboxData); +} + +static bool Woff2SizeValidator(size_t aLength, size_t aSize, size_t aLimit) { + if (aSize < aLength) { + NS_WARNING("Size of decompressed WOFF 2.0 is less than compressed size"); + return false; + } else if (aSize == 0) { + NS_WARNING("Size of decompressed WOFF 2.0 is set to 0"); + return false; + } else if (aSize > aLimit) { + NS_WARNING( + nsPrintfCString("Size of decompressed WOFF 2.0 font exceeds %gMB", + aLimit / (1024.0 * 1024.0)) + .get()); + return false; + } + return true; +} + +// Code replicated from modules/woff2/src/woff2_dec.cc +// This is used both to compute the expected size of the Woff2 RLBox sandbox +// as well as internally by WOFF2 as a performance hint +static uint32_t ComputeWOFF2FinalSize(const uint8_t* aData, size_t aLength, + size_t aLimit) { + // Expected size is stored as a 4 byte value starting from the 17th byte + if (aLength < 20) { + return 0; + } + + uint32_t decompressedSize = 0; + const void* location = &(aData[16]); + std::memcpy(&decompressedSize, location, sizeof(decompressedSize)); + decompressedSize = ots_ntohl(decompressedSize); + + if (!Woff2SizeValidator(aLength, decompressedSize, aLimit)) { + return 0; + } + + return decompressedSize; +} + +template <typename T> +using TransferBufferToWOFF2 = + mozilla::RLBoxTransferBufferToSandbox<T, rlbox_woff2_sandbox_type>; +template <typename T> +using WOFF2Alloc = mozilla::RLBoxAllocateInSandbox<T, rlbox_woff2_sandbox_type>; + +bool RLBoxProcessWOFF2(ots::FontFile* aHeader, ots::OTSStream* aOutput, + const uint8_t* aData, size_t aLength, uint32_t aIndex, + ProcessTTCFunc* aProcessTTC, + ProcessTTFFunc* aProcessTTF) { + MOZ_ASSERT(aProcessTTC); + MOZ_ASSERT(aProcessTTF); + + // We index into aData before processing it (very end of this function). Our + // validator ensures that the untrusted size is greater than aLength, so we + // just need to conservatively ensure that aLength is greater than the highest + // index (7). + NS_ENSURE_TRUE(aLength >= 8, false); + + size_t limit = + std::min(size_t(OTS_MAX_DECOMPRESSED_FILE_SIZE), aOutput->size()); + uint32_t expectedSize = ComputeWOFF2FinalSize(aData, aLength, limit); + NS_ENSURE_TRUE(expectedSize > 0, false); + + // The sandbox should have space for the input, output and misc allocations + // To account for misc allocations, we'll set the sandbox size to: + // twice the size of (input + output) + + const uint64_t expectedSandboxSize = + static_cast<uint64_t>(2 * (aLength + expectedSize)); + auto sandboxPoolData = + RLBoxWOFF2SandboxPool::sSingleton->PopOrCreate(expectedSandboxSize); + NS_ENSURE_TRUE(sandboxPoolData, false); + + const auto* sandboxData = + static_cast<const RLBoxWOFF2SandboxData*>(sandboxPoolData->SandboxData()); + MOZ_ASSERT(sandboxData); + + auto* sandbox = sandboxData->Sandbox(); + + // Transfer aData into the sandbox. + + auto data = TransferBufferToWOFF2<char>( + sandbox, reinterpret_cast<const char*>(aData), aLength); + NS_ENSURE_TRUE(*data, false); + + // Perform the actual conversion to TTF. + + auto sizep = WOFF2Alloc<unsigned long>(sandbox); + auto bufp = WOFF2Alloc<char*>(sandbox); + auto bufOwnerString = + WOFF2Alloc<void*>(sandbox); // pointer to string that owns the bufer + + if (!sandbox + ->invoke_sandbox_function(RLBoxConvertWOFF2ToTTF, *data, aLength, + expectedSize, sizep.get(), + bufOwnerString.get(), bufp.get()) + .unverified_safe_because( + "The ProcessTT* functions validate the decompressed data.")) { + return false; + } + + auto bufCleanup = mozilla::MakeScopeExit([&sandbox, &bufOwnerString] { + // Delete the string created by RLBoxConvertWOFF2ToTTF. + sandbox->invoke_sandbox_function(RLBoxDeleteWOFF2String, + bufOwnerString.get()); + }); + + // Get the actual decompression size and validate it. + // We need to validate the size again. RLBoxConvertWOFF2ToTTF works even if + // the computed size (with ComputeWOFF2FinalSize) is wrong, so we can't + // trust the expectedSize to be the same as size sizep. + bool validateOK = false; + unsigned long actualSize = + (*sizep.get()).copy_and_verify([&](unsigned long val) { + validateOK = Woff2SizeValidator(aLength, val, limit); + return val; + }); + + NS_ENSURE_TRUE(validateOK, false); + + const uint8_t* decompressed = reinterpret_cast<const uint8_t*>( + (*bufp.get()) + .unverified_safe_pointer_because( + actualSize, + "Only care that the buffer is within sandbox boundary.")); + + // Since ProcessTT* memcpy from the buffer, make sure it's not null. + NS_ENSURE_TRUE(decompressed, false); + + if (aData[4] == 't' && aData[5] == 't' && aData[6] == 'c' && + aData[7] == 'f') { + return aProcessTTC(aHeader, aOutput, decompressed, actualSize, aIndex); + } + ots::Font font(aHeader); + return aProcessTTF(aHeader, &font, aOutput, decompressed, actualSize, 0); +} |