RLBoxWOFF2Host.cpp (9225B)
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/. */ 6 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 14 15 using namespace rlbox; 16 using namespace mozilla; 17 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; 25 } 26 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). 31 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")); 37 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")); 43 44 BrotliDecoderResult res = BrotliDecoderDecompress( 45 encodedSize, encodedBuffer, &decodedSize, decodedBuffer); 46 47 *aDecodedSize = decodedSize; 48 49 return res; 50 } 51 52 UniquePtr<RLBoxSandboxDataBase> RLBoxWOFF2SandboxPool::CreateSandboxData( 53 uint64_t aSize) { 54 // Create woff2 sandbox 55 auto sandbox = MakeUnique<rlbox_sandbox_woff2>(); 56 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(/* shouldAbortOnFailure = */ false, 61 &capacity, "rlbox_wasm2c_woff2"); 62 #else 63 bool createOK = sandbox->create_sandbox(); 64 #endif 65 NS_ENSURE_TRUE(createOK, nullptr); 66 67 UniquePtr<RLBoxWOFF2SandboxData> sbxData = 68 MakeUnique<RLBoxWOFF2SandboxData>(aSize, std::move(sandbox)); 69 70 // Register brotli callback 71 sbxData->mDecompressCallback = sbxData->Sandbox()->register_callback( 72 RLBoxBrotliDecoderDecompressCallback); 73 sbxData->Sandbox()->invoke_sandbox_function(RegisterWOFF2Callback, 74 sbxData->mDecompressCallback); 75 76 return sbxData; 77 } 78 79 StaticRefPtr<RLBoxWOFF2SandboxPool> RLBoxWOFF2SandboxPool::sSingleton; 80 81 void RLBoxWOFF2SandboxPool::Initalize(size_t aDelaySeconds) { 82 AssertIsOnMainThread(); 83 RLBoxWOFF2SandboxPool::sSingleton = new RLBoxWOFF2SandboxPool(aDelaySeconds); 84 ClearOnShutdown(&RLBoxWOFF2SandboxPool::sSingleton); 85 } 86 87 RLBoxWOFF2SandboxData::RLBoxWOFF2SandboxData( 88 uint64_t aSize, mozilla::UniquePtr<rlbox_sandbox_woff2> aSandbox) 89 : mozilla::RLBoxSandboxDataBase(aSize), mSandbox(std::move(aSandbox)) { 90 MOZ_COUNT_CTOR(RLBoxWOFF2SandboxData); 91 } 92 93 RLBoxWOFF2SandboxData::~RLBoxWOFF2SandboxData() { 94 MOZ_ASSERT(mSandbox); 95 mDecompressCallback.unregister(); 96 mSandbox->destroy_sandbox(); 97 MOZ_COUNT_DTOR(RLBoxWOFF2SandboxData); 98 } 99 100 static bool Woff2SizeValidator(size_t aLength, size_t aSize, size_t aLimit) { 101 if (aSize < aLength) { 102 NS_WARNING("Size of decompressed WOFF 2.0 is less than compressed size"); 103 return false; 104 } else if (aSize == 0) { 105 NS_WARNING("Size of decompressed WOFF 2.0 is set to 0"); 106 return false; 107 } else if (aSize > aLimit) { 108 NS_WARNING( 109 nsPrintfCString("Size of decompressed WOFF 2.0 font exceeds %gMB", 110 aLimit / (1024.0 * 1024.0)) 111 .get()); 112 return false; 113 } 114 return true; 115 } 116 117 // Code replicated from modules/woff2/src/woff2_dec.cc 118 // This is used both to compute the expected size of the Woff2 RLBox sandbox 119 // as well as internally by WOFF2 as a performance hint 120 static uint32_t ComputeWOFF2FinalSize(const uint8_t* aData, size_t aLength, 121 size_t aLimit) { 122 // Expected size is stored as a 4 byte value starting from the 17th byte 123 if (aLength < 20) { 124 return 0; 125 } 126 127 uint32_t decompressedSize = 0; 128 const void* location = &(aData[16]); 129 std::memcpy(&decompressedSize, location, sizeof(decompressedSize)); 130 decompressedSize = ots_ntohl(decompressedSize); 131 132 // We bump the decompressedSize slightly because it seems that some fonts 133 // have an incorrectly-set value that results in decompression failure. 134 // (See https://bugzilla.mozilla.org/show_bug.cgi?id=1934606, and original 135 // discussion in https://github.com/harfbuzz/harfbuzz/issues/4962.) 136 decompressedSize = 137 std::max(decompressedSize, decompressedSize + (decompressedSize >> 4)); 138 139 if (!Woff2SizeValidator(aLength, decompressedSize, aLimit)) { 140 return 0; 141 } 142 143 return decompressedSize; 144 } 145 146 template <typename T> 147 using TransferBufferToWOFF2 = 148 mozilla::RLBoxTransferBufferToSandbox<T, rlbox_woff2_sandbox_type>; 149 template <typename T> 150 using WOFF2Alloc = mozilla::RLBoxAllocateInSandbox<T, rlbox_woff2_sandbox_type>; 151 152 bool RLBoxProcessWOFF2(ots::FontFile* aHeader, ots::OTSStream* aOutput, 153 const uint8_t* aData, size_t aLength, uint32_t aIndex, 154 ProcessTTCFunc* aProcessTTC, 155 ProcessTTFFunc* aProcessTTF) { 156 MOZ_ASSERT(aProcessTTC); 157 MOZ_ASSERT(aProcessTTF); 158 159 // We index into aData before processing it (very end of this function). Our 160 // validator ensures that the untrusted size is greater than aLength, so we 161 // just need to conservatively ensure that aLength is greater than the highest 162 // index (7). 163 NS_ENSURE_TRUE(aLength >= 8, false); 164 165 size_t limit = 166 std::min(size_t(OTS_MAX_DECOMPRESSED_FILE_SIZE), aOutput->size()); 167 uint32_t expectedSize = ComputeWOFF2FinalSize(aData, aLength, limit); 168 NS_ENSURE_TRUE(expectedSize > 0, false); 169 170 // The sandbox should have space for the input, output and misc allocations 171 // To account for misc allocations, we'll set the sandbox size to: 172 // twice the size of (input + output) 173 174 const uint64_t expectedSandboxSize = 175 static_cast<uint64_t>(2 * (aLength + expectedSize)); 176 auto sandboxPoolData = 177 RLBoxWOFF2SandboxPool::sSingleton->PopOrCreate(expectedSandboxSize); 178 NS_ENSURE_TRUE(sandboxPoolData, false); 179 180 const auto* sandboxData = 181 static_cast<const RLBoxWOFF2SandboxData*>(sandboxPoolData->SandboxData()); 182 MOZ_ASSERT(sandboxData); 183 184 auto* sandbox = sandboxData->Sandbox(); 185 186 // Transfer aData into the sandbox. 187 188 auto data = TransferBufferToWOFF2<char>( 189 sandbox, reinterpret_cast<const char*>(aData), aLength); 190 NS_ENSURE_TRUE(*data, false); 191 192 // Perform the actual conversion to TTF. 193 194 auto sizep = WOFF2Alloc<unsigned long>(sandbox); 195 auto bufp = WOFF2Alloc<char*>(sandbox); 196 auto bufOwnerString = 197 WOFF2Alloc<void*>(sandbox); // pointer to string that owns the bufer 198 199 if (!sandbox 200 ->invoke_sandbox_function(RLBoxConvertWOFF2ToTTF, *data, aLength, 201 expectedSize, sizep.get(), 202 bufOwnerString.get(), bufp.get()) 203 .unverified_safe_because( 204 "The ProcessTT* functions validate the decompressed data.")) { 205 return false; 206 } 207 208 auto bufCleanup = mozilla::MakeScopeExit([&sandbox, &bufOwnerString] { 209 // Delete the string created by RLBoxConvertWOFF2ToTTF. 210 sandbox->invoke_sandbox_function(RLBoxDeleteWOFF2String, 211 bufOwnerString.get()); 212 }); 213 214 // Get the actual decompression size and validate it. 215 // We need to validate the size again. RLBoxConvertWOFF2ToTTF works even if 216 // the computed size (with ComputeWOFF2FinalSize) is wrong, so we can't 217 // trust the expectedSize to be the same as size sizep. 218 bool validateOK = false; 219 unsigned long actualSize = 220 (*sizep.get()).copy_and_verify([&](unsigned long val) { 221 validateOK = Woff2SizeValidator(aLength, val, limit); 222 return val; 223 }); 224 225 NS_ENSURE_TRUE(validateOK, false); 226 227 const uint8_t* decompressed = reinterpret_cast<const uint8_t*>( 228 (*bufp.get()) 229 .unverified_safe_pointer_because( 230 actualSize, 231 "Only care that the buffer is within sandbox boundary.")); 232 233 // Since ProcessTT* memcpy from the buffer, make sure it's not null. 234 NS_ENSURE_TRUE(decompressed, false); 235 236 if (aData[4] == 't' && aData[5] == 't' && aData[6] == 'c' && 237 aData[7] == 'f') { 238 return aProcessTTC(aHeader, aOutput, decompressed, actualSize, aIndex); 239 } 240 ots::Font font(aHeader); 241 return aProcessTTF(aHeader, &font, aOutput, decompressed, actualSize, 0); 242 }