Bug 1803984 - Add tests for the interaction between speculative preloading and module...
[gecko.git] / gfx / ots / RLBoxWOFF2Host.cpp
blob172300f8dd3a9c94434efa201b164aa0bdd35fd8
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "RLBoxWOFF2Host.h"
8 #include "nsPrintfCString.h"
9 #include "nsThreadUtils.h"
10 #include "mozilla/ClearOnShutdown.h"
11 #include "mozilla/RLBoxUtils.h"
12 #include "mozilla/ScopeExit.h"
13 #include "opentype-sanitiser.h" // For ots_ntohl
15 using namespace rlbox;
16 using namespace mozilla;
18 tainted_woff2<BrotliDecoderResult> RLBoxBrotliDecoderDecompressCallback(
19 rlbox_sandbox_woff2& aSandbox, tainted_woff2<unsigned long> aEncodedSize,
20 tainted_woff2<const char*> aEncodedBuffer,
21 tainted_woff2<unsigned long*> aDecodedSize,
22 tainted_woff2<char*> aDecodedBuffer) {
23 if (!aEncodedBuffer || !aDecodedSize || !aDecodedBuffer) {
24 return BROTLI_DECODER_RESULT_ERROR;
27 // We don't create temporary buffers for brotli to operate on. Instead we
28 // pass a pointer to the in (encoded) and out (decoded) buffers. We check
29 // (specifically, unverified_safe_pointer checks) that the buffers are within
30 // the sandbox boundary (for the given sizes).
32 size_t encodedSize =
33 aEncodedSize.unverified_safe_because("Any size within sandbox is ok.");
34 const uint8_t* encodedBuffer = reinterpret_cast<const uint8_t*>(
35 aEncodedBuffer.unverified_safe_pointer_because(
36 encodedSize, "Pointer fits within sandbox"));
38 size_t decodedSize =
39 (*aDecodedSize).unverified_safe_because("Any size within sandbox is ok.");
40 uint8_t* decodedBuffer =
41 reinterpret_cast<uint8_t*>(aDecodedBuffer.unverified_safe_pointer_because(
42 decodedSize, "Pointer fits within sandbox"));
44 BrotliDecoderResult res = BrotliDecoderDecompress(
45 encodedSize, encodedBuffer, &decodedSize, decodedBuffer);
47 *aDecodedSize = decodedSize;
49 return res;
52 UniquePtr<RLBoxSandboxDataBase> RLBoxWOFF2SandboxPool::CreateSandboxData(
53 uint64_t aSize) {
54 // Create woff2 sandbox
55 auto sandbox = MakeUnique<rlbox_sandbox_woff2>();
57 #if defined(MOZ_WASM_SANDBOXING_WOFF2)
58 const w2c_mem_capacity capacity =
59 get_valid_wasm2c_memory_capacity(aSize, true /* 32-bit wasm memory*/);
60 bool createOK = sandbox->create_sandbox(/* infallible = */ false, &capacity);
61 #else
62 bool createOK = sandbox->create_sandbox();
63 #endif
64 NS_ENSURE_TRUE(createOK, nullptr);
66 UniquePtr<RLBoxWOFF2SandboxData> sbxData =
67 MakeUnique<RLBoxWOFF2SandboxData>(aSize, std::move(sandbox));
69 // Register brotli callback
70 sbxData->mDecompressCallback = sbxData->Sandbox()->register_callback(
71 RLBoxBrotliDecoderDecompressCallback);
72 sbxData->Sandbox()->invoke_sandbox_function(RegisterWOFF2Callback,
73 sbxData->mDecompressCallback);
75 return sbxData;
78 StaticRefPtr<RLBoxWOFF2SandboxPool> RLBoxWOFF2SandboxPool::sSingleton;
80 void RLBoxWOFF2SandboxPool::Initalize(size_t aDelaySeconds) {
81 AssertIsOnMainThread();
82 RLBoxWOFF2SandboxPool::sSingleton = new RLBoxWOFF2SandboxPool(aDelaySeconds);
83 ClearOnShutdown(&RLBoxWOFF2SandboxPool::sSingleton);
86 RLBoxWOFF2SandboxData::RLBoxWOFF2SandboxData(
87 uint64_t aSize, mozilla::UniquePtr<rlbox_sandbox_woff2> aSandbox)
88 : mozilla::RLBoxSandboxDataBase(aSize), mSandbox(std::move(aSandbox)) {
89 MOZ_COUNT_CTOR(RLBoxWOFF2SandboxData);
92 RLBoxWOFF2SandboxData::~RLBoxWOFF2SandboxData() {
93 MOZ_ASSERT(mSandbox);
94 mDecompressCallback.unregister();
95 mSandbox->destroy_sandbox();
96 MOZ_COUNT_DTOR(RLBoxWOFF2SandboxData);
99 static bool Woff2SizeValidator(size_t aLength, size_t aSize, size_t aLimit) {
100 if (aSize < aLength) {
101 NS_WARNING("Size of decompressed WOFF 2.0 is less than compressed size");
102 return false;
103 } else if (aSize == 0) {
104 NS_WARNING("Size of decompressed WOFF 2.0 is set to 0");
105 return false;
106 } else if (aSize > aLimit) {
107 NS_WARNING(
108 nsPrintfCString("Size of decompressed WOFF 2.0 font exceeds %gMB",
109 aLimit / (1024.0 * 1024.0))
110 .get());
111 return false;
113 return true;
116 // Code replicated from modules/woff2/src/woff2_dec.cc
117 // This is used both to compute the expected size of the Woff2 RLBox sandbox
118 // as well as internally by WOFF2 as a performance hint
119 static uint32_t ComputeWOFF2FinalSize(const uint8_t* aData, size_t aLength,
120 size_t aLimit) {
121 // Expected size is stored as a 4 byte value starting from the 17th byte
122 if (aLength < 20) {
123 return 0;
126 uint32_t decompressedSize = 0;
127 const void* location = &(aData[16]);
128 std::memcpy(&decompressedSize, location, sizeof(decompressedSize));
129 decompressedSize = ots_ntohl(decompressedSize);
131 if (!Woff2SizeValidator(aLength, decompressedSize, aLimit)) {
132 return 0;
135 return decompressedSize;
138 template <typename T>
139 using TransferBufferToWOFF2 =
140 mozilla::RLBoxTransferBufferToSandbox<T, rlbox_woff2_sandbox_type>;
141 template <typename T>
142 using WOFF2Alloc = mozilla::RLBoxAllocateInSandbox<T, rlbox_woff2_sandbox_type>;
144 bool RLBoxProcessWOFF2(ots::FontFile* aHeader, ots::OTSStream* aOutput,
145 const uint8_t* aData, size_t aLength, uint32_t aIndex,
146 ProcessTTCFunc* aProcessTTC,
147 ProcessTTFFunc* aProcessTTF) {
148 MOZ_ASSERT(aProcessTTC);
149 MOZ_ASSERT(aProcessTTF);
151 // We index into aData before processing it (very end of this function). Our
152 // validator ensures that the untrusted size is greater than aLength, so we
153 // just need to conservatively ensure that aLength is greater than the highest
154 // index (7).
155 NS_ENSURE_TRUE(aLength >= 8, false);
157 size_t limit =
158 std::min(size_t(OTS_MAX_DECOMPRESSED_FILE_SIZE), aOutput->size());
159 uint32_t expectedSize = ComputeWOFF2FinalSize(aData, aLength, limit);
160 NS_ENSURE_TRUE(expectedSize > 0, false);
162 // The sandbox should have space for the input, output and misc allocations
163 // To account for misc allocations, we'll set the sandbox size to:
164 // twice the size of (input + output)
166 const uint64_t expectedSandboxSize =
167 static_cast<uint64_t>(2 * (aLength + expectedSize));
168 auto sandboxPoolData =
169 RLBoxWOFF2SandboxPool::sSingleton->PopOrCreate(expectedSandboxSize);
170 NS_ENSURE_TRUE(sandboxPoolData, false);
172 const auto* sandboxData =
173 static_cast<const RLBoxWOFF2SandboxData*>(sandboxPoolData->SandboxData());
174 MOZ_ASSERT(sandboxData);
176 auto* sandbox = sandboxData->Sandbox();
178 // Transfer aData into the sandbox.
180 auto data = TransferBufferToWOFF2<char>(
181 sandbox, reinterpret_cast<const char*>(aData), aLength);
182 NS_ENSURE_TRUE(*data, false);
184 // Perform the actual conversion to TTF.
186 auto sizep = WOFF2Alloc<unsigned long>(sandbox);
187 auto bufp = WOFF2Alloc<char*>(sandbox);
188 auto bufOwnerString =
189 WOFF2Alloc<void*>(sandbox); // pointer to string that owns the bufer
191 if (!sandbox
192 ->invoke_sandbox_function(RLBoxConvertWOFF2ToTTF, *data, aLength,
193 expectedSize, sizep.get(),
194 bufOwnerString.get(), bufp.get())
195 .unverified_safe_because(
196 "The ProcessTT* functions validate the decompressed data.")) {
197 return false;
200 auto bufCleanup = mozilla::MakeScopeExit([&sandbox, &bufOwnerString] {
201 // Delete the string created by RLBoxConvertWOFF2ToTTF.
202 sandbox->invoke_sandbox_function(RLBoxDeleteWOFF2String,
203 bufOwnerString.get());
206 // Get the actual decompression size and validate it.
207 // We need to validate the size again. RLBoxConvertWOFF2ToTTF works even if
208 // the computed size (with ComputeWOFF2FinalSize) is wrong, so we can't
209 // trust the expectedSize to be the same as size sizep.
210 bool validateOK = false;
211 unsigned long actualSize =
212 (*sizep.get()).copy_and_verify([&](unsigned long val) {
213 validateOK = Woff2SizeValidator(aLength, val, limit);
214 return val;
217 NS_ENSURE_TRUE(validateOK, false);
219 const uint8_t* decompressed = reinterpret_cast<const uint8_t*>(
220 (*bufp.get())
221 .unverified_safe_pointer_because(
222 actualSize,
223 "Only care that the buffer is within sandbox boundary."));
225 // Since ProcessTT* memcpy from the buffer, make sure it's not null.
226 NS_ENSURE_TRUE(decompressed, false);
228 if (aData[4] == 't' && aData[5] == 't' && aData[6] == 'c' &&
229 aData[7] == 'f') {
230 return aProcessTTC(aHeader, aOutput, decompressed, actualSize, aIndex);
232 ots::Font font(aHeader);
233 return aProcessTTF(aHeader, &font, aOutput, decompressed, actualSize, 0);