Bug 1674777 part 10 - Fix XPConnect callers for JS_GetTypedArrayLength returning size_t. r=kmag
authorJan de Mooij <jdemooij@mozilla.com>
Wed, 10 Feb 2021 11:22:59 +0000
changeset 566799 03c71d1fa7f823e8bb23433503446d0fce37cff8
parent 566798 01813d1260e00a5597137f02e60c93b35d080d39
child 566800 ce348dc698371692817127de918fe4e1e262dffb
push id38190
push userbtara@mozilla.com
push dateWed, 10 Feb 2021 21:50:51 +0000
treeherdermozilla-central@569826c0fd47 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskmag
bugs1674777
milestone87.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1674777 part 10 - Fix XPConnect callers for JS_GetTypedArrayLength returning size_t. r=kmag For enumerateNames on Xrays, throw an OOM exception upfront. This matches what we do inside the JS engine when enumerating large typed arrays. Differential Revision: https://phabricator.services.mozilla.com/D103902
js/xpconnect/src/XPCConvert.cpp
js/xpconnect/src/XPCWrappedNative.cpp
js/xpconnect/tests/chrome/chrome.ini
js/xpconnect/tests/chrome/test_xrayLargeTypedArray.html
js/xpconnect/wrappers/XrayWrapper.cpp
--- a/js/xpconnect/src/XPCConvert.cpp
+++ b/js/xpconnect/src/XPCConvert.cpp
@@ -1439,17 +1439,26 @@ bool XPCConvert::JSArray2Native(JSContex
         return false;
     }
     if (aEltType.Tag() != tag) {
       return false;
     }
 
     // Allocate the backing buffer before getting the view data in case
     // allocFixupLen can cause GCs.
-    uint32_t length = JS_GetTypedArrayLength(jsarray);
+    uint32_t length;
+    {
+      // nsTArray and code below uses uint32_t lengths, so reject large typed
+      // arrays.
+      size_t fullLength = JS_GetTypedArrayLength(jsarray);
+      if (fullLength > UINT32_MAX) {
+        return false;
+      }
+      length = uint32_t(fullLength);
+    }
     void* buf = allocFixupLen(&length);
     if (!buf) {
       return false;
     }
 
     // Get the backing memory buffer to copy out of.
     JS::AutoCheckCannotGC nogc;
     bool isShared = false;
--- a/js/xpconnect/src/XPCWrappedNative.cpp
+++ b/js/xpconnect/src/XPCWrappedNative.cpp
@@ -1222,18 +1222,21 @@ bool CallMethodHelper::GetArraySizeFromP
     MOZ_ASSERT(mMethodInfo->Param(argnum).IsOptional());
     RootedObject arrayOrNull(mCallContext, &maybeArray.toObject());
 
     bool isArray;
     bool ok = false;
     if (JS::IsArrayObject(mCallContext, maybeArray, &isArray) && isArray) {
       ok = JS::GetArrayLength(mCallContext, arrayOrNull, lengthp);
     } else if (JS_IsTypedArrayObject(&maybeArray.toObject())) {
-      *lengthp = JS_GetTypedArrayLength(&maybeArray.toObject());
-      ok = true;
+      size_t len = JS_GetTypedArrayLength(&maybeArray.toObject());
+      if (len <= UINT32_MAX) {
+        *lengthp = len;
+        ok = true;
+      }
     }
 
     if (!ok) {
       return Throw(NS_ERROR_XPC_CANT_CONVERT_OBJECT_TO_ARRAY, mCallContext);
     }
   }
 
   *result = *lengthp;
--- a/js/xpconnect/tests/chrome/chrome.ini
+++ b/js/xpconnect/tests/chrome/chrome.ini
@@ -28,17 +28,18 @@ support-files =
   !/js/xpconnect/tests/mochitest/file_empty.html
   !/js/xpconnect/tests/mochitest/file_exnstack.html
   !/js/xpconnect/tests/mochitest/file_expandosharing.html
   !/js/xpconnect/tests/mochitest/file_nodelists.html
   !/js/xpconnect/tests/mochitest/file_evalInSandbox.html
   !/js/xpconnect/tests/mochitest/file_xrayic.html
 prefs =
   javascript.options.experimental.private_fields=true
