Bug 1688616 part 2 - Check for large ArrayBuffer{View}s in WebIDL bindings. r=edgar,lth

The dom::TypedArray type currently represents its length as uint32_t. Changing
that to size_t/uint64_t would cause problems (truncation) for many 'Length()' callers.
Instead of requiring a length check for each of those call sites, for now check for
and reject large array buffers and views in the generated bindings.

Code and tests are based on the implementation of the [AllowShared] attribute.
Additional tests for the new JSAPIs will be added as part of bug 1674777.

Differential Revision: https://phabricator.services.mozilla.com/D102912
This commit is contained in:
Jan de Mooij 2021-01-31 07:33:41 +00:00
Родитель 6b0004f4f5
Коммит 8d290159a6
10 изменённых файлов: 187 добавлений и 6 удалений

Просмотреть файл

@ -5680,6 +5680,12 @@ def getJSToNativeConversionInfo(
"%s" % (firstCap(sourceDescription), exceptionCode)
)
def onFailureIsLarge():
return CGGeneric(
'cx.ThrowErrorMessage<MSG_TYPEDARRAY_IS_LARGE>("%s");\n'
"%s" % (firstCap(sourceDescription), exceptionCode)
)
def onFailureNotCallable(failureCode):
return CGGeneric(
failureCode
@ -6899,21 +6905,37 @@ def getJSToNativeConversionInfo(
objRef=objRef,
badType=onFailureBadType(failureCode, type.name).define(),
)
if not isAllowShared and type.isBufferSource():
if type.isBufferSource():
if type.isArrayBuffer():
isSharedMethod = "JS::IsSharedArrayBufferObject"
isLargeMethod = "JS::IsLargeArrayBufferMaybeShared"
else:
assert type.isArrayBufferView() or type.isTypedArray()
isSharedMethod = "JS::IsArrayBufferViewShared"
isLargeMethod = "JS::IsLargeArrayBufferView"
if not isAllowShared:
template += fill(
"""
if (${isSharedMethod}(${objRef}.Obj())) {
$*{badType}
}
""",
isSharedMethod=isSharedMethod,
objRef=objRef,
badType=onFailureIsShared().define(),
)
# For now reject large (> 2 GB) ArrayBuffers and ArrayBufferViews.
# Supporting this will require changing dom::TypedArray and
# consumers.
template += fill(
"""
if (${isSharedMethod}(${objRef}.Obj())) {
if (${isLargeMethod}(${objRef}.Obj())) {
$*{badType}
}
""",
isSharedMethod=isSharedMethod,
isLargeMethod=isLargeMethod,
objRef=objRef,
badType=onFailureIsShared().define(),
badType=onFailureIsLarge().define(),
)
template = wrapObjectTemplate(
template, type, "${declName}.SetNull();\n", failureCode

Просмотреть файл

@ -72,6 +72,7 @@ MSG_DEF(MSG_IS_NOT_PROMISE, 1, true, JSEXN_TYPEERR, "{0}Argument is not a Promis
MSG_DEF(MSG_SW_INSTALL_ERROR, 3, true, JSEXN_TYPEERR, "{0}ServiceWorker script at {1} for scope {2} encountered an error during installation.")
MSG_DEF(MSG_SW_SCRIPT_THREW, 3, true, JSEXN_TYPEERR, "{0}ServiceWorker script at {1} for scope {2} threw an exception during script evaluation.")
MSG_DEF(MSG_TYPEDARRAY_IS_SHARED, 2, true, JSEXN_TYPEERR, "{0}{1} can't be a SharedArrayBuffer or an ArrayBufferView backed by a SharedArrayBuffer")
MSG_DEF(MSG_TYPEDARRAY_IS_LARGE, 2, true, JSEXN_TYPEERR, "{0}{1} can't be an ArrayBuffer or an ArrayBufferView larger than 2 GB")
MSG_DEF(MSG_CACHE_ADD_FAILED_RESPONSE, 4, true, JSEXN_TYPEERR, "{0}Cache got {1} response with bad status {2} while trying to add request {3}")
MSG_DEF(MSG_SW_UPDATE_BAD_REGISTRATION, 3, true, JSEXN_TYPEERR, "{0}Failed to update the ServiceWorker for scope {1} because the registration has been {2} since the update was scheduled.")
MSG_DEF(MSG_INVALID_DURATION_ERROR, 2, true, JSEXN_TYPEERR, "{0}Invalid duration '{1}'.")

Просмотреть файл

@ -132,7 +132,11 @@ struct TypedArray_base : public SpiderMonkeyInterfaceObjectStorage,
inline void ComputeState() const {
MOZ_ASSERT(inited());
MOZ_ASSERT(!mComputed);
GetLengthAndDataAndSharedness(mImplObj, &mLength, &mShared, &mData);
uint32_t length;
GetLengthAndDataAndSharedness(mImplObj, &length, &mShared, &mData);
MOZ_RELEASE_ASSERT(length <= INT32_MAX,
"Bindings must have checked ArrayBuffer{View} length");
mLength = length;
mComputed = true;
}

Просмотреть файл

@ -7,6 +7,8 @@ support-files =
file_proxies_via_xray.html
forOf_iframe.html
!/js/xpconnect/tests/mochitest/file_empty.html
prefs =
javascript.options.large_arraybuffers=true
[test_async_stacks.html]
[test_ByteString.html]
@ -88,3 +90,5 @@ skip-if = debug == false
skip-if = debug == false
[test_attributes_on_types.html]
skip-if = debug == false
[test_large_arraybuffers.html]
skip-if = (debug == false || bits == 32) # Large ArrayBuffers are only supported on 64-bit platforms.

Просмотреть файл

@ -0,0 +1,97 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=1688616
-->
<head>
<meta charset="utf-8">
<title>Test for large ArrayBuffers and views</title>
<script src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1688616">Mozilla Bug 1688616</a>
<p id="display"></p>
<div id="content" style="display: none">
</div>
<pre id="test">
</pre>
<script type="application/javascript">
/* global TestFunctions */
SimpleTest.waitForExplicitFinish();
SpecialPowers.pushPrefEnv({set: [["dom.expose_test_interfaces", true]]}, go);
function checkThrowsTooLarge(f) {
let ex;
try{
f();
ok(false, "Should have thrown!");
} catch (e) {
ex = e;
}
ok(ex.toString().includes("larger than 2 GB"), "Got exception: " + ex);
}
function go() {
let test = new TestFunctions();
const gb = 1 * 1024 * 1024 * 1024;
let ab = new ArrayBuffer(5 * gb);
checkThrowsTooLarge(() => test.testNotAllowShared(ab));
checkThrowsTooLarge(() => test.testAllowShared(ab));
checkThrowsTooLarge(() => test.testDictWithAllowShared({arrayBuffer: ab}));
checkThrowsTooLarge(() => test.testUnionOfBuffferSource(ab));
checkThrowsTooLarge(() => { test.arrayBuffer = ab; });
checkThrowsTooLarge(() => { test.allowSharedArrayBuffer = ab; });
checkThrowsTooLarge(() => { test.sequenceOfArrayBuffer = [ab]; });
checkThrowsTooLarge(() => { test.sequenceOfAllowSharedArrayBuffer = [ab]; });
let ta = new Int8Array(ab, 0, 3 * gb);
checkThrowsTooLarge(() => test.testNotAllowShared(ta));
checkThrowsTooLarge(() => test.testAllowShared(ta));
checkThrowsTooLarge(() => test.testDictWithAllowShared({arrayBufferView: ta}));
checkThrowsTooLarge(() => test.testUnionOfBuffferSource(ta));
checkThrowsTooLarge(() => { test.arrayBufferView = ta; });
checkThrowsTooLarge(() => { test.allowSharedArrayBufferView = ta; });
checkThrowsTooLarge(() => { test.sequenceOfArrayBufferView = [ta]; });
checkThrowsTooLarge(() => { test.sequenceOfAllowSharedArrayBufferView = [ta]; });
let dv = new DataView(ab);
checkThrowsTooLarge(() => test.testNotAllowShared(dv));
checkThrowsTooLarge(() => test.testAllowShared(dv));
checkThrowsTooLarge(() => test.testDictWithAllowShared({arrayBufferView: dv}));
checkThrowsTooLarge(() => test.testUnionOfBuffferSource(dv));
checkThrowsTooLarge(() => { test.arrayBufferView = dv; });
checkThrowsTooLarge(() => { test.allowSharedArrayBufferView = dv; });
checkThrowsTooLarge(() => { test.sequenceOfArrayBufferView = [dv]; });
checkThrowsTooLarge(() => { test.sequenceOfAllowSharedArrayBufferView = [dv]; });
if (this.SharedArrayBuffer) {
let sab = new SharedArrayBuffer(5 * gb);
checkThrowsTooLarge(() => test.testAllowShared(sab));
checkThrowsTooLarge(() => test.testDictWithAllowShared({allowSharedArrayBuffer: sab}));
checkThrowsTooLarge(() => test.testUnionOfAllowSharedBuffferSource(sab));
checkThrowsTooLarge(() => { test.allowSharedArrayBuffer = sab; });
checkThrowsTooLarge(() => { test.sequenceOfAllowSharedArrayBuffer = [sab]; });
let sta = new Int8Array(sab);
checkThrowsTooLarge(() => test.testAllowShared(sta));
checkThrowsTooLarge(() => test.testDictWithAllowShared({allowSharedArrayBufferView: sta}));
checkThrowsTooLarge(() => test.testUnionOfAllowSharedBuffferSource(sta));
checkThrowsTooLarge(() => { test.allowSharedArrayBufferView = sta; });
checkThrowsTooLarge(() => { test.sequenceOfAllowSharedArrayBufferView = [sta]; });
}
// Small views on large buffers are fine.
let ta2 = new Int8Array(ab, 4 * gb);
is(ta2.byteLength, 1 * gb, "Small view on large ArrayBuffer");
test.testNotAllowShared(ta2);
test.arrayBufferView = ta2;
SimpleTest.finish();
}
</script>
</body>
</html>

Просмотреть файл

@ -89,6 +89,14 @@ extern JS_PUBLIC_API void GetArrayBufferMaybeSharedLengthAndData(
extern JS_PUBLIC_API uint8_t* GetArrayBufferMaybeSharedData(
JSObject* obj, bool* isSharedMemory, const AutoRequireNoGC&);
/**
* Returns whether the passed array buffer is 'large': its byteLength >= 2 GB.
* See also SetLargeArrayBuffersEnabled.
*
* |obj| must pass a JS::IsArrayBufferObjectMaybeShared test.
*/
extern JS_FRIEND_API bool IsLargeArrayBufferMaybeShared(JSObject* obj);
} // namespace JS
#endif /* js_ArrayBufferMaybeShared_h */

Просмотреть файл

@ -419,4 +419,16 @@ JS_FRIEND_API JSObject* JS_NewDataView(JSContext* cx,
JS::Handle<JSObject*> buffer,
size_t byteOffset, size_t byteLength);
namespace JS {
/*
* Returns whether the passed array buffer view is 'large': its byteLength >= 2
* GB. See also SetLargeArrayBuffersEnabled.
*
* |obj| must pass a JS_IsArrayBufferViewObject test.
*/
JS_FRIEND_API bool IsLargeArrayBufferView(JSObject* obj);
} // namespace JS
#endif // js_experimental_TypedData_h

Просмотреть файл

@ -184,6 +184,8 @@ class ArrayBufferObject : public ArrayBufferObjectMaybeShared {
static bool supportLargeBuffers;
static constexpr size_t MaxByteLengthForSmallBuffer = INT32_MAX;
// The length of an ArrayBuffer or SharedArrayBuffer can be at most
// INT32_MAX. Allow a larger limit on 64-bit platforms if the experimental
// large-buffers flag is used.
@ -193,7 +195,7 @@ class ArrayBufferObject : public ArrayBufferObjectMaybeShared {
return size_t(8) * 1024 * 1024 * 1024; // 8 GB.
}
#endif
return INT32_MAX;
return MaxByteLengthForSmallBuffer;
}
/** The largest number of bytes that can be stored inline. */

Просмотреть файл

@ -58,3 +58,19 @@ JS_PUBLIC_API uint8_t* JS::GetArrayBufferMaybeSharedData(
return nullptr;
}
JS_PUBLIC_API bool JS::IsLargeArrayBufferMaybeShared(JSObject* obj) {
#ifdef JS_64BIT
obj = UnwrapArrayBufferMaybeShared(obj);
MOZ_ASSERT(obj);
size_t len = obj->is<ArrayBufferObject>()
? obj->as<ArrayBufferObject>().byteLength().get()
: obj->as<SharedArrayBufferObject>().byteLength().get();
return len > ArrayBufferObject::MaxByteLengthForSmallBuffer;
#else
// Large ArrayBuffers are not supported on 32-bit.
MOZ_ASSERT(ArrayBufferObject::maxBufferByteLength() ==
ArrayBufferObject::MaxByteLengthForSmallBuffer);
return false;
#endif
}

Просмотреть файл

@ -296,3 +296,18 @@ JS_PUBLIC_API bool JS::IsArrayBufferViewShared(JSObject* obj) {
}
return view->isSharedMemory();
}
JS_FRIEND_API bool JS::IsLargeArrayBufferView(JSObject* obj) {
#ifdef JS_64BIT
obj = &obj->unwrapAs<ArrayBufferViewObject>();
BufferSize len = obj->is<DataViewObject>()
? obj->as<DataViewObject>().byteLength()
: obj->as<TypedArrayObject>().byteLength();
return len.get() > ArrayBufferObject::MaxByteLengthForSmallBuffer;
#else
// Large ArrayBuffers are not supported on 32-bit.
MOZ_ASSERT(ArrayBufferObject::maxBufferByteLength() ==
ArrayBufferObject::MaxByteLengthForSmallBuffer);
return false;
#endif
}