ebpf-for-windows/ebpfcore/ebpf_drv.c

409 строки
13 KiB
C

// Copyright (c) Microsoft Corporation
// SPDX-License-Identifier: MIT
/*++
Abstract:
WDF based driver that does the following:
1. Initializes the eBPF execution context.
2. Opens an IOCTL surface that forwards commands to ebfp_core.
Environment:
Kernel mode
--*/
// ntddk.h needs to be included first due to inter header dependencies on Windows.
#include <ntddk.h>
#include <netiodef.h>
#include <wdf.h>
#include "ebpf_core.h"
#include "ebpf_object.h"
// Driver global variables
static DEVICE_OBJECT* _ebpf_driver_device_object;
static BOOLEAN _ebpf_driver_unloading_flag = FALSE;
// SID for ebpfsvc (generated using command "sc.exe showsid ebpfsvc"):
// S-1-5-80-3453964624-2861012444-1105579853-3193141192-1897355174
//
// SDDL_DEVOBJ_SYS_ALL_ADM_ALL + SID for ebpfsvc.
#define EBPF_EXECUTION_CONTEXT_DEVICE_SDDL \
L"D:P(A;;GA;;;S-1-5-80-3453964624-2861012444-1105579853-3193141192-1897355174)(A;;GA;;;BA)(A;;GA;;;SY)"
#ifndef CTL_CODE
#define CTL_CODE(DeviceType, Function, Method, Access) \
(((DeviceType) << 16) | ((Access) << 14) | ((Function) << 2) | (Method))
#endif
// Device type
#define EBPF_IOCTL_TYPE FILE_DEVICE_NETWORK
// Function codes from 0x800 to 0xFFF are for customer use.
#define IOCTL_EBPFCTL_METHOD_BUFFERED CTL_CODE(EBPF_IOCTL_TYPE, 0x900, METHOD_BUFFERED, FILE_ANY_ACCESS)
//
// Pre-Declarations
//
static EVT_WDF_FILE_CLOSE _ebpf_driver_file_close;
static EVT_WDF_IO_QUEUE_IO_DEVICE_CONTROL _ebpf_driver_io_device_control;
static EVT_WDFDEVICE_WDM_IRP_PREPROCESS _ebpf_driver_query_volume_information;
static EVT_WDF_REQUEST_CANCEL _ebpf_driver_io_device_control_cancel;
DRIVER_INITIALIZE DriverEntry;
static VOID
_ebpf_driver_io_device_control(
_In_ WDFQUEUE queue,
_In_ WDFREQUEST request,
size_t output_buffer_length,
size_t input_buffer_length,
ULONG io_control_code);
static _Function_class_(EVT_WDF_DRIVER_UNLOAD) _IRQL_requires_same_
_IRQL_requires_max_(PASSIVE_LEVEL) void _ebpf_driver_unload(_In_ WDFDRIVER driver_object)
{
UNREFERENCED_PARAMETER(driver_object);
_ebpf_driver_unloading_flag = TRUE;
ebpf_core_terminate();
}
//
// Create a basic WDF driver, set up the device object
// for a callout driver and setup the ioctl surface
//
static NTSTATUS
_ebpf_driver_initialize_objects(
_Inout_ DRIVER_OBJECT* driver_object,
_In_ const UNICODE_STRING* registry_path,
_Out_ WDFDRIVER* driver,
_Out_ WDFDEVICE* device)
{
NTSTATUS status;
WDF_DRIVER_CONFIG driver_configuration;
PWDFDEVICE_INIT device_initialize = NULL;
WDF_IO_QUEUE_CONFIG io_queue_configuration;
UNICODE_STRING ebpf_device_name;
UNICODE_STRING ebpf_symbolic_device_name;
BOOLEAN device_create_flag = FALSE;
WDF_OBJECT_ATTRIBUTES attributes;
WDF_FILEOBJECT_CONFIG file_object_config;
WDF_DRIVER_CONFIG_INIT(&driver_configuration, WDF_NO_EVENT_CALLBACK);
DECLARE_CONST_UNICODE_STRING(security_descriptor, EBPF_EXECUTION_CONTEXT_DEVICE_SDDL);
driver_configuration.DriverInitFlags |= WdfDriverInitNonPnpDriver;
driver_configuration.EvtDriverUnload = _ebpf_driver_unload;
status = WdfDriverCreate(driver_object, registry_path, WDF_NO_OBJECT_ATTRIBUTES, &driver_configuration, driver);
if (!NT_SUCCESS(status)) {
goto Exit;
}
device_initialize = WdfControlDeviceInitAllocate(
*driver,
&security_descriptor // only kernel/system, administrators, and ebpfsvc.
);
if (!device_initialize) {
status = STATUS_INSUFFICIENT_RESOURCES;
goto Exit;
}
WdfDeviceInitSetDeviceType(device_initialize, FILE_DEVICE_NULL);
WdfDeviceInitSetCharacteristics(device_initialize, FILE_DEVICE_SECURE_OPEN, FALSE);
WdfDeviceInitSetCharacteristics(device_initialize, FILE_AUTOGENERATED_DEVICE_NAME, TRUE);
RtlInitUnicodeString(&ebpf_device_name, EBPF_DEVICE_NAME);
status = WdfDeviceInitAssignName(device_initialize, &ebpf_device_name);
if (!NT_SUCCESS(status)) {
goto Exit;
}
WDF_OBJECT_ATTRIBUTES_INIT(&attributes);
attributes.SynchronizationScope = WdfSynchronizationScopeNone;
WDF_FILEOBJECT_CONFIG_INIT(
&file_object_config,
NULL,
_ebpf_driver_file_close,
WDF_NO_EVENT_CALLBACK // No cleanup callback function
);
WdfDeviceInitSetFileObjectConfig(device_initialize, &file_object_config, &attributes);
// WDF framework doesn't handle IRP_MJ_QUERY_VOLUME_INFORMATION.
// Register a handler for this IRP.
status = WdfDeviceInitAssignWdmIrpPreprocessCallback(
device_initialize, _ebpf_driver_query_volume_information, IRP_MJ_QUERY_VOLUME_INFORMATION, NULL, 0);
if (!NT_SUCCESS(status)) {
goto Exit;
}
status = WdfDeviceCreate(&device_initialize, WDF_NO_OBJECT_ATTRIBUTES, device);
if (!NT_SUCCESS(status)) {
// do not free if any other call
// after WdfDeviceCreate fails.
WdfDeviceInitFree(device_initialize);
device_initialize = NULL;
goto Exit;
}
device_create_flag = TRUE;
// Create symbolic link for control object for user mode.
RtlInitUnicodeString(&ebpf_symbolic_device_name, EBPF_SYMBOLIC_DEVICE_NAME);
status = WdfDeviceCreateSymbolicLink(*device, &ebpf_symbolic_device_name);
if (!NT_SUCCESS(status)) {
goto Exit;
}
// parallel default queue
WDF_IO_QUEUE_CONFIG_INIT_DEFAULT_QUEUE(&io_queue_configuration, WdfIoQueueDispatchParallel);
io_queue_configuration.EvtIoDeviceControl = _ebpf_driver_io_device_control;
status = WdfIoQueueCreate(
*device,
&io_queue_configuration,
WDF_NO_OBJECT_ATTRIBUTES,
WDF_NO_HANDLE // pointer to default queue
);
if (!NT_SUCCESS(status)) {
goto Exit;
}
status = ebpf_result_to_ntstatus(ebpf_core_initiate());
if (!NT_SUCCESS(status)) {
goto Exit;
}
WdfControlFinishInitializing(*device);
Exit:
if (!NT_SUCCESS(status)) {
if (device_create_flag && device != NULL) {
//
// Release the reference on the newly created object, since
// we couldn't initialize it.
//
WdfObjectDelete(*device);
}
}
return status;
}
static void
_ebpf_driver_file_close(WDFFILEOBJECT wdf_file_object)
{
FILE_OBJECT* file_object = WdfFileObjectWdmGetFileObject(wdf_file_object);
ebpf_object_release_reference(file_object->FsContext2);
}
static void
_ebpf_driver_io_device_control_complete(void* context, size_t output_buffer_length, ebpf_result_t result)
{
NTSTATUS status;
WDFREQUEST request = (WDFREQUEST)context;
status = WdfRequestUnmarkCancelable(request);
UNREFERENCED_PARAMETER(status);
WdfRequestCompleteWithInformation(request, ebpf_result_to_ntstatus(result), output_buffer_length);
WdfObjectDereference(request);
}
static void
_ebpf_driver_io_device_control_cancel(WDFREQUEST request)
{
// https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/wdfrequest/nc-wdfrequest-evt_wdf_request_cancel
ebpf_core_cancel_protocol_handler(request);
}
static VOID
_ebpf_driver_io_device_control(
_In_ WDFQUEUE queue,
_In_ WDFREQUEST request,
size_t output_buffer_length,
size_t input_buffer_length,
ULONG io_control_code)
{
NTSTATUS status = STATUS_SUCCESS;
WDFDEVICE device;
void* input_buffer = NULL;
void* output_buffer = NULL;
size_t actual_input_length = 0;
size_t actual_output_length = 0;
const struct _ebpf_operation_header* user_request = NULL;
struct _ebpf_operation_header* user_reply = NULL;
bool async = false;
bool wdf_request_ref_acquired = false;
device = WdfIoQueueGetDevice(queue);
switch (io_control_code) {
case IOCTL_EBPFCTL_METHOD_BUFFERED:
// Verify that length of the input buffer supplied to the request object
// is not zero
if (input_buffer_length != 0) {
// Retrieve the input buffer associated with the request object
status = WdfRequestRetrieveInputBuffer(
request, // Request object
input_buffer_length, // Length of input buffer
&input_buffer, // Pointer to buffer
&actual_input_length // Length of buffer
);
if (!NT_SUCCESS(status)) {
KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "EbpfCore: Input buffer failure %d\n", status));
goto Done;
}
if (input_buffer == NULL) {
status = STATUS_INVALID_PARAMETER;
goto Done;
}
if (input_buffer != NULL) {
size_t minimum_request_size = 0;
size_t minimum_reply_size = 0;
void* async_context = NULL;
user_request = input_buffer;
if (actual_input_length < sizeof(struct _ebpf_operation_header)) {
status = STATUS_INVALID_PARAMETER;
goto Done;
}
status = ebpf_result_to_ntstatus(ebpf_core_get_protocol_handler_properties(
user_request->id, &minimum_request_size, &minimum_reply_size, &async));
if (status != STATUS_SUCCESS)
goto Done;
// Be aware: Input and output buffer point to the same memory.
if (minimum_reply_size > 0) {
// Retrieve output buffer associated with the request object
status = WdfRequestRetrieveOutputBuffer(
request, output_buffer_length, &output_buffer, &actual_output_length);
if (!NT_SUCCESS(status)) {
KdPrintEx(
(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "EbpfCore: Output buffer failure %d\n", status));
goto Done;
}
if (output_buffer == NULL) {
status = STATUS_INVALID_PARAMETER;
goto Done;
}
if (actual_output_length < minimum_reply_size) {
status = STATUS_BUFFER_TOO_SMALL;
goto Done;
}
user_reply = output_buffer;
}
if (async) {
WdfObjectReference(request);
async_context = request;
WdfRequestMarkCancelable(request, _ebpf_driver_io_device_control_cancel);
wdf_request_ref_acquired = true;
}
status = ebpf_result_to_ntstatus(ebpf_core_invoke_protocol_handler(
user_request->id,
user_request,
(uint16_t)actual_input_length,
user_reply,
(uint16_t)actual_output_length,
async_context,
_ebpf_driver_io_device_control_complete));
goto Done;
}
} else {
status = STATUS_INVALID_PARAMETER;
goto Done;
}
break;
default:
break;
}
Done:
if (status != STATUS_PENDING) {
if (wdf_request_ref_acquired) {
ebpf_assert(status != STATUS_SUCCESS);
// Async operation failed. Remove cancellable marker.
(void)WdfRequestUnmarkCancelable(request);
WdfObjectDereference(request);
}
WdfRequestCompleteWithInformation(request, status, output_buffer_length);
}
return;
}
NTSTATUS
DriverEntry(_In_ DRIVER_OBJECT* driver_object, _In_ UNICODE_STRING* registry_path)
{
NTSTATUS status;
WDFDRIVER driver;
WDFDEVICE device;
// Request NX Non-Paged Pool when available
ExInitializeDriverRuntime(DrvRtPoolNxOptIn);
KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "EbpfCore: DriverEntry\n"));
status = _ebpf_driver_initialize_objects(driver_object, registry_path, &driver, &device);
if (!NT_SUCCESS(status)) {
goto Exit;
}
_ebpf_driver_device_object = WdfDeviceWdmGetDeviceObject(device);
Exit:
return status;
}
DEVICE_OBJECT*
ebpf_driver_get_device_object()
{
return _ebpf_driver_device_object;
}
// The C runtime queries the file type via GetFileType when creating a file
// descriptor. GetFileType queries volume information to get device type via
// FileFsDeviceInformation information class.
NTSTATUS
_ebpf_driver_query_volume_information(_In_ WDFDEVICE device, _Inout_ IRP* irp)
{
NTSTATUS status;
IO_STACK_LOCATION* irp_stack_location;
UNREFERENCED_PARAMETER(device);
irp_stack_location = IoGetCurrentIrpStackLocation(irp);
switch (irp_stack_location->Parameters.QueryVolume.FsInformationClass) {
case FileFsDeviceInformation:
if (irp_stack_location->Parameters.DeviceIoControl.OutputBufferLength < sizeof(FILE_FS_DEVICE_INFORMATION)) {
status = STATUS_BUFFER_TOO_SMALL;
} else {
FILE_FS_DEVICE_INFORMATION* device_info = (FILE_FS_DEVICE_INFORMATION*)irp->AssociatedIrp.SystemBuffer;
device_info->DeviceType = FILE_DEVICE_NULL;
device_info->Characteristics = 0;
status = STATUS_SUCCESS;
}
break;
default:
status = STATUS_NOT_SUPPORTED;
break;
}
irp->IoStatus.Status = status;
IoCompleteRequest(irp, 0);
return status;
}