-  
+  javascript.options.large_arraybuffers=true
+
 [test_APIExposer.xhtml]
 [test_bug361111.xhtml]
 [test_bug448587.xhtml]
 [test_bug484459.xhtml]
 skip-if = os == 'win' || os == 'mac' || (os == 'linux' && !debug) # bug 1131110, 1255284
 [test_bug500931.xhtml]
 [test_bug503926.xhtml]
 [test_bug533596.xhtml]
@@ -113,11 +114,13 @@ skip-if = os == 'win' || os == 'mac' || 
 [test_scripterror.html]
 [test_sharedChromeCompartment.html]
 [test_weakmap_keys_preserved.xhtml]
 [test_weakmap_keys_preserved2.xhtml]
 [test_weakref.xhtml]
 [test_windowProxyDeadWrapper.html]
 [test_wrappers.xhtml]
 [test_xrayic.xhtml]
+[test_xrayLargeTypedArray.html]
+skip-if = bits == 32 # Large ArrayBuffers not supported on 32-bit.
 [test_xrayToJS.xhtml]
 [test_bug1530146.html]
 support-files = file_bug1530146.html file_bug1530146_inner.html
new file mode 100644
--- /dev/null
+++ b/js/xpconnect/tests/chrome/test_xrayLargeTypedArray.html
@@ -0,0 +1,47 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1674777
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Test for Bug 1674777</title>
+  <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="chrome://global/skin"/>
+  <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+  <script type="application/javascript">
+
+/** Test for Bug 1674777 **/
+
+function go() {
+    SimpleTest.waitForExplicitFinish();
+
+    let win = document.getElementById('ifr').contentWindow;
+
+    const nbytes = 3 * 1024 * 1024 * 1024; // 3 GB.
+    let tarray = new win.Int8Array(nbytes);
+    ok(Cu.isXrayWrapper(tarray), "Should be Xray");
+    is(tarray.length, nbytes, "Length should match");
+    is(tarray.byteLength, nbytes, "byteLength should match");
+
+    // Expect OOM when getting all property names.
+    let ex;
+    try {
+        Object.getOwnPropertyNames(tarray);
+    } catch (e) {
+        ex = e;
+    }
+    is(ex, "out of memory", "Expected OOM");
+
+    SimpleTest.finish();
+}
+
+  </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1674777">Mozilla Bug 1674777</a>
+
+<iframe id="ifr" onload="go();" src="http://example.org/tests/js/xpconnect/tests/mochitest/file_empty.html" />
+
+</body>
+</html>
--- a/js/xpconnect/wrappers/XrayWrapper.cpp
+++ b/js/xpconnect/wrappers/XrayWrapper.cpp
@@ -925,23 +925,33 @@ bool JSXrayTraits::enumerateNames(JSCont
         }
       }
       for (size_t i = 0; i < props.length(); ++i) {
         JS_MarkCrossZoneId(cx, props[i]);
       }
       return true;
     }
     if (IsTypedArrayKey(key)) {
-      uint32_t length = JS_GetTypedArrayLength(target);
+      size_t length = JS_GetTypedArrayLength(target);
       // TypedArrays enumerate every indexed property in range, but
       // |length| is a getter that lives on the proto, like it should be.
+
+      // Fail early if the typed array is enormous, because this will be very
+      // slow and will likely report OOM. This also means we don't need to
+      // handle indices greater than JSID_INT_MAX in the loop below.
+      static_assert(JSID_INT_MAX >= INT32_MAX);
+      if (length > INT32_MAX) {
+        JS_ReportOutOfMemory(cx);
+        return false;
+      }
+
       if (!props.reserve(length)) {
         return false;
       }
-      for (int32_t i = 0; i <= int32_t(length - 1); ++i) {
+      for (int32_t i = 0; i < int32_t(length); ++i) {
         props.infallibleAppend(INT_TO_JSID(i));
       }
     } else if (key == JSProto_Function) {
       if (!props.append(GetJSIDByIndex(cx, XPCJSContext::IDX_LENGTH))) {
         return false;
       }
       if (!props.append(GetJSIDByIndex(cx, XPCJSContext::IDX_NAME))) {
         return false;