1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242
|
/* -*- 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(/* shouldAbortOnFailure = */ false,
&capacity, "rlbox_wasm2c_woff2");
#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);
// We bump the decompressedSize slightly because it seems that some fonts
// have an incorrectly-set value that results in decompression failure.
// (See https://bugzilla.mozilla.org/show_bug.cgi?id=1934606, and original
// discussion in https://github.com/harfbuzz/harfbuzz/issues/4962.)
decompressedSize =
std::max(decompressedSize, decompressedSize + (decompressedSize >> 4));
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);
}
|