mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-30 08:12:05 +00:00
ed0bb19d78
Differential Revision: https://phabricator.services.mozilla.com/D175439
235 lines
8.5 KiB
C++
235 lines
8.5 KiB
C++
/* -*- 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);
|
|
}
|