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).
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"));
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
;
52 UniquePtr
<RLBoxSandboxDataBase
> RLBoxWOFF2SandboxPool::CreateSandboxData(
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
);
62 bool createOK
= sandbox
->create_sandbox();
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
);
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() {
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");
103 } else if (aSize
== 0) {
104 NS_WARNING("Size of decompressed WOFF 2.0 is set to 0");
106 } else if (aSize
> aLimit
) {
108 nsPrintfCString("Size of decompressed WOFF 2.0 font exceeds %gMB",
109 aLimit
/ (1024.0 * 1024.0))
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
,
121 // Expected size is stored as a 4 byte value starting from the 17th byte
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
)) {
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
155 NS_ENSURE_TRUE(aLength
>= 8, false);
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
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.")) {
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
);
217 NS_ENSURE_TRUE(validateOK
, false);
219 const uint8_t* decompressed
= reinterpret_cast<const uint8_t*>(
221 .unverified_safe_pointer_because(
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' &&
230 return aProcessTTC(aHeader
, aOutput
, decompressed
, actualSize
, aIndex
);
232 ots::Font
font(aHeader
);
233 return aProcessTTF(aHeader
, &font
, aOutput
, decompressed
, actualSize
, 0);