ported from UXP: Fix resizable DataView out-of-bounds semantics (aea80980)

This commit is contained in:
2026-05-20 14:33:43 +08:00
parent 0e76421998
commit 7f0c3f82c6
3 changed files with 58 additions and 10 deletions
+1
View File
@@ -559,6 +559,7 @@ MSG_DEF(JSMSG_TYPED_ARRAY_BAD_ARGS, 0, JSEXN_TYPEERR, "invalid arguments")
MSG_DEF(JSMSG_TYPED_ARRAY_NEGATIVE_ARG,1, JSEXN_RANGEERR, "argument {0} must be >= 0")
MSG_DEF(JSMSG_TYPED_ARRAY_DETACHED, 0, JSEXN_TYPEERR, "attempting to access detached ArrayBuffer")
MSG_DEF(JSMSG_TYPED_ARRAY_CONSTRUCT_BOUNDS, 0, JSEXN_RANGEERR, "attempting to construct out-of-bounds TypedArray on ArrayBuffer")
MSG_DEF(JSMSG_DATA_VIEW_OUT_OF_BOUNDS, 0, JSEXN_TYPEERR, "attempting to access out-of-bounds DataView")
MSG_DEF(JSMSG_TYPED_ARRAY_CALL_OR_CONSTRUCT, 1, JSEXN_TYPEERR, "cannot directly {0} builtin %TypedArray%")
MSG_DEF(JSMSG_NON_TYPED_ARRAY_RETURNED, 0, JSEXN_TYPEERR, "constructor didn't return TypedArray object")
MSG_DEF(JSMSG_SHORT_TYPED_ARRAY_RETURNED, 2, JSEXN_TYPEERR, "expected TypedArray of at least length {0}, but constructor returned TypedArray of length {1}")
@@ -41,9 +41,9 @@ dv.setUint8(0, 44);
assertEq(tracking[4], 44);
rab.resize(3);
assertEq(dv.byteOffset, 0);
assertEq(dv.byteLength, 0);
assertThrowsInstanceOf(() => dv.getUint8(0), RangeError);
assertThrowsInstanceOf(() => dv.byteOffset, TypeError);
assertThrowsInstanceOf(() => dv.byteLength, TypeError);
assertThrowsInstanceOf(() => dv.getUint8(0), TypeError);
rab.resize(6);
assertEq(dv.byteOffset, 4);
@@ -52,12 +52,40 @@ assertEq(dv.getUint8(0), 0);
var fixedDv = new DataView(rab, 4, 2);
rab.resize(5);
assertEq(fixedDv.byteOffset, 0);
assertEq(fixedDv.byteLength, 0);
assertThrowsInstanceOf(() => fixedDv.byteOffset, TypeError);
assertThrowsInstanceOf(() => fixedDv.byteLength, TypeError);
assertThrowsInstanceOf(() => fixedDv.getUint8(0), TypeError);
rab.resize(6);
assertEq(fixedDv.byteOffset, 4);
assertEq(fixedDv.byteLength, 2);
var ctorRab = new ArrayBuffer(8, { maxByteLength: 8 });
var ShrinkingNewTarget = new Proxy(function() {}, {
get(target, prop, receiver) {
if (prop === "prototype") {
ctorRab.resize(2);
return DataView.prototype;
}
return Reflect.get(target, prop, receiver);
}
});
assertThrowsInstanceOf(() => Reflect.construct(DataView, [ctorRab, 4], ShrinkingNewTarget),
RangeError);
var fixedCtorRab = new ArrayBuffer(8, { maxByteLength: 8 });
var FixedShrinkingNewTarget = new Proxy(function() {}, {
get(target, prop, receiver) {
if (prop === "prototype") {
fixedCtorRab.resize(5);
return DataView.prototype;
}
return Reflect.get(target, prop, receiver);
}
});
assertThrowsInstanceOf(() => Reflect.construct(DataView, [fixedCtorRab, 4, 2],
FixedShrinkingNewTarget),
RangeError);
var gsab = new SharedArrayBuffer(4, { maxByteLength: 16 });
var sharedTracking = new Uint8Array(gsab);
assertEq(sharedTracking.length, 4);
+24 -5
View File
@@ -1885,7 +1885,18 @@ DataViewObject::create(JSContext* cx, uint32_t byteOffset, uint32_t byteLength,
MOZ_ASSERT(byteOffset <= INT32_MAX);
MOZ_ASSERT(byteLength <= INT32_MAX);
MOZ_ASSERT(byteOffset + byteLength < UINT32_MAX);
uint32_t bufferByteLength = arrayBuffer->byteLength();
if (byteOffset > bufferByteLength ||
(!lengthTracking && byteLength > bufferByteLength - byteOffset))
{
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_ARG_INDEX_OUT_OF_RANGE,
"1");
return nullptr;
}
if (lengthTracking)
byteLength = bufferByteLength - byteOffset;
RootedObject proto(cx, protoArg);
RootedObject obj(cx);
@@ -1909,9 +1920,6 @@ DataViewObject::create(JSContext* cx, uint32_t byteOffset, uint32_t byteLength,
}
}
// Caller should have established these preconditions, and no
// (non-self-hosted) JS code has had an opportunity to run so nothing can
// have invalidated them.
MOZ_ASSERT(byteOffset <= arrayBuffer->byteLength());
MOZ_ASSERT(byteOffset + byteLength <= arrayBuffer->byteLength());
@@ -2171,6 +2179,11 @@ template <typename NativeType>
DataViewObject::getDataPointer(JSContext* cx, Handle<DataViewObject*> obj, uint64_t offset,
bool* isSharedMemory)
{
if (obj->isOutOfBounds()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_DATA_VIEW_OUT_OF_BOUNDS);
return SharedMem<uint8_t*>::unshared(nullptr);
}
const size_t TypeSize = sizeof(NativeType);
if (offset > UINT32_MAX - TypeSize || offset + TypeSize > obj->byteLength()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_ARG_INDEX_OUT_OF_RANGE,
@@ -3208,7 +3221,13 @@ template<Value ValueGetter(DataViewObject* view)>
bool
DataViewObject::getterImpl(JSContext* cx, const CallArgs& args)
{
args.rval().set(ValueGetter(&args.thisv().toObject().as<DataViewObject>()));
Rooted<DataViewObject*> view(cx, &args.thisv().toObject().as<DataViewObject>());
if (ValueGetter != bufferValue && view->isOutOfBounds()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_DATA_VIEW_OUT_OF_BOUNDS);
return false;
}
args.rval().set(ValueGetter(view));
return true;
}