Skip to content

sway/desktop/transaction: skip freeing dirty nodes#9101

Open
llyyr wants to merge 1 commit intoswaywm:masterfrom
llyyr:fix-use-after-free-take3
Open

sway/desktop/transaction: skip freeing dirty nodes#9101
llyyr wants to merge 1 commit intoswaywm:masterfrom
llyyr:fix-use-after-free-take3

Conversation

@llyyr
Copy link
Copy Markdown
Contributor

@llyyr llyyr commented Apr 4, 2026

This fixes a race that causes UAF when turning on multiple outputs after they've been off for a while.

When output_begin_destroy is called while a transaction that references the output is in-flight, node_set_dirty adds the node to server.dirty_nodes list and ntxnrefs is still held by that transaction. Once the transaction completes and ntxnrefs drops to 0, transaction_destroy frees the output, leaving a dangling pointer in server.dirty_nodes. The next transaction_commit_dirty call then walks the dirty_nodes list and crashes

The fix is to skip the free in transaction_destroy if node->dirty is set, this means transaction_commit_dirty hasn't processed this node yet and will bump ntxnrefs shortly. The free will happen once that transaction completes and ntxnrefs reaches 0 again and transaction_commit_dirty will allocate a fresh instruction and increment ntxnrefs again when it processes the node.

This time actually the correct fix for #9060 and #8969

@llyyr
Copy link
Copy Markdown
Contributor Author

llyyr commented Apr 4, 2026

I verified that this works by adding a log message and this morning it did hit this case!

diff --git a/sway/desktop/transaction.c b/sway/desktop/transaction.c
index 0aa841b05148..0e0d28362d42 100644
--- a/sway/desktop/transaction.c
+++ b/sway/desktop/transaction.c
@@ -61,6 +61,8 @@ static void transaction_destroy(struct sway_transaction *transaction) {
 		}
 		if (node->destroying && node->ntxnrefs == 0) {
 			if (node->dirty) {
+				sway_log(SWAY_ERROR, "Node %p is dirty but has no transaction references, skipping destroy",
+						node);
 				continue;
 			}
 			switch (node->type) {

@llyyr llyyr force-pushed the fix-use-after-free-take3 branch from 83fb95f to 15cf51a Compare April 4, 2026 01:30
This fixes a race that causes UAF when turning on multiple outputs after
they've been off for a while.

When output_begin_destroy is called while a transaction that references
the output is in-flight, node_set_dirty adds the node to
server.dirty_nodes list and ntxnrefs is still held by that transaction.
Once the transaction completes and ntxnrefs drops to 0,
transaction_destroy frees the output, leaving a dangling pointer in
server.dirty_nodes. The next transaction_commit_dirty call then walks
the dirty_nodes list and crashes

The fix is to skip the free in transaction_destroy if node->dirty is
set, this means transaction_commit_dirty hasn't processed this node yet
and will bump ntxnrefs shortly. The free will happen once that
transaction completes and ntxnrefs reaches 0 again and
transaction_commit_dirty will allocate a fresh instruction and
increment ntxnrefs again when it processes the node.
@llyyr llyyr force-pushed the fix-use-after-free-take3 branch from 15cf51a to 306ad8b Compare April 4, 2026 01:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant