From 0a5e1f20753585c9c603c31213c27a225fe4b7fd Mon Sep 17 00:00:00 2001
From: Deian Stefan <deian@cs.ucsd.edu>
Date: Sat, 27 Nov 2021 04:41:21 +0000
Subject: [PATCH] Bug 1732201 - Sandbox woff2 in OTS using RLBox r=bholley

Differential Revision: https://phabricator.services.mozilla.com/D126435
---
 .clang-format-ignore                |   7 +-
 config/recurse.mk                   |   2 +-
 gfx/ots/README.mozilla              |   1 +
 gfx/ots/RLBoxWOFF2Host.cpp          | 200 ++++++++++++++++++++++++++++
 gfx/ots/RLBoxWOFF2Host.h            |  58 ++++++++
 gfx/ots/RLBoxWOFF2Types.h           |  37 +++++
 gfx/ots/ots-rlbox.patch             |  56 ++++++++
 gfx/ots/src/moz.build               |   6 +
 gfx/ots/src/ots.cc                  |  38 +-----
 gfx/ots/sync.sh                     |   3 +
 layout/build/moz.build              |   1 +
 layout/build/nsLayoutStatics.cpp    |   3 +
 modules/woff2/README.mozilla        |   2 +
 modules/woff2/RLBoxWOFF2Sandbox.cpp |  66 +++++++++
 modules/woff2/RLBoxWOFF2Sandbox.h   |  42 ++++++
 modules/woff2/moz.build             |  14 +-
 modules/woff2/sources.mozbuild      |  14 ++
 modules/woff2/src/woff2_dec.cc      |   5 +-
 modules/woff2/update.sh             |   3 +
 modules/woff2/woff2-rlbox.patch     |  29 ++++
 parser/htmlparser/nsExpatDriver.cpp |  31 +----
 security/rlbox/moz.build            |   5 +
 xpcom/base/RLBoxUtils.h             |  70 ++++++++++
 xpcom/base/moz.build                |   1 +
 24 files changed, 622 insertions(+), 72 deletions(-)
 create mode 100644 gfx/ots/RLBoxWOFF2Host.cpp
 create mode 100644 gfx/ots/RLBoxWOFF2Host.h
 create mode 100644 gfx/ots/RLBoxWOFF2Types.h
 create mode 100644 gfx/ots/ots-rlbox.patch
 create mode 100644 modules/woff2/RLBoxWOFF2Sandbox.cpp
 create mode 100644 modules/woff2/RLBoxWOFF2Sandbox.h
 create mode 100644 modules/woff2/sources.mozbuild
 create mode 100644 modules/woff2/woff2-rlbox.patch
 create mode 100644 xpcom/base/RLBoxUtils.h

diff --git a/.clang-format-ignore b/.clang-format-ignore
index 881d824e7d24e..89f4a0d658337 100644
--- a/.clang-format-ignore
+++ b/.clang-format-ignore
@@ -96,7 +96,9 @@ gfx/angle/.*
 gfx/cairo/.*
 gfx/graphite2/.*
 gfx/harfbuzz/.*
-gfx/ots/.*
+gfx/ots/src/.*
+gfx/ots/include/.*
+gfx/ots/tests/.*
 gfx/qcms/.*
 gfx/sfntly/.*
 gfx/skia/.*
@@ -167,7 +169,8 @@ modules/fdlibm/.*
 modules/freetype2/.*
 modules/libbz2/.*
 modules/pdfium/.*
-modules/woff2/.*
+modules/woff2/include/.*
+modules/woff2/src/.*
 modules/xz-embedded/.*
 modules/zlib/.*
 mozglue/misc/decimal/.*
diff --git a/config/recurse.mk b/config/recurse.mk
index 4fd4bd57d67ff..5e109e5172f9e 100644
--- a/config/recurse.mk
+++ b/config/recurse.mk
@@ -217,7 +217,7 @@ endif
 ifdef MOZ_USING_WASM_SANDBOXING
 security/rlbox/target-objects: config/external/wasm2c_sandbox_compiler/host
 security/rlbox/target: security/rlbox/target-objects
-dom/media/ogg/target-objects extensions/spellcheck/hunspell/glue/target-objects gfx/thebes/target-objects parser/expat/target-objects parser/htmlparser/target-objects: security/rlbox/target-objects
+dom/media/ogg/target-objects extensions/spellcheck/hunspell/glue/target-objects gfx/thebes/target-objects parser/expat/target-objects parser/htmlparser/target-objects gfx/ots/src/target-objects: security/rlbox/target-objects
 endif
 
 # Most things are built during compile (target/host), but some things happen during export
diff --git a/gfx/ots/README.mozilla b/gfx/ots/README.mozilla
index a5320be469629..15243ab4d3f62 100644
--- a/gfx/ots/README.mozilla
+++ b/gfx/ots/README.mozilla
@@ -10,3 +10,4 @@ Additional files: README.mozilla, src/moz.build
 
 Additional patch: ots-visibility.patch (bug 711079).
 Additional patch: ots-lz4.patch
+Additional patch: ots-rlbox.patch (bug 1732201).
diff --git a/gfx/ots/RLBoxWOFF2Host.cpp b/gfx/ots/RLBoxWOFF2Host.cpp
new file mode 100644
index 0000000000000..8b95feae096c5
--- /dev/null
+++ b/gfx/ots/RLBoxWOFF2Host.cpp
@@ -0,0 +1,200 @@
+/* -*- 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"
+
+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() {
+  // Create woff2 sandbox
+  auto sandbox = MakeUnique<rlbox_sandbox_woff2>();
+
+#ifdef MOZ_WASM_SANDBOXING_WOFF2
+  bool createOK = sandbox->create_sandbox(/* infallible = */ false);
+#else
+  bool createOK = sandbox->create_sandbox();
+#endif
+  NS_ENSURE_TRUE(createOK, nullptr);
+
+  UniquePtr<RLBoxWOFF2SandboxData> sbxData =
+      MakeUnique<RLBoxWOFF2SandboxData>(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(
+    mozilla::UniquePtr<rlbox_sandbox_woff2> aSandbox)
+    : mSandbox(std::move(aSandbox)) {
+  MOZ_COUNT_CTOR(RLBoxWOFF2SandboxData);
+}
+
+RLBoxWOFF2SandboxData::~RLBoxWOFF2SandboxData() {
+  MOZ_ASSERT(mSandbox);
+  mDecompressCallback.unregister();
+  mSandbox->destroy_sandbox();
+  MOZ_COUNT_DTOR(RLBoxWOFF2SandboxData);
+}
+
+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);
+
+  auto sandboxPoolData = RLBoxWOFF2SandboxPool::sSingleton->PopOrCreate();
+  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<uint8_t>(sandbox, aData, aLength);
+  NS_ENSURE_TRUE(*data, false);
+
+  // Validator for the decompression size.
+  // Returns the size and sets validateOK to true if size is valid (and false
+  // otherwise).
+  bool validateOK = false;
+  auto sizeValidator = [aLength, &validateOK](auto size) {
+    validateOK = false;
+    if (size < aLength) {
+      NS_ERROR("Size of decompressed WOFF 2.0 is less than compressed size");
+    } else if (size == 0) {
+      NS_ERROR("Size of decompressed WOFF 2.0 is set to 0");
+    } else if (size > OTS_MAX_DECOMPRESSED_FILE_SIZE) {
+      NS_ERROR(
+          nsPrintfCString("Size of decompressed WOFF 2.0 font exceeds %gMB",
+                          OTS_MAX_DECOMPRESSED_FILE_SIZE / (1024.0 * 1024.0))
+              .get());
+    } else {
+      validateOK = true;
+    }
+    return size;
+  };
+
+  // Get the (estimated) decompression size and validate it.
+
+  size_t decompressedSize =
+      sandbox
+          ->invoke_sandbox_function(RLBoxComputeWOFF2FinalSize, *data, aLength)
+          .copy_and_verify(sizeValidator);
+  NS_ENSURE_TRUE(validateOK, false);
+
+  // Perform the actual conversion to TTF.
+
+  auto sizep = WOFF2Alloc<size_t>(sandbox);
+  auto bufp = WOFF2Alloc<uint8_t*>(sandbox);
+  auto bufOwnerString =
+      WOFF2Alloc<void*>(sandbox);  // pointer to string that owns the bufer
+
+  if (!sandbox
+           ->invoke_sandbox_function(RLBoxConvertWOFF2ToTTF, *data, aLength,
+                                     decompressedSize, 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 RLBoxComputeWOFF2FinalSize) is wrong, so we can't
+  // trust the decompressedSize to be the same as size sizep.
+  size_t size = (*sizep.get()).copy_and_verify(sizeValidator);
+  NS_ENSURE_TRUE(validateOK, false);
+
+  const uint8_t* decompressed =
+      (*bufp.get())
+          .unverified_safe_pointer_because(
+              size, "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, size, aIndex);
+  }
+  ots::Font font(aHeader);
+  return aProcessTTF(aHeader, &font, aOutput, decompressed, size, 0);
+}
diff --git a/gfx/ots/RLBoxWOFF2Host.h b/gfx/ots/RLBoxWOFF2Host.h
new file mode 100644
index 0000000000000..94a6f3c660b8b
--- /dev/null
+++ b/gfx/ots/RLBoxWOFF2Host.h
@@ -0,0 +1,58 @@
+/* -*- Mode: C++; tab-width: 20; 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/. */
+
+#ifndef MODULES_WOFF2_RLBOXWOFF2_HOST_H_
+#define MODULES_WOFF2_RLBOXWOFF2_HOST_H_
+
+#include "RLBoxWOFF2Types.h"
+
+// Load general firefox configuration of RLBox
+#include "mozilla/rlbox/rlbox_config.h"
+
+#ifdef MOZ_WASM_SANDBOXING_WOFF2
+// Include the generated header file so that we are able to resolve the symbols
+// in the wasm binary
+#  include "rlbox.wasm.h"
+#  define RLBOX_USE_STATIC_CALLS() rlbox_wasm2c_sandbox_lookup_symbol
+#  include "mozilla/rlbox/rlbox_wasm2c_sandbox.hpp"
+#else
+// Extra configuration for no-op sandbox
+#  define RLBOX_USE_STATIC_CALLS() rlbox_noop_sandbox_lookup_symbol
+#  include "mozilla/rlbox/rlbox_noop_sandbox.hpp"
+#endif
+
+#include "mozilla/rlbox/rlbox.hpp"
+
+#include "woff2/RLBoxWOFF2Sandbox.h"
+#include "./src/ots.h"
+
+class RLBoxWOFF2SandboxData : public mozilla::RLBoxSandboxDataBase {
+  friend class RLBoxWOFF2SandboxPool;
+
+ public:
+  RLBoxWOFF2SandboxData(mozilla::UniquePtr<rlbox_sandbox_woff2> aSandbox);
+  ~RLBoxWOFF2SandboxData();
+
+  rlbox_sandbox_woff2* Sandbox() const { return mSandbox.get(); }
+
+ private:
+  mozilla::UniquePtr<rlbox_sandbox_woff2> mSandbox;
+  sandbox_callback_woff2<BrotliDecompressCallback*> mDecompressCallback;
+};
+
+using ProcessTTCFunc = bool(ots::FontFile* aHeader, ots::OTSStream* aOutput,
+                            const uint8_t* aData, size_t aLength,
+                            uint32_t aIndex);
+
+using ProcessTTFFunc = bool(ots::FontFile* aHeader, ots::Font* aFont,
+                            ots::OTSStream* aOutput, const uint8_t* aData,
+                            size_t aLength, uint32_t aOffset);
+
+bool RLBoxProcessWOFF2(ots::FontFile* aHeader, ots::OTSStream* aOutput,
+                       const uint8_t* aData, size_t aLength, uint32_t aIndex,
+                       ProcessTTCFunc* aProcessTTC,
+                       ProcessTTFFunc* aProcessTTF);
+#endif
diff --git a/gfx/ots/RLBoxWOFF2Types.h b/gfx/ots/RLBoxWOFF2Types.h
new file mode 100644
index 0000000000000..6b5f81673e206
--- /dev/null
+++ b/gfx/ots/RLBoxWOFF2Types.h
@@ -0,0 +1,37 @@
+/* -*- Mode: C++; tab-width: 20; 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/. */
+
+#ifndef MODULES_WOFF2_RLBOXWOFF2TYPES_H_
+#define MODULES_WOFF2_RLBOXWOFF2TYPES_H_
+
+#include <stddef.h>
+#include "mozilla/rlbox/rlbox_types.hpp"
+#include "woff2/decode.h"
+
+#ifdef MOZ_WASM_SANDBOXING_WOFF2
+RLBOX_DEFINE_BASE_TYPES_FOR(woff2, wasm2c)
+#else
+RLBOX_DEFINE_BASE_TYPES_FOR(woff2, noop)
+#endif
+
+#include "mozilla/RLBoxSandboxPool.h"
+#include "mozilla/StaticPtr.h"
+
+class RLBoxWOFF2SandboxPool : public mozilla::RLBoxSandboxPool {
+ public:
+  explicit RLBoxWOFF2SandboxPool(size_t aDelaySeconds)
+      : RLBoxSandboxPool(aDelaySeconds) {}
+
+  static mozilla::StaticRefPtr<RLBoxWOFF2SandboxPool> sSingleton;
+  static void Initalize(size_t aDelaySeconds = 10);
+
+ protected:
+  mozilla::UniquePtr<mozilla::RLBoxSandboxDataBase> CreateSandboxData()
+      override;
+  ~RLBoxWOFF2SandboxPool() = default;
+};
+
+#endif
diff --git a/gfx/ots/ots-rlbox.patch b/gfx/ots/ots-rlbox.patch
new file mode 100644
index 0000000000000..8989cdb66746d
--- /dev/null
+++ b/gfx/ots/ots-rlbox.patch
@@ -0,0 +1,56 @@
+diff --git a/gfx/ots/src/ots.cc b/gfx/ots/src/ots.cc
+--- a/gfx/ots/src/ots.cc
++++ b/gfx/ots/src/ots.cc
+@@ -14,7 +14,7 @@
+ #include <map>
+ #include <vector>
+ 
+-#include <woff2/decode.h>
++#include "../RLBoxWOFF2Host.h"
+ 
+ // The OpenType Font File
+ // http://www.microsoft.com/typography/otspec/otff.htm
+@@ -511,39 +511,9 @@ bool ProcessWOFF(ots::FontFile *header,
+   return ProcessGeneric(header, font, woff_tag, output, data, length, tables, file);
+ }
+ 
+-bool ProcessWOFF2(ots::FontFile *header,
+-                  ots::OTSStream *output,
+-                  const uint8_t *data,
+-                  size_t length,
+-                  uint32_t index) {
+-  size_t decompressed_size = woff2::ComputeWOFF2FinalSize(data, length);
+-
+-  if (decompressed_size < length) {
+-    return OTS_FAILURE_MSG_HDR("Size of decompressed WOFF 2.0 is less than compressed size");
+-  }
+-
+-  if (decompressed_size == 0) {
+-    return OTS_FAILURE_MSG_HDR("Size of decompressed WOFF 2.0 is set to 0");
+-  }
+-  // decompressed font must be <= OTS_MAX_DECOMPRESSED_FILE_SIZE
+-  if (decompressed_size > OTS_MAX_DECOMPRESSED_FILE_SIZE) {
+-    return OTS_FAILURE_MSG_HDR("Size of decompressed WOFF 2.0 font exceeds %gMB",
+-                               OTS_MAX_DECOMPRESSED_FILE_SIZE / (1024.0 * 1024.0));
+-  }
+-
+-  std::string buf(decompressed_size, 0);
+-  woff2::WOFF2StringOut out(&buf);
+-  if (!woff2::ConvertWOFF2ToTTF(data, length, &out)) {
+-    return OTS_FAILURE_MSG_HDR("Failed to convert WOFF 2.0 font to SFNT");
+-  }
+-  const uint8_t *decompressed = reinterpret_cast<const uint8_t*>(buf.data());
+-
+-  if (data[4] == 't' && data[5] == 't' && data[6] == 'c' && data[7] == 'f') {
+-    return ProcessTTC(header, output, decompressed, out.Size(), index);
+-  } else {
+-    ots::Font font(header);
+-    return ProcessTTF(header, &font, output, decompressed, out.Size());
+-  }
++bool ProcessWOFF2(ots::FontFile* header, ots::OTSStream* output,
++                  const uint8_t* data, size_t length, uint32_t index) {
++  return RLBoxProcessWOFF2(header, output, data, length, index, ProcessTTC, ProcessTTF);
+ }
+ 
+ ots::TableAction GetTableAction(const ots::FontFile *header, uint32_t tag) {
+
diff --git a/gfx/ots/src/moz.build b/gfx/ots/src/moz.build
index 7ea8d1cdc89bb..874032828463d 100644
--- a/gfx/ots/src/moz.build
+++ b/gfx/ots/src/moz.build
@@ -7,6 +7,11 @@
 EXPORTS += [
     '../include/opentype-sanitiser.h',
     '../include/ots-memory-stream.h',
+    '../RLBoxWOFF2Types.h',
+]
+
+UNIFIED_SOURCES += [
+    '../RLBoxWOFF2Host.cpp'
 ]
 
 UNIFIED_SOURCES += [
@@ -71,5 +76,6 @@ USE_LIBS += [
 ]
 
 LOCAL_INCLUDES += [
+    '!/security/rlbox',
     '/modules/woff2/src',
 ]
diff --git a/gfx/ots/src/ots.cc b/gfx/ots/src/ots.cc
index b41566fa22bf6..bbebeb6ac5d01 100644
--- a/gfx/ots/src/ots.cc
+++ b/gfx/ots/src/ots.cc
@@ -14,7 +14,7 @@
 #include <map>
 #include <vector>
 
-#include <woff2/decode.h>
+#include "../RLBoxWOFF2Host.h"
 
 // The OpenType Font File
 // http://www.microsoft.com/typography/otspec/otff.htm
@@ -511,39 +511,9 @@ bool ProcessWOFF(ots::FontFile *header,
   return ProcessGeneric(header, font, woff_tag, output, data, length, tables, file);
 }
 
-bool ProcessWOFF2(ots::FontFile *header,
-                  ots::OTSStream *output,
-                  const uint8_t *data,
-                  size_t length,
-                  uint32_t index) {
-  size_t decompressed_size = woff2::ComputeWOFF2FinalSize(data, length);
-
-  if (decompressed_size < length) {
-    return OTS_FAILURE_MSG_HDR("Size of decompressed WOFF 2.0 is less than compressed size");
-  }
-
-  if (decompressed_size == 0) {
-    return OTS_FAILURE_MSG_HDR("Size of decompressed WOFF 2.0 is set to 0");
-  }
-  // decompressed font must be <= OTS_MAX_DECOMPRESSED_FILE_SIZE
-  if (decompressed_size > OTS_MAX_DECOMPRESSED_FILE_SIZE) {
-    return OTS_FAILURE_MSG_HDR("Size of decompressed WOFF 2.0 font exceeds %gMB",
-                               OTS_MAX_DECOMPRESSED_FILE_SIZE / (1024.0 * 1024.0));
-  }
-
-  std::string buf(decompressed_size, 0);
-  woff2::WOFF2StringOut out(&buf);
-  if (!woff2::ConvertWOFF2ToTTF(data, length, &out)) {
-    return OTS_FAILURE_MSG_HDR("Failed to convert WOFF 2.0 font to SFNT");
-  }
-  const uint8_t *decompressed = reinterpret_cast<const uint8_t*>(buf.data());
-
-  if (data[4] == 't' && data[5] == 't' && data[6] == 'c' && data[7] == 'f') {
-    return ProcessTTC(header, output, decompressed, out.Size(), index);
-  } else {
-    ots::Font font(header);
-    return ProcessTTF(header, &font, output, decompressed, out.Size());
-  }
+bool ProcessWOFF2(ots::FontFile* header, ots::OTSStream* output,
+                  const uint8_t* data, size_t length, uint32_t index) {
+  return RLBoxProcessWOFF2(header, output, data, length, index, ProcessTTC, ProcessTTF);
 }
 
 ots::TableAction GetTableAction(const ots::FontFile *header, uint32_t tag) {
diff --git a/gfx/ots/sync.sh b/gfx/ots/sync.sh
index 81738f9e1d787..162407af9accb 100755
--- a/gfx/ots/sync.sh
+++ b/gfx/ots/sync.sh
@@ -36,3 +36,6 @@ patch -p3 < ots-visibility.patch
 
 echo "Applying ots-lz4.patch..."
 patch -p3 < ots-lz4.patch
+
+echo "Applying ots-rlbox.patch..."
+patch -p3 < ots-rlbox.patch
diff --git a/layout/build/moz.build b/layout/build/moz.build
index d8893cc04501c..4ddc8703e2e63 100644
--- a/layout/build/moz.build
+++ b/layout/build/moz.build
@@ -19,6 +19,7 @@ UNIFIED_SOURCES += [
 include("/ipc/chromium/chromium-config.mozbuild")
 
 LOCAL_INCLUDES += [
+    "!/security/rlbox",
     "../base",
     "../forms",
     "../generic",
diff --git a/layout/build/nsLayoutStatics.cpp b/layout/build/nsLayoutStatics.cpp
index 36cd3e1eaf239..7d20e43b3a6e1 100644
--- a/layout/build/nsLayoutStatics.cpp
+++ b/layout/build/nsLayoutStatics.cpp
@@ -129,6 +129,7 @@
 #include "mozilla/intl/nsComplexBreaker.h"
 
 #include "nsRLBoxExpatDriver.h"
+#include "RLBoxWOFF2Types.h"
 
 using namespace mozilla;
 using namespace mozilla::net;
@@ -298,6 +299,8 @@ nsresult nsLayoutStatics::Initialize() {
 
   RLBoxExpatSandboxPool::Initialize();
 
+  RLBoxWOFF2SandboxPool::Initalize();
+
   return NS_OK;
 }
 
diff --git a/modules/woff2/README.mozilla b/modules/woff2/README.mozilla
index 7ff15836d3496..bd35dc45209ec 100644
--- a/modules/woff2/README.mozilla
+++ b/modules/woff2/README.mozilla
@@ -12,3 +12,5 @@ The in-tree copy is updated by running
 from within the modules/woff2 directory.
 
 Current version: [commit 1bccf208bca986e53a647dfe4811322adb06ecf8].
+
+Additional patch: woff2-rlbox.patch (bug 1732201).
diff --git a/modules/woff2/RLBoxWOFF2Sandbox.cpp b/modules/woff2/RLBoxWOFF2Sandbox.cpp
new file mode 100644
index 0000000000000..77d3ed16ab079
--- /dev/null
+++ b/modules/woff2/RLBoxWOFF2Sandbox.cpp
@@ -0,0 +1,66 @@
+/* -*- 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 <woff2/decode.h>
+#include <cassert>
+#include "RLBoxWOFF2Sandbox.h"
+
+bool RLBoxConvertWOFF2ToTTF(const uint8_t* aData, size_t aLength,
+                            size_t aDecompressedSize, size_t* aResultSize,
+                            void** aResultOwningStr, uint8_t** aResultData) {
+  std::unique_ptr<std::string> buf =
+      std::make_unique<std::string>(aDecompressedSize, 0);
+  woff2::WOFF2StringOut out(buf.get());
+  if (!woff2::ConvertWOFF2ToTTF(aData, aLength, &out)) {
+    return false;
+  }
+  *aResultSize = out.Size();
+  // Return the string and its underlying C string. We need both to make sure we
+  // can free the string (which we do with RLBoxDeleteWOFF2String).
+  *aResultData = reinterpret_cast<uint8_t*>(buf->data());
+  *aResultOwningStr = static_cast<void*>(buf.release());
+  return true;
+}
+
+void RLBoxDeleteWOFF2String(void** aStr) {
+  std::string* buf = static_cast<std::string*>(*aStr);
+  delete buf;
+}
+
+size_t RLBoxComputeWOFF2FinalSize(const uint8_t* aData, size_t aLength) {
+  return woff2::ComputeWOFF2FinalSize(aData, aLength);
+}
+
+BrotliDecompressCallback* sRLBoxBrotliDecompressCallback = nullptr;
+
+void RegisterWOFF2Callback(BrotliDecompressCallback* aCallback) {
+#ifdef MOZ_IN_WASM_SANDBOX
+  // When Woff2 is wasmboxed, we need to register a callback for brotli
+  // decompression. The easiest way to store this is in a static variable. This
+  // is thread-safe because each (potentially-concurrent) woff2 instance gets
+  // its own sandbox with its own copy of the statics.
+  //
+  // When the sandbox is disabled (replaced with the noop sandbox), setting the
+  // callback is actually racey. However, we don't actually need a callback in
+  // that case, and can just invoke brotli directly.
+  sRLBoxBrotliDecompressCallback = aCallback;
+#endif
+}
+
+BrotliDecoderResult RLBoxBrotliDecoderDecompress(size_t aEncodedSize,
+                                                 const uint8_t* aEncodedBuffer,
+                                                 size_t* aDecodedSize,
+                                                 uint8_t* aDecodedBuffer) {
+#ifdef MOZ_IN_WASM_SANDBOX
+  assert(sRLBoxBrotliDecompressCallback);
+  return sRLBoxBrotliDecompressCallback(
+      aEncodedSize, reinterpret_cast<const char*>(aEncodedBuffer), aDecodedSize,
+      reinterpret_cast<char*>(aDecodedBuffer));
+#else
+  return BrotliDecoderDecompress(aEncodedSize, aEncodedBuffer, aDecodedSize,
+                                 aDecodedBuffer);
+#endif
+}
diff --git a/modules/woff2/RLBoxWOFF2Sandbox.h b/modules/woff2/RLBoxWOFF2Sandbox.h
new file mode 100644
index 0000000000000..cb6ebbc1ccde8
--- /dev/null
+++ b/modules/woff2/RLBoxWOFF2Sandbox.h
@@ -0,0 +1,42 @@
+/* -*- Mode: C++; tab-width: 20; 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/. */
+
+#ifndef MODULES_WOFF2_RLBOX_WOFF2_SANDBOX_H_
+#define MODULES_WOFF2_RLBOX_WOFF2_SANDBOX_H_
+
+#include <brotli/decode.h>
+
+extern "C" {
+
+// Since RLBox doesn't support C++ APIs, we expose C wrappers for the WOFF2.
+size_t RLBoxComputeWOFF2FinalSize(const uint8_t* aData, size_t aLength);
+bool RLBoxConvertWOFF2ToTTF(const uint8_t* aData, size_t aLength,
+                            size_t aDecompressedSize, size_t* aResultSize,
+                            void** aResultOwningStr, uint8_t** aResultData);
+// RLBoxDeleteWOFF2String is used to delete the C++ string allocated by
+// RLBoxConvertWOFF2ToTTF.
+void RLBoxDeleteWOFF2String(void** aStr);
+
+// Type of brotli decoder function. Because RLBox doesn't (yet) cleanly support
+// {size,uint8}_t types for callbacks, we're using unsigned long instead of
+// size_t and char instead of uint8_t.
+
+typedef BrotliDecoderResult(BrotliDecompressCallback)(
+    unsigned long aEncodedSize, const char* aEncodedBuffer,
+    unsigned long* aDecodedSize, char* aDecodedBuffer);
+
+// Callback to the unsandboxed Brotli.
+
+extern BrotliDecompressCallback* sRLBoxBrotliDecompressCallback;
+
+void RegisterWOFF2Callback(BrotliDecompressCallback* aCallback);
+BrotliDecoderResult RLBoxBrotliDecoderDecompress(size_t aEncodedSize,
+                                                 const uint8_t* aEncodedBuffer,
+                                                 size_t* aDecodedSize,
+                                                 uint8_t* aDecodedBuffer);
+};
+
+#endif
diff --git a/modules/woff2/moz.build b/modules/woff2/moz.build
index 2076ec7fd0486..fc60f993408f5 100644
--- a/modules/woff2/moz.build
+++ b/modules/woff2/moz.build
@@ -4,24 +4,24 @@
 # 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("sources.mozbuild")
+
 with Files('**'):
     BUG_COMPONENT = ('Core', 'Graphics: Text')
 
-UNIFIED_SOURCES += [
-    'src/table_tags.cc',
-    'src/variable_length.cc',
-    'src/woff2_common.cc',
-    'src/woff2_dec.cc',
-    'src/woff2_out.cc',
-]
+UNIFIED_SOURCES += woff2_sources
 
 EXPORTS.woff2 += [
     'include/woff2/decode.h',
     'include/woff2/encode.h',
     'include/woff2/output.h',
+    'RLBoxWOFF2Sandbox.h',
+
 ]
 
 # We allow warnings for third-party code that can be updated from upstream.
 AllowCompilerWarnings()
 
 Library('woff2')
+
+REQUIRES_UNIFIED_BUILD = True
diff --git a/modules/woff2/sources.mozbuild b/modules/woff2/sources.mozbuild
new file mode 100644
index 0000000000000..74ec9c9952dd9
--- /dev/null
+++ b/modules/woff2/sources.mozbuild
@@ -0,0 +1,14 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+woff2_sources = [
+    'RLBoxWOFF2Sandbox.cpp',
+    'src/table_tags.cc',
+    'src/variable_length.cc',
+    'src/woff2_common.cc',
+    'src/woff2_dec.cc',
+    'src/woff2_out.cc',
+]
diff --git a/modules/woff2/src/woff2_dec.cc b/modules/woff2/src/woff2_dec.cc
index 8186c8e5d9e5d..50806d7d19cc1 100644
--- a/modules/woff2/src/woff2_dec.cc
+++ b/modules/woff2/src/woff2_dec.cc
@@ -19,7 +19,6 @@
 #include <memory>
 #include <utility>
 
-#include <brotli/decode.h>
 #include "./buffer.h"
 #include "./port.h"
 #include "./round.h"
@@ -28,6 +27,8 @@
 #include "./variable_length.h"
 #include "./woff2_common.h"
 
+#include "../RLBoxWOFF2Sandbox.h"
+
 namespace woff2 {
 
 namespace {
@@ -758,7 +759,7 @@ bool ReconstructTransformedHmtx(const uint8_t* transformed_buf,
 bool Woff2Uncompress(uint8_t* dst_buf, size_t dst_size,
   const uint8_t* src_buf, size_t src_size) {
   size_t uncompressed_size = dst_size;
-  BrotliDecoderResult result = BrotliDecoderDecompress(
+  BrotliDecoderResult result = RLBoxBrotliDecoderDecompress(
       src_size, src_buf, &uncompressed_size, dst_buf);
   if (PREDICT_FALSE(result != BROTLI_DECODER_RESULT_SUCCESS ||
                     uncompressed_size != dst_size)) {
diff --git a/modules/woff2/update.sh b/modules/woff2/update.sh
index 9a9b7c883576d..1f3c34cd8e3ba 100755
--- a/modules/woff2/update.sh
+++ b/modules/woff2/update.sh
@@ -22,3 +22,6 @@ echo "###"
 echo "### Updated woff2 to $COMMIT."
 echo "### Remember to verify and commit the changes to source control!"
 echo "###"
+
+echo "Applying woff2-rlbox.patch..."
+patch -p3 < woff2-rlbox.patch
diff --git a/modules/woff2/woff2-rlbox.patch b/modules/woff2/woff2-rlbox.patch
new file mode 100644
index 0000000000000..28fd86734527e
--- /dev/null
+++ b/modules/woff2/woff2-rlbox.patch
@@ -0,0 +1,29 @@
+diff --git a/modules/woff2/src/woff2_dec.cc b/modules/woff2/src/woff2_dec.cc
+--- a/modules/woff2/src/woff2_dec.cc
++++ b/modules/woff2/src/woff2_dec.cc
+@@ -19,7 +19,6 @@
+ #include <memory>
+ #include <utility>
+ 
+-#include <brotli/decode.h>
+ #include "./buffer.h"
+ #include "./port.h"
+ #include "./round.h"
+@@ -28,6 +27,8 @@
+ #include "./variable_length.h"
+ #include "./woff2_common.h"
+ 
++#include "../RLBoxWOFF2Sandbox.h"
++
+ namespace woff2 {
+ 
+ namespace {
+@@ -758,7 +759,7 @@ bool ReconstructTransformedHmtx(const uint8_t* transformed_buf,
+ bool Woff2Uncompress(uint8_t* dst_buf, size_t dst_size,
+   const uint8_t* src_buf, size_t src_size) {
+   size_t uncompressed_size = dst_size;
+-  BrotliDecoderResult result = BrotliDecoderDecompress(
++  BrotliDecoderResult result = RLBoxBrotliDecoderDecompress(
+       src_size, src_buf, &uncompressed_size, dst_buf);
+   if (PREDICT_FALSE(result != BROTLI_DECODER_RESULT_SUCCESS ||
+                     uncompressed_size != dst_size)) {
diff --git a/parser/htmlparser/nsExpatDriver.cpp b/parser/htmlparser/nsExpatDriver.cpp
index 637d6db582c13..c3dfca96efae8 100644
--- a/parser/htmlparser/nsExpatDriver.cpp
+++ b/parser/htmlparser/nsExpatDriver.cpp
@@ -36,6 +36,7 @@
 
 #include "nsThreadUtils.h"
 #include "mozilla/ClearOnShutdown.h"
+#include "mozilla/RLBoxUtils.h"
 #include "mozilla/UniquePtr.h"
 
 #include "mozilla/Logging.h"
@@ -106,34 +107,12 @@ static const XML_Char* unverified_xml_string(uintptr_t ptr) {
 }
 
 /* The TransferBuffer class is used to copy (or directly expose in the
- * noop-sandbox case) buffers into the sandbox that are automatically freed
- * when the TransferBuffer is out of scope.  NOTE: The sandbox lifetime must
- * outlive all of its TransferBuffers.
+ * noop-sandbox case) buffers into the expat sandbox (and automatically
+ * when out of scope).
  */
 template <typename T>
-class MOZ_STACK_CLASS TransferBuffer {
- public:
-  TransferBuffer() = delete;
-  TransferBuffer(rlbox_sandbox_expat* aSandbox, const T* aBuf,
-                 const size_t aLen)
-      : mSandbox(aSandbox), mCopied(false), mBuf(nullptr) {
-    if (aBuf) {
-      mBuf = rlbox::copy_memory_or_grant_access(
-          *mSandbox, aBuf, aLen * sizeof(T), false, mCopied);
-    }
-  };
-  ~TransferBuffer() {
-    if (mCopied) {
-      mSandbox->free_in_sandbox(mBuf);
-    }
-  };
-  tainted_expat<const T*> operator*() const { return mBuf; };
-
- private:
-  rlbox_sandbox_expat* mSandbox;
-  bool mCopied;
-  tainted_expat<const T*> mBuf;
-};
+using TransferBuffer =
+    mozilla::RLBoxTransferBufferToSandbox<T, rlbox_expat_sandbox_type>;
 
 /*************************** END RLBOX HELPERS ******************************/
 
diff --git a/security/rlbox/moz.build b/security/rlbox/moz.build
index 3426652b0c7bf..b2e742fbade21 100644
--- a/security/rlbox/moz.build
+++ b/security/rlbox/moz.build
@@ -75,3 +75,8 @@ if CONFIG["MOZ_WASM_SANDBOXING_EXPAT"]:
     for k, v in expat_defines:
         WASM_DEFINES[k] = v
     LOCAL_INCLUDES += ["/parser/expat/lib/"]
+
+if CONFIG["MOZ_WASM_SANDBOXING_WOFF2"]:
+    include("/modules/woff2/sources.mozbuild")
+    WASM_SOURCES += ["/modules/woff2/" + s for s in woff2_sources]
+    LOCAL_INCLUDES += ["/modules/woff2/include"]
diff --git a/xpcom/base/RLBoxUtils.h b/xpcom/base/RLBoxUtils.h
new file mode 100644
index 0000000000000..c619df1362846
--- /dev/null
+++ b/xpcom/base/RLBoxUtils.h
@@ -0,0 +1,70 @@
+/* -*- Mode: C++; tab-width: 20; 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/. */
+
+#ifndef SECURITY_RLBOX_UTILS_H_
+#define SECURITY_RLBOX_UTILS_H_
+
+#include "mozilla/rlbox/rlbox_types.hpp"
+
+namespace mozilla {
+
+/* The RLBoxTransferBufferToSandbox class is used to copy (or directly expose in
+ * the noop-sandbox case) buffers into the sandbox that are automatically freed
+ * when the RLBoxTransferBufferToSandbox is out of scope.  NOTE: The sandbox
+ * lifetime must outlive all of its RLBoxTransferBufferToSandbox.
+ */
+template <typename T, typename S>
+class MOZ_STACK_CLASS RLBoxTransferBufferToSandbox {
+ public:
+  RLBoxTransferBufferToSandbox() = delete;
+  RLBoxTransferBufferToSandbox(rlbox::rlbox_sandbox<S>* aSandbox, const T* aBuf,
+                               const size_t aLen)
+      : mSandbox(aSandbox), mCopied(false), mBuf(nullptr) {
+    if (aBuf) {
+      mBuf = rlbox::copy_memory_or_grant_access(
+          *mSandbox, aBuf, aLen * sizeof(T), false, mCopied);
+    }
+  };
+  ~RLBoxTransferBufferToSandbox() {
+    if (mCopied) {
+      mSandbox->free_in_sandbox(mBuf);
+    }
+  };
+  rlbox::tainted<const T*, S> operator*() const { return mBuf; };
+
+ private:
+  rlbox::rlbox_sandbox<S>* mSandbox;
+  bool mCopied;
+  rlbox::tainted<const T*, S> mBuf;
+};
+
+/* The RLBoxAllocateInSandbox class is used to allocate data int sandbox that is
+ * automatically freed when the RLBoxAllocateInSandbox is out of scope.  NOTE:
+ * The sandbox lifetime must outlive all of its RLBoxAllocateInSandbox'ations.
+ */
+template <typename T, typename S>
+class MOZ_STACK_CLASS RLBoxAllocateInSandbox {
+ public:
+  RLBoxAllocateInSandbox() = delete;
+  explicit RLBoxAllocateInSandbox(rlbox::rlbox_sandbox<S>* aSandbox)
+      : mSandbox(aSandbox) {
+    mPtr = mSandbox->template malloc_in_sandbox<T>();
+  };
+  ~RLBoxAllocateInSandbox() {
+    if (mPtr) {
+      mSandbox->free_in_sandbox(mPtr);
+    }
+  };
+  rlbox::tainted<T*, S> get() const { return mPtr; };
+
+ private:
+  rlbox::rlbox_sandbox<S>* mSandbox;
+  rlbox::tainted<T*, S> mPtr;
+};
+
+}  // namespace mozilla
+
+#endif
diff --git a/xpcom/base/moz.build b/xpcom/base/moz.build
index a4d56668dfb9b..07aa783b47278 100644
--- a/xpcom/base/moz.build
+++ b/xpcom/base/moz.build
@@ -131,6 +131,7 @@ EXPORTS.mozilla += [
     "NSPRLogModulesParser.h",
     "OwningNonNull.h",
     "RLBoxSandboxPool.h",
+    "RLBoxUtils.h",
     "ShutdownPhase.h",
     "SizeOfState.h",
     "StaticLocalPtr.h",
-- 
GitLab