reactos/ntoskrnl/ex/timer.c
George Bișoc a330b56787
[NTOS:PS] Enable alignment probing for thread/process information classes
In addition to that, here are some stuff done in this commit whilst testing:

- ICIF_QUERY_SIZE_VARIABLE and friends were badly misused, they should be used only when an information class whose information length size is dyanmic and not fixed. By removing such flags from erroneous classes, this fixes the STATUS_INFO_LENGTH_MISMATCH testcases.

- Use CHAR instead of UCHAR for classes that do not need alignment probing, as every other class in the table do, for the sake of consistency.

- ProcessEnableAlignmentFaultFixup uses BOOLEAN as type size, not CHAR. This fixes a testcase failure on ROS.

- Check for information length size before proceeding further on querying the process' cookie information.

- ProcessHandleTracing wants an alignment of a ULONG, not CHAR.

- Move PROCESS_LDT_INFORMATION and PROCESS_LDT_SIZE outside of NTOS_MODE_USER macro case. This fixes a compilation issue when enabling the alignment probing. My mistake of having them inside NTOS_MODE_USER case, sorry.

- On functions like NtQueryInformationThread and the Process equivalent, complete probing is not done at the beginning of the function, complete probing including if the buffer is writable alongside with datatype misalignment check that is. Instead such check is done on each information class case basis. With that said, we have to explicitly tell DefaultQueryInfoBufferCheck if we want a complete probing or not initially.
2021-06-06 17:14:22 +02:00

758 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]),
TimerInformation,
TimerInformationLength,
ReturnLength,
NULL,
PreviousMode,
TRUE);
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 (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;
}