Skip to content

Fix use-after-free when freeing a WeakMap with symbol values - #1844

Open
wleeaf wants to merge 1 commit into
quickjs-ng:masterfrom
wleeaf:fix-weakmap-finalizer-uaf
Open

wleeaf wants to merge 1 commit into
quickjs-ng:masterfrom
wleeaf:fix-weakmap-finalizer-uaf

Conversation

@wleeaf

@wleeaf wleeaf commented Oct 24, 2026

Copy link
Copy Markdown

Fixes #1835.

js_map_finalizer() walks the records of a WeakMap and, for each one, removes its weak reference and then frees its value. When the value is the last reference to a symbol, freeing it runs reset_weak_ref() on that symbol, which unlinks and frees every record of the same map that uses the symbol as a key. If that record is the one list_for_each_safe() saved as the next element, the loop continues on freed memory. Release builds segfault, builds with asserts stop on wr != NULL in delete_map_weak_ref(), and ASan reports a heap-use-after-free in js_map_finalizer().

This change removes the weak references of all records in a first pass and frees the values in a second pass, the same split that js_finrec_finalizer() and reset_weak_ref() already use. Freeing a value then can no longer reach a record of the map being finalized. Map and Set are unchanged.

tests/bug1835.js covers both reproductions from the issue: the map freed when the runtime is freed, and the map freed when a local variable is cleared. It also covers a chain of three symbols. Without the fix, the test aborts on the assertion in a Debug build and fails with heap-use-after-free under ASan. With the fix, the test passes in Debug, ASan+UBSan and GC stress builds. The rest of make test, and test262 under ASan+UBSan, show no new failures.

js_map_finalizer() walked the records of a WeakMap and, for each one,
removed its weak reference and then freed its value. When the value is
the last reference to a symbol, freeing it runs reset_weak_ref() on the
symbol, which unlinks and frees every record of the same map that uses
the symbol as a key. If that record was the next one in the list, the
loop went on with freed memory: a segfault, a heap-use-after-free under
ASan, or the `wr != NULL` assertion in delete_map_weak_ref().

Remove the weak references of all records first and free the values in
a second pass, like reset_weak_ref() does, so that freeing a value can
no longer reach the records of the map being finalized. Map and Set are
unchanged.

Fixes: quickjs-ng#1835
Copilot AI balanced review requested due to automatic review settings October 24, 2026 09:38

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Crash in js_map_finalizer when a WeakMap value is the symbol key of the next entry

3 participants