blob: 81188b1baad6d2052c04ad0fa06edb2ed8ddda7b [file] [log] [blame]
/*
* Copyright (c) 2016, The OpenThread Authors.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the copyright holder nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#include "precomp.h"
#include "device.tmh"
// IoControl Device Object from IoCreateDeviceSecure
PDEVICE_OBJECT IoDeviceObject = NULL;
// Global context for device control callbacks
POTLWF_DEVICE_EXTENSION FilterDeviceExtension = NULL;
/*
Powershell script to generate security desciptors:
$sddl = "D:P(A;;GA;;;SY)(A;;GA;;;NS)(A;;GA;;;BA)(A;;GA;;;WD)(A;;GA;;;S-1-15-3-3)"
$blob = ([wmiclass]"Win32_SecurityDescriptorHelper").SDDLToBinarySD($sddl).BinarySD
$string = [BitConverter]::ToString($blob)
$string = $string -replace '-', ''
$string = $string -replace '(..)(..)(..)(..)', '0x$4$3$2$1, '
$string -replace '(.{10}, .{10}, .{10}, .{10},) ', "$&`n"
*/
const unsigned long c_sdThreadLwf[] =
{
0x90040001, 0x00000000, 0x00000000, 0x00000000,
0x00000014, 0x00740002, 0x00000005, 0x00140000,
0x10000000, 0x00000101, 0x05000000, 0x00000012,
0x00140000, 0x10000000, 0x00000101, 0x05000000,
0x00000014, 0x00180000, 0x10000000, 0x00000201,
0x05000000, 0x00000020, 0x00000220, 0x00140000,
0x10000000, 0x00000101, 0x01000000, 0x00000000,
0x00180000, 0x10000000, 0x00000201, 0x0F000000,
0x00000003, 0x00000003
};
_No_competing_thread_
INITCODE
NDIS_STATUS
otLwfRegisterDevice(
VOID
)
{
NTSTATUS Status = NDIS_STATUS_SUCCESS;
UNICODE_STRING DeviceName;
UNICODE_STRING DeviceLinkUnicodeString;
PDEVICE_OBJECT DeviceObject;
LogFuncEntry(DRIVER_DEFAULT);
NT_ASSERT(FilterDeviceExtension == NULL);
NdisInitUnicodeString(&DeviceName, NTDEVICE_STRING);
NdisInitUnicodeString(&DeviceLinkUnicodeString, LINKNAME_STRING);
Status = IoCreateDeviceSecure(FilterDriverObject, // DriverObject
sizeof(OTLWF_DEVICE_EXTENSION), // DeviceExtension
&DeviceName, // DeviceName
FILE_DEVICE_NETWORK, // DeviceType
FILE_DEVICE_SECURE_OPEN, // DeviceCharacteristics
FALSE, // Exclusive
&SDDL_DEVOBJ_KERNEL_ONLY, // security attributes
NULL, // security override device class
&DeviceObject); // DeviceObject
if (NT_SUCCESS(Status))
{
DeviceObject->Flags &= ~DO_DEVICE_INITIALIZING;
Status = IoCreateSymbolicLink(&DeviceLinkUnicodeString, &DeviceName);
if (!NT_SUCCESS(Status))
{
LogError(DRIVER_DEFAULT, "IoCreateSymbolicLink failed, %!STATUS!", Status);
IoDeleteDevice(DeviceObject);
}
else
{
FilterDeviceExtension = (POTLWF_DEVICE_EXTENSION)DeviceObject->DeviceExtension;
RtlZeroMemory(FilterDeviceExtension, sizeof(OTLWF_DEVICE_EXTENSION));
FilterDeviceExtension->Signature = 'FTDR';
FilterDeviceExtension->Handle = FilterDriverHandle;
NdisAllocateSpinLock(&FilterDeviceExtension->Lock);
InitializeListHead(&FilterDeviceExtension->ClientList);
#pragma push
#pragma warning(disable:28168) // The function 'otLwfDispatch' does not have a _Dispatch_type_ annotation matching dispatch table position *
FilterDriverObject->MajorFunction[IRP_MJ_CREATE] = otLwfDispatch;
FilterDriverObject->MajorFunction[IRP_MJ_CLEANUP] = otLwfDispatch;
FilterDriverObject->MajorFunction[IRP_MJ_CLOSE] = otLwfDispatch;
FilterDriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = otLwfDeviceIoControl;
#pragma pop
HANDLE fileHandle;
Status = ObOpenObjectByPointer(DeviceObject,
OBJ_KERNEL_HANDLE,
NULL,
WRITE_DAC,
0,
KernelMode,
&fileHandle);
if (NT_SUCCESS(Status))
{
Status = ZwSetSecurityObject(fileHandle,
DACL_SECURITY_INFORMATION,
(PSECURITY_DESCRIPTOR)c_sdThreadLwf);
if (!NT_SUCCESS(Status))
{
LogError(DRIVER_DEFAULT, "ZwSetSecurityObject failed, %!STATUS!", Status);
}
ZwClose(fileHandle);
}
else
{
LogError(DRIVER_DEFAULT, "ObOpenObjectByPointer failed, %!STATUS!", Status);
}
IoDeviceObject = DeviceObject;
}
}
else
{
LogError(DRIVER_DEFAULT, "IoCreateDeviceSecure failed, %!STATUS!", Status);
}
LogFuncExitNT(DRIVER_DEFAULT, Status);
return (NDIS_STATUS)Status;
}
PIRP
otLwfDeviceClientCleanup(
POTLWF_DEVICE_CLIENT DeviceClient
)
{
PIRP IrpToCancel = NULL;
// Clean the FileObject context
DeviceClient->FileObject->FsContext2 = NULL;
// Release pending IRP
if (DeviceClient->PendingNotificationIRP)
{
IrpToCancel = DeviceClient->PendingNotificationIRP;
DeviceClient->PendingNotificationIRP = NULL;
}
// Free all pending notifications
NT_ASSERT(DeviceClient->NotificationSize <= OTLWF_MAX_PENDING_NOTIFICATIONS_PER_CLIENT);
for (UCHAR i = 0; i < DeviceClient->NotificationSize; i++)
{
UCHAR index = (DeviceClient->NotificationOffset + i) % OTLWF_MAX_PENDING_NOTIFICATIONS_PER_CLIENT;
otLwfReleaseNotification(DeviceClient->PendingNotifications[index]);
}
return IrpToCancel;
}
_No_competing_thread_
_IRQL_requires_max_(PASSIVE_LEVEL)
VOID
otLwfDeregisterDevice(
VOID
)
{
LogFuncEntry(DRIVER_DEFAULT);
if (IoDeviceObject != NULL)
{
NT_ASSERT(FilterDeviceExtension);
NdisFreeSpinLock(&FilterDeviceExtension->Lock);
// Clean up all pending clients
PLIST_ENTRY Link = FilterDeviceExtension->ClientList.Flink;
while (Link != &FilterDeviceExtension->ClientList)
{
POTLWF_DEVICE_CLIENT DeviceClient = CONTAINING_RECORD(Link, OTLWF_DEVICE_CLIENT, Link);
PIRP IrpToCancel = NULL;
// Set next link
Link = Link->Flink;
// Make sure to clean up any left overs from the device client
IrpToCancel = otLwfDeviceClientCleanup(DeviceClient);
// Complete the pending IRP since we are shutting down
if (IrpToCancel)
{
// Before we are allowed to complete the pending IRP, we must remove the cancel routine
KIRQL irql;
IoAcquireCancelSpinLock(&irql);
IoSetCancelRoutine(IrpToCancel, NULL);
IoReleaseCancelSpinLock(irql);
IrpToCancel->IoStatus.Status = STATUS_CANCELLED;
IrpToCancel->IoStatus.Information = 0;
IoCompleteRequest(IrpToCancel, IO_NO_INCREMENT);
}
// Remove the device client from the list
RemoveEntryList(&DeviceClient->Link);
// Delete the device client
NdisFreeMemory(DeviceClient, 0, 0);
}
IoDeleteDevice(IoDeviceObject);
}
IoDeviceObject = NULL;
LogFuncExit(DRIVER_DEFAULT);
}
_Use_decl_annotations_
NTSTATUS
otLwfDispatch(
PDEVICE_OBJECT DeviceObject,
PIRP Irp
)
{
PIO_STACK_LOCATION IrpStack;
NTSTATUS Status = STATUS_SUCCESS;
PIRP IrpToCancel = NULL;
POTLWF_DEVICE_CLIENT DeviceClient = NULL;
UNREFERENCED_PARAMETER(DeviceObject);
LogFuncEntry(DRIVER_IOCTL);
IrpStack = IoGetCurrentIrpStackLocation(Irp);
NdisAcquireSpinLock(&FilterDeviceExtension->Lock);
switch (IrpStack->MajorFunction)
{
case IRP_MJ_CREATE:
LogInfo(DRIVER_IOCTL, "Client %p attached.", IrpStack->FileObject);
if (FilterDeviceExtension->ClientListSize >= OTLWF_MAX_CLIENTS)
{
LogError(DRIVER_IOCTL, "Already have max clients!");
Status = STATUS_TOO_MANY_SESSIONS;
break;
}
DeviceClient = FILTER_ALLOC_DEVICE_CLIENT();
if (DeviceClient)
{
RtlZeroMemory(DeviceClient, sizeof(OTLWF_DEVICE_CLIENT));
DeviceClient->FileObject = IrpStack->FileObject;
NT_ASSERT(IrpStack->FileObject->FsContext2 == NULL);
IrpStack->FileObject->FsContext2 = DeviceClient;
// Insert into the client list
InsertTailList(&FilterDeviceExtension->ClientList, &DeviceClient->Link);
FilterDeviceExtension->ClientListSize++;
}
else
{
Status = STATUS_INSUFFICIENT_RESOURCES;
}
break;
case IRP_MJ_CLEANUP:
LogInfo(DRIVER_IOCTL, "Client %p cleaning up.", IrpStack->FileObject);
DeviceClient = (POTLWF_DEVICE_CLIENT)IrpStack->FileObject->FsContext2;
// Make sure to clean up any left overs from the device client
IrpToCancel = otLwfDeviceClientCleanup(DeviceClient);
// Remove the device client from the list
RemoveEntryList(&DeviceClient->Link);
FilterDeviceExtension->ClientListSize--;
// Delete the device client
NdisFreeMemory(DeviceClient, 0, 0);
break;
case IRP_MJ_CLOSE:
LogInfo(DRIVER_IOCTL, "Client %p detatched.", IrpStack->FileObject);
break;
default:
break;
}
NdisReleaseSpinLock(&FilterDeviceExtension->Lock);
// Cancel the pending notification IRP if set
if (IrpToCancel)
{
// Complete the pending IRP
IrpToCancel->IoStatus.Status = STATUS_CANCELLED;
IrpToCancel->IoStatus.Information = 0;
IoCompleteRequest(IrpToCancel, IO_NO_INCREMENT);
}
Irp->IoStatus.Status = Status;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
LogFuncExitNT(DRIVER_IOCTL, Status);
return Status;
}
_Use_decl_annotations_
NTSTATUS
otLwfDeviceIoControl(
PDEVICE_OBJECT DeviceObject,
PIRP Irp
)
{
NTSTATUS status = STATUS_SUCCESS;
BOOLEAN CompleteIRP = TRUE;
PVOID IoBuffer = Irp->AssociatedIrp.SystemBuffer;
PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);
ULONG InputBufferLength = IrpSp->Parameters.DeviceIoControl.InputBufferLength;
ULONG OutputBufferLength = IrpSp->Parameters.DeviceIoControl.OutputBufferLength;
ULONG IoControlCode = IrpSp->Parameters.DeviceIoControl.IoControlCode;
ULONG FuncCode = (IoControlCode >> 2) & 0xFFF;
LogFuncEntryMsg(DRIVER_IOCTL, "%p", IrpSp->FileObject);
#if DBG
ASSERT(((POTLWF_DEVICE_EXTENSION)DeviceObject->DeviceExtension)->Signature == 'FTDR');
#else
UNREFERENCED_PARAMETER(DeviceObject);
#endif
// We only allow PASSIVE_LEVEL calls
if (KeGetCurrentIrql() > PASSIVE_LEVEL)
{
LogWarning(DRIVER_IOCTL, "FilterDeviceIoControl called higher than PASSIVE.");
status = STATUS_NOT_SUPPORTED;
RtlZeroMemory(IoBuffer, OutputBufferLength);
OutputBufferLength = 0;
goto error;
}
if (FuncCode >= MIN_OTLWF_IOCTL_FUNC_CODE && FuncCode <= MAX_OTLWF_IOCTL_FUNC_CODE)
{
CompleteIRP = FALSE;
status = otLwfIoCtlOpenThreadControl(Irp);
goto error;
}
// Check the IoControlCode to determine which IOCTL we are processing
switch (IoControlCode)
{
case IOCTL_OTLWF_QUERY_NOTIFICATION:
CompleteIRP = FALSE;
status = otLwfQueryNextNotification(Irp);
break;
case IOCTL_OTLWF_ENUMERATE_DEVICES:
status =
otLwfIoCtlEnumerateInterfaces(
IoBuffer, InputBufferLength,
IoBuffer, &OutputBufferLength
);
break;
case IOCTL_OTLWF_QUERY_DEVICE:
status =
otLwfIoCtlQueryInterface(
IoBuffer, InputBufferLength,
IoBuffer, &OutputBufferLength
);
break;
default:
status = STATUS_NOT_IMPLEMENTED;
RtlZeroMemory(IoBuffer, OutputBufferLength);
OutputBufferLength = 0;
break;
}
error:
if (CompleteIRP)
{
Irp->IoStatus.Status = status;
Irp->IoStatus.Information = OutputBufferLength;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
}
LogFuncExitNT(DRIVER_IOCTL, status);
return status;
}
_Use_decl_annotations_
PMS_FILTER
otLwfFindAndRefInterface(
_In_ PGUID InterfaceGuid
)
{
PMS_FILTER pOutput = NULL;
NdisAcquireSpinLock(&FilterListLock);
for (PLIST_ENTRY Link = FilterModuleList.Flink; Link != &FilterModuleList; Link = Link->Flink)
{
PMS_FILTER pFilter = CONTAINING_RECORD(Link, MS_FILTER, FilterModuleLink);
if (pFilter->State == FilterRunning &&
memcmp(InterfaceGuid, &pFilter->InterfaceGuid, sizeof(GUID)) == 0)
{
if (ExAcquireRundownProtection(&pFilter->ExternalRefs))
{
pOutput = pFilter;
}
break;
}
}
NdisReleaseSpinLock(&FilterListLock);
return pOutput;
}
//
// Notification Functions
//
_IRQL_requires_max_(DISPATCH_LEVEL)
VOID
otLwfReleaseNotification(
_In_ PFILTER_NOTIFICATION_ENTRY NotifEntry
)
{
if (RtlDecrementReferenceCount(&NotifEntry->RefCount))
{
NdisFreeMemory(NotifEntry, 0, 0);
}
}
// Indicates a new notification
_IRQL_requires_max_(DISPATCH_LEVEL)
VOID
otLwfIndicateNotification(
_In_ PFILTER_NOTIFICATION_ENTRY NotifEntry
)
{
PIRP IrpsToComplete[OTLWF_MAX_CLIENTS] = {0};
UCHAR IrpOffset = 0;
LogFuncEntry(DRIVER_IOCTL);
// Initialize with a local ref
NotifEntry->RefCount = 1;
if (FilterDeviceExtension == NULL) goto error;
NdisAcquireSpinLock(&FilterDeviceExtension->Lock);
// Pend the notification for each client
PLIST_ENTRY Link = FilterDeviceExtension->ClientList.Flink;
while (Link != &FilterDeviceExtension->ClientList)
{
POTLWF_DEVICE_CLIENT DeviceClient = CONTAINING_RECORD(Link, OTLWF_DEVICE_CLIENT, Link);
// Set next link
Link = Link->Flink;
KIRQL irql;
IoAcquireCancelSpinLock(&irql);
// If there are other pending notifications or we don't have a pending IRP saved
// then just go ahead and add the notification to the list
NT_ASSERT(DeviceClient->NotificationSize <= OTLWF_MAX_PENDING_NOTIFICATIONS_PER_CLIENT);
if (DeviceClient->NotificationSize != 0 ||
DeviceClient->PendingNotificationIRP == NULL
)
{
// Calculate the next index
UCHAR Index = (DeviceClient->NotificationOffset + DeviceClient->NotificationSize) % OTLWF_MAX_PENDING_NOTIFICATIONS_PER_CLIENT;
// Add additional ref to the notif
RtlIncrementReferenceCount(&NotifEntry->RefCount);
// If we are at the max already, release the oldest
if (DeviceClient->NotificationSize == OTLWF_MAX_PENDING_NOTIFICATIONS_PER_CLIENT)
{
LogWarning(DRIVER_IOCTL, "Dropping old notification!");
otLwfReleaseNotification(DeviceClient->PendingNotifications[DeviceClient->NotificationOffset]);
DeviceClient->NotificationOffset = (DeviceClient->NotificationOffset + 1) % OTLWF_MAX_PENDING_NOTIFICATIONS_PER_CLIENT;
}
else
{
DeviceClient->NotificationSize++;
}
// Copy the notification to the next space
DeviceClient->PendingNotifications[Index] = NotifEntry;
}
else
{
// Before we are allowed to complete the pending IRP, we must remove the cancel routine
IoSetCancelRoutine(DeviceClient->PendingNotificationIRP, NULL);
IrpsToComplete[IrpOffset] = DeviceClient->PendingNotificationIRP;
IrpOffset++;
DeviceClient->PendingNotificationIRP = NULL;
}
// Release the cancel spin lock
IoReleaseCancelSpinLock(irql);
}
NdisReleaseSpinLock(&FilterDeviceExtension->Lock);
// Complete any IRPs now, outside the lock
for (UCHAR i = 0; i < IrpOffset; i++)
{
PIRP IrpToComplete = IrpsToComplete[i];
// Copy the notification payload
PVOID IoBuffer = IrpToComplete->AssociatedIrp.SystemBuffer;
memcpy(IoBuffer, &NotifEntry->Notif, sizeof(OTLWF_NOTIFICATION));
IrpToComplete->IoStatus.Information = sizeof(OTLWF_NOTIFICATION);
// Complete the IRP
IrpToComplete->IoStatus.Status = STATUS_SUCCESS;
IoCompleteRequest(IrpToComplete, IO_NO_INCREMENT);
}
error:
// Release local ref on the notification
otLwfReleaseNotification(NotifEntry);
LogFuncExit(DRIVER_IOCTL);
}
DRIVER_CANCEL otLwfQueryNotificationCancelled;
_Use_decl_annotations_
VOID
otLwfQueryNotificationCancelled(
_Inout_ PDEVICE_OBJECT DeviceObject,
_Inout_ _IRQL_uses_cancel_ struct _IRP *Irp
)
{
UNREFERENCED_PARAMETER(DeviceObject);
LogFuncEntry(DRIVER_IOCTL);
PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);
POTLWF_DEVICE_CLIENT DeviceClient = (POTLWF_DEVICE_CLIENT)IrpSp->FileObject->FsContext2;
if (DeviceClient)
{
DeviceClient->PendingNotificationIRP = NULL;
}
IoReleaseCancelSpinLock(Irp->CancelIrql);
Irp->IoStatus.Status = STATUS_CANCELLED;
Irp->IoStatus.Information = 0;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
LogFuncExit(DRIVER_IOCTL);
}
// Queries the next notification
_IRQL_requires_max_(DISPATCH_LEVEL)
NTSTATUS
otLwfQueryNextNotification(
_In_ PIRP Irp
)
{
NTSTATUS status = STATUS_SUCCESS;
POTLWF_DEVICE_CLIENT DeviceClient = NULL;
PFILTER_NOTIFICATION_ENTRY NotifEntry = NULL;
LogFuncEntry(DRIVER_IOCTL);
PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);
ULONG OutputBufferLength = IrpSp->Parameters.DeviceIoControl.OutputBufferLength;
// Validate we have a big enough buffer
if (OutputBufferLength < sizeof(OTLWF_NOTIFICATION))
{
RtlZeroMemory(Irp->AssociatedIrp.SystemBuffer, OutputBufferLength);
status = STATUS_INSUFFICIENT_RESOURCES;
goto error;
}
DeviceClient = (POTLWF_DEVICE_CLIENT)IrpSp->FileObject->FsContext2;
if (DeviceClient == NULL)
{
status = STATUS_DEVICE_NOT_READY;
goto error;
}
NdisAcquireSpinLock(&FilterDeviceExtension->Lock);
// Check to see if there are any notifications available
if (DeviceClient->NotificationSize == 0)
{
// Set the cancel routine
IoSetCancelRoutine(Irp, otLwfQueryNotificationCancelled);
// Mark the Irp as pending
IoMarkIrpPending(Irp);
// Save the IRP to complete later, when we have a notification
DeviceClient->PendingNotificationIRP = Irp;
}
else
{
// Get the notification
NotifEntry = DeviceClient->PendingNotifications[DeviceClient->NotificationOffset];
DeviceClient->PendingNotifications[DeviceClient->NotificationOffset] = NULL;
// Increment the offset and decrement the size
DeviceClient->NotificationOffset = (DeviceClient->NotificationOffset + 1) % OTLWF_MAX_PENDING_NOTIFICATIONS_PER_CLIENT;
DeviceClient->NotificationSize--;
}
NdisReleaseSpinLock(&FilterDeviceExtension->Lock);
// If we found a notification, complete the IRP with it
if (NotifEntry)
{
// Copy the notification payload
PVOID IoBuffer = Irp->AssociatedIrp.SystemBuffer;
memcpy(IoBuffer, &NotifEntry->Notif, sizeof(OTLWF_NOTIFICATION));
Irp->IoStatus.Information = sizeof(OTLWF_NOTIFICATION);
// Free the notification
otLwfReleaseNotification(NotifEntry);
}
else
{
// Otherwise, set status to indicate we are pending the IRP
status = STATUS_PENDING;
}
error:
// Complete the IRP if we aren't pending
if (status != STATUS_PENDING)
{
Irp->IoStatus.Status = status;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
}
LogFuncExitNT(DRIVER_IOCTL, status);
return status;
}