tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit 7d5ae0c50c497e7fbe1a9b29f6d6bb97eab8570c
parent 8799d24ce809163424401961ceef201787c92fb2
Author: Tooru Fujisawa <arai_a@mac.com>
Date:   Tue, 25 Nov 2025 09:11:15 +0000

Bug 2001452 - Clear the raw pointer references when AsyncFutexWaiter/WaitAsyncNotifyTask/WaitAsyncTimeoutTask are getting deleted. r=iain

Differential Revision: https://phabricator.services.mozilla.com/D273713

Diffstat:
Mjs/src/builtin/AtomicsObject.cpp | 53++++++++++++++++++++++++++++++++++++++++++++++++++++-
Ajs/src/jit-test/tests/atomics/waitAsync-references.js | 9+++++++++
2 files changed, 61 insertions(+), 1 deletion(-)

diff --git a/js/src/builtin/AtomicsObject.cpp b/js/src/builtin/AtomicsObject.cpp @@ -788,6 +788,10 @@ class AsyncFutexWaiter : public FutexWaiter { AsyncFutexWaiter(JSContext* cx, size_t offset) : FutexWaiter(cx, offset, FutexWaiterKind::Async) {} + // NOTE: AsyncFutexWaiter is deleted only by UniquePtr<AsyncFutexWaiter>, + // and thus the destructor is not virtual. + ~AsyncFutexWaiter(); + WaitAsyncNotifyTask* notifyTask() { return notifyTask_; } void setNotifyTask(WaitAsyncNotifyTask* task) { @@ -795,11 +799,15 @@ class AsyncFutexWaiter : public FutexWaiter { notifyTask_ = task; } + void resetNotifyTask() { notifyTask_ = nullptr; } + void setTimeoutTask(WaitAsyncTimeoutTask* task) { MOZ_ASSERT(!timeoutTask_); timeoutTask_ = task; } + void resetTimeoutTask() { timeoutTask_ = nullptr; } + bool hasTimeout() const { return !!timeoutTask_; } WaitAsyncTimeoutTask* timeoutTask() const { return timeoutTask_; } @@ -809,7 +817,15 @@ class AsyncFutexWaiter : public FutexWaiter { // Both of these pointers are borrowed pointers. The notifyTask is owned by // the runtime's cancellable list, while the timeout task (if it exists) is // owned by the embedding's timeout manager. + // + // Set by setNotifyTask immediately after construction, and reset by + // resetNotifyTask when the notify task is getting deleted. + // WaitAsyncNotifyTask is responsible for calling resetNotifyTask WaitAsyncNotifyTask* notifyTask_ = nullptr; + + // Set by setTimeoutTask immediately after construction, and reset by + // resetTimeoutTask when the timeout task is getting deleted. + // WaitAsyncTimeoutTask is responsible for calling resetTimeoutTask WaitAsyncTimeoutTask* timeoutTask_ = nullptr; }; @@ -833,16 +849,27 @@ class WaitAsyncNotifyTask : public OffThreadPromiseTask { // A back-edge to the waiter so that it can be cleaned up when the // Notify Task is dispatched and destroyed. + // + // Set by setWaiter immediately after construction, and reset by resetWaiter + // when the waiter is getting deleted. AsyncFutexWaiter is responsible for + // calling resetWaiter. AsyncFutexWaiter* waiter_ = nullptr; public: WaitAsyncNotifyTask(JSContext* cx, Handle<PromiseObject*> promise) : OffThreadPromiseTask(cx, promise) {} + ~WaitAsyncNotifyTask() override { + if (waiter_) { + waiter_->resetNotifyTask(); + } + } + void setWaiter(AsyncFutexWaiter* waiter) { MOZ_ASSERT(!waiter_); waiter_ = waiter; } + void resetWaiter() { waiter_ = nullptr; } void setResult(Result result, AutoLockFutexAPI& lock) { result_ = result; } @@ -875,20 +902,43 @@ class WaitAsyncNotifyTask : public OffThreadPromiseTask { // // See [SMDOC] Atomics.wait for more details. class WaitAsyncTimeoutTask : public JS::Dispatchable { + // Set by the constructor, and reset by resetWaiter when the waiter is getting + // deleted. AsyncFutexWaiter is responsible for calling resetWaiter. AsyncFutexWaiter* waiter_; public: explicit WaitAsyncTimeoutTask(AsyncFutexWaiter* waiter) : waiter_(waiter) { MOZ_ASSERT(waiter_); } + ~WaitAsyncTimeoutTask() { + if (waiter_) { + waiter_->resetTimeoutTask(); + } + } + + void resetWaiter() { waiter_ = nullptr; } - void clear(AutoLockFutexAPI&) { waiter_ = nullptr; } + void clear(AutoLockFutexAPI&) { + if (waiter_) { + waiter_->resetTimeoutTask(); + } + waiter_ = nullptr; + } bool cleared(AutoLockFutexAPI&) { return !waiter_; } void run(JSContext*, MaybeShuttingDown maybeshuttingdown) final; void transferToRuntime() final; }; +AsyncFutexWaiter::~AsyncFutexWaiter() { + if (notifyTask_) { + notifyTask_->resetWaiter(); + } + if (timeoutTask_) { + timeoutTask_->resetWaiter(); + } +} + } // namespace js // https://tc39.es/ecma262/#sec-addwaiter @@ -1021,6 +1071,7 @@ void WaitAsyncTimeoutTask::run(JSContext* cx, // Take ownership of the async waiter, so that it will be freed // when we return. UniquePtr<AsyncFutexWaiter> asyncWaiter(RemoveAsyncWaiter(waiter_, lock)); + asyncWaiter->resetTimeoutTask(); // Dispatch a task to resolve the promise with value "timed-out". WaitAsyncNotifyTask* task = asyncWaiter->notifyTask(); diff --git a/js/src/jit-test/tests/atomics/waitAsync-references.js b/js/src/jit-test/tests/atomics/waitAsync-references.js @@ -0,0 +1,9 @@ +// The OOM during the construction or any step should clear the references +// between the waiters and tasks, and shouldn't result in UAF. +function f() { + var x = new Int32Array(new SharedArrayBuffer(4)); + x[0] = 1; + Atomics.waitAsync(x, 0, 1, 65535); + oomTest(f); +} +f();