sway/desktop/transaction: skip freeing dirty nodes

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 commit is contained in:
llyyr 2026-04-01 01:24:28 +05:30
parent 909a2ddb5f
commit 306ad8b4b7

View file

@ -59,7 +59,7 @@ static void transaction_destroy(struct sway_transaction *transaction) {
if (node->instruction == instruction) {
node->instruction = NULL;
}
if (node->destroying && node->ntxnrefs == 0) {
if (node->destroying && node->ntxnrefs == 0 && !node->dirty) {
switch (node->type) {
case N_ROOT:
sway_assert(false, "Never reached");