summaryrefslogtreecommitdiffstats
path: root/gfx/ots/RLBoxWOFF2Host.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
commit6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch)
treea68f146d7fa01f0134297619fbe7e33db084e0aa /gfx/ots/RLBoxWOFF2Host.cpp
parentInitial commit. (diff)
downloadthunderbird-upstream.tar.xz
thunderbird-upstream.zip
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'gfx/ots/RLBoxWOFF2Host.cpp')
-rw-r--r--gfx/ots/RLBoxWOFF2Host.cpp234
1 files changed, 234 insertions, 0 deletions
diff --git a/gfx/ots/RLBoxWOFF2Host.cpp b/gfx/ots/RLBoxWOFF2Host.cpp
new file mode 100644
index 0000000000..172300f8dd
--- /dev/null
+++ b/gfx/ots/RLBoxWOFF2Host.cpp
@@ -0,0 +1,234 @@
+/* -*- 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(/* 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);
+}