From 92a167ab3fda0bee41cf97f6a40a4c01c67d85d4 Mon Sep 17 00:00:00 2001 From: Peter Hutterer Date: Mon, 20 Apr 2026 11:17:08 +1000 Subject: [PATCH] sync: restart trigger list iteration in SyncChangeCounter after TriggerFired This is the equivalent check to miSyncTriggerFence() from commit f19ab94ba9c8 ("miext/sync: Fix use-after-free in miSyncTriggerFence()") When a trigger fires via SyncAwaitTriggerFired, the resulting FreeResource/FreeAwait call invokes SyncDeleteTriggerFromSyncObject for every trigger in the same Await group. This unlinks and frees the corresponding trigger list nodes - potentially including the node pnext points to. Fix by restarting iteration from the list head after a trigger fires, since TriggerFired may have arbitrarily mutated the list. Triggers that have fired are removed from the list by FreeAwait, so restarting cannot cause infinite loops. This vulnerability was discovered by: Anonymous working with TrendAI Zero Day Initiative ZDI-CAN-30164 Assisted-by: Claude:claude-opus-4-6 (cherry picked from commit bdd7bf57af208b1ddf57d4683d67104443b44812) Part-of: --- Xext/sync.c | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/Xext/sync.c b/Xext/sync.c index f4f1036d1..0e70dd08a 100644 --- a/Xext/sync.c +++ b/Xext/sync.c @@ -720,8 +720,29 @@ SyncChangeCounter(SyncCounter * pCounter, int64_t newval) /* run through triggers to see if any become true */ for (ptl = pCounter->sync.pTriglist; ptl; ptl = pnext) { pnext = ptl->next; - if ((*ptl->pTrigger->CheckTrigger) (ptl->pTrigger, oldval)) + if ((*ptl->pTrigger->CheckTrigger) (ptl->pTrigger, oldval)) { (*ptl->pTrigger->TriggerFired) (ptl->pTrigger); + /* TriggerFired may have called SyncDeleteTriggerFromSyncObject + * for sibling triggers in the same Await group, freeing their + * trigger list nodes - potentially including pnext. Verify + * pnext is still on the counter's trigger list; if not, + * restart from the list head. + * + * Unlike miSyncTriggerFence() we cannot use a do/while + * restart loop here: counter trigger lists may contain alarm + * triggers which are not removed after firing and would cause + * an infinite loop when delta is 0. + */ + if (pnext) { + SyncTriggerList *tmp; + for (tmp = pCounter->sync.pTriglist; tmp; tmp = tmp->next) { + if (tmp == pnext) + break; + } + if (!tmp) + pnext = pCounter->sync.pTriglist; + } + } } if (IsSystemCounter(pCounter)) {