From 1d3718c3334ca66074ea6c79611a741765d1e824 Mon Sep 17 00:00:00 2001 From: Michael Agun Date: Wed, 5 Aug 2026 13:39:06 -0700 Subject: [PATCH] Add KeDelayExecutionThread mock Add a user-mode mock for KeDelayExecutionThread, the WDM primitive a kernel thread uses to delay its own execution for a relative or absolute interval. This API was not previously provided by usersim. - inc/usersim/ke.h: declare KeDelayExecutionThread with SAL matching the WDK wdm.h contract (_IRQL_requires_min_(PASSIVE_LEVEL), _IRQL_requires_max_(APC_LEVEL)). - src/ke.cpp: mock implementation. A negative Interval is a delay relative to the current time, converted from 100-ns units to milliseconds (rounded up) and passed to Sleep; a non-negative (absolute) Interval is not tracked by the mock and is treated as no delay. Returns STATUS_SUCCESS (non-alertable waits only; STATUS_ALERTED/STATUS_USER_APC are not modeled). - tests/ke_test.cpp: add a test verifying a relative interval blocks for at least the requested duration and a zero interval returns immediately. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 3e1f4d8c-9f26-40cc-ae7d-089fa6c8f959 --- inc/usersim/ke.h | 7 +++++++ src/ke.cpp | 23 +++++++++++++++++++++++ tests/ke_test.cpp | 21 +++++++++++++++++++++ 3 files changed, 51 insertions(+) diff --git a/inc/usersim/ke.h b/inc/usersim/ke.h index 4413cc8..da77b46 100644 --- a/inc/usersim/ke.h +++ b/inc/usersim/ke.h @@ -259,6 +259,13 @@ _IRQL_requires_min_(PASSIVE_LEVEL) _When_((timeout == NULL || timeout->QuadPart _In_ BOOLEAN alertable, _In_opt_ PLARGE_INTEGER timeout); +USERSIM_API +_IRQL_requires_min_(PASSIVE_LEVEL) _IRQL_requires_max_(APC_LEVEL) NTKERNELAPI NTSTATUS + KeDelayExecutionThread( + _In_ __drv_strictType(KPROCESSOR_MODE / enum _MODE, __drv_typeConst) KPROCESSOR_MODE wait_mode, + _In_ BOOLEAN alertable, + _In_ PLARGE_INTEGER interval); + USERSIM_API _IRQL_requires_same_ ULONG64 KeQueryUnbiasedInterruptTimePrecise(_Out_ PULONG64 qpc_time_stamp); diff --git a/src/ke.cpp b/src/ke.cpp index cf26c71..c38be09 100644 --- a/src/ke.cpp +++ b/src/ke.cpp @@ -532,6 +532,29 @@ KeReadStateSemaphore(_In_ PRKSEMAPHORE semaphore) #pragma endregion semaphores +_IRQL_requires_min_(PASSIVE_LEVEL) _IRQL_requires_max_(APC_LEVEL) NTKERNELAPI NTSTATUS + KeDelayExecutionThread( + _In_ __drv_strictType(KPROCESSOR_MODE / enum _MODE, __drv_typeConst) KPROCESSOR_MODE wait_mode, + _In_ BOOLEAN alertable, + _In_ PLARGE_INTEGER interval) +{ + UNREFERENCED_PARAMETER(wait_mode); + UNREFERENCED_PARAMETER(alertable); + + // Interval is expressed in 100-ns units. A negative value is a delay relative to the current time; a + // non-negative value is an absolute expiration time, which the user-mode mock does not track and therefore + // treats as no delay. + DWORD delay_ms = 0; + if (interval->QuadPart < 0) { + // Convert the relative 100-ns interval to milliseconds, rounding up so a sub-millisecond request still + // yields a non-zero wait. + delay_ms = (DWORD)((-interval->QuadPart + 9999) / 10000); + } + + Sleep(delay_ms); + return STATUS_SUCCESS; +} + _IRQL_requires_max_(APC_LEVEL) NTKERNELAPI VOID KeStackAttachProcess(_Inout_ PRKPROCESS process, _Out_ PRKAPC_STATE apc_state) { diff --git a/tests/ke_test.cpp b/tests/ke_test.cpp index 1fedaee..8019fa2 100644 --- a/tests/ke_test.cpp +++ b/tests/ke_test.cpp @@ -489,3 +489,24 @@ TEST_CASE("event", "[ke]") REQUIRE(wait_status == STATUS_TIMEOUT); REQUIRE(end_time - start_time >= 1000); } + +TEST_CASE("KeDelayExecutionThread", "[ke]") +{ + // A negative interval is a relative delay; the call should block for at least the requested duration. + LARGE_INTEGER interval = {0}; + interval.QuadPart = -10 * 1000 * 10ll; // 10 ms, expressed in 100-ns units. + + uint64_t qpc_time; + uint64_t start_time = KeQueryUnbiasedInterruptTimePrecise(&qpc_time); + + REQUIRE(KeDelayExecutionThread(KernelMode, FALSE, &interval) == STATUS_SUCCESS); + + uint64_t end_time = KeQueryUnbiasedInterruptTimePrecise(&qpc_time); + + // Returned time is in 100-ns units. Assert a conservative lower bound (1 ms) to avoid timer-granularity flakiness. + REQUIRE(end_time - start_time >= 10000); + + // A zero interval must return immediately without blocking. + interval.QuadPart = 0; + REQUIRE(KeDelayExecutionThread(KernelMode, FALSE, &interval) == STATUS_SUCCESS); +}