ebpf-for-windows/netebpfext/net_ebpf_ext_hook_provider.c

508 строки
20 KiB
C

// Copyright (c) Microsoft Corporation
// SPDX-License-Identifier: MIT
#include "net_ebpf_ext.h"
#include "net_ebpf_ext_hook_provider.h"
#include "ebpf_extension_uuids.h"
/**
* @brief Pointer to function to invoke the eBPF program associated with the hook NPI client.
* This is the only function in the client's dispatch table.
*/
typedef ebpf_result_t (*ebpf_invoke_program_function_t)(
_In_ const void* client_binding_context, _In_ const void* context, _Out_ uint32_t* result);
typedef struct _net_ebpf_ext_hook_client_rundown
{
EX_RUNDOWN_REF protection;
bool rundown_occurred;
} net_ebpf_ext_hook_client_rundown_t;
struct _net_ebpf_extension_hook_provider;
/**
* @brief Data structure representing a hook NPI client (attached eBPF program). This is returned
* as the provider binding context in the NMR client attach callback.
*/
typedef struct _net_ebpf_extension_hook_client
{
LIST_ENTRY link; ///< Link to next client (if any).
HANDLE nmr_binding_handle; ///< NMR binding handle.
GUID client_module_id; ///< NMR module Id.
const void* client_binding_context; ///< Client supplied context to be passed when invoking eBPF program.
const ebpf_extension_data_t* client_data; ///< Client supplied attach parameters.
ebpf_invoke_program_function_t invoke_program; ///< Pointer to function to invoke eBPF program.
void* provider_data; ///< Opaque pointer to hook specific data associated with this client.
struct _net_ebpf_extension_hook_provider* provider_context; ///< Pointer to the hook NPI provider context.
PIO_WORKITEM detach_work_item; ///< Pointer to IO work item that is invoked to detach the client.
net_ebpf_ext_hook_client_rundown_t rundown; ///< Pointer to rundown object used to synchronize detach operation.
} net_ebpf_extension_hook_client_t;
typedef struct _net_ebpf_extension_hook_clients_list
{
EX_PUSH_LOCK lock;
LIST_ENTRY attached_clients_list;
} net_ebpf_extension_hook_clients_list_t;
typedef struct _net_ebpf_extension_hook_provider
{
NPI_PROVIDER_CHARACTERISTICS characteristics; ///< NPI Provider characteristics.
HANDLE nmr_provider_handle; ///< NMR binding handle.
EX_PUSH_LOCK lock; ///< Lock for synchronization.
net_ebpf_extension_hook_on_client_attach attach_callback; /*!< Pointer to hook specific callback to be invoked
when a client attaches. */
net_ebpf_extension_hook_on_client_detach detach_callback; /*!< Pointer to hook specific callback to be invoked
when a client detaches. */
const void* custom_data; ///< Opaque pointer to hook specific data associated for this provider.
_Guarded_by_(lock)
LIST_ENTRY attached_clients_list; ///< Linked list of hook NPI clients that are attached to this provider.
} net_ebpf_extension_hook_provider_t;
#define _ACQUIRE_PUSH_LOCK(lock, mode) \
{ \
KeEnterCriticalRegion(); \
ExAcquirePushLock##mode(lock); \
}
#define _RELEASE_PUSH_LOCK(lock, mode) \
{ \
ExReleasePushLock##mode(lock); \
KeLeaveCriticalRegion(); \
}
#define ACQUIRE_PUSH_LOCK_EXCLUSIVE(lock) _ACQUIRE_PUSH_LOCK(lock, Exclusive)
#define ACQUIRE_PUSH_LOCK_SHARED(lock) _ACQUIRE_PUSH_LOCK(lock, Shared)
#define RELEASE_PUSH_LOCK_EXCLUSIVE(lock) _RELEASE_PUSH_LOCK(lock, Exclusive)
#define RELEASE_PUSH_LOCK_SHARED(lock) _RELEASE_PUSH_LOCK(lock, Shared)
/**
* @brief Initialize the hook client rundown state.
*
* @param[in, out] hook_client Pointer to the attached hook NPI client.
*
* @retval STATUS_SUCCESS Operation succeeded.
* @retval STATUS_INSUFFICIENT_RESOURCES IO work item could not be allocated.
*/
static NTSTATUS
_ebpf_ext_attach_init_rundown(net_ebpf_extension_hook_client_t* hook_client)
{
NTSTATUS status = STATUS_SUCCESS;
net_ebpf_ext_hook_client_rundown_t* rundown = &hook_client->rundown;
//
// Allocate work item for client detach processing.
//
hook_client->detach_work_item = IoAllocateWorkItem(_net_ebpf_ext_driver_device_object);
if (hook_client->detach_work_item == NULL) {
status = STATUS_INSUFFICIENT_RESOURCES;
goto Exit;
}
ExInitializeRundownProtection(&rundown->protection);
rundown->rundown_occurred = FALSE;
Exit:
return status;
}
/**
* @brief Block execution of the thread until all invocations are completed.
*
* @param[in, out] rundown Rundown object to wait for.
*
*/
static void
_ebpf_ext_attach_wait_for_rundown(_Inout_ net_ebpf_ext_hook_client_rundown_t* rundown)
{
ExWaitForRundownProtectionRelease(&rundown->protection);
rundown->rundown_occurred = TRUE;
}
IO_WORKITEM_ROUTINE _net_ebpf_extension_detach_client_completion;
#if !defined(__cplusplus)
#pragma alloc_text(PAGE, _net_ebpf_extension_detach_client_completion)
#endif
/**
* @brief IO work item routine callback that waits on client rundown to complete.
*
* @param[in] device_object IO Device object.
* @param[in] context Pointer to work item context.
*
*/
void
_net_ebpf_extension_detach_client_completion(_In_ DEVICE_OBJECT* device_object, _In_opt_ void* context)
{
net_ebpf_extension_hook_client_t* hook_client = (net_ebpf_extension_hook_client_t*)context;
PIO_WORKITEM work_item;
PAGED_CODE();
NET_EBPF_EXT_LOG_ENTRY();
UNREFERENCED_PARAMETER(device_object);
ASSERT(hook_client != NULL);
_Analysis_assume_(hook_client != NULL);
work_item = hook_client->detach_work_item;
// The NMR model is async, but the only Windows run-down protection API available is a blocking API, so the
// following call will block until all using threads are complete. This should be fixed in the future.
// Issue: https://github.com/microsoft/ebpf-for-windows/issues/1854
// Wait for any in progress callbacks to complete.
_ebpf_ext_attach_wait_for_rundown(&hook_client->rundown);
// Note: This frees the provider binding context (hook_client).
NmrProviderDetachClientComplete(hook_client->nmr_binding_handle);
IoFreeWorkItem(work_item);
NET_EBPF_EXT_LOG_EXIT();
}
bool
net_ebpf_extension_hook_client_enter_rundown(_Inout_ net_ebpf_extension_hook_client_t* hook_client)
{
net_ebpf_ext_hook_client_rundown_t* rundown = &hook_client->rundown;
return ExAcquireRundownProtection(&rundown->protection);
}
void
net_ebpf_extension_hook_client_leave_rundown(_Inout_ net_ebpf_extension_hook_client_t* hook_client)
{
net_ebpf_ext_hook_client_rundown_t* rundown = &hook_client->rundown;
ExReleaseRundownProtection(&rundown->protection);
}
const ebpf_extension_data_t*
net_ebpf_extension_hook_client_get_client_data(_In_ const net_ebpf_extension_hook_client_t* hook_client)
{
return hook_client->client_data;
}
void
net_ebpf_extension_hook_client_set_provider_data(_In_ net_ebpf_extension_hook_client_t* hook_client, const void* data)
{
hook_client->provider_data = (void*)data;
}
const void*
net_ebpf_extension_hook_client_get_provider_data(_In_ const net_ebpf_extension_hook_client_t* hook_client)
{
return hook_client->provider_data;
}
const void*
net_ebpf_extension_hook_provider_get_custom_data(_In_ const net_ebpf_extension_hook_provider_t* provider_context)
{
return provider_context->custom_data;
}
_Must_inspect_result_ ebpf_result_t
net_ebpf_extension_hook_invoke_program(
_In_ const net_ebpf_extension_hook_client_t* client, _In_ const void* context, _Out_ uint32_t* result)
{
ebpf_invoke_program_function_t invoke_program = client->invoke_program;
const void* client_binding_context = client->client_binding_context;
ebpf_result_t invoke_result = invoke_program(client_binding_context, context, result);
if (invoke_result != EBPF_SUCCESS)
NET_EBPF_EXT_LOG_FUNCTION_ERROR(invoke_result);
return invoke_result;
}
_Must_inspect_result_ ebpf_result_t
net_ebpf_extension_hook_check_attach_parameter(
size_t attach_parameter_size,
_In_reads_(attach_parameter_size) const void* attach_parameter,
_In_reads_(attach_parameter_size) const void* wild_card_attach_parameter,
_Inout_ net_ebpf_extension_hook_provider_t* provider_context)
{
ebpf_result_t result = EBPF_SUCCESS;
bool using_wild_card_attach_parameter = FALSE;
bool lock_held = FALSE;
NET_EBPF_EXT_LOG_ENTRY();
if (memcmp(attach_parameter, wild_card_attach_parameter, attach_parameter_size) == 0)
using_wild_card_attach_parameter = TRUE;
ACQUIRE_PUSH_LOCK_SHARED(&provider_context->lock);
lock_held = TRUE;
if (using_wild_card_attach_parameter) {
// Client requested wild card attach parameter. This will only be allowed if there are no other clients
// attached.
if (!IsListEmpty(&provider_context->attached_clients_list)) {
result = EBPF_ACCESS_DENIED;
goto Exit;
}
} else {
// Ensure there are no other clients with wild card attach parameter or with the same attach parameter as the
// requesting client.
LIST_ENTRY* link = provider_context->attached_clients_list.Flink;
while (link != &provider_context->attached_clients_list) {
net_ebpf_extension_hook_client_t* next_client =
(net_ebpf_extension_hook_client_t*)CONTAINING_RECORD(link, net_ebpf_extension_hook_client_t, link);
const ebpf_extension_data_t* next_client_data = next_client->client_data;
const void* next_client_attach_parameter =
(next_client_data->data == NULL) ? wild_card_attach_parameter : next_client_data->data;
if (((memcmp(wild_card_attach_parameter, next_client_attach_parameter, attach_parameter_size) == 0)) ||
(memcmp(attach_parameter, next_client_attach_parameter, attach_parameter_size) == 0)) {
result = EBPF_ACCESS_DENIED;
goto Exit;
}
link = link->Flink;
}
}
Exit:
if (lock_held)
RELEASE_PUSH_LOCK_SHARED(&provider_context->lock);
NET_EBPF_EXT_RETURN_RESULT(result);
}
/**
* @brief Callback invoked when an eBPF hook NPI client (a.k.a eBPF link object) attaches.
*
* @param[in] nmr_binding_handle NMR binding between the client module and the provider module.
* @param[in] provider_context Provider module's context.
* @param[in] client_registration_instance Client module's registration data.
* @param[in] client_binding_context Client module's context for binding with provider.
* @param[in] client_dispatch Client module's dispatch table. Contains the function pointer
* to invoke the eBPF program.
* @param[out] provider_binding_context Pointer to provider module's binding context with the client module.
* @param[out] provider_dispatch Pointer to provider module's dispatch table.
* @retval STATUS_SUCCESS The operation succeeded.
* @retval STATUS_NO_MEMORY Failed to allocate provider binding context.
* @retval STATUS_INVALID_PARAMETER One or more arguments are incorrect.
*/
static NTSTATUS
_net_ebpf_extension_hook_provider_attach_client(
_In_ HANDLE nmr_binding_handle,
_In_ const void* provider_context,
_In_ const NPI_REGISTRATION_INSTANCE* client_registration_instance,
_In_ const void* client_binding_context,
_In_ const void* client_dispatch,
_Outptr_ void** provider_binding_context,
_Outptr_result_maybenull_ const void** provider_dispatch)
{
NTSTATUS status = STATUS_SUCCESS;
net_ebpf_extension_hook_provider_t* local_provider_context = (net_ebpf_extension_hook_provider_t*)provider_context;
net_ebpf_extension_hook_client_t* hook_client = NULL;
ebpf_extension_dispatch_table_t* client_dispatch_table;
ebpf_result_t result = EBPF_SUCCESS;
NET_EBPF_EXT_LOG_ENTRY();
if ((provider_binding_context == NULL) || (provider_dispatch == NULL) || (local_provider_context == NULL)) {
status = STATUS_INVALID_PARAMETER;
goto Exit;
}
*provider_binding_context = NULL;
*provider_dispatch = NULL;
hook_client = (net_ebpf_extension_hook_client_t*)ExAllocatePoolUninitialized(
NonPagedPoolNx, sizeof(net_ebpf_extension_hook_client_t), NET_EBPF_EXTENSION_POOL_TAG);
if (hook_client == NULL) {
status = STATUS_NO_MEMORY;
goto Exit;
}
memset(hook_client, 0, sizeof(net_ebpf_extension_hook_client_t));
hook_client->nmr_binding_handle = nmr_binding_handle;
hook_client->client_module_id = client_registration_instance->ModuleId->Guid;
hook_client->client_binding_context = client_binding_context;
hook_client->client_data = (const ebpf_extension_data_t*)client_registration_instance->NpiSpecificCharacteristics;
client_dispatch_table = (ebpf_extension_dispatch_table_t*)client_dispatch;
if (client_dispatch_table == NULL) {
status = STATUS_INVALID_PARAMETER;
goto Exit;
}
hook_client->invoke_program = (ebpf_invoke_program_function_t)client_dispatch_table->function[0];
hook_client->provider_context = local_provider_context;
// Invoke the hook specific callback to process client attach.
result = local_provider_context->attach_callback(hook_client, local_provider_context);
if (result == EBPF_SUCCESS) {
status = _ebpf_ext_attach_init_rundown(hook_client);
if (status == STATUS_SUCCESS) {
ACQUIRE_PUSH_LOCK_EXCLUSIVE(&local_provider_context->lock);
InsertTailList(&local_provider_context->attached_clients_list, &hook_client->link);
RELEASE_PUSH_LOCK_EXCLUSIVE(&local_provider_context->lock);
}
} else {
status = STATUS_ACCESS_DENIED;
}
Exit:
if (NT_SUCCESS(status)) {
*provider_binding_context = hook_client;
hook_client = NULL;
} else {
if (hook_client)
ExFreePool(hook_client);
}
NET_EBPF_EXT_RETURN_NTSTATUS(status);
}
/**
* @brief Callback invoked when a hook NPI client (a.k.a eBPF link object) detaches.
*
* @param[in] provider_binding_context Provider module's context for binding with the client.
* @retval STATUS_SUCCESS The operation succeeded.
* @retval STATUS_INVALID_PARAMETER One or more parameters are invalid.
*/
static NTSTATUS
_net_ebpf_extension_hook_provider_detach_client(_In_ const void* provider_binding_context)
{
NTSTATUS status = STATUS_PENDING;
NET_EBPF_EXT_LOG_ENTRY();
net_ebpf_extension_hook_client_t* local_client_context =
(net_ebpf_extension_hook_client_t*)provider_binding_context;
net_ebpf_extension_hook_provider_t* local_provider_context = local_client_context->provider_context;
if (local_client_context == NULL) {
status = STATUS_INVALID_PARAMETER;
goto Exit;
}
// Invoke hook specific handler for processing client detach.
local_provider_context->detach_callback(local_client_context);
ACQUIRE_PUSH_LOCK_EXCLUSIVE(&local_provider_context->lock);
RemoveEntryList(&local_client_context->link);
RELEASE_PUSH_LOCK_EXCLUSIVE(&local_provider_context->lock);
IoQueueWorkItem(
local_client_context->detach_work_item,
_net_ebpf_extension_detach_client_completion,
DelayedWorkQueue,
(PVOID)local_client_context);
Exit:
NET_EBPF_EXT_RETURN_NTSTATUS(status);
}
static void
_net_ebpf_extension_hook_provider_cleanup_binding_context(_Frees_ptr_ void* provider_binding_context)
{
ExFreePool(provider_binding_context);
}
void
net_ebpf_extension_hook_provider_unregister(_Frees_ptr_opt_ net_ebpf_extension_hook_provider_t* provider_context)
{
NET_EBPF_EXT_LOG_ENTRY();
if (provider_context != NULL) {
NTSTATUS status = NmrDeregisterProvider(provider_context->nmr_provider_handle);
if (status == STATUS_PENDING)
// Wait for clients to detach.
NmrWaitForProviderDeregisterComplete(provider_context->nmr_provider_handle);
ExFreePool(provider_context);
}
NET_EBPF_EXT_LOG_EXIT();
}
NTSTATUS
net_ebpf_extension_hook_provider_register(
_In_ const net_ebpf_extension_hook_provider_parameters_t* parameters,
_In_ net_ebpf_extension_hook_on_client_attach attach_callback,
_In_ net_ebpf_extension_hook_on_client_detach detach_callback,
_In_opt_ const void* custom_data,
_Outptr_ net_ebpf_extension_hook_provider_t** provider_context)
{
NTSTATUS status = STATUS_SUCCESS;
net_ebpf_extension_hook_provider_t* local_provider_context = NULL;
NPI_PROVIDER_CHARACTERISTICS* characteristics;
NET_EBPF_EXT_LOG_ENTRY();
local_provider_context = (net_ebpf_extension_hook_provider_t*)ExAllocatePoolUninitialized(
NonPagedPoolNx, sizeof(net_ebpf_extension_hook_provider_t), NET_EBPF_EXTENSION_POOL_TAG);
if (local_provider_context == NULL) {
status = STATUS_NO_MEMORY;
goto Exit;
}
memset(local_provider_context, 0, sizeof(net_ebpf_extension_hook_provider_t));
ExInitializePushLock(&local_provider_context->lock);
InitializeListHead(&local_provider_context->attached_clients_list);
characteristics = &local_provider_context->characteristics;
characteristics->Length = sizeof(NPI_PROVIDER_CHARACTERISTICS);
characteristics->ProviderAttachClient =
(PNPI_PROVIDER_ATTACH_CLIENT_FN)_net_ebpf_extension_hook_provider_attach_client;
characteristics->ProviderDetachClient =
(PNPI_PROVIDER_DETACH_CLIENT_FN)_net_ebpf_extension_hook_provider_detach_client;
characteristics->ProviderCleanupBindingContext = _net_ebpf_extension_hook_provider_cleanup_binding_context;
characteristics->ProviderRegistrationInstance.Size = sizeof(NPI_REGISTRATION_INSTANCE);
characteristics->ProviderRegistrationInstance.NpiId = &EBPF_HOOK_EXTENSION_IID;
characteristics->ProviderRegistrationInstance.NpiSpecificCharacteristics = parameters->provider_data;
characteristics->ProviderRegistrationInstance.ModuleId = parameters->provider_module_id;
local_provider_context->attach_callback = attach_callback;
local_provider_context->detach_callback = detach_callback;
local_provider_context->custom_data = custom_data;
status = NmrRegisterProvider(characteristics, local_provider_context, &local_provider_context->nmr_provider_handle);
if (!NT_SUCCESS(status))
goto Exit;
*provider_context = local_provider_context;
local_provider_context = NULL;
Exit:
if (!NT_SUCCESS(status))
net_ebpf_extension_hook_provider_unregister(local_provider_context);
NET_EBPF_EXT_RETURN_NTSTATUS(status);
}
net_ebpf_extension_hook_client_t*
net_ebpf_extension_hook_get_attached_client(_Inout_ net_ebpf_extension_hook_provider_t* provider_context)
{
net_ebpf_extension_hook_client_t* client_context = NULL;
ACQUIRE_PUSH_LOCK_SHARED(&provider_context->lock);
if (!IsListEmpty(&provider_context->attached_clients_list))
client_context = (net_ebpf_extension_hook_client_t*)CONTAINING_RECORD(
provider_context->attached_clients_list.Flink, net_ebpf_extension_hook_client_t, link);
RELEASE_PUSH_LOCK_SHARED(&provider_context->lock);
return client_context;
}
net_ebpf_extension_hook_client_t*
net_ebpf_extension_hook_get_next_attached_client(
_Inout_ net_ebpf_extension_hook_provider_t* provider_context,
_In_opt_ const net_ebpf_extension_hook_client_t* client_context)
{
net_ebpf_extension_hook_client_t* next_client = NULL;
ACQUIRE_PUSH_LOCK_SHARED(&provider_context->lock);
if (client_context == NULL) {
// Return the first attached client (if any).
if (!IsListEmpty(&provider_context->attached_clients_list))
next_client = (net_ebpf_extension_hook_client_t*)CONTAINING_RECORD(
provider_context->attached_clients_list.Flink, net_ebpf_extension_hook_client_t, link);
} else {
// Return the next client, unless this is the last one.
if (client_context->link.Flink != &provider_context->attached_clients_list) {
next_client = (net_ebpf_extension_hook_client_t*)CONTAINING_RECORD(
client_context->link.Flink, net_ebpf_extension_hook_client_t, link);
}
}
RELEASE_PUSH_LOCK_SHARED(&provider_context->lock);
return next_client;
}