No issue - Add range checking to SharedArrayBuffer references.

This avoids a potential crash when juggling SAB allocations.
This commit is contained in:
Moonchild 2025-03-22 20:42:50 +01:00 • committed by roytam1
commit 788cc4acb2
5 changed files with 57 additions and 27 deletions

View file

@ -445,6 +445,7 @@ MSG_DEF(JSMSG_SC_UNSUPPORTED_TYPE, 0, JSEXN_TYPEERR, "unsupported type for s
MSG_DEF(JSMSG_SC_NOT_CLONABLE, 1, JSEXN_TYPEERR, "{0} cannot be cloned in this context") MSG_DEF(JSMSG_SC_NOT_CLONABLE, 1, JSEXN_TYPEERR, "{0} cannot be cloned in this context")
MSG_DEF(JSMSG_SC_SAB_TRANSFER, 0, JSEXN_WARN, "SharedArrayBuffer must not be in the transfer list") MSG_DEF(JSMSG_SC_SAB_TRANSFER, 0, JSEXN_WARN, "SharedArrayBuffer must not be in the transfer list")
MSG_DEF(JSMSG_SC_SAB_DISABLED, 0, JSEXN_TYPEERR, "SharedArrayBuffer not cloned - shared memory disabled in receiver") MSG_DEF(JSMSG_SC_SAB_DISABLED, 0, JSEXN_TYPEERR, "SharedArrayBuffer not cloned - shared memory disabled in receiver")
MSG_DEF(JSMSG_SC_SAB_TOO_MANY_REFS, 0, JSEXN_TYPEERR, "SharedArrayBuffer has too many references")
// Debugger // Debugger
MSG_DEF(JSMSG_ASSIGN_FUNCTION_OR_NULL, 1, JSEXN_TYPEERR, "value assigned to {0} must be a function or null") MSG_DEF(JSMSG_ASSIGN_FUNCTION_OR_NULL, 1, JSEXN_TYPEERR, "value assigned to {0} must be a function or null")

View file

@ -5347,25 +5347,31 @@ GetSharedArrayBuffer(JSContext* cx, unsigned argc, Value* vp)
{ {
CallArgs args = CallArgsFromVp(argc, vp); CallArgs args = CallArgsFromVp(argc, vp);
JSObject* newObj = nullptr; JSObject* newObj = nullptr;
bool rval = true;
sharedArrayBufferMailboxLock->lock(); {
SharedArrayRawBuffer* buf = sharedArrayBufferMailbox; sharedArrayBufferMailboxLock->lock();
if (buf) { auto unlockMailbox = MakeScopeExit([]() { sharedArrayBufferMailboxLock->unlock(); });
buf->addReference();
// Shared memory is enabled globally in the shell: there can't be a worker SharedArrayRawBuffer* buf = sharedArrayBufferMailbox;
// that does not enable it if the main thread has it. if (buf) {
MOZ_ASSERT(cx->compartment()->creationOptions().getSharedMemoryAndAtomicsEnabled()); if (!buf->addReference()) {
newObj = SharedArrayBufferObject::New(cx, buf); JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_SC_SAB_REFCNT_OFLO);
if (!newObj) { return false;
buf->dropReference(); }
rval = false;
// Shared memory is enabled globally in the shell: there can't be a worker
// that does not enable it if the main thread has it.
MOZ_ASSERT(cx->compartment()->creationOptions().getSharedMemoryAndAtomicsEnabled());
newObj = SharedArrayBufferObject::New(cx, buf);
if (!newObj) {
buf->dropReference();
return false;
}
} }
} }
sharedArrayBufferMailboxLock->unlock();
args.rval().setObjectOrNull(newObj); args.rval().setObjectOrNull(newObj);
return rval; return true;
} }
static bool static bool
@ -5379,18 +5385,24 @@ SetSharedArrayBuffer(JSContext* cx, unsigned argc, Value* vp)
} }
else if (args.get(0).isObject() && args[0].toObject().is<SharedArrayBufferObject>()) { else if (args.get(0).isObject() && args[0].toObject().is<SharedArrayBufferObject>()) {
newBuffer = args[0].toObject().as<SharedArrayBufferObject>().rawBufferObject(); newBuffer = args[0].toObject().as<SharedArrayBufferObject>().rawBufferObject();
newBuffer->addReference(); if (!newBuffer->addReference()) {
JS_ReportErrorASCII(cx, "Reference count overflow on SharedArrayBuffer");
return false;
}
} else { } else {
JS_ReportErrorASCII(cx, "Only a SharedArrayBuffer can be installed in the global mailbox"); JS_ReportErrorASCII(cx, "Only a SharedArrayBuffer can be installed in the global mailbox");
return false; return false;
} }
sharedArrayBufferMailboxLock->lock(); {
SharedArrayRawBuffer* oldBuffer = sharedArrayBufferMailbox; sharedArrayBufferMailboxLock->lock();
if (oldBuffer) auto unlockMailbox = MakeScopeExit([]() { sharedArrayBufferMailboxLock->unlock(); });
oldBuffer->dropReference();
sharedArrayBufferMailbox = newBuffer; SharedArrayRawBuffer* oldBuffer = sharedArrayBufferMailbox;
sharedArrayBufferMailboxLock->unlock(); if (oldBuffer)
oldBuffer->dropReference();
sharedArrayBufferMailbox = newBuffer;
}
args.rval().setUndefined(); args.rval().setUndefined();
return true; return true;

View file

@ -166,16 +166,30 @@ SharedArrayRawBuffer::New(JSContext* cx, uint32_t length)
return rawbuf; return rawbuf;
} }
void bool
SharedArrayRawBuffer::addReference() SharedArrayRawBuffer::addReference()
{ {
MOZ_ASSERT(this->refcount_ > 0); MOZ_ASSERT(this->refcount_ > 0);
++this->refcount_; // Atomic.
// Be careful never to overflow the refcount field.
for (;;) {
uint32_t old_refcount = this->refcount_;
uint32_t new_refcount = old_refcount+1;
if (new_refcount == 0)
return false;
if (this->refcount_.compareExchange(old_refcount, new_refcount))
return true;
}
} }
void void
SharedArrayRawBuffer::dropReference() SharedArrayRawBuffer::dropReference()
{ {
// Normally if the refcount is zero then the memory will have been unmapped
// and this test may just crash, but if the memory has been retained for any
// reason we will catch the underflow here.
MOZ_RELEASE_ASSERT(this->refcount_ > 0);
// Drop the reference to the buffer. // Drop the reference to the buffer.
uint32_t refcount = --this->refcount_; // Atomic. uint32_t refcount = --this->refcount_; // Atomic.
if (refcount) if (refcount)

View file

@ -91,7 +91,7 @@ class SharedArrayRawBuffer
uint32_t refcount() const { return refcount_; } uint32_t refcount() const { return refcount_; }
void addReference(); [[nodiscard]] bool addReference();
void dropReference(); void dropReference();
}; };

View file

@ -1201,9 +1201,12 @@ JSStructuredCloneWriter::writeSharedArrayBuffer(HandleObject obj)
Rooted<SharedArrayBufferObject*> sharedArrayBuffer(context(), &CheckedUnwrap(obj)->as<SharedArrayBufferObject>()); Rooted<SharedArrayBufferObject*> sharedArrayBuffer(context(), &CheckedUnwrap(obj)->as<SharedArrayBufferObject>());
SharedArrayRawBuffer* rawbuf = sharedArrayBuffer->rawBufferObject(); SharedArrayRawBuffer* rawbuf = sharedArrayBuffer->rawBufferObject();
// Avoids a race condition where the parent thread frees the buffer // Adding the reference here avoids a race condition where the parent thread frees the buffer
// before the child has accepted the transferable. // before the child has accepted the transferable.
rawbuf->addReference(); if (!rawbuf->addReference()) {
JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr, JSMSG_SC_SAB_TOO_MANY_REFS);
return false;
}
intptr_t p = reinterpret_cast<intptr_t>(rawbuf); intptr_t p = reinterpret_cast<intptr_t>(rawbuf);
return out.writePair(SCTAG_SHARED_ARRAY_BUFFER_OBJECT, static_cast<uint32_t>(sizeof(p))) && return out.writePair(SCTAG_SHARED_ARRAY_BUFFER_OBJECT, static_cast<uint32_t>(sizeof(p))) &&