reactos/ntoskrnl/ex/timer.c
George Bișoc d0d86ab588
[NTOSKRNL] Force a probe against ReturnLength on query & Misc ICIF stuff
NtQueryInformationToken is by far the only system call in NT where ReturnLength simply cannot be optional. On Windows this parameter is always probed and an argument to NULL directly leads to an access violation exception.
This is due to the fact of how tokens work, as its information contents (token user, owner, primary group, et al) are dynamic and can vary throughout over time in memory.

What happens on current ReactOS master however is that ReturnLength is only probed if the parameter is not NULL. On a NULL case scenario the probing checks succeed and NtQueryInformationToken fails later. For this, just get rid of CompleteProbing
parameter and opt in for a bit mask flag based approach, with ICIF_FORCE_RETURN_LENGTH_PROBE being set on DefaultQueryInfoBufferCheck which NtQueryInformationToken calls it to do sanity checks.

In addition to that...

- Document the ICIF probe helpers
- Annotate the ICIF prope helpers with SAL
- With the riddance of CompleteProbing and adoption of flags based approach, add ICIF_PROBE_READ_WRITE and ICIF_PROBE_READ flags alongside with ICIF_FORCE_RETURN_LENGTH_PROBE
2022-06-12 11:05:05 +02:00

761 lines
22 KiB
C

/*
* PROJECT: ReactOS Kernel
* LICENSE: GPL - See COPYING in the top level directory
* FILE: ntoskrnl/ex/timer.c
* PURPOSE: Executive Timer Implementation
* PROGRAMMERS: Alex Ionescu (alex.ionescu@reactos.org)
*/
/* INCLUDES ******************************************************************/
#include <ntoskrnl.h>
#define NDEBUG
#include <debug.h>
/* GLOBALS *******************************************************************/
/* Timer Object Type */
POBJECT_TYPE ExTimerType = NULL;
KSPIN_LOCK ExpWakeListLock;
LIST_ENTRY ExpWakeList;
/* Timer Mapping */
static GENERIC_MAPPING ExpTimerMapping =
{
STANDARD_RIGHTS_READ | TIMER_QUERY_STATE,
STANDARD_RIGHTS_WRITE | TIMER_MODIFY_STATE,
STANDARD_RIGHTS_EXECUTE | SYNCHRONIZE,
TIMER_ALL_ACCESS
};
/* Timer Information Classes */
static const INFORMATION_CLASS_INFO ExTimerInfoClass[] =
{
/* TimerBasicInformation */
IQS_SAME(TIMER_BASIC_INFORMATION, ULONG, ICIF_QUERY),
};
/* PRIVATE FUNCTIONS *********************************************************/
VOID
NTAPI
ExTimerRundown(VOID)
{
PETHREAD Thread = PsGetCurrentThread();
KIRQL OldIrql;
PLIST_ENTRY CurrentEntry;
PETIMER Timer;
ULONG DerefsToDo;
/* Lock the Thread's Active Timer List and loop it */
KeAcquireSpinLock(&Thread->ActiveTimerListLock, &OldIrql);
CurrentEntry = Thread->ActiveTimerListHead.Flink;
while (CurrentEntry != &Thread->ActiveTimerListHead)
{
/* Get the timer */
Timer = CONTAINING_RECORD(CurrentEntry, ETIMER, ActiveTimerListEntry);
/* Reference it */
ObReferenceObject(Timer);
DerefsToDo = 1;
/* Unlock the list */
KeReleaseSpinLock(&Thread->ActiveTimerListLock, OldIrql);
/* Lock the Timer */
KeAcquireSpinLock(&Timer->Lock, &OldIrql);
/* Lock the list again */
KeAcquireSpinLockAtDpcLevel(&Thread->ActiveTimerListLock);
/* Make sure that the timer is valid */
if ((Timer->ApcAssociated) && (&Thread->Tcb == Timer->TimerApc.Thread))
{
/* Remove it from the list */
RemoveEntryList(&Timer->ActiveTimerListEntry);
Timer->ApcAssociated = FALSE;
/* Cancel the timer and remove its DPC and APC */
KeCancelTimer(&Timer->KeTimer);
KeRemoveQueueDpc(&Timer->TimerDpc);
if (KeRemoveQueueApc(&Timer->TimerApc)) DerefsToDo++;
/* Add another dereference to do */
DerefsToDo++;
}
/* Unlock the list */
KeReleaseSpinLockFromDpcLevel(&Thread->ActiveTimerListLock);
/* Unlock the Timer */
KeReleaseSpinLock(&Timer->Lock, OldIrql);
/* Dereference it */
ObDereferenceObjectEx(Timer, DerefsToDo);
/* Loop again */
KeAcquireSpinLock(&Thread->ActiveTimerListLock, &OldIrql);
CurrentEntry = Thread->ActiveTimerListHead.Flink;
}
/* Release lock and return */
KeReleaseSpinLock(&Thread->ActiveTimerListLock, OldIrql);
}
VOID
NTAPI
ExpDeleteTimer(IN PVOID ObjectBody)
{
KIRQL OldIrql;
PETIMER Timer = ObjectBody;
/* Check if it has a Wait List */
if (Timer->WakeTimerListEntry.Flink)
{
/* Lock the Wake List */
KeAcquireSpinLock(&ExpWakeListLock, &OldIrql);
/* Check again, since it might've changed before we locked */
if (Timer->WakeTimerListEntry.Flink)
{
/* Remove it from the Wait List */
RemoveEntryList(&Timer->WakeTimerListEntry);
Timer->WakeTimerListEntry.Flink = NULL;
}
/* Release the Wake List */
KeReleaseSpinLock(&ExpWakeListLock, OldIrql);
}
/* Tell the Kernel to cancel the Timer and flush all queued DPCs */
KeCancelTimer(&Timer->KeTimer);
KeFlushQueuedDpcs();
}
_Function_class_(KDEFERRED_ROUTINE)
VOID
NTAPI
ExpTimerDpcRoutine(IN PKDPC Dpc,
IN PVOID DeferredContext,
IN PVOID SystemArgument1,
IN PVOID SystemArgument2)
{
PETIMER Timer = DeferredContext;
BOOLEAN Inserted = FALSE;
/* Reference the timer */
if (!ObReferenceObjectSafe(Timer)) return;
/* Lock the Timer */
KeAcquireSpinLockAtDpcLevel(&Timer->Lock);
/* Check if the timer is associated */
if (Timer->ApcAssociated)
{
/* Queue the APC */
Inserted = KeInsertQueueApc(&Timer->TimerApc,
SystemArgument1,
SystemArgument2,
IO_NO_INCREMENT);
}
/* Release the Timer */
KeReleaseSpinLockFromDpcLevel(&Timer->Lock);
/* Dereference it if we couldn't queue the APC */
if (!Inserted) ObDereferenceObject(Timer);
}
VOID
NTAPI
ExpTimerApcKernelRoutine(IN PKAPC Apc,
IN OUT PKNORMAL_ROUTINE* NormalRoutine,
IN OUT PVOID* NormalContext,
IN OUT PVOID* SystemArgument1,
IN OUT PVOID* SystemArguemnt2)
{
PETIMER Timer;
KIRQL OldIrql;
ULONG DerefsToDo = 1;
PETHREAD Thread = PsGetCurrentThread();
/* We need to find out which Timer we are */
Timer = CONTAINING_RECORD(Apc, ETIMER, TimerApc);
/* Lock the Timer */
KeAcquireSpinLock(&Timer->Lock, &OldIrql);
/* Lock the Thread's Active Timer List*/
KeAcquireSpinLockAtDpcLevel(&Thread->ActiveTimerListLock);
/* Make sure that the Timer is valid, and that it belongs to this thread */
if ((Timer->ApcAssociated) && (&Thread->Tcb == Timer->TimerApc.Thread))
{
/* Check if it's not periodic */
if (!Timer->Period)
{
/* Remove it from the Active Timers List */
RemoveEntryList(&Timer->ActiveTimerListEntry);
/* Disable it */
Timer->ApcAssociated = FALSE;
DerefsToDo++;
}
}
else
{
/* Clear the normal routine */
*NormalRoutine = NULL;
}
/* Release locks */
KeReleaseSpinLockFromDpcLevel(&Thread->ActiveTimerListLock);
KeReleaseSpinLock(&Timer->Lock, OldIrql);
/* Dereference as needed */
ObDereferenceObjectEx(Timer, DerefsToDo);
}
CODE_SEG("INIT")
BOOLEAN
NTAPI
ExpInitializeTimerImplementation(VOID)
{
OBJECT_TYPE_INITIALIZER ObjectTypeInitializer;
UNICODE_STRING Name;
NTSTATUS Status;
/* Create the Timer Object Type */
RtlZeroMemory(&ObjectTypeInitializer, sizeof(ObjectTypeInitializer));
RtlInitUnicodeString(&Name, L"Timer");
ObjectTypeInitializer.Length = sizeof(ObjectTypeInitializer);
ObjectTypeInitializer.InvalidAttributes = OBJ_OPENLINK;
ObjectTypeInitializer.DefaultNonPagedPoolCharge = sizeof(ETIMER);
ObjectTypeInitializer.GenericMapping = ExpTimerMapping;
ObjectTypeInitializer.PoolType = NonPagedPool;
ObjectTypeInitializer.ValidAccessMask = TIMER_ALL_ACCESS;
ObjectTypeInitializer.DeleteProcedure = ExpDeleteTimer;
Status = ObCreateObjectType(&Name, &ObjectTypeInitializer, NULL, &ExTimerType);
if (!NT_SUCCESS(Status)) return FALSE;
/* Initialize the Wait List and Lock */
KeInitializeSpinLock(&ExpWakeListLock);
InitializeListHead(&ExpWakeList);
return TRUE;
}
/* PUBLIC FUNCTIONS **********************************************************/
NTSTATUS
NTAPI
NtCancelTimer(IN HANDLE TimerHandle,
OUT PBOOLEAN CurrentState OPTIONAL)
{
PETIMER Timer;
KPROCESSOR_MODE PreviousMode = ExGetPreviousMode();
BOOLEAN State;
KIRQL OldIrql;
PETHREAD TimerThread;
ULONG DerefsToDo = 1;
NTSTATUS Status;
PAGED_CODE();
/* Check if we need to probe */
if ((CurrentState) && (PreviousMode != KernelMode))
{
_SEH2_TRY
{
/* Make sure the pointer is valid */
ProbeForWriteBoolean(CurrentState);
}
_SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
{
/* Return the exception code */
_SEH2_YIELD(return _SEH2_GetExceptionCode());
}
_SEH2_END;
}
/* Get the Timer Object */
Status = ObReferenceObjectByHandle(TimerHandle,
TIMER_MODIFY_STATE,
ExTimerType,
PreviousMode,
(PVOID*)&Timer,
NULL);
if (NT_SUCCESS(Status))
{
/* Lock the Timer */
KeAcquireSpinLock(&Timer->Lock, &OldIrql);
/* Check if it's enabled */
if (Timer->ApcAssociated)
{
/* Get the Thread. */
TimerThread = CONTAINING_RECORD(Timer->TimerApc.Thread,
ETHREAD,
Tcb);
/* Lock its active list */
KeAcquireSpinLockAtDpcLevel(&TimerThread->ActiveTimerListLock);
/* Remove it */
RemoveEntryList(&Timer->ActiveTimerListEntry);
Timer->ApcAssociated = FALSE;
/* Unlock the list */
KeReleaseSpinLockFromDpcLevel(&TimerThread->ActiveTimerListLock);
/* Cancel the Timer */
KeCancelTimer(&Timer->KeTimer);
KeRemoveQueueDpc(&Timer->TimerDpc);
if (KeRemoveQueueApc(&Timer->TimerApc)) DerefsToDo++;
DerefsToDo++;
}
else
{
/* If timer was disabled, we still need to cancel it */
KeCancelTimer(&Timer->KeTimer);
}
/* Handle a Wake Timer */
if (Timer->WakeTimerListEntry.Flink)
{
/* Lock the Wake List */
KeAcquireSpinLockAtDpcLevel(&ExpWakeListLock);
/* Check again, since it might've changed before we locked */
if (Timer->WakeTimerListEntry.Flink)
{
/* Remove it from the Wait List */
RemoveEntryList(&Timer->WakeTimerListEntry);
Timer->WakeTimerListEntry.Flink = NULL;
}
/* Release the Wake List */
KeReleaseSpinLockFromDpcLevel(&ExpWakeListLock);
}
/* Unlock the Timer */
KeReleaseSpinLock(&Timer->Lock, OldIrql);
/* Read the old State */
State = KeReadStateTimer(&Timer->KeTimer);
/* Dereference the Object */
ObDereferenceObjectEx(Timer, DerefsToDo);
/* Check if caller wants the state */
if (CurrentState)
{
_SEH2_TRY
{
/* Return the Timer State */
*CurrentState = State;
}
_SEH2_EXCEPT(ExSystemExceptionFilter())
{
/* Do nothing */
(void)0;
}
_SEH2_END;
}
}
/* Return to Caller */
return Status;
}
NTSTATUS
NTAPI
NtCreateTimer(OUT PHANDLE TimerHandle,
IN ACCESS_MASK DesiredAccess,
IN POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL,
IN TIMER_TYPE TimerType)
{
PETIMER Timer;
HANDLE hTimer;
KPROCESSOR_MODE PreviousMode = ExGetPreviousMode();
NTSTATUS Status;
PAGED_CODE();
/* Check for correct timer type */
if ((TimerType != NotificationTimer) &&
(TimerType != SynchronizationTimer))
{
/* Fail */
return STATUS_INVALID_PARAMETER_4;
}
/* Check if we need to probe */
if (PreviousMode != KernelMode)
{
_SEH2_TRY
{
/* Make sure the pointer is valid */
ProbeForWriteHandle(TimerHandle);
}
_SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
{
/* Return the exception code */
_SEH2_YIELD(return _SEH2_GetExceptionCode());
}
_SEH2_END;
}
/* Create the Object */
Status = ObCreateObject(PreviousMode,
ExTimerType,
ObjectAttributes,
PreviousMode,
NULL,
sizeof(ETIMER),
0,
0,
(PVOID*)&Timer);
if (NT_SUCCESS(Status))
{
/* Initialize the DPC */
KeInitializeDpc(&Timer->TimerDpc, ExpTimerDpcRoutine, Timer);
/* Initialize the Kernel Timer */
KeInitializeTimerEx(&Timer->KeTimer, TimerType);
/* Initialize the timer fields */
KeInitializeSpinLock(&Timer->Lock);
Timer->ApcAssociated = FALSE;
Timer->WakeTimer = FALSE;
Timer->WakeTimerListEntry.Flink = NULL;
/* Insert the Timer */
Status = ObInsertObject((PVOID)Timer,
NULL,
DesiredAccess,
0,
NULL,
&hTimer);
/* Check for success */
if (NT_SUCCESS(Status))
{
/* Enter SEH */
_SEH2_TRY
{
/* Return the Timer Handle */
*TimerHandle = hTimer;
}
_SEH2_EXCEPT(ExSystemExceptionFilter())
{
/* Do nothing */
(void)0;
}
_SEH2_END;
}
}
/* Return to Caller */
return Status;
}
NTSTATUS
NTAPI
NtOpenTimer(OUT PHANDLE TimerHandle,
IN ACCESS_MASK DesiredAccess,
IN POBJECT_ATTRIBUTES ObjectAttributes)
{
HANDLE hTimer;
KPROCESSOR_MODE PreviousMode = ExGetPreviousMode();
NTSTATUS Status;
PAGED_CODE();
/* Check Parameter Validity */
if (PreviousMode != KernelMode)
{
_SEH2_TRY
{
/* Make sure the pointer is valid */
ProbeForWriteHandle(TimerHandle);
}
_SEH2_EXCEPT(ExSystemExceptionFilter())
{
/* Return the exception code */
_SEH2_YIELD(return _SEH2_GetExceptionCode());
}
_SEH2_END;
}
/* Open the Timer */
Status = ObOpenObjectByName(ObjectAttributes,
ExTimerType,
PreviousMode,
NULL,
DesiredAccess,
NULL,
&hTimer);
if (NT_SUCCESS(Status))
{
/* Enter SEH */
_SEH2_TRY
{
/* Return the Timer Handle */
*TimerHandle = hTimer;
}
_SEH2_EXCEPT(ExSystemExceptionFilter())
{
/* Do nothing */
(void)0;
}
_SEH2_END;
}
/* Return to Caller */
return Status;
}
NTSTATUS
NTAPI
NtQueryTimer(IN HANDLE TimerHandle,
IN TIMER_INFORMATION_CLASS TimerInformationClass,
OUT PVOID TimerInformation,
IN ULONG TimerInformationLength,
OUT PULONG ReturnLength OPTIONAL)
{
PETIMER Timer;
KPROCESSOR_MODE PreviousMode = ExGetPreviousMode();
NTSTATUS Status;
PTIMER_BASIC_INFORMATION BasicInfo = TimerInformation;
PAGED_CODE();
/* Check Validity */
Status = DefaultQueryInfoBufferCheck(TimerInformationClass,
ExTimerInfoClass,
sizeof(ExTimerInfoClass) /
sizeof(ExTimerInfoClass[0]),
ICIF_PROBE_READ_WRITE,
TimerInformation,
TimerInformationLength,
ReturnLength,
NULL,
PreviousMode);
if (!NT_SUCCESS(Status)) return Status;
/* Get the Timer Object */
Status = ObReferenceObjectByHandle(TimerHandle,
TIMER_QUERY_STATE,
ExTimerType,
PreviousMode,
(PVOID*)&Timer,
NULL);
if (NT_SUCCESS(Status))
{
/* Return the Basic Information */
_SEH2_TRY
{
/* Return the remaining time, corrected */
BasicInfo->TimeRemaining.QuadPart = Timer->
KeTimer.DueTime.QuadPart -
KeQueryInterruptTime();
/* Return the current state */
BasicInfo->SignalState = KeReadStateTimer(&Timer->KeTimer);
/* Return the buffer length if requested */
if (ReturnLength) *ReturnLength = sizeof(TIMER_BASIC_INFORMATION);
}
_SEH2_EXCEPT(ExSystemExceptionFilter())
{
/* Get the exception code */
Status = _SEH2_GetExceptionCode();
}
_SEH2_END;
/* Dereference Object */
ObDereferenceObject(Timer);
}
/* Return Status */
return Status;
}
NTSTATUS
NTAPI
NtSetTimer(IN HANDLE TimerHandle,
IN PLARGE_INTEGER DueTime,
IN PTIMER_APC_ROUTINE TimerApcRoutine OPTIONAL,
IN PVOID TimerContext OPTIONAL,
IN BOOLEAN WakeTimer,
IN LONG Period OPTIONAL,
OUT PBOOLEAN PreviousState OPTIONAL)
{
PETIMER Timer;
KIRQL OldIrql;
BOOLEAN State;
KPROCESSOR_MODE PreviousMode = ExGetPreviousMode();
PETHREAD Thread = PsGetCurrentThread();
LARGE_INTEGER TimerDueTime;
PETHREAD TimerThread;
ULONG DerefsToDo = 1;
NTSTATUS Status = STATUS_SUCCESS;
PAGED_CODE();
/* Check for a valid Period */
if (Period < 0) return STATUS_INVALID_PARAMETER_6;
/* Check if we need to probe */
if (PreviousMode != KernelMode)
{
_SEH2_TRY
{
/* Probe and capture the due time */
TimerDueTime = ProbeForReadLargeInteger(DueTime);
/* Probe the state pointer if one was passed */
if (PreviousState) ProbeForWriteBoolean(PreviousState);
}
_SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
{
/* Return the exception code */
_SEH2_YIELD(return _SEH2_GetExceptionCode());
}
_SEH2_END;
}
else
{
/* Capture the time directly */
TimerDueTime = *DueTime;
}
/* Get the Timer Object */
Status = ObReferenceObjectByHandle(TimerHandle,
TIMER_MODIFY_STATE,
ExTimerType,
PreviousMode,
(PVOID*)&Timer,
NULL);
/*
* Tell the user we don't support Wake Timers...
* when we have the ability to use/detect the Power Management
* functionality required to support them, make this check dependent
* on the actual PM capabilities
*/
if (NT_SUCCESS(Status) && WakeTimer)
{
Status = STATUS_TIMER_RESUME_IGNORED;
}
/* Check status */
if (NT_SUCCESS(Status))
{
/* Lock the Timer */
KeAcquireSpinLock(&Timer->Lock, &OldIrql);
/* Cancel Running Timer */
if (Timer->ApcAssociated)
{
/* Get the Thread. */
TimerThread = CONTAINING_RECORD(Timer->TimerApc.Thread,
ETHREAD,
Tcb);
/* Lock its active list */
KeAcquireSpinLockAtDpcLevel(&TimerThread->ActiveTimerListLock);
/* Remove it */
RemoveEntryList(&Timer->ActiveTimerListEntry);
Timer->ApcAssociated = FALSE;
/* Unlock the list */
KeReleaseSpinLockFromDpcLevel(&TimerThread->ActiveTimerListLock);
/* Cancel the Timer */
KeCancelTimer(&Timer->KeTimer);
KeRemoveQueueDpc(&Timer->TimerDpc);
if (KeRemoveQueueApc(&Timer->TimerApc)) DerefsToDo++;
DerefsToDo++;
}
else
{
/* If timer was disabled, we still need to cancel it */
KeCancelTimer(&Timer->KeTimer);
}
/* Read the State */
State = KeReadStateTimer(&Timer->KeTimer);
/* Handle Wake Timers */
Timer->WakeTimer = WakeTimer;
KeAcquireSpinLockAtDpcLevel(&ExpWakeListLock);
if ((WakeTimer) && !(Timer->WakeTimerListEntry.Flink))
{
/* Insert it into the list */
InsertTailList(&ExpWakeList, &Timer->WakeTimerListEntry);
}
else if (!(WakeTimer) && (Timer->WakeTimerListEntry.Flink))
{
/* Remove it from the list */
RemoveEntryList(&Timer->WakeTimerListEntry);
Timer->WakeTimerListEntry.Flink = NULL;
}
KeReleaseSpinLockFromDpcLevel(&ExpWakeListLock);
/* Set up the APC Routine if specified */
Timer->Period = Period;
if (TimerApcRoutine)
{
/* Initialize the APC */
KeInitializeApc(&Timer->TimerApc,
&Thread->Tcb,
CurrentApcEnvironment,
ExpTimerApcKernelRoutine,
(PKRUNDOWN_ROUTINE)NULL,
(PKNORMAL_ROUTINE)TimerApcRoutine,
PreviousMode,
TimerContext);
/* Lock the Thread's Active List and Insert */
KeAcquireSpinLockAtDpcLevel(&Thread->ActiveTimerListLock);
InsertTailList(&Thread->ActiveTimerListHead,
&Timer->ActiveTimerListEntry);
Timer->ApcAssociated = TRUE;
KeReleaseSpinLockFromDpcLevel(&Thread->ActiveTimerListLock);
/* One less dereference to do */
DerefsToDo--;
}
/* Enable and Set the Timer */
KeSetTimerEx(&Timer->KeTimer,
TimerDueTime,
Period,
TimerApcRoutine ? &Timer->TimerDpc : NULL);
/* Unlock the Timer */
KeReleaseSpinLock(&Timer->Lock, OldIrql);
/* Dereference if it was previously enabled */
if (DerefsToDo) ObDereferenceObjectEx(Timer, DerefsToDo);
/* Check if we need to return the State */
if (PreviousState)
{
/* Enter SEH */
_SEH2_TRY
{
/* Return the Timer State */
*PreviousState = State;
}
_SEH2_EXCEPT(ExSystemExceptionFilter())
{
/* Do nothing */
(void)0;
}
_SEH2_END;
}
}
/* Return to Caller */
return Status;
}