mirror of
https://gitlab.freedesktop.org/xorg/xserver.git
synced 2026-06-06 23:28:19 +02:00
Both FreeCounter() and miSyncDestroyFence() iterate over the trigger list
and invoke the CounterDestroyed callback on each trigger.
The CounterDestroyed callback (e.g. SyncAwaitTriggerFired) may call
FreeResource/FreeAwait, which frees the SyncAwaitUnion containing all
SyncAwait structs in the same Await group.
When multiple conditions in a single Await reference the same sync
object (counter or fence), the first callback frees all SyncAwait
structs while subsequent trigger list nodes still reference them. On the
next iteration, reading ptl->next or ptl->pTrigger dereferences freed
memory, leading to a use-after-free.
We need separate fixes for separate issues here to fix this in one go
- use our null-terminated list macro to make sure our next pointer stays
valid (the code accessed ptl->next after freeing it)
- update the list head before deleting the trigger, eventually this ends
up being NULL anyway but meanwhile the list head is a valid list
during CounterDestroyed
- check if we actually do have a trigger before dereferencing the
callback
- Set all triggers to NULL if they are shared so we don't dereference
potentially freed memory
This vulnerability was discovered by:
Anonymous working with TrendAI Zero Day Initiative
ZDI-CAN-30159 (miSyncDestroyFence), ZDI-CAN-30163 (FreeCounter)
Assisted-by: Claude:claude-opus-4-6
(cherry picked from commit f5abfb6199)
Part-of: <https://gitlab.freedesktop.org/xorg/xserver/-/merge_requests/2230>
201 lines
5.3 KiB
C
201 lines
5.3 KiB
C
/*
|
|
* Copyright © 2010 NVIDIA Corporation
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a
|
|
* copy of this software and associated documentation files (the "Software"),
|
|
* to deal in the Software without restriction, including without limitation
|
|
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
* and/or sell copies of the Software, and to permit persons to whom the
|
|
* Software is furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice (including the next
|
|
* paragraph) shall be included in all copies or substantial portions of the
|
|
* Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
* DEALINGS IN THE SOFTWARE.
|
|
*/
|
|
|
|
#ifdef HAVE_DIX_CONFIG_H
|
|
#include <dix-config.h>
|
|
#endif
|
|
|
|
#include "scrnintstr.h"
|
|
#include "misync_priv.h"
|
|
#include "misyncstr.h"
|
|
|
|
DevPrivateKeyRec miSyncScreenPrivateKey;
|
|
|
|
/* Default implementations of the sync screen functions */
|
|
void
|
|
miSyncScreenCreateFence(ScreenPtr pScreen, SyncFence * pFence,
|
|
Bool initially_triggered)
|
|
{
|
|
(void) pScreen;
|
|
|
|
pFence->triggered = initially_triggered;
|
|
}
|
|
|
|
void
|
|
miSyncScreenDestroyFence(ScreenPtr pScreen, SyncFence * pFence)
|
|
{
|
|
(void) pScreen;
|
|
(void) pFence;
|
|
}
|
|
|
|
/* Default implementations of the per-object functions */
|
|
void
|
|
miSyncFenceSetTriggered(SyncFence * pFence)
|
|
{
|
|
pFence->triggered = TRUE;
|
|
}
|
|
|
|
void
|
|
miSyncFenceReset(SyncFence * pFence)
|
|
{
|
|
pFence->triggered = FALSE;
|
|
}
|
|
|
|
Bool
|
|
miSyncFenceCheckTriggered(SyncFence * pFence)
|
|
{
|
|
return pFence->triggered;
|
|
}
|
|
|
|
void
|
|
miSyncFenceAddTrigger(SyncTrigger * pTrigger)
|
|
{
|
|
(void) pTrigger;
|
|
|
|
return;
|
|
}
|
|
|
|
void
|
|
miSyncFenceDeleteTrigger(SyncTrigger * pTrigger)
|
|
{
|
|
(void) pTrigger;
|
|
|
|
return;
|
|
}
|
|
|
|
/* Machine independent portion of the fence sync object implementation */
|
|
void
|
|
miSyncInitFence(ScreenPtr pScreen, SyncFence * pFence, Bool initially_triggered)
|
|
{
|
|
SyncScreenPrivPtr pScreenPriv = SYNC_SCREEN_PRIV(pScreen);
|
|
|
|
static const SyncFenceFuncsRec miSyncFenceFuncs = {
|
|
&miSyncFenceSetTriggered,
|
|
&miSyncFenceReset,
|
|
&miSyncFenceCheckTriggered,
|
|
&miSyncFenceAddTrigger,
|
|
&miSyncFenceDeleteTrigger
|
|
};
|
|
|
|
pFence->pScreen = pScreen;
|
|
pFence->funcs = miSyncFenceFuncs;
|
|
|
|
pScreenPriv->funcs.CreateFence(pScreen, pFence, initially_triggered);
|
|
|
|
pFence->sync.initialized = TRUE;
|
|
}
|
|
|
|
void
|
|
miSyncDestroyFence(SyncFence * pFence)
|
|
{
|
|
pFence->sync.beingDestroyed = TRUE;
|
|
|
|
if (pFence->sync.initialized) {
|
|
ScreenPtr pScreen = pFence->pScreen;
|
|
SyncScreenPrivPtr pScreenPriv = SYNC_SCREEN_PRIV(pScreen);
|
|
SyncTriggerList *ptl, *pNext;
|
|
|
|
/* tell all the fence's triggers that the fence has been destroyed.
|
|
* Update pTriglist before each callback and free so that FreeAwait
|
|
* sees a valid list head when scanning for triggers to NULL out.
|
|
*/
|
|
nt_list_for_each_entry_safe(ptl, pNext, pFence->sync.pTriglist, next) {
|
|
pFence->sync.pTriglist = pNext;
|
|
if (ptl->pTrigger)
|
|
(*ptl->pTrigger->CounterDestroyed) (ptl->pTrigger);
|
|
free(ptl); /* destroy the trigger list as we go */
|
|
}
|
|
|
|
pScreenPriv->funcs.DestroyFence(pScreen, pFence);
|
|
}
|
|
|
|
dixFreeObjectWithPrivates(pFence, PRIVATE_SYNC_FENCE);
|
|
}
|
|
|
|
void
|
|
miSyncTriggerFence(SyncFence * pFence)
|
|
{
|
|
SyncTriggerList *ptl;
|
|
Bool triggered;
|
|
|
|
pFence->funcs.SetTriggered(pFence);
|
|
|
|
/* run through triggers to see if any fired */
|
|
do {
|
|
triggered = FALSE;
|
|
for (ptl = pFence->sync.pTriglist; ptl; ptl = ptl->next) {
|
|
if ((*ptl->pTrigger->CheckTrigger) (ptl->pTrigger, 0)) {
|
|
(*ptl->pTrigger->TriggerFired) (ptl->pTrigger);
|
|
triggered = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
} while (triggered);
|
|
}
|
|
|
|
SyncScreenFuncsPtr
|
|
miSyncGetScreenFuncs(ScreenPtr pScreen)
|
|
{
|
|
SyncScreenPrivPtr pScreenPriv = SYNC_SCREEN_PRIV(pScreen);
|
|
|
|
return &pScreenPriv->funcs;
|
|
}
|
|
|
|
static Bool
|
|
SyncCloseScreen(ScreenPtr pScreen)
|
|
{
|
|
SyncScreenPrivPtr pScreenPriv = SYNC_SCREEN_PRIV(pScreen);
|
|
|
|
pScreen->CloseScreen = pScreenPriv->CloseScreen;
|
|
|
|
return (*pScreen->CloseScreen) (pScreen);
|
|
}
|
|
|
|
Bool
|
|
miSyncSetup(ScreenPtr pScreen)
|
|
{
|
|
SyncScreenPrivPtr pScreenPriv;
|
|
|
|
static const SyncScreenFuncsRec miSyncScreenFuncs = {
|
|
&miSyncScreenCreateFence,
|
|
&miSyncScreenDestroyFence
|
|
};
|
|
|
|
if (!dixPrivateKeyRegistered(&miSyncScreenPrivateKey)) {
|
|
if (!dixRegisterPrivateKey(&miSyncScreenPrivateKey, PRIVATE_SCREEN,
|
|
sizeof(SyncScreenPrivRec)))
|
|
return FALSE;
|
|
}
|
|
|
|
pScreenPriv = SYNC_SCREEN_PRIV(pScreen);
|
|
|
|
if (!pScreenPriv->funcs.CreateFence) {
|
|
pScreenPriv->funcs = miSyncScreenFuncs;
|
|
|
|
/* Wrap CloseScreen to clean up */
|
|
pScreenPriv->CloseScreen = pScreen->CloseScreen;
|
|
pScreen->CloseScreen = SyncCloseScreen;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|