ebpf-for-windows/ebpfcore/ebpf_drv.c

446 строки
17 KiB
C

// Copyright (c) Microsoft Corporation
// SPDX-License-Identifier: MIT
/**
* @file
* WDF based driver that does the following:
* 1. Initializes the eBPF execution context.
* 2. Opens an IOCTL surface that forwards commands to ebpf_core.
*/
#include "ebpf_core.h"
#include "ebpf_tracelog.h"
#include "ebpf_version.h"
#include "git_commit_id.h"
#include <wdf.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_EBPF_CTL_METHOD_BUFFERED CTL_CODE(EBPF_IOCTL_TYPE, 0x900, METHOD_BUFFERED, FILE_ANY_ACCESS)
const char ebpf_core_version[] = EBPF_VERSION " " GIT_COMMIT_ID;
//
// 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,
unsigned long 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();
}
static _Check_return_ NTSTATUS
_ebpf_driver_initialize_device(WDFDRIVER driver_handle, _Out_ WDFDEVICE* device)
{
NTSTATUS status;
PWDFDEVICE_INIT device_initialize = NULL;
WDF_OBJECT_ATTRIBUTES attributes;
UNICODE_STRING ebpf_device_name;
WDF_FILEOBJECT_CONFIG file_object_config;
UNICODE_STRING ebpf_symbolic_device_name;
// Log the version of the driver at startup.
// This is useful for debugging purposes and to ensure that the version string is present in the binary.
EBPF_LOG_MESSAGE(EBPF_TRACELOG_LEVEL_VERBOSE, EBPF_TRACELOG_KEYWORD_CORE, ebpf_core_version);
// Allow access to kernel/system, administrators, and ebpfsvc only.
DECLARE_CONST_UNICODE_STRING(security_descriptor, EBPF_EXECUTION_CONTEXT_DEVICE_SDDL);
device_initialize = WdfControlDeviceInitAllocate(driver_handle, &security_descriptor);
if (!device_initialize) {
status = STATUS_INSUFFICIENT_RESOURCES;
EBPF_LOG_NTSTATUS_API_FAILURE(EBPF_TRACELOG_KEYWORD_ERROR, WdfControlDeviceInitAllocate, status);
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)) {
EBPF_LOG_NTSTATUS_API_FAILURE(EBPF_TRACELOG_KEYWORD_ERROR, WdfDeviceInitAssignName, 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);
WdfDeviceInitSetFileObjectConfig(device_initialize, &file_object_config, &attributes);
// WDF framework doesn't handle IRP_MJ_QUERY_VOLUME_INFORMATION so 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)) {
EBPF_LOG_NTSTATUS_API_FAILURE(EBPF_TRACELOG_KEYWORD_ERROR, WdfDeviceInitAssignWdmIrpPreprocessCallback, 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);
EBPF_LOG_NTSTATUS_API_FAILURE(EBPF_TRACELOG_KEYWORD_ERROR, WdfDeviceCreate, status);
goto Exit;
}
// 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)) {
EBPF_LOG_NTSTATUS_API_FAILURE(EBPF_TRACELOG_KEYWORD_ERROR, WdfDeviceCreateSymbolicLink, status);
goto Exit;
}
Exit:
return status;
}
// Create a basic WDF driver, set up the device object for a callout driver and set up the ioctl surface.
static _Check_return_ NTSTATUS
_ebpf_driver_initialize_objects(
_Inout_ DRIVER_OBJECT* driver_object,
_In_ const UNICODE_STRING* registry_path,
_Out_ WDFDRIVER* driver_handle,
_Out_ WDFDEVICE* device)
{
NTSTATUS status;
WDF_DRIVER_CONFIG driver_configuration;
WDF_IO_QUEUE_CONFIG io_queue_configuration;
BOOLEAN device_create_flag = FALSE;
// IMPORTANT NOTE: The choice of implementing part of the driver initialization in another function
// (_ebpf_driver_initialize_device()) is deliberate. We perform a lot of standard WDF driver initialization here
// (and ebpf support code as well) and consequently need quite a few local variables (most of 'struct' type). Some
// of these are quite large and end up chewing up a lot of stack space. This causes Code Analysis tools to flag
// compile-time stack overflow errors when these variables (together) exceed the default stack size of 1024 bytes.
//
// This split between multiple functions ensures we don't hit this condition. Please keep this mind when
// refactoring/enhancing this function.
//
// One way to ensure this would be to run Code Analysis tools locally to catch such issues very early rather than
// wait for them to be flagged at the CI/CD gate during PR validation.
//
// OTOH, the CI/CD pipeline performs this check on a 'Draft PR' as well, so that's an option too.
WDF_DRIVER_CONFIG_INIT(&driver_configuration, WDF_NO_EVENT_CALLBACK);
driver_configuration.DriverInitFlags |= WdfDriverInitNonPnpDriver;
driver_configuration.EvtDriverUnload = _ebpf_driver_unload;
status =
WdfDriverCreate(driver_object, registry_path, WDF_NO_OBJECT_ATTRIBUTES, &driver_configuration, driver_handle);
if (!NT_SUCCESS(status)) {
EBPF_LOG_NTSTATUS_API_FAILURE(EBPF_TRACELOG_KEYWORD_ERROR, WdfDriverCreate, status);
goto Exit;
}
status = _ebpf_driver_initialize_device(*driver_handle, device);
if (!NT_SUCCESS(status)) {
EBPF_LOG_MESSAGE_NTSTATUS(
EBPF_TRACELOG_LEVEL_CRITICAL, EBPF_TRACELOG_KEYWORD_ERROR, (char*)"_ebpf_driver_initialize_device", status);
goto Exit;
}
device_create_flag = TRUE;
// Create 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);
if (!NT_SUCCESS(status)) {
EBPF_LOG_NTSTATUS_API_FAILURE(EBPF_TRACELOG_KEYWORD_ERROR, WdfIoQueueCreate, status);
goto Exit;
}
status = ebpf_result_to_ntstatus(ebpf_core_initiate());
if (!NT_SUCCESS(status)) {
EBPF_LOG_NTSTATUS_API_FAILURE(EBPF_TRACELOG_KEYWORD_ERROR, ebpf_core_initiate, 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_core_close_context(file_object->FsContext2);
}
static void
_ebpf_driver_io_device_control_complete(_Inout_ 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,
unsigned long 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_EBPF_CTL_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)) {
EBPF_LOG_NTSTATUS_API_FAILURE(EBPF_TRACELOG_KEYWORD_ERROR, WdfRequestRetrieveInputBuffer, status);
goto Done;
}
if (input_buffer == NULL) {
status = STATUS_INVALID_PARAMETER;
EBPF_LOG_NTSTATUS_API_FAILURE_MESSAGE(
EBPF_TRACELOG_KEYWORD_ERROR, "WdfRequestRetrieveInputBuffer", status, "Input buffer is null");
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)) {
EBPF_LOG_MESSAGE(
EBPF_TRACELOG_LEVEL_ERROR, EBPF_TRACELOG_KEYWORD_ERROR, "Input buffer is too small");
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) {
EBPF_LOG_NTSTATUS_API_FAILURE(
EBPF_TRACELOG_KEYWORD_ERROR, ebpf_core_get_protocol_handler_properties, status);
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)) {
EBPF_LOG_NTSTATUS_API_FAILURE(
EBPF_TRACELOG_KEYWORD_ERROR, WdfRequestRetrieveOutputBuffer, status);
goto Done;
}
if (output_buffer == NULL) {
status = STATUS_INVALID_PARAMETER;
EBPF_LOG_NTSTATUS_API_FAILURE_MESSAGE(
EBPF_TRACELOG_KEYWORD_ERROR,
"WdfRequestRetrieveOutputBuffer",
status,
"Output buffer is null");
goto Done;
}
if (actual_output_length < minimum_reply_size) {
EBPF_LOG_MESSAGE(
EBPF_TRACELOG_LEVEL_ERROR, EBPF_TRACELOG_KEYWORD_ERROR, "Output buffer is too small");
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));
if (status != STATUS_SUCCESS) {
EBPF_LOG_NTSTATUS_API_FAILURE(
EBPF_TRACELOG_KEYWORD_ERROR, "ebpf_core_invoke_protocol_handler", status);
}
goto Done;
}
} else {
EBPF_LOG_MESSAGE(EBPF_TRACELOG_LEVEL_ERROR, EBPF_TRACELOG_KEYWORD_ERROR, "Zero length input buffer");
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_handle;
WDFDEVICE device;
status = ebpf_trace_initiate();
if (!NT_SUCCESS(status)) {
// Fail silently as there is no other mechanism to indicate this failure. Note that in this case, the
// EBPF_LOG_EXIT() call at the end will not log anything either.
goto Exit;
}
EBPF_LOG_ENTRY();
// Request NX Non-Paged Pool when available
ExInitializeDriverRuntime(DrvRtPoolNxOptIn);
status = _ebpf_driver_initialize_objects(driver_object, registry_path, &driver_handle, &device);
if (!NT_SUCCESS(status)) {
EBPF_LOG_MESSAGE_NTSTATUS(
EBPF_TRACELOG_LEVEL_CRITICAL,
EBPF_TRACELOG_KEYWORD_ERROR,
(char*)"_ebpf_driver_initialize_objects failed",
status);
goto Exit;
}
_ebpf_driver_device_object = WdfDeviceWdmGetDeviceObject(device);
Exit:
EBPF_LOG_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;
}