/* -*- 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 RLBoxBrotliDecoderDecompressCallback( rlbox_sandbox_woff2& aSandbox, tainted_woff2 aEncodedSize, tainted_woff2 aEncodedBuffer, tainted_woff2 aDecodedSize, tainted_woff2 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( 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(aDecodedBuffer.unverified_safe_pointer_because( decodedSize, "Pointer fits within sandbox")); BrotliDecoderResult res = BrotliDecoderDecompress( encodedSize, encodedBuffer, &decodedSize, decodedBuffer); *aDecodedSize = decodedSize; return res; } UniquePtr RLBoxWOFF2SandboxPool::CreateSandboxData( uint64_t aSize) { // Create woff2 sandbox auto sandbox = MakeUnique(); #if defined(MOZ_WASM_SANDBOXING_WOFF2) bool createOK = sandbox->create_sandbox(/* infallible = */ false, aSize); #else bool createOK = sandbox->create_sandbox(); #endif NS_ENSURE_TRUE(createOK, nullptr); UniquePtr sbxData = MakeUnique(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::sSingleton; void RLBoxWOFF2SandboxPool::Initalize(size_t aDelaySeconds) { AssertIsOnMainThread(); RLBoxWOFF2SandboxPool::sSingleton = new RLBoxWOFF2SandboxPool(aDelaySeconds); ClearOnShutdown(&RLBoxWOFF2SandboxPool::sSingleton); } RLBoxWOFF2SandboxData::RLBoxWOFF2SandboxData( uint64_t aSize, mozilla::UniquePtr 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 using TransferBufferToWOFF2 = mozilla::RLBoxTransferBufferToSandbox; template using WOFF2Alloc = mozilla::RLBoxAllocateInSandbox; 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(2 * (aLength + expectedSize)); auto sandboxPoolData = RLBoxWOFF2SandboxPool::sSingleton->PopOrCreate(expectedSandboxSize); NS_ENSURE_TRUE(sandboxPoolData, false); const auto* sandboxData = static_cast(sandboxPoolData->SandboxData()); MOZ_ASSERT(sandboxData); auto* sandbox = sandboxData->Sandbox(); // Transfer aData into the sandbox. auto data = TransferBufferToWOFF2( sandbox, reinterpret_cast(aData), aLength); NS_ENSURE_TRUE(*data, false); // Perform the actual conversion to TTF. auto sizep = WOFF2Alloc(sandbox); auto bufp = WOFF2Alloc(sandbox); auto bufOwnerString = WOFF2Alloc(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( (*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); }