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:
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();