[KMTESTS]

- 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
This commit is contained in:
Thomas Faber 2013-04-27 18:18:17 +00:00
parent b74ef49f87
commit 4c309ccd7f
4 changed files with 341 additions and 0 deletions

View file

@ -17,6 +17,12 @@
#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

View 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

View file

@ -11,9 +11,74 @@
#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;
}

View file

@ -19,6 +19,22 @@
#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);
}