[KMTESTS]
authorThomas Faber <thomas.faber@reactos.org>
Sat, 27 Apr 2013 18:18:17 +0000 (18:18 +0000)
committerThomas Faber <thomas.faber@reactos.org>
Sat, 27 Apr 2013 18:18:17 +0000 (18:18 +0000)
- Implement a mechanism for kmtests to retrieve information from user-mode. Patch by Nikolay Borisov (nib9 at aber dot ac dot uk).
- Currently supported function: QueryVirtualMemory
ROSTESTS-96 #resolve

svn path=/trunk/; revision=58873

rostests/kmtests/include/kmt_public.h
rostests/kmtests/include/kmt_test.h
rostests/kmtests/kmtest/support.c
rostests/kmtests/kmtest_drv/kmtest_drv.c

index abb260d..4067af9 100644 (file)
 #define IOCTL_KMTEST_SET_RESULTBUFFER  \
     CTL_CODE(FILE_DEVICE_UNKNOWN, 0x802, METHOD_NEITHER, FILE_READ_DATA | FILE_WRITE_DATA)
 
+#define IOCTL_KMTEST_USERMODE_SEND_RESPONSE \
+    CTL_CODE(FILE_DEVICE_UNKNOWN, 0x803, METHOD_IN_DIRECT, FILE_WRITE_DATA)
+
+#define IOCTL_KMTEST_USERMODE_AWAIT_REQ \
+    CTL_CODE(FILE_DEVICE_UNKNOWN, 0x804, METHOD_BUFFERED, FILE_READ_DATA)
+
 #define KMTEST_DEVICE_NAME L"Kmtest"
 #define KMTEST_DEVICE_DRIVER_PATH L"\\Device\\" KMTEST_DEVICE_NAME
 #define KMTEST_DEVICE_PATH L"\\\\.\\Global\\GLOBALROOT" KMTEST_DEVICE_DRIVER_PATH
index d9ed2d8..b00962a 100644 (file)
@@ -38,6 +38,48 @@ typedef struct
     CHAR LogBuffer[ANYSIZE_ARRAY];
 } KMT_RESULTBUFFER, *PKMT_RESULTBUFFER;
 
+#ifndef KMT_STANDALONE_DRIVER
+
+/* usermode call-back mechanism */
+
+/* list of supported operations */
+typedef enum _KMT_CALLBACK_INFORMATION_CLASS
+{
+    QueryVirtualMemory
+} KMT_CALLBACK_INFORMATION_CLASS, *PKMT_CALLBACK_INFORMATION_CLASS;
+
+/* TODO: "response" is a little generic */
+typedef union _KMT_RESPONSE
+{
+    MEMORY_BASIC_INFORMATION MemInfo;
+} KMT_RESPONSE, *PKMT_RESPONSE;
+
+/* this struct is sent from driver to usermode */
+typedef struct _KMT_CALLBACK_REQUEST_PACKET
+{
+    ULONG RequestId;
+    KMT_CALLBACK_INFORMATION_CLASS OperationClass;
+    PVOID Parameters;
+} KMT_CALLBACK_REQUEST_PACKET, *PKMT_CALLBACK_REQUEST_PACKET;
+
+PKMT_RESPONSE KmtUserModeCallback(KMT_CALLBACK_INFORMATION_CLASS Operation, PVOID Parameters);
+VOID KmtFreeCallbackResponse(PKMT_RESPONSE Response);
+
+//macro to simplify using the mechanism
+#define Test_NtQueryVirtualMemory(BaseAddress, Size, AllocationType, ProtectionType)            \
+    do {                                                                                        \
+    PKMT_RESPONSE NtQueryTest = KmtUserModeCallback(QueryVirtualMemory, BaseAddress);           \
+    if (NtQueryTest != NULL)                                                                    \
+    {                                                                                           \
+        ok_eq_hex(NtQueryTest->MemInfo.Protect, ProtectionType);                                \
+        ok_eq_hex(NtQueryTest->MemInfo.State, AllocationType);                                  \
+        ok_eq_size(NtQueryTest->MemInfo.RegionSize, Size);                                      \
+        KmtFreeCallbackResponse(NtQueryTest);                                                   \
+    }                                                                                           \
+    } while (0)                                                                                 \
+
+#endif
+
 #ifdef KMT_STANDALONE_DRIVER
 #define KMT_KERNEL_MODE
 
index c45c4e4..7f09d28 100644 (file)
 #include <kmt_public.h>
 
 #include <assert.h>
+#include <debug.h>
 
 extern HANDLE KmtestHandle;
 
