Source code
Revision control
Copy as Markdown
Other Tools
/* -*- 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
#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);
}