+/**
+ * @name KmtUserCallbackThread
+ *
+ * Thread routine which awaits callback requests from kernel-mode
+ *
+ * @return Win32 error code
+ */
+DWORD
+WINAPI
+KmtUserCallbackThread(
+    PVOID Parameter)
+{
+    DWORD Error = ERROR_SUCCESS;
+    /* TODO: RequestPacket? */
+    KMT_CALLBACK_REQUEST_PACKET RequestPacket;
+    KMT_RESPONSE Response;
+    DWORD BytesReturned;
+    HANDLE LocalKmtHandle;
+
+    UNREFERENCED_PARAMETER(Parameter);
+
+    /* concurrent IoCtls on the same (non-overlapped) handle aren't possible,
+     * so open a separate one.
+     * For more info http://www.osronline.com/showthread.cfm?link=230782 */
+    LocalKmtHandle = CreateFile(KMTEST_DEVICE_PATH, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
+    if (LocalKmtHandle == INVALID_HANDLE_VALUE)
+        error_goto(Error, cleanup);
+
+    while (1)
+    {
+        if (!DeviceIoControl(LocalKmtHandle, IOCTL_KMTEST_USERMODE_AWAIT_REQ, NULL, 0, &RequestPacket, sizeof(RequestPacket), &BytesReturned, NULL))
+            error_goto(Error, cleanup);
+        ASSERT(BytesReturned == sizeof(RequestPacket));
+
+        switch (RequestPacket.OperationClass)
+        {
+            case QueryVirtualMemory:
+            {
+                SIZE_T InfoBufferSize = VirtualQuery(RequestPacket.Parameters, &Response.MemInfo, sizeof(Response.MemInfo));
+                /* FIXME: an error is a valid result. That should go as a response to kernel mode instead of terminating the thread */
+                if (InfoBufferSize == 0)
+                    error_goto(Error, cleanup);
+
+                if (!DeviceIoControl(LocalKmtHandle, IOCTL_KMTEST_USERMODE_SEND_RESPONSE, &RequestPacket.RequestId, sizeof(RequestPacket.RequestId), &Response, sizeof(Response), &BytesReturned, NULL))
+                    error_goto(Error, cleanup);
+                ASSERT(BytesReturned == 0);
+
+                break;
+            }
+            default:
+                DPRINT1("Unrecognized user-mode callback request\n");
+                break;
+        }
+    }
+
+cleanup:
+    if (LocalKmtHandle != INVALID_HANDLE_VALUE)
+        CloseHandle(LocalKmtHandle);
+
+    DPRINT("Callback handler dying! Error code %lu", Error);
+    return Error;
+}
+
+
 /**
  * @name KmtRunKernelTest
  *
@@ -28,12 +93,18 @@ DWORD
 KmtRunKernelTest(
     IN PCSTR TestName)
 {
+    HANDLE CallbackThread;
     DWORD Error = ERROR_SUCCESS;
     DWORD BytesRead;
 
+    CallbackThread = CreateThread(NULL, 0, KmtUserCallbackThread, NULL, 0, NULL);
+
     if (!DeviceIoControl(KmtestHandle, IOCTL_KMTEST_RUN_TEST, (PVOID)TestName, (DWORD)strlen(TestName), NULL, 0, &BytesRead, NULL))
         error(Error);
 
+    if (CallbackThread != NULL)
+         CloseHandle(CallbackThread);
+
     return Error;
 }
 
index 580eb0f..9e9749d 100644 (file)
 #define KMT_DEFINE_TEST_FUNCTIONS
 #include <kmt_test.h>
 
+/* Usermode callback definitions */
+typedef struct _KMT_USER_WORK_ENTRY
+{
+    LIST_ENTRY ListEntry;
+    KEVENT WorkDoneEvent;
+    KMT_CALLBACK_REQUEST_PACKET Request;
+    PKMT_RESPONSE Response;
+} KMT_USER_WORK_ENTRY, *PKMT_USER_WORK_ENTRY;
+
+typedef struct _KMT_USER_WORK_LIST
+{
+    LIST_ENTRY ListHead;
+    FAST_MUTEX Lock;
+    KEVENT NewWorkEvent;
+} KMT_USER_WORK_LIST, *PKMT_USER_WORK_LIST;
+
 /* Prototypes */
 DRIVER_INITIALIZE DriverEntry;
 static DRIVER_UNLOAD DriverUnload;
@@ -30,10 +46,13 @@ __drv_dispatchType(IRP_MJ_CLOSE)
 static DRIVER_DISPATCH DriverClose;
 __drv_dispatchType(IRP_MJ_DEVICE_CONTROL)
 static DRIVER_DISPATCH DriverIoControl;
+static VOID KmtCleanUsermodeCallbacks(VOID);
 
 /* Globals */
 static PDEVICE_OBJECT MainDeviceObject;
 PDRIVER_OBJECT KmtDriverObject = NULL;
+static KMT_USER_WORK_LIST WorkList;
+static ULONG RequestId = 0;
 
 /* Entry */
 /**
@@ -92,6 +111,10 @@ DriverEntry(
     DriverObject->MajorFunction[IRP_MJ_CLOSE] = DriverClose;
     DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DriverIoControl;
 
+    ExInitializeFastMutex(&WorkList.Lock);
+    KeInitializeEvent(&WorkList.NewWorkEvent, NotificationEvent, FALSE);
+    InitializeListHead(&WorkList.ListHead);
+
 cleanup:
     if (MainDeviceObject && !NT_SUCCESS(Status))
     {
@@ -123,6 +146,8 @@ DriverUnload(
 
     DPRINT("DriverUnload\n");
 
+    KmtCleanUsermodeCallbacks();
+
     if (MainDeviceObject)
     {
         PKMT_DEVICE_EXTENSION DeviceExtension = MainDeviceObject->DeviceExtension;
@@ -403,6 +428,93 @@ DriverIoControl(
                     ResultBuffer->LogBufferLength, ResultBuffer->LogBufferMaxLength);
             break;
         }
+        case IOCTL_KMTEST_USERMODE_AWAIT_REQ:
+        {
+            PLIST_ENTRY Entry;
+            PKMT_USER_WORK_ENTRY WorkItem;
+
+            DPRINT("DriverIoControl. IOCTL_KMTEST_USERMODE_AWAIT_REQ, len=%lu\n",
+                    IoStackLocation->Parameters.DeviceIoControl.OutputBufferLength);
+
+            /* TODO: prevent multiple concurrent invocations */
+            Status = KeWaitForSingleObject(&WorkList.NewWorkEvent, UserRequest, UserMode, FALSE, NULL);
+            if (Status == STATUS_USER_APC || Status == STATUS_KERNEL_APC)
+                break;
+
+            if (IoStackLocation->Parameters.DeviceIoControl.OutputBufferLength < sizeof(KMT_CALLBACK_REQUEST_PACKET))
+            {
+                Status = STATUS_INVALID_BUFFER_SIZE;
+                break;
+            }
+
+            ASSERT(!IsListEmpty(&WorkList.ListHead));
+
+            Entry = WorkList.ListHead.Flink;
+            WorkItem = CONTAINING_RECORD(Entry, KMT_USER_WORK_ENTRY, ListEntry);
+
+            Length = sizeof(WorkItem->Request);
+            RtlCopyMemory(Irp->AssociatedIrp.SystemBuffer, &WorkItem->Request, Length);
+            Status = STATUS_SUCCESS;
+
+            KeResetEvent(&WorkList.NewWorkEvent);
+            break;
+
+        }
+        case IOCTL_KMTEST_USERMODE_SEND_RESPONSE:
+        {
+            PLIST_ENTRY Entry;
+            PKMT_USER_WORK_ENTRY WorkEntry;
+            PVOID Response;
+            ULONG ResponseSize = IoStackLocation->Parameters.DeviceIoControl.OutputBufferLength;
+
+            DPRINT("DriverIoControl. IOCTL_KMTEST_USERMODE_SEND_RESPONSE, inlen=%lu, outlen=%lu\n",
+                    IoStackLocation->Parameters.DeviceIoControl.InputBufferLength,
+                    IoStackLocation->Parameters.DeviceIoControl.OutputBufferLength);
+
+            if (IoStackLocation->Parameters.DeviceIoControl.InputBufferLength != sizeof(ULONG) || ResponseSize != sizeof(KMT_RESPONSE))
+            {
+                Status = STATUS_INVALID_BUFFER_SIZE;
+                break;
+            }
+
+            /* FIXME: don't misuse the output buffer as an input! */
+            Response = MmGetSystemAddressForMdlSafe(Irp->MdlAddress, NormalPagePriority);
+            if (Response == NULL)
+            {
+                Status = STATUS_INSUFFICIENT_RESOURCES;
+                break;
+            }
+
+            ExAcquireFastMutex(&WorkList.Lock);
+
+            Status = STATUS_OBJECTID_NOT_FOUND;
+
+            Entry = WorkList.ListHead.Flink;
+            while (Entry != &WorkList.ListHead)
+            {
+                WorkEntry = CONTAINING_RECORD(Entry, KMT_USER_WORK_ENTRY, ListEntry);
+                if (WorkEntry->Request.RequestId == *(PULONG)Irp->AssociatedIrp.SystemBuffer)
+                {
+                    WorkEntry->Response = ExAllocatePoolWithTag(PagedPool, sizeof(KMT_RESPONSE), 'pseR');
+                    if (WorkEntry->Response == NULL)
+                    {
+                        Status = STATUS_INSUFFICIENT_RESOURCES;
+                        break;
+                    }
+
+                    RtlCopyMemory(WorkEntry->Response, Response, ResponseSize);
+                    KeSetEvent(&WorkEntry->WorkDoneEvent, IO_NO_INCREMENT, FALSE);
+                    Status = STATUS_SUCCESS;
+                    break;
+                }
+
+                Entry = Entry->Flink;
+            }
+
+            ExReleaseFastMutex(&WorkList.Lock);
+
+            break;
+        }
         default:
             DPRINT1("DriverIoControl. Invalid IoCtl code 0x%08X\n",
                      IoStackLocation->Parameters.DeviceIoControl.IoControlCode);
@@ -417,3 +529,113 @@ DriverIoControl(
 
     return Status;
 }
+
+/**
+ * @name KmtUserModeCallback
+ *
+ * Enqueue a request to the usermode callback queue and blocks until the work
+ * is finished.
+ *
+ * @param Operation
+ *        TODO
+ * @param Parameters
+ *        TODO
+ *        TODO: why is this PVOID?
+ *
+ * @return Response from user mode
+ */
+PKMT_RESPONSE
+KmtUserModeCallback(
+    IN KMT_CALLBACK_INFORMATION_CLASS Operation,
+    IN PVOID Parameters)
+{
+    PKMT_RESPONSE Result;
+    NTSTATUS Status;
+    PKMT_USER_WORK_ENTRY WorkEntry;
+    LARGE_INTEGER Timeout;
+
+    PAGED_CODE();
+
+    WorkEntry = ExAllocatePoolWithTag(PagedPool, sizeof(KMT_USER_WORK_ENTRY), 'ekrW');
+    if (WorkEntry == NULL)
+        return NULL;
+
+    KeInitializeEvent(&WorkEntry->WorkDoneEvent, NotificationEvent, FALSE);
+    WorkEntry->Request.RequestId = RequestId++;
+    WorkEntry->Request.OperationClass = Operation;
+    WorkEntry->Request.Parameters = Parameters;
+    WorkEntry->Response = NULL;
+
+    ExAcquireFastMutex(&WorkList.Lock);
+    InsertTailList(&WorkList.ListHead, &WorkEntry->ListEntry);
+    ExReleaseFastMutex(&WorkList.Lock);
+
+    KeSetEvent(&WorkList.NewWorkEvent, IO_NO_INCREMENT, FALSE);
+
+    Timeout.QuadPart = -10 * 1000 * 1000 * 10; //wait for 10 seconds
+    Status = KeWaitForSingleObject(&WorkEntry->WorkDoneEvent, Executive, UserMode, FALSE, &Timeout);
+
+    if (Status == STATUS_USER_APC || Status == STATUS_KERNEL_APC || Status == STATUS_TIMEOUT)
+    {
+        DPRINT1("Unexpected callback abortion! Reason: %lx\n", Status);
+    }
+
+    ExAcquireFastMutex(&WorkList.Lock);
+    RemoveEntryList(&WorkEntry->ListEntry);
+    ExReleaseFastMutex(&WorkList.Lock);
+
+    Result = WorkEntry->Response;
+
+    ExFreePoolWithTag(WorkEntry, 'ekrW');
+
+    return Result;
+}
+
+/**
+ * @name KmtFreeCallbackResponse
+ *
+ * TODO
+ *
+ * @param Response
+ *        TODO
+ */
+VOID
+KmtFreeCallbackResponse(
+    PKMT_RESPONSE Response)
+{
+    PAGED_CODE();
+
+    ExFreePoolWithTag(Response, 'pseR');
+}
+
+/**
+ * @name KmtCleanUsermodeCallbacks
+ *
+ * TODO
+ */
+static
+VOID
+KmtCleanUsermodeCallbacks(VOID)
+{
+    PLIST_ENTRY Entry;
+
+    PAGED_CODE();
+
+    ExAcquireFastMutex(&WorkList.Lock);
+
+    Entry = WorkList.ListHead.Flink;
+    while (Entry != &WorkList.ListHead)
+    {
+        PKMT_USER_WORK_ENTRY WorkEntry = CONTAINING_RECORD(Entry, KMT_USER_WORK_ENTRY, ListEntry);
+        if (WorkEntry->Response != NULL)
+        {
+            KmtFreeCallbackResponse(WorkEntry->Response);
+        }
+
+        Entry = Entry->Flink;
+
+        ExFreePoolWithTag(WorkEntry, 'ekrW');
+    }
+
+    ExReleaseFastMutex(&WorkList.Lock);
+}