ebpf-for-windows/netebpfext/net_ebpf_ext_sock_addr.c

1680 строки
67 KiB
C

// Copyright (c) Microsoft Corporation
// SPDX-License-Identifier: MIT
/**
* @file
* @brief This file implements the hook for the CGROUP_SOCK_ADDR program type and associated attach types, on eBPF for
* Windows.
*/
#include "ebpf_store_helper.h"
#include "net_ebpf_ext_sock_addr.h"
#define TARGET_PROCESS_ID 1234
#define EXPIRY_TIME 60000 // 60 seconds in ms.
#define CONVERT_100NS_UNITS_TO_MS(x) ((x) / 10000)
typedef struct _net_ebpf_bpf_sock_addr
{
bpf_sock_addr_t base;
TOKEN_ACCESS_INFORMATION* access_information;
uint64_t process_id;
uint32_t flags;
} net_ebpf_sock_addr_t;
/**
* Connection context info does not contain the source IP address because
* the source IP address is not always available at connect_redirect layer.
* Source port is however available and included below for a stricter check.
*/
typedef struct _net_ebpf_ext_connect_context_address_info
{
uint32_t family;
union
{
uint32_t ipv4;
uint32_t ipv6[4];
} destination_ip;
uint16_t destination_port;
uint16_t source_port;
} net_ebpf_ext_connect_context_address_info_t;
typedef struct _net_ebpf_extension_connection_context
{
uint64_t transport_endpoint_handle;
net_ebpf_ext_connect_context_address_info_t address_info;
uint32_t compartment_id;
uint16_t protocol;
uint64_t timestamp;
LIST_ENTRY list_entry;
} net_ebpf_extension_connection_context_t;
typedef struct _net_ebpf_extension_redirect_handle_entry
{
LIST_ENTRY list_entry;
uint64_t filter_id;
HANDLE redirect_handle;
} net_ebpf_extension_redirect_handle_entry_t;
typedef struct _net_ebpf_ext_sock_addr_statistics
{
volatile long permit_connection_count;
volatile long redirect_connection_count;
volatile long block_connection_count;
} net_ebpf_ext_sock_addr_statistics_t;
static net_ebpf_ext_sock_addr_statistics_t _net_ebpf_ext_statistics;
static EX_SPIN_LOCK _net_ebpf_ext_sock_addr_lock;
// TODO: Issue #1675 (Use hash table to store connection contexts in netebpfext)
_Guarded_by_(_net_ebpf_ext_sock_addr_lock) static LIST_ENTRY _net_ebpf_ext_redirect_handle_list;
_Guarded_by_(_net_ebpf_ext_sock_addr_lock) static LIST_ENTRY _net_ebpf_ext_connect_context_list;
static uint32_t _net_ebpf_ext_connect_context_count = 0;
static SECURITY_DESCRIPTOR* _net_ebpf_ext_security_descriptor_admin = NULL;
static ACL* _net_ebpf_ext_dacl_admin = NULL;
static GENERIC_MAPPING _net_ebpf_ext_generic_mapping = {0};
//
// sock_addr helper functions.
//
static uint32_t
_get_process_id()
{
return (uint32_t)(uintptr_t)PsGetCurrentProcessId();
}
static uint32_t
_get_thread_id()
{
return (uint32_t)(uintptr_t)PsGetCurrentThreadId();
}
static uint64_t
_ebpf_sock_addr_get_current_pid_tgid(_In_ const bpf_sock_addr_t* ctx)
{
net_ebpf_sock_addr_t* sock_addr_ctx = CONTAINING_RECORD(ctx, net_ebpf_sock_addr_t, base);
return (sock_addr_ctx->process_id << 32 | _get_thread_id());
}
static uint64_t
_ebpf_sock_addr_get_current_logon_id(_In_ const bpf_sock_addr_t* ctx)
{
uint64_t logon_id = 0;
net_ebpf_sock_addr_t* sock_addr_ctx = CONTAINING_RECORD(ctx, net_ebpf_sock_addr_t, base);
logon_id = *(uint64_t*)(&(sock_addr_ctx->access_information->AuthenticationId));
return logon_id;
}
_IRQL_requires_max_(DISPATCH_LEVEL) static NTSTATUS _perform_access_check(
_In_ SECURITY_DESCRIPTOR* security_descriptor,
_In_ TOKEN_ACCESS_INFORMATION* access_information,
_Out_ BOOLEAN* access_allowed)
{
ACCESS_MASK granted_access;
NTSTATUS status;
*access_allowed = SeAccessCheckFromState(
security_descriptor,
access_information,
NULL,
FILE_WRITE_ACCESS,
0,
NULL,
&_net_ebpf_ext_generic_mapping,
UserMode,
&granted_access,
&status);
// Not tracing error as this function can be called in hot path.
// Non-success status means access not granted, and does not mean failure.
return status;
}
static int32_t
_ebpf_sock_addr_is_current_admin(_In_ const bpf_sock_addr_t* ctx)
{
NTSTATUS status;
BOOLEAN access_allowed;
net_ebpf_sock_addr_t* sock_addr_ctx = NULL;
int32_t is_admin;
sock_addr_ctx = CONTAINING_RECORD(ctx, net_ebpf_sock_addr_t, base);
status = _perform_access_check(
_net_ebpf_ext_security_descriptor_admin, sock_addr_ctx->access_information, &access_allowed);
if (access_allowed) {
is_admin = 1;
} else {
is_admin = 0;
}
return is_admin;
}
//
// WFP filter related types & globals for SOCK_ADDR hook.
//
const ebpf_attach_type_t* _net_ebpf_extension_sock_addr_attach_types[] = {
&EBPF_ATTACH_TYPE_CGROUP_INET4_CONNECT,
&EBPF_ATTACH_TYPE_CGROUP_INET4_RECV_ACCEPT,
&EBPF_ATTACH_TYPE_CGROUP_INET6_CONNECT,
&EBPF_ATTACH_TYPE_CGROUP_INET6_RECV_ACCEPT};
const uint32_t _net_ebpf_extension_sock_addr_bpf_attach_types[] = {
BPF_CGROUP_INET4_CONNECT, BPF_CGROUP_INET4_RECV_ACCEPT, BPF_CGROUP_INET6_CONNECT, BPF_CGROUP_INET6_RECV_ACCEPT};
#define NET_EBPF_SOCK_ADDR_HOOK_PROVIDER_COUNT EBPF_COUNT_OF(_net_ebpf_extension_sock_addr_attach_types)
net_ebpf_extension_wfp_filter_parameters_t _cgroup_inet4_connect_filter_parameters[] = {
{&FWPM_LAYER_ALE_AUTH_CONNECT_V4,
NULL, // Default sublayer.
&EBPF_HOOK_ALE_AUTH_CONNECT_V4_CALLOUT,
L"net eBPF sock_addr hook",
L"net eBPF sock_addr hook WFP filter"},
{&FWPM_LAYER_ALE_CONNECT_REDIRECT_V4,
NULL, // Default sublayer.
&EBPF_HOOK_ALE_CONNECT_REDIRECT_V4_CALLOUT,
L"net eBPF sock_addr hook",
L"net eBPF sock_addr hook WFP filter"},
{&FWPM_LAYER_ALE_CONNECT_REDIRECT_V6,
&EBPF_HOOK_CGROUP_CONNECT_V4_SUBLAYER,
&EBPF_HOOK_ALE_CONNECT_REDIRECT_V6_CALLOUT,
L"net eBPF sock_addr hook",
L"net eBPF sock_addr hook WFP filter"}};
net_ebpf_extension_wfp_filter_parameters_t _cgroup_inet6_connect_filter_parameters[] = {
{&FWPM_LAYER_ALE_AUTH_CONNECT_V6,
NULL, // Default sublayer.
&EBPF_HOOK_ALE_AUTH_CONNECT_V6_CALLOUT,
L"net eBPF sock_addr hook",
L"net eBPF sock_addr hook WFP filter"},
{&FWPM_LAYER_ALE_CONNECT_REDIRECT_V6,
&EBPF_HOOK_CGROUP_CONNECT_V6_SUBLAYER,
&EBPF_HOOK_ALE_CONNECT_REDIRECT_V6_CALLOUT,
L"net eBPF sock_addr hook",
L"net eBPF sock_addr hook WFP filter"}};
net_ebpf_extension_wfp_filter_parameters_t _cgroup_inet4_recv_accept_filter_parameters[] = {
{&FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V4,
NULL, // Default sublayer.
&EBPF_HOOK_ALE_AUTH_RECV_ACCEPT_V4_CALLOUT,
L"net eBPF sock_addr hook",
L"net eBPF sock_addr hook WFP filter"}};
net_ebpf_extension_wfp_filter_parameters_t _cgroup_inet6_recv_accept_filter_parameters[] = {
{&FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V6,
NULL,
&EBPF_HOOK_ALE_AUTH_RECV_ACCEPT_V6_CALLOUT,
L"net eBPF sock_addr hook",
L"net eBPF sock_addr hook WFP filter"}};
const net_ebpf_extension_wfp_filter_parameters_array_t _net_ebpf_extension_sock_addr_wfp_filter_parameters[] = {
{&EBPF_ATTACH_TYPE_CGROUP_INET4_CONNECT,
EBPF_COUNT_OF(_cgroup_inet4_connect_filter_parameters),
&_cgroup_inet4_connect_filter_parameters[0]},
{&EBPF_ATTACH_TYPE_CGROUP_INET4_RECV_ACCEPT,
EBPF_COUNT_OF(_cgroup_inet4_recv_accept_filter_parameters),
&_cgroup_inet4_recv_accept_filter_parameters[0]},
{&EBPF_ATTACH_TYPE_CGROUP_INET6_CONNECT,
EBPF_COUNT_OF(_cgroup_inet6_connect_filter_parameters),
&_cgroup_inet6_connect_filter_parameters[0]},
{&EBPF_ATTACH_TYPE_CGROUP_INET4_RECV_ACCEPT,
EBPF_COUNT_OF(_cgroup_inet6_recv_accept_filter_parameters),
&_cgroup_inet6_recv_accept_filter_parameters[0]},
};
typedef struct _net_ebpf_extension_sock_addr_wfp_filter_context
{
net_ebpf_extension_wfp_filter_context_t base;
uint32_t compartment_id;
BOOLEAN v4_attach_type;
} net_ebpf_extension_sock_addr_wfp_filter_context_t;
static ebpf_result_t
_ebpf_sock_addr_context_create(
_In_reads_bytes_opt_(data_size_in) const uint8_t* data_in,
size_t data_size_in,
_In_reads_bytes_opt_(context_size_in) const uint8_t* context_in,
size_t context_size_in,
_Outptr_ void** context);
static void
_ebpf_sock_addr_context_destroy(
_In_opt_ void* context,
_Out_writes_bytes_to_(*data_size_out, *data_size_out) uint8_t* data_out,
_Inout_ size_t* data_size_out,
_Out_writes_bytes_to_(*context_size_out, *context_size_out) uint8_t* context_out,
_Inout_ size_t* context_size_out);
//
// SOCK_ADDR Program Information NPI Provider.
//
static const void* _ebpf_sock_addr_specific_helper_functions[] = {(void*)_ebpf_sock_addr_get_current_pid_tgid};
static ebpf_helper_function_addresses_t _ebpf_sock_addr_specific_helper_function_address_table = {
EBPF_COUNT_OF(_ebpf_sock_addr_specific_helper_functions), (uint64_t*)_ebpf_sock_addr_specific_helper_functions};
static const void* _ebpf_sock_addr_global_helper_functions[] = {
(void*)_ebpf_sock_addr_get_current_logon_id, (void*)_ebpf_sock_addr_is_current_admin};
static ebpf_helper_function_addresses_t _ebpf_sock_addr_global_helper_function_address_table = {
EBPF_COUNT_OF(_ebpf_sock_addr_global_helper_functions), (uint64_t*)_ebpf_sock_addr_global_helper_functions};
static ebpf_program_data_t _ebpf_sock_addr_program_data = {
.program_info = &_ebpf_sock_addr_program_info,
.program_type_specific_helper_function_addresses = &_ebpf_sock_addr_specific_helper_function_address_table,
.global_helper_function_addresses = &_ebpf_sock_addr_global_helper_function_address_table,
.context_create = &_ebpf_sock_addr_context_create,
.context_destroy = &_ebpf_sock_addr_context_destroy};
static ebpf_extension_data_t _ebpf_sock_addr_program_info_provider_data = {
NET_EBPF_EXTENSION_NPI_PROVIDER_VERSION, sizeof(_ebpf_sock_addr_program_data), &_ebpf_sock_addr_program_data};
NPI_MODULEID DECLSPEC_SELECTANY _ebpf_sock_addr_program_info_provider_moduleid = {sizeof(NPI_MODULEID), MIT_GUID, {0}};
static net_ebpf_extension_program_info_provider_t* _ebpf_sock_addr_program_info_provider_context = NULL;
//
// SOCK_ADDR Hook NPI Provider.
//
ebpf_attach_provider_data_t _net_ebpf_sock_addr_hook_provider_data[NET_EBPF_SOCK_ADDR_HOOK_PROVIDER_COUNT] = {0};
ebpf_extension_data_t _net_ebpf_extension_sock_addr_hook_provider_data[NET_EBPF_SOCK_ADDR_HOOK_PROVIDER_COUNT] = {0};
NPI_MODULEID DECLSPEC_SELECTANY _ebpf_sock_addr_hook_provider_moduleid[NET_EBPF_SOCK_ADDR_HOOK_PROVIDER_COUNT] = {0};
static net_ebpf_extension_hook_provider_t*
_ebpf_sock_addr_hook_provider_context[NET_EBPF_SOCK_ADDR_HOOK_PROVIDER_COUNT] = {0};
//
// NMR Registration Helper Routines.
//
static ebpf_result_t
_net_ebpf_extension_sock_addr_on_client_attach(
_In_ const net_ebpf_extension_hook_client_t* attaching_client,
_In_ const net_ebpf_extension_hook_provider_t* provider_context)
{
ebpf_result_t result = EBPF_SUCCESS;
const ebpf_extension_data_t* client_data = net_ebpf_extension_hook_client_get_client_data(attaching_client);
uint32_t compartment_id;
uint32_t wild_card_compartment_id = UNSPECIFIED_COMPARTMENT_ID;
net_ebpf_extension_wfp_filter_parameters_array_t* filter_parameters_array = NULL;
FWPM_FILTER_CONDITION condition = {0};
net_ebpf_extension_sock_addr_wfp_filter_context_t* filter_context = NULL;
NET_EBPF_EXT_LOG_ENTRY();
// SOCK_ADDR hook clients must always provide data.
if (client_data == NULL) {
result = EBPF_INVALID_ARGUMENT;
goto Exit;
}
if (client_data->size > 0) {
if ((client_data->size != sizeof(uint32_t)) || (client_data->data == NULL)) {
result = EBPF_INVALID_ARGUMENT;
goto Exit;
}
compartment_id = *(uint32_t*)client_data->data;
} else {
// If the client did not specify any attach parameters, we treat that as a wildcard compartment id.
compartment_id = wild_card_compartment_id;
}
result = net_ebpf_extension_hook_check_attach_parameter(
sizeof(compartment_id),
&compartment_id,
&wild_card_compartment_id,
(net_ebpf_extension_hook_provider_t*)provider_context);
NET_EBPF_EXT_BAIL_ON_ERROR_RESULT(result);
if (client_data->data != NULL) {
compartment_id = *(uint32_t*)client_data->data;
}
// Set compartment id (if not UNSPECIFIED_COMPARTMENT_ID) as WFP filter condition.
if (compartment_id != UNSPECIFIED_COMPARTMENT_ID) {
condition.fieldKey = FWPM_CONDITION_COMPARTMENT_ID;
condition.matchType = FWP_MATCH_EQUAL;
condition.conditionValue.type = FWP_UINT32;
condition.conditionValue.uint32 = compartment_id;
}
result = net_ebpf_extension_wfp_filter_context_create(
sizeof(net_ebpf_extension_sock_addr_wfp_filter_context_t),
attaching_client,
(net_ebpf_extension_wfp_filter_context_t**)&filter_context);
NET_EBPF_EXT_BAIL_ON_ERROR_RESULT(result);
filter_context->compartment_id = compartment_id;
// Get the WFP filter parameters for this hook type.
filter_parameters_array =
(net_ebpf_extension_wfp_filter_parameters_array_t*)net_ebpf_extension_hook_provider_get_custom_data(
provider_context);
ASSERT(filter_parameters_array != NULL);
filter_context->base.filter_ids_count = filter_parameters_array->count;
// Special case of connect_redirect. If the attach type is v4, set v4_attach_type in the filter
// context to TRUE.
if (memcmp(filter_parameters_array->attach_type, &EBPF_ATTACH_TYPE_CGROUP_INET4_CONNECT, sizeof(GUID)) == 0) {
filter_context->v4_attach_type = TRUE;
}
// Add a single WFP filter at the WFP layer corresponding to the hook type, and set the hook NPI client as the
// filter's raw context.
result = net_ebpf_extension_add_wfp_filters(
filter_parameters_array->count, // filter_count
filter_parameters_array->filter_parameters,
(compartment_id == UNSPECIFIED_COMPARTMENT_ID) ? 0 : 1,
(compartment_id == UNSPECIFIED_COMPARTMENT_ID) ? NULL : &condition,
(net_ebpf_extension_wfp_filter_context_t*)filter_context,
&filter_context->base.filter_ids);
NET_EBPF_EXT_BAIL_ON_ERROR_RESULT(result);
// Set the filter context as the client context's provider data.
net_ebpf_extension_hook_client_set_provider_data(
(net_ebpf_extension_hook_client_t*)attaching_client, filter_context);
Exit:
if (result != EBPF_SUCCESS) {
if (filter_context != NULL) {
ExFreePool(filter_context);
}
}
NET_EBPF_EXT_RETURN_RESULT(result);
}
static void
_net_ebpf_extension_sock_addr_on_client_detach(_In_ const net_ebpf_extension_hook_client_t* detaching_client)
{
NET_EBPF_EXT_LOG_ENTRY();
net_ebpf_extension_sock_addr_wfp_filter_context_t* filter_context =
(net_ebpf_extension_sock_addr_wfp_filter_context_t*)net_ebpf_extension_hook_client_get_provider_data(
detaching_client);
ASSERT(filter_context != NULL);
net_ebpf_extension_delete_wfp_filters(filter_context->base.filter_ids_count, filter_context->base.filter_ids);
net_ebpf_extension_wfp_filter_context_cleanup((net_ebpf_extension_wfp_filter_context_t*)filter_context);
}
static NTSTATUS
_net_ebpf_sock_addr_update_store_entries()
{
NTSTATUS status;
// Update section information.
uint32_t section_info_count = sizeof(_ebpf_sock_addr_section_info) / sizeof(ebpf_program_section_info_t);
status = _ebpf_store_update_section_information(&_ebpf_sock_addr_section_info[0], section_info_count);
if (!NT_SUCCESS(status)) {
NET_EBPF_EXT_RETURN_NTSTATUS(status);
}
// Update program information.
status = _ebpf_store_update_program_information(&_ebpf_sock_addr_program_info, 1);
NET_EBPF_EXT_RETURN_NTSTATUS(status);
}
static NTSTATUS
_net_ebpf_sock_addr_create_security_descriptor()
{
NTSTATUS status;
ACL* dacl = NULL;
uint32_t acl_length = 0;
ACCESS_MASK access_mask = GENERIC_ALL;
SECURITY_DESCRIPTOR* admin_security_descriptor = NULL;
_net_ebpf_ext_generic_mapping = *(IoGetFileObjectGenericMapping());
RtlMapGenericMask(&access_mask, &_net_ebpf_ext_generic_mapping);
admin_security_descriptor = (SECURITY_DESCRIPTOR*)ExAllocatePoolUninitialized(
NonPagedPoolNx, sizeof(SECURITY_DESCRIPTOR), NET_EBPF_EXTENSION_POOL_TAG);
NET_EBPF_EXT_BAIL_ON_ALLOC_FAILURE_STATUS(admin_security_descriptor, "admin_sd", status);
status = RtlCreateSecurityDescriptor(admin_security_descriptor, SECURITY_DESCRIPTOR_REVISION);
if (!NT_SUCCESS(status)) {
NET_EBPF_EXT_LOG_NTSTATUS_API_FAILURE(
NET_EBPF_EXT_TRACELOG_KEYWORD_SOCK_ADDR, "RtlCreateSecurityDescriptor", status);
goto Exit;
}
acl_length += sizeof(ACL);
acl_length += RtlLengthSid(SeExports->SeAliasAdminsSid) + FIELD_OFFSET(ACCESS_ALLOWED_ACE, SidStart);
acl_length += RtlLengthSid(SeExports->SeLocalSystemSid) + FIELD_OFFSET(ACCESS_ALLOWED_ACE, SidStart);
dacl = (ACL*)ExAllocatePoolUninitialized(NonPagedPoolNx, acl_length, NET_EBPF_EXTENSION_POOL_TAG);
NET_EBPF_EXT_BAIL_ON_ALLOC_FAILURE_STATUS(dacl, "dacl", status);
RtlCreateAcl(dacl, acl_length, ACL_REVISION);
status = RtlAddAccessAllowedAce(dacl, ACL_REVISION, access_mask, SeExports->SeAliasAdminsSid);
if (!NT_SUCCESS(status)) {
NET_EBPF_EXT_LOG_NTSTATUS_API_FAILURE(
NET_EBPF_EXT_TRACELOG_KEYWORD_SOCK_ADDR, "RtlAddAccessAllowedAce", status);
goto Exit;
}
status = RtlAddAccessAllowedAce(dacl, ACL_REVISION, access_mask, SeExports->SeLocalSystemSid);
if (!NT_SUCCESS(status)) {
NET_EBPF_EXT_LOG_NTSTATUS_API_FAILURE(
NET_EBPF_EXT_TRACELOG_KEYWORD_SOCK_ADDR, "RtlAddAccessAllowedAce", status);
goto Exit;
}
status = RtlSetDaclSecurityDescriptor(admin_security_descriptor, TRUE, dacl, FALSE);
if (!NT_SUCCESS(status)) {
NET_EBPF_EXT_LOG_NTSTATUS_API_FAILURE(
NET_EBPF_EXT_TRACELOG_KEYWORD_SOCK_ADDR, "RtlSetDaclSecurityDescriptor", status);
goto Exit;
}
_net_ebpf_ext_security_descriptor_admin = admin_security_descriptor;
admin_security_descriptor = NULL;
_net_ebpf_ext_dacl_admin = dacl;
dacl = NULL;
Exit:
if (dacl != NULL) {
ExFreePool(dacl);
}
if (admin_security_descriptor != NULL) {
ExFreePool(admin_security_descriptor);
}
NET_EBPF_EXT_RETURN_NTSTATUS(status);
}
static void
_net_ebpf_sock_addr_clean_up_security_descriptor()
{
if (_net_ebpf_ext_dacl_admin != NULL) {
ExFreePool(_net_ebpf_ext_dacl_admin);
_net_ebpf_ext_dacl_admin = NULL;
}
if (_net_ebpf_ext_security_descriptor_admin != NULL) {
ExFreePool(_net_ebpf_ext_security_descriptor_admin);
_net_ebpf_ext_security_descriptor_admin = NULL;
}
}
static void
_net_ebpf_sock_addr_initialize_globals()
{
InitializeListHead(&_net_ebpf_ext_redirect_handle_list);
InitializeListHead(&_net_ebpf_ext_connect_context_list);
}
static NTSTATUS
_net_ebpf_ext_sock_addr_update_redirect_handle(uint64_t filter_id, HANDLE redirect_handle)
{
NTSTATUS status = STATUS_SUCCESS;
KIRQL old_irql;
net_ebpf_extension_redirect_handle_entry_t* entry =
(net_ebpf_extension_redirect_handle_entry_t*)ExAllocatePoolUninitialized(
NonPagedPoolNx, sizeof(net_ebpf_extension_redirect_handle_entry_t), NET_EBPF_EXTENSION_POOL_TAG);
NET_EBPF_EXT_BAIL_ON_ALLOC_FAILURE_STATUS(entry, "redirect_handle", status);
memset(entry, 0, sizeof(net_ebpf_extension_redirect_handle_entry_t));
entry->filter_id = filter_id;
entry->redirect_handle = redirect_handle;
old_irql = ExAcquireSpinLockExclusive(&_net_ebpf_ext_sock_addr_lock);
InsertTailList(&_net_ebpf_ext_redirect_handle_list, &entry->list_entry);
ExReleaseSpinLockExclusive(&_net_ebpf_ext_sock_addr_lock, old_irql);
Exit:
NET_EBPF_EXT_RETURN_NTSTATUS(status);
}
static void
_net_ebpf_ext_sock_addr_delete_redirect_handle(uint64_t filter_id)
{
KIRQL old_irql;
old_irql = ExAcquireSpinLockExclusive(&_net_ebpf_ext_sock_addr_lock);
LIST_ENTRY* list_entry = _net_ebpf_ext_redirect_handle_list.Flink;
while (list_entry != &_net_ebpf_ext_redirect_handle_list) {
net_ebpf_extension_redirect_handle_entry_t* entry =
CONTAINING_RECORD(list_entry, net_ebpf_extension_redirect_handle_entry_t, list_entry);
if (entry->filter_id == filter_id) {
RemoveEntryList(list_entry);
ExFreePool(entry);
break;
}
list_entry = list_entry->Flink;
}
ExReleaseSpinLockExclusive(&_net_ebpf_ext_sock_addr_lock, old_irql);
}
static NTSTATUS
_net_ebpf_ext_sock_addr_get_redirect_handle(uint64_t filter_id, _Out_ HANDLE* redirect_handle)
{
NTSTATUS status = STATUS_NOT_FOUND;
KIRQL old_irql;
old_irql = ExAcquireSpinLockShared(&_net_ebpf_ext_sock_addr_lock);
LIST_ENTRY* list_entry = _net_ebpf_ext_redirect_handle_list.Flink;
while (list_entry != &_net_ebpf_ext_redirect_handle_list) {
net_ebpf_extension_redirect_handle_entry_t* entry =
CONTAINING_RECORD(list_entry, net_ebpf_extension_redirect_handle_entry_t, list_entry);
if (entry->filter_id == filter_id) {
*redirect_handle = entry->redirect_handle;
status = STATUS_SUCCESS;
break;
}
list_entry = list_entry->Flink;
}
ExReleaseSpinLockShared(&_net_ebpf_ext_sock_addr_lock, old_irql);
NET_EBPF_EXT_RETURN_NTSTATUS(status);
}
/**
* @brief Compare the destination address in the two provided bpf_sock_addr_t structs.
*
* @param[in] addr1 Pointer to first sock_addr struct to compare.
* @param[in] addr2 Pointer to second sock_addr struct to compare.
* @return TRUE The addresses are same.
@return FALSE The addresses are different.
*/
static inline BOOLEAN
_net_ebpf_ext_compare_destination_address(_In_ const bpf_sock_addr_t* addr1, _In_ const bpf_sock_addr_t* addr2)
{
ASSERT(addr1->family == addr2->family);
if (addr1->family != addr2->family) {
return FALSE;
}
return INET_ADDR_EQUAL((ADDRESS_FAMILY)addr1->family, &addr1->user_ip4, &addr2->user_ip4);
}
#define CONNECTION_CONTEXT_INITIALIZATION_SET_TIMESTAMP 0x1
static void
_net_ebpf_extension_connection_context_initialize(
_In_ const bpf_sock_addr_t* sock_addr_ctx,
uint64_t transport_endpoint_handle,
uint32_t flags,
_Out_ net_ebpf_extension_connection_context_t* connection_context)
{
bool set_timestamp = flags & CONNECTION_CONTEXT_INITIALIZATION_SET_TIMESTAMP;
RtlCopyMemory(
connection_context->address_info.destination_ip.ipv6,
sock_addr_ctx->user_ip6,
sizeof(connection_context->address_info.destination_ip));
connection_context->address_info.destination_port = sock_addr_ctx->user_port;
connection_context->address_info.family = sock_addr_ctx->family;
connection_context->address_info.source_port = sock_addr_ctx->msg_src_port;
connection_context->transport_endpoint_handle = transport_endpoint_handle;
connection_context->protocol = (uint16_t)sock_addr_ctx->protocol;
connection_context->compartment_id = sock_addr_ctx->compartment_id;
if (set_timestamp) {
connection_context->timestamp = CONVERT_100NS_UNITS_TO_MS(KeQueryInterruptTime());
}
}
static net_ebpf_extension_connection_context_t*
_net_ebpf_ext_get_and_remove_connection_context(
uint64_t transport_endpoint_handle, _In_ const bpf_sock_addr_t* sock_addr_ctx)
{
KIRQL old_irql;
net_ebpf_extension_connection_context_t local_connection_context = {0};
net_ebpf_extension_connection_context_t* connection_context = NULL;
_net_ebpf_extension_connection_context_initialize(
sock_addr_ctx, transport_endpoint_handle, 0, &local_connection_context);
old_irql = ExAcquireSpinLockExclusive(&_net_ebpf_ext_sock_addr_lock);
LIST_ENTRY* list_entry = _net_ebpf_ext_connect_context_list.Flink;
while (list_entry != &_net_ebpf_ext_connect_context_list) {
net_ebpf_extension_connection_context_t* entry =
CONTAINING_RECORD(list_entry, net_ebpf_extension_connection_context_t, list_entry);
if (memcmp(
&local_connection_context, entry, EBPF_OFFSET_OF(net_ebpf_extension_connection_context_t, timestamp)) ==
0) {
// Found matching entry. Remove it from the list and return.
RemoveEntryList(&entry->list_entry);
_net_ebpf_ext_connect_context_count--;
connection_context = entry;
break;
}
list_entry = list_entry->Flink;
}
ExReleaseSpinLockExclusive(&_net_ebpf_ext_sock_addr_lock, old_irql);
NET_EBPF_EXT_RETURN_POINTER(net_ebpf_extension_connection_context_t*, connection_context);
}
_Requires_exclusive_lock_held_(_net_ebpf_ext_sock_addr_lock) static void _net_ebpf_ext_purge_lru_contexts_under_lock(
BOOLEAN delete_all)
{
uint64_t expiry_time = CONVERT_100NS_UNITS_TO_MS(KeQueryInterruptTime()) - EXPIRY_TIME;
LIST_ENTRY* list_entry = _net_ebpf_ext_connect_context_list.Blink;
while (list_entry != &_net_ebpf_ext_connect_context_list) {
net_ebpf_extension_connection_context_t* entry =
CONTAINING_RECORD(list_entry, net_ebpf_extension_connection_context_t, list_entry);
if (!delete_all && entry->timestamp > expiry_time) {
break;
}
list_entry = list_entry->Blink;
RemoveEntryList(&entry->list_entry);
_net_ebpf_ext_connect_context_count--;
NET_EBPF_EXT_LOG_MESSAGE_UINT64(
NET_EBPF_EXT_TRACELOG_LEVEL_VERBOSE,
NET_EBPF_EXT_TRACELOG_KEYWORD_SOCK_ADDR,
"_net_ebpf_ext_purge_lru_contexts_under_lock: Delete",
entry->transport_endpoint_handle);
ExFreePool(entry);
}
NET_EBPF_EXT_LOG_MESSAGE_UINT64(
NET_EBPF_EXT_TRACELOG_LEVEL_INFO,
NET_EBPF_EXT_TRACELOG_KEYWORD_SOCK_ADDR,
"_net_ebpf_ext_purge_lru_contexts_under_lock",
_net_ebpf_ext_connect_context_count);
}
static void
_net_ebpf_ext_purge_lru_contexts(BOOLEAN delete_all)
{
KIRQL old_irql = ExAcquireSpinLockExclusive(&_net_ebpf_ext_sock_addr_lock);
_net_ebpf_ext_purge_lru_contexts_under_lock(delete_all);
ExReleaseSpinLockExclusive(&_net_ebpf_ext_sock_addr_lock, old_irql);
}
static void
_net_ebpf_ext_insert_connection_context_to_list(_Inout_ net_ebpf_extension_connection_context_t* connection_context)
{
KIRQL old_irql = ExAcquireSpinLockExclusive(&_net_ebpf_ext_sock_addr_lock);
// Insert the most recent entry at the head.
InsertHeadList(&_net_ebpf_ext_connect_context_list, &connection_context->list_entry);
_net_ebpf_ext_connect_context_count++;
// Purge stale entries from the list.
_net_ebpf_ext_purge_lru_contexts_under_lock(FALSE);
ExReleaseSpinLockExclusive(&_net_ebpf_ext_sock_addr_lock, old_irql);
}
NTSTATUS
net_ebpf_ext_sock_addr_register_providers()
{
NTSTATUS status = STATUS_SUCCESS;
NET_EBPF_EXT_LOG_ENTRY();
status = _net_ebpf_sock_addr_update_store_entries();
if (!NT_SUCCESS(status)) {
NET_EBPF_EXT_RETURN_NTSTATUS(status);
}
status = _net_ebpf_sock_addr_create_security_descriptor();
if (!NT_SUCCESS(status)) {
NET_EBPF_EXT_RETURN_NTSTATUS(status);
}
_net_ebpf_sock_addr_initialize_globals();
const net_ebpf_extension_program_info_provider_parameters_t program_info_provider_parameters = {
&_ebpf_sock_addr_program_info_provider_moduleid, &_ebpf_sock_addr_program_info_provider_data};
// Set the program type as the provider module id.
_ebpf_sock_addr_program_info_provider_moduleid.Guid = EBPF_PROGRAM_TYPE_CGROUP_SOCK_ADDR;
status = net_ebpf_extension_program_info_provider_register(
&program_info_provider_parameters, &_ebpf_sock_addr_program_info_provider_context);
if (!NT_SUCCESS(status)) {
goto Exit;
}
for (int i = 0; i < NET_EBPF_SOCK_ADDR_HOOK_PROVIDER_COUNT; i++) {
const net_ebpf_extension_hook_provider_parameters_t hook_provider_parameters = {
&_ebpf_sock_addr_hook_provider_moduleid[i], &_net_ebpf_extension_sock_addr_hook_provider_data[i]};
_net_ebpf_sock_addr_hook_provider_data[i].supported_program_type = EBPF_PROGRAM_TYPE_CGROUP_SOCK_ADDR;
_net_ebpf_sock_addr_hook_provider_data[i].bpf_attach_type =
(bpf_attach_type_t)_net_ebpf_extension_sock_addr_bpf_attach_types[i];
_net_ebpf_sock_addr_hook_provider_data[i].link_type = BPF_LINK_TYPE_CGROUP;
_net_ebpf_extension_sock_addr_hook_provider_data[i].version = EBPF_ATTACH_PROVIDER_DATA_VERSION;
_net_ebpf_extension_sock_addr_hook_provider_data[i].data = &_net_ebpf_sock_addr_hook_provider_data[i];
_net_ebpf_extension_sock_addr_hook_provider_data[i].size = sizeof(ebpf_attach_provider_data_t);
// Set the attach type as the provider module id.
_ebpf_sock_addr_hook_provider_moduleid[i].Length = sizeof(NPI_MODULEID);
_ebpf_sock_addr_hook_provider_moduleid[i].Type = MIT_GUID;
_ebpf_sock_addr_hook_provider_moduleid[i].Guid = *_net_ebpf_extension_sock_addr_attach_types[i];
// Register the provider context and pass the pointer to the WFP filter parameters
// corresponding to this hook type as custom data.
status = net_ebpf_extension_hook_provider_register(
&hook_provider_parameters,
_net_ebpf_extension_sock_addr_on_client_attach,
_net_ebpf_extension_sock_addr_on_client_detach,
&_net_ebpf_extension_sock_addr_wfp_filter_parameters[i],
&_ebpf_sock_addr_hook_provider_context[i]);
if (!NT_SUCCESS(status)) {
goto Exit;
}
}
Exit:
if (!NT_SUCCESS(status)) {
net_ebpf_ext_sock_addr_unregister_providers();
_net_ebpf_sock_addr_clean_up_security_descriptor();
}
NET_EBPF_EXT_RETURN_NTSTATUS(status);
}
void
net_ebpf_ext_sock_addr_unregister_providers()
{
for (int i = 0; i < NET_EBPF_SOCK_ADDR_HOOK_PROVIDER_COUNT; i++) {
if (_ebpf_sock_addr_hook_provider_context[i]) {
net_ebpf_extension_hook_provider_unregister(_ebpf_sock_addr_hook_provider_context[i]);
_ebpf_sock_addr_hook_provider_context[i] = NULL;
}
}
if (_ebpf_sock_addr_program_info_provider_context) {
net_ebpf_extension_program_info_provider_unregister(_ebpf_sock_addr_program_info_provider_context);
_ebpf_sock_addr_program_info_provider_context = NULL;
}
_net_ebpf_ext_purge_lru_contexts(TRUE);
_net_ebpf_sock_addr_clean_up_security_descriptor();
}
typedef enum _net_ebpf_extension_sock_addr_connection_direction
{
EBPF_HOOK_SOCK_ADDR_INGRESS = 0,
EBPF_HOOK_SOCK_ADDR_EGRESS
} net_ebpf_extension_sock_addr_connection_direction_t;
static net_ebpf_extension_sock_addr_connection_direction_t
_net_ebpf_extension_sock_addr_get_connection_direction_from_hook_id(net_ebpf_extension_hook_id_t hook_id)
{
return ((hook_id == EBPF_HOOK_ALE_AUTH_CONNECT_V4) || (hook_id == EBPF_HOOK_ALE_AUTH_CONNECT_V6) ||
(hook_id == EBPF_HOOK_ALE_CONNECT_REDIRECT_V4) || (hook_id == EBPF_HOOK_ALE_CONNECT_REDIRECT_V6))
? EBPF_HOOK_SOCK_ADDR_EGRESS
: EBPF_HOOK_SOCK_ADDR_INGRESS;
}
const wfp_ale_layer_fields_t wfp_connection_fields[] = {
// EBPF_HOOK_ALE_AUTH_CONNECT_V4
{FWPS_FIELD_ALE_AUTH_CONNECT_V4_IP_LOCAL_ADDRESS,
FWPS_FIELD_ALE_AUTH_CONNECT_V4_IP_LOCAL_PORT,
FWPS_FIELD_ALE_AUTH_CONNECT_V4_IP_REMOTE_ADDRESS,
FWPS_FIELD_ALE_AUTH_CONNECT_V4_IP_REMOTE_PORT,
FWPS_FIELD_ALE_AUTH_CONNECT_V4_IP_PROTOCOL,
0, // No direction field in this layer.
FWPS_FIELD_ALE_AUTH_CONNECT_V4_COMPARTMENT_ID,
FWPS_FIELD_ALE_AUTH_CONNECT_V4_IP_LOCAL_INTERFACE,
FWPS_FIELD_ALE_AUTH_CONNECT_V4_ALE_USER_ID,
FWPS_FIELD_ALE_AUTH_CONNECT_V4_FLAGS},
// EBPF_HOOK_ALE_AUTH_CONNECT_V6
{FWPS_FIELD_ALE_AUTH_CONNECT_V6_IP_LOCAL_ADDRESS,
FWPS_FIELD_ALE_AUTH_CONNECT_V6_IP_LOCAL_PORT,
FWPS_FIELD_ALE_AUTH_CONNECT_V6_IP_REMOTE_ADDRESS,
FWPS_FIELD_ALE_AUTH_CONNECT_V6_IP_REMOTE_PORT,
FWPS_FIELD_ALE_AUTH_CONNECT_V6_IP_PROTOCOL,
0, // No direction field in this layer.
FWPS_FIELD_ALE_AUTH_CONNECT_V6_COMPARTMENT_ID,
FWPS_FIELD_ALE_AUTH_CONNECT_V6_IP_LOCAL_INTERFACE,
FWPS_FIELD_ALE_AUTH_CONNECT_V6_ALE_USER_ID,
FWPS_FIELD_ALE_AUTH_CONNECT_V6_FLAGS},
// EBPF_HOOK_ALE_CONNECT_REDIRECT_V4
{FWPS_FIELD_ALE_CONNECT_REDIRECT_V4_IP_LOCAL_ADDRESS,
FWPS_FIELD_ALE_CONNECT_REDIRECT_V4_IP_LOCAL_PORT,
FWPS_FIELD_ALE_CONNECT_REDIRECT_V4_IP_REMOTE_ADDRESS,
FWPS_FIELD_ALE_CONNECT_REDIRECT_V4_IP_REMOTE_PORT,
FWPS_FIELD_ALE_CONNECT_REDIRECT_V4_IP_PROTOCOL,
0, // No direction field in this layer.
FWPS_FIELD_ALE_CONNECT_REDIRECT_V4_COMPARTMENT_ID,
0, // No interface luid in this layer.
FWPS_FIELD_ALE_CONNECT_REDIRECT_V4_ALE_USER_ID,
FWPS_FIELD_ALE_CONNECT_REDIRECT_V4_FLAGS},
// EBPF_HOOK_ALE_CONNECT_REDIRECT_V6
{FWPS_FIELD_ALE_CONNECT_REDIRECT_V6_IP_LOCAL_ADDRESS,
FWPS_FIELD_ALE_CONNECT_REDIRECT_V6_IP_LOCAL_PORT,
FWPS_FIELD_ALE_CONNECT_REDIRECT_V6_IP_REMOTE_ADDRESS,
FWPS_FIELD_ALE_CONNECT_REDIRECT_V6_IP_REMOTE_PORT,
FWPS_FIELD_ALE_CONNECT_REDIRECT_V6_IP_PROTOCOL,
0, // No direction field in this layer.
FWPS_FIELD_ALE_CONNECT_REDIRECT_V6_COMPARTMENT_ID,
0, // No interface luid in this layer.
FWPS_FIELD_ALE_CONNECT_REDIRECT_V6_ALE_USER_ID,
FWPS_FIELD_ALE_CONNECT_REDIRECT_V6_FLAGS},
// EBPF_HOOK_ALE_AUTH_RECV_ACCEPT_V4
{FWPS_FIELD_ALE_AUTH_RECV_ACCEPT_V4_IP_LOCAL_ADDRESS,
FWPS_FIELD_ALE_AUTH_RECV_ACCEPT_V4_IP_LOCAL_PORT,
FWPS_FIELD_ALE_AUTH_RECV_ACCEPT_V4_IP_REMOTE_ADDRESS,
FWPS_FIELD_ALE_AUTH_RECV_ACCEPT_V4_IP_REMOTE_PORT,
FWPS_FIELD_ALE_AUTH_RECV_ACCEPT_V4_IP_PROTOCOL,
0, // No direction field in this layer.
FWPS_FIELD_ALE_AUTH_RECV_ACCEPT_V4_COMPARTMENT_ID,
FWPS_FIELD_ALE_AUTH_RECV_ACCEPT_V4_IP_LOCAL_INTERFACE,
FWPS_FIELD_ALE_AUTH_RECV_ACCEPT_V4_ALE_USER_ID,
FWPS_FIELD_ALE_AUTH_RECV_ACCEPT_V4_FLAGS},
// EBPF_HOOK_ALE_AUTH_RECV_ACCEPT_V6
{FWPS_FIELD_ALE_AUTH_RECV_ACCEPT_V6_IP_LOCAL_ADDRESS,
FWPS_FIELD_ALE_AUTH_RECV_ACCEPT_V6_IP_LOCAL_PORT,
FWPS_FIELD_ALE_AUTH_RECV_ACCEPT_V6_IP_REMOTE_ADDRESS,
FWPS_FIELD_ALE_AUTH_RECV_ACCEPT_V6_IP_REMOTE_PORT,
FWPS_FIELD_ALE_AUTH_RECV_ACCEPT_V6_IP_PROTOCOL,
0, // No direction field in this layer.
FWPS_FIELD_ALE_AUTH_RECV_ACCEPT_V6_COMPARTMENT_ID,
FWPS_FIELD_ALE_AUTH_RECV_ACCEPT_V6_IP_LOCAL_INTERFACE,
FWPS_FIELD_ALE_AUTH_RECV_ACCEPT_V6_ALE_USER_ID,
FWPS_FIELD_ALE_AUTH_RECV_ACCEPT_V6_FLAGS}};
static void
_net_ebpf_extension_sock_addr_copy_wfp_connection_fields(
_In_ const FWPS_INCOMING_VALUES* incoming_fixed_values,
_In_ const FWPS_INCOMING_METADATA_VALUES* incoming_metadata_values,
_Out_ net_ebpf_sock_addr_t* sock_addr_ctx)
{
net_ebpf_extension_hook_id_t hook_id =
net_ebpf_extension_get_hook_id_from_wfp_layer_id(incoming_fixed_values->layerId);
net_ebpf_extension_sock_addr_connection_direction_t direction =
_net_ebpf_extension_sock_addr_get_connection_direction_from_hook_id(hook_id);
const wfp_ale_layer_fields_t* fields = &wfp_connection_fields[hook_id - EBPF_HOOK_ALE_AUTH_CONNECT_V4];
uint16_t source_ip_address_field =
(direction == EBPF_HOOK_SOCK_ADDR_EGRESS) ? fields->local_ip_address_field : fields->remote_ip_address_field;
uint16_t source_port_field =
(direction == EBPF_HOOK_SOCK_ADDR_EGRESS) ? fields->local_port_field : fields->remote_port_field;
uint16_t destination_ip_address_field =
(direction == EBPF_HOOK_SOCK_ADDR_EGRESS) ? fields->remote_ip_address_field : fields->local_ip_address_field;
uint16_t destination_port_field =
(direction == EBPF_HOOK_SOCK_ADDR_EGRESS) ? fields->remote_port_field : fields->local_port_field;
FWPS_INCOMING_VALUE0* incoming_values = incoming_fixed_values->incomingValue;
// Copy IP address fields.
if ((hook_id == EBPF_HOOK_ALE_AUTH_CONNECT_V4) || (hook_id == EBPF_HOOK_ALE_AUTH_RECV_ACCEPT_V4) ||
(hook_id == EBPF_HOOK_ALE_CONNECT_REDIRECT_V4)) {
sock_addr_ctx->base.family = AF_INET;
sock_addr_ctx->base.msg_src_ip4 = htonl(incoming_values[source_ip_address_field].value.uint32);
sock_addr_ctx->base.user_ip4 = htonl(incoming_values[destination_ip_address_field].value.uint32);
} else {
sock_addr_ctx->base.family = AF_INET6;
RtlCopyMemory(
sock_addr_ctx->base.msg_src_ip6,
incoming_values[source_ip_address_field].value.byteArray16,
sizeof(FWP_BYTE_ARRAY16));
RtlCopyMemory(
sock_addr_ctx->base.user_ip6,
incoming_values[destination_ip_address_field].value.byteArray16,
sizeof(FWP_BYTE_ARRAY16));
}
sock_addr_ctx->base.msg_src_port = htons(incoming_values[source_port_field].value.uint16);
sock_addr_ctx->base.user_port = htons(incoming_values[destination_port_field].value.uint16);
sock_addr_ctx->base.protocol = incoming_values[fields->protocol_field].value.uint8;
sock_addr_ctx->base.compartment_id = incoming_values[fields->compartment_id_field].value.uint32;
if (hook_id == EBPF_HOOK_ALE_CONNECT_REDIRECT_V4 || hook_id == EBPF_HOOK_ALE_CONNECT_REDIRECT_V6) {
sock_addr_ctx->base.interface_luid = 0;
} else {
sock_addr_ctx->base.interface_luid = *incoming_values[fields->interface_luid_field].value.uint64;
}
// USER_ID is available for all sock_addr attach types.
sock_addr_ctx->access_information =
(TOKEN_ACCESS_INFORMATION*)(incoming_values[fields->user_id_field].value.byteBlob->data);
if (incoming_metadata_values->currentMetadataValues & FWPS_METADATA_FIELD_PROCESS_ID) {
sock_addr_ctx->process_id = incoming_metadata_values->processId;
} else {
NET_EBPF_EXT_LOG_MESSAGE_UINT64(
NET_EBPF_EXT_TRACELOG_LEVEL_VERBOSE,
NET_EBPF_EXT_TRACELOG_KEYWORD_SOCK_ADDR,
"FWPS_METADATA_FIELD_PROCESS_ID not present",
hook_id);
sock_addr_ctx->process_id = 0;
}
// Store the FLAGS field.
sock_addr_ctx->flags = incoming_values[fields->flags_field].value.uint32;
}
NTSTATUS
net_ebpf_ext_connect_redirect_filter_change_notify(
FWPS_CALLOUT_NOTIFY_TYPE callout_notification_type, _In_ const GUID* filter_key, _Inout_ FWPS_FILTER* filter)
{
NET_EBPF_EXT_LOG_ENTRY();
NTSTATUS status = STATUS_SUCCESS;
if (callout_notification_type == FWPS_CALLOUT_NOTIFY_ADD_FILTER) {
HANDLE redirect_handle;
status = FwpsRedirectHandleCreate(&EBPF_HOOK_ALE_CONNECT_REDIRECT_PROVIDER, 0, &redirect_handle);
NET_EBPF_EXT_BAIL_ON_ERROR_STATUS(status);
status = _net_ebpf_ext_sock_addr_update_redirect_handle(filter->filterId, redirect_handle);
NET_EBPF_EXT_BAIL_ON_ERROR_STATUS(status);
} else if (callout_notification_type == FWPS_CALLOUT_NOTIFY_DELETE_FILTER) {
_net_ebpf_ext_sock_addr_delete_redirect_handle(filter->filterId);
}
net_ebpf_ext_filter_change_notify(callout_notification_type, filter_key, filter);
Exit:
NET_EBPF_EXT_RETURN_NTSTATUS(status);
}
#define DEFINE_SOCK_ADDR_CLASSIFY_LOG_FUNCTION(family) \
static void _net_ebpf_ext_log_sock_addr_classify_v##family##( \
_In_z_ const char* message, \
uint64_t transport_endpoint_handle, \
_In_ const bpf_sock_addr_t* original_context, \
_In_opt_ const bpf_sock_addr_t* redirected_context, \
uint32_t verdict) \
{ \
if (verdict == BPF_SOCK_ADDR_VERDICT_REJECT) { \
NET_EBPF_EXT_LOG_SOCK_ADDR_CLASSIFY_IPV##family##( \
NET_EBPF_EXT_TRACELOG_LEVEL_INFO, \
message, \
transport_endpoint_handle, \
original_context->protocol, \
original_context->msg_src_ip##family##, \
ntohs(original_context->msg_src_port), \
original_context->user_ip##family##, \
ntohs(original_context->user_port), \
verdict); \
} else if (redirected_context != NULL) { \
NET_EBPF_EXT_LOG_SOCK_ADDR_REDIRECT_CLASSIFY_IPV##family##( \
message, \
transport_endpoint_handle, \
original_context->protocol, \
original_context->msg_src_ip##family##, \
ntohs(original_context->msg_src_port), \
original_context->user_ip##family##, \
ntohs(original_context->user_port), \
redirected_context->user_ip##family##, \
ntohs(redirected_context->user_port), \
verdict); \
} else { \
NET_EBPF_EXT_LOG_SOCK_ADDR_CLASSIFY_IPV##family##( \
NET_EBPF_EXT_TRACELOG_LEVEL_VERBOSE, \
message, \
transport_endpoint_handle, \
original_context->protocol, \
original_context->msg_src_ip##family##, \
ntohs(original_context->msg_src_port), \
original_context->msg_src_ip##family##, \
ntohs(original_context->user_port), \
verdict); \
} \
}
DEFINE_SOCK_ADDR_CLASSIFY_LOG_FUNCTION(4)
DEFINE_SOCK_ADDR_CLASSIFY_LOG_FUNCTION(6)
static void
_net_ebpf_ext_log_sock_addr_classify(
_In_z_ const char* message,
uint64_t transport_endpoint_handle,
_In_ const bpf_sock_addr_t* original_context,
_In_opt_ const bpf_sock_addr_t* redirected_context,
uint32_t verdict)
{
if (original_context->family == AF_INET) {
_net_ebpf_ext_log_sock_addr_classify_v4(
message, transport_endpoint_handle, original_context, redirected_context, verdict);
} else {
_net_ebpf_ext_log_sock_addr_classify_v6(
message, transport_endpoint_handle, original_context, redirected_context, verdict);
}
}
//
// WFP callout callback functions.
//
void
net_ebpf_extension_sock_addr_authorize_recv_accept_classify(
_In_ const FWPS_INCOMING_VALUES* incoming_fixed_values,
_In_ const FWPS_INCOMING_METADATA_VALUES* incoming_metadata_values,
_Inout_opt_ void* layer_data,
_In_opt_ const void* classify_context,
_In_ const FWPS_FILTER* filter,
uint64_t flow_context,
_Inout_ FWPS_CLASSIFY_OUT* classify_output)
{
uint32_t result;
net_ebpf_extension_sock_addr_wfp_filter_context_t* filter_context = NULL;
net_ebpf_extension_hook_client_t* attached_client = NULL;
net_ebpf_sock_addr_t net_ebpf_sock_addr_ctx = {0};
bpf_sock_addr_t* sock_addr_ctx = &net_ebpf_sock_addr_ctx.base;
uint32_t compartment_id = UNSPECIFIED_COMPARTMENT_ID;
UNREFERENCED_PARAMETER(incoming_metadata_values);
UNREFERENCED_PARAMETER(layer_data);
UNREFERENCED_PARAMETER(classify_context);
UNREFERENCED_PARAMETER(flow_context);
classify_output->actionType = FWP_ACTION_PERMIT;
filter_context = (net_ebpf_extension_sock_addr_wfp_filter_context_t*)filter->context;
ASSERT(filter_context != NULL);
if (filter_context == NULL) {
goto Exit;
}
attached_client = (net_ebpf_extension_hook_client_t*)filter_context->base.client_context;
if (attached_client == NULL) {
goto Exit;
}
if (!net_ebpf_extension_hook_client_enter_rundown(attached_client)) {
attached_client = NULL;
goto Exit;
}
_net_ebpf_extension_sock_addr_copy_wfp_connection_fields(
incoming_fixed_values, incoming_metadata_values, &net_ebpf_sock_addr_ctx);
// eBPF programs will not be invoked on connection re-authorization.
if (net_ebpf_sock_addr_ctx.flags & FWP_CONDITION_FLAG_IS_REAUTHORIZE) {
goto Exit;
}
compartment_id = filter_context->compartment_id;
ASSERT((compartment_id == UNSPECIFIED_COMPARTMENT_ID) || (compartment_id == sock_addr_ctx->compartment_id));
if (compartment_id != UNSPECIFIED_COMPARTMENT_ID && compartment_id != sock_addr_ctx->compartment_id) {
// The client is not interested in this compartment Id.
NET_EBPF_EXT_LOG_MESSAGE_UINT32(
NET_EBPF_EXT_TRACELOG_LEVEL_VERBOSE,
NET_EBPF_EXT_TRACELOG_KEYWORD_SOCK_ADDR,
"The cgroup_sock_addr eBPF program is not interested in this compartment ID",
sock_addr_ctx->compartment_id);
goto Exit;
}
if (net_ebpf_extension_hook_invoke_program(attached_client, sock_addr_ctx, &result) != EBPF_SUCCESS) {
// Block the request if we failed to invoke the eBPF program.
classify_output->actionType = FWP_ACTION_BLOCK;
goto Exit;
}
classify_output->actionType = (result == BPF_SOCK_ADDR_VERDICT_PROCEED) ? FWP_ACTION_PERMIT : FWP_ACTION_BLOCK;
if (classify_output->actionType == FWP_ACTION_BLOCK) {
classify_output->rights &= ~FWPS_RIGHT_ACTION_WRITE;
}
_net_ebpf_ext_log_sock_addr_classify(
"recv_accept_classify", incoming_metadata_values->transportEndpointHandle, sock_addr_ctx, NULL, result);
Exit:
if (attached_client) {
net_ebpf_extension_hook_client_leave_rundown(attached_client);
}
}
/*
* Default action is BLOCK. If this callout is being invoked, it means at least one
* eBPF program is attached. Hence no connection should be allowed unless allowed by
* the eBPF program.
*/
void
net_ebpf_extension_sock_addr_authorize_connection_classify(
_In_ const FWPS_INCOMING_VALUES* incoming_fixed_values,
_In_ const FWPS_INCOMING_METADATA_VALUES* incoming_metadata_values,
_Inout_opt_ void* layer_data,
_In_opt_ const void* classify_context,
_In_ const FWPS_FILTER* filter,
uint64_t flow_context,
_Inout_ FWPS_CLASSIFY_OUT* classify_output)
{
uint32_t verdict = BPF_SOCK_ADDR_VERDICT_REJECT;
net_ebpf_extension_sock_addr_wfp_filter_context_t* filter_context = NULL;
net_ebpf_sock_addr_t net_ebpf_sock_addr_ctx = {0};
bpf_sock_addr_t* sock_addr_ctx = &net_ebpf_sock_addr_ctx.base;
uint32_t compartment_id = UNSPECIFIED_COMPARTMENT_ID;
net_ebpf_extension_connection_context_t* connection_context = NULL;
UNREFERENCED_PARAMETER(incoming_metadata_values);
UNREFERENCED_PARAMETER(layer_data);
UNREFERENCED_PARAMETER(classify_context);
UNREFERENCED_PARAMETER(flow_context);
filter_context = (net_ebpf_extension_sock_addr_wfp_filter_context_t*)filter->context;
ASSERT(filter_context != NULL);
if (filter_context == NULL) {
goto Exit;
}
_net_ebpf_extension_sock_addr_copy_wfp_connection_fields(
incoming_fixed_values, incoming_metadata_values, &net_ebpf_sock_addr_ctx);
if (net_ebpf_sock_addr_ctx.flags & FWP_CONDITION_FLAG_IS_REAUTHORIZE) {
// This is a re-authorization of a connection that was previously authorized by the
// eBPF program. Permit it.
verdict = BPF_SOCK_ADDR_VERDICT_PROCEED;
goto Exit;
}
compartment_id = filter_context->compartment_id;
ASSERT((compartment_id == UNSPECIFIED_COMPARTMENT_ID) || (compartment_id == sock_addr_ctx->compartment_id));
if (compartment_id != UNSPECIFIED_COMPARTMENT_ID && compartment_id != sock_addr_ctx->compartment_id) {
// The client is not interested in this compartment Id.
NET_EBPF_EXT_LOG_MESSAGE_UINT32(
NET_EBPF_EXT_TRACELOG_LEVEL_VERBOSE,
NET_EBPF_EXT_TRACELOG_KEYWORD_SOCK_ADDR,
"The cgroup_sock_addr eBPF program is not interested in this compartment ID",
sock_addr_ctx->compartment_id);
verdict = BPF_SOCK_ADDR_VERDICT_PROCEED;
goto Exit;
}
// Find and remove the connection context for this connection.
connection_context = _net_ebpf_ext_get_and_remove_connection_context(
incoming_metadata_values->transportEndpointHandle, sock_addr_ctx);
if (connection_context == NULL) {
// No blocked connection context was found for this AUTH request. So the connection is allowed.
verdict = BPF_SOCK_ADDR_VERDICT_PROCEED;
goto Exit;
}
verdict = BPF_SOCK_ADDR_VERDICT_REJECT;
ExFreePool(connection_context);
connection_context = NULL;
Exit:
classify_output->actionType = (verdict == BPF_SOCK_ADDR_VERDICT_PROCEED) ? FWP_ACTION_PERMIT : FWP_ACTION_BLOCK;
// Clear FWPS_RIGHT_ACTION_WRITE for block action.
if (classify_output->actionType == FWP_ACTION_BLOCK) {
classify_output->rights &= ~FWPS_RIGHT_ACTION_WRITE;
}
_net_ebpf_ext_log_sock_addr_classify(
"auth_classify", incoming_metadata_values->transportEndpointHandle, sock_addr_ctx, NULL, verdict);
return;
}
static BOOLEAN
_net_ebpf_ext_sock_addr_is_connection_locally_redirected_by_others(
_In_ const FWPS_CONNECT_REQUEST* connect_request, uint64_t filter_id)
{
FWPS_CONNECT_REQUEST* previous_connect_request = connect_request->previousVersion;
while (previous_connect_request != NULL) {
if ((previous_connect_request->modifierFilterId != filter_id) &&
(previous_connect_request->localRedirectHandle != NULL)) {
NET_EBPF_EXT_LOG_MESSAGE_UINT64(
NET_EBPF_EXT_TRACELOG_LEVEL_INFO,
NET_EBPF_EXT_TRACELOG_KEYWORD_SOCK_ADDR,
"Connection previously locally redirected",
previous_connect_request->modifierFilterId);
return TRUE;
}
previous_connect_request = previous_connect_request->previousVersion;
}
return FALSE;
}
static _Must_inspect_result_ NTSTATUS
_net_ebpf_ext_process_redirect_verdict(
_In_ const bpf_sock_addr_t* original_context,
_In_ const bpf_sock_addr_t* redirected_context,
_In_ const FWPS_FILTER* filter,
uint64_t classify_handle,
HANDLE redirect_handle,
_Out_ bool* redirected,
_Inout_ FWPS_CLASSIFY_OUT* classify_output)
{
NTSTATUS status = STATUS_SUCCESS;
FWPS_CONNECT_REQUEST* connect_request = NULL;
BOOLEAN commit_layer_data = FALSE;
*redirected = FALSE;
// Check if destination IP and/or port have been modified.
BOOLEAN address_changed = !_net_ebpf_ext_compare_destination_address(redirected_context, original_context);
if (redirected_context->user_port != original_context->user_port || address_changed) {
*redirected = TRUE;
status = FwpsAcquireWritableLayerDataPointer(
classify_handle, filter->filterId, 0, (PVOID*)&connect_request, classify_output);
if (!NT_SUCCESS(status)) {
NET_EBPF_EXT_LOG_NTSTATUS_API_FAILURE_UINT64_UINT64(
NET_EBPF_EXT_TRACELOG_KEYWORD_SOCK_ADDR,
"FwpsAcquireWritableLayerDataPointer",
status,
filter->filterId,
(uint64_t)redirected_context->compartment_id);
goto Exit;
}
commit_layer_data = TRUE;
if (_net_ebpf_ext_sock_addr_is_connection_locally_redirected_by_others(connect_request, filter->filterId)) {
// Since this connection has been redirected to a local proxy, it should not be redirected once more.
// Once the local proxy sends out another outbound connection to the original destination,
// that connection will get intercepted again and the eBPF program will be invoked again.
goto Exit;
}
if (redirected_context->user_port != original_context->user_port) {
INETADDR_SET_PORT((PSOCKADDR)&connect_request->remoteAddressAndPort, redirected_context->user_port);
}
if (address_changed) {
uint8_t* address;
if (redirected_context->family == AF_INET) {
address = (uint8_t*)&redirected_context->user_ip4;
} else {
address = (uint8_t*)&(redirected_context->user_ip6[0]);
}
INETADDR_SET_ADDRESS((PSOCKADDR)&connect_request->remoteAddressAndPort, address);
}
connect_request->localRedirectTargetPID = TARGET_PROCESS_ID;
connect_request->localRedirectHandle = redirect_handle;
}
Exit:
if (commit_layer_data) {
FwpsApplyModifiedLayerData(classify_handle, connect_request, 0);
}
NET_EBPF_EXT_RETURN_NTSTATUS(status);
}
/**
* @brief This function determines if the sock_addr eBPF program should be invoked as part of processing the classify
* callback at CONNECT_REDIRECT layer.
*
* @param[in] filter_context Pointer to net_ebpf_extension_sock_addr_wfp_filter_context_t associated with WFP filter.
* @param[in] sock_addr_ctx Pointer to bpf_sock_addr_t struct to be passed to eBPF program.
* @param[in] v4_mapped Boolean indicating if the IP address in sock_addr is v4 mapped v6 address or not.
*
* @returns True if eBPF program should be invoked, False otherwise.
*/
_Must_inspect_result_ bool
_net_ebpf_extension_sock_addr_should_invoke_ebpf_program(
_In_ const net_ebpf_extension_sock_addr_wfp_filter_context_t* filter_context,
_In_ const bpf_sock_addr_t* sock_addr_ctx,
bool v4_mapped)
{
bool process_classify = TRUE;
// If the callout is invoked for v4, then it is safe to invoke the eBPF program.
if (sock_addr_ctx->family == AF_INET) {
goto Exit;
}
// If the callout is invoked for v6:
// 1. Check if the destination is v4-mapped v6 address or pure v6 address.
// 2. If it is v4-mapped v6 address, then we should proceed only if this callout
// is invoked for v4 attach type.
// 3. If it is pure v6 address, then we should proceed only if this callout is
// invoked for v6 attach type.
if (v4_mapped) {
if (!filter_context->v4_attach_type) {
// This filter is for v6 attach type, but address is v4-mapped v6 address.
NET_EBPF_EXT_LOG_MESSAGE(
NET_EBPF_EXT_TRACELOG_LEVEL_VERBOSE,
NET_EBPF_EXT_TRACELOG_KEYWORD_SOCK_ADDR,
"net_ebpf_extension_sock_addr_redirect_connection_classify: v6 attach type, v4mapped address, "
"ignoring");
process_classify = FALSE;
goto Exit;
}
} else if (filter_context->v4_attach_type) {
// This filter is for v4 attach type, but address is a pure v6 address.
NET_EBPF_EXT_LOG_MESSAGE(
NET_EBPF_EXT_TRACELOG_LEVEL_VERBOSE,
NET_EBPF_EXT_TRACELOG_KEYWORD_SOCK_ADDR,
"net_ebpf_extension_sock_addr_redirect_connection_classify: v4 attach type, IPv6 address, ignoring");
process_classify = FALSE;
goto Exit;
}
Exit:
NET_EBPF_EXT_RETURN_BOOL(process_classify);
}
/*
* For every eBPF sock_addr program attached to INET_CONNECT attach point (for a given compartment), a WFP filter
* is added to the WFP CONNECT_REDIRECT (with the compartment Id as filter condition). So, this classify callback
* function will be invoked for every new connection in the compartment. And so, the eBPF program attached
* to the compartment will get invoked for every new connection.
* If the program returns a PROCEED verdict, the connection is permitted by the callout.
* If the program modifies the destination IP of the connection, connection redirection will be performed by this
* callout. If the If on the other hand, the program returns a REJECT verdict, that decision will be cached and enforced
* later by a corresponding callout at WFP AUTH_CONNECT layer.
* By default, the local variable for verdict is set to REJECT.
*/
void
net_ebpf_extension_sock_addr_redirect_connection_classify(
_In_ const FWPS_INCOMING_VALUES* incoming_fixed_values,
_In_ const FWPS_INCOMING_METADATA_VALUES* incoming_metadata_values,
_Inout_opt_ void* layer_data,
_In_opt_ const void* classify_context,
_In_ const FWPS_FILTER* filter,
uint64_t flow_context,
_Inout_ FWPS_CLASSIFY_OUT* classify_output)
{
uint32_t verdict = BPF_SOCK_ADDR_VERDICT_REJECT;
NTSTATUS status = STATUS_SUCCESS;
ebpf_result_t result = EBPF_SUCCESS;
net_ebpf_extension_sock_addr_wfp_filter_context_t* filter_context = NULL;
net_ebpf_extension_hook_client_t* attached_client = NULL;
net_ebpf_sock_addr_t net_ebpf_sock_addr_ctx = {0};
bpf_sock_addr_t* sock_addr_ctx = (bpf_sock_addr_t*)&net_ebpf_sock_addr_ctx.base;
bpf_sock_addr_t sock_addr_ctx_original = {0};
uint32_t compartment_id = UNSPECIFIED_COMPARTMENT_ID;
bool v4_mapped = FALSE;
FWPS_CONNECTION_REDIRECT_STATE redirect_state;
HANDLE redirect_handle;
uint64_t classify_handle = 0;
bool classify_handle_acquired = FALSE;
bool redirected = FALSE;
net_ebpf_extension_connection_context_t* blocked_connection_context = NULL;
UNREFERENCED_PARAMETER(layer_data);
UNREFERENCED_PARAMETER(flow_context);
if ((classify_output->rights & FWPS_RIGHT_ACTION_WRITE) == 0) {
// A callout with higher weight has revoked the write permission. Bail out.
NET_EBPF_EXT_LOG_MESSAGE(
NET_EBPF_EXT_TRACELOG_LEVEL_VERBOSE,
NET_EBPF_EXT_TRACELOG_KEYWORD_SOCK_ADDR,
"No \"write\" right; exiting.");
verdict = BPF_SOCK_ADDR_VERDICT_PROCEED;
return;
}
filter_context = (net_ebpf_extension_sock_addr_wfp_filter_context_t*)filter->context;
ASSERT(filter_context != NULL);
if (filter_context == NULL) {
NET_EBPF_EXT_LOG_MESSAGE_NTSTATUS(
NET_EBPF_EXT_TRACELOG_LEVEL_ERROR,
NET_EBPF_EXT_TRACELOG_KEYWORD_SOCK_ADDR,
"filter_context is NULL.",
STATUS_INVALID_PARAMETER);
goto Exit;
}
compartment_id = filter_context->compartment_id;
ASSERT((compartment_id == UNSPECIFIED_COMPARTMENT_ID) || (compartment_id == sock_addr_ctx->compartment_id));
if (compartment_id != UNSPECIFIED_COMPARTMENT_ID && compartment_id != sock_addr_ctx->compartment_id) {
// The client is not interested in this compartment Id.
NET_EBPF_EXT_LOG_MESSAGE_UINT32(
NET_EBPF_EXT_TRACELOG_LEVEL_VERBOSE,
NET_EBPF_EXT_TRACELOG_KEYWORD_SOCK_ADDR,
"The cgroup_sock_addr eBPF program is not interested in this compartment ID.",
sock_addr_ctx->compartment_id);
verdict = BPF_SOCK_ADDR_VERDICT_PROCEED;
goto Exit;
}
attached_client = (net_ebpf_extension_hook_client_t*)filter_context->base.client_context;
if (attached_client == NULL) {
NET_EBPF_EXT_LOG_MESSAGE_NTSTATUS(
NET_EBPF_EXT_TRACELOG_LEVEL_ERROR,
NET_EBPF_EXT_TRACELOG_KEYWORD_SOCK_ADDR,
"attached_client is NULL.",
STATUS_INVALID_PARAMETER);
goto Exit;
}
if (!net_ebpf_extension_hook_client_enter_rundown(attached_client)) {
attached_client = NULL;
// Client is detaching.
NET_EBPF_EXT_LOG_MESSAGE(
NET_EBPF_EXT_TRACELOG_LEVEL_VERBOSE, NET_EBPF_EXT_TRACELOG_KEYWORD_SOCK_ADDR, "Client is detaching.");
verdict = BPF_SOCK_ADDR_VERDICT_PROCEED;
goto Exit;
}
// Get the redirect handle for this filter.
status = _net_ebpf_ext_sock_addr_get_redirect_handle(filter->filterId, &redirect_handle);
if (!NT_SUCCESS(status)) {
NET_EBPF_EXT_LOG_MESSAGE_UINT64_UINT64(
NET_EBPF_EXT_TRACELOG_LEVEL_ERROR,
NET_EBPF_EXT_TRACELOG_KEYWORD_SOCK_ADDR,
"Failed to get redirect handle.",
filter->filterId,
(uint64_t)sock_addr_ctx->compartment_id);
goto Exit;
}
// Fetch redirect state.
redirect_state = FwpsQueryConnectionRedirectState(incoming_metadata_values->redirectRecords, redirect_handle, NULL);
if (redirect_state == FWPS_CONNECTION_REDIRECTED_BY_SELF ||
redirect_state == FWPS_CONNECTION_PREVIOUSLY_REDIRECTED_BY_SELF) {
NET_EBPF_EXT_LOG_MESSAGE_UINT64_UINT64(
NET_EBPF_EXT_TRACELOG_LEVEL_ERROR,
NET_EBPF_EXT_TRACELOG_KEYWORD_SOCK_ADDR,
"Connection redirected by self, ignoring.",
filter->filterId,
(uint64_t)sock_addr_ctx->compartment_id);
// This connection was previously redirected.
verdict = BPF_SOCK_ADDR_VERDICT_PROCEED;
goto Exit;
}
// Populate the sock_addr context with WFP classify input fields.
_net_ebpf_extension_sock_addr_copy_wfp_connection_fields(
incoming_fixed_values, incoming_metadata_values, &net_ebpf_sock_addr_ctx);
memcpy(&sock_addr_ctx_original, sock_addr_ctx, sizeof(sock_addr_ctx_original));
v4_mapped = (sock_addr_ctx->family == AF_INET6) && IN6_IS_ADDR_V4MAPPED((IN6_ADDR*)sock_addr_ctx->user_ip6);
// Check if the eBPF program should be invoked based on the IP address family and the hook attach type.
if (!_net_ebpf_extension_sock_addr_should_invoke_ebpf_program(filter_context, sock_addr_ctx, v4_mapped)) {
verdict = BPF_SOCK_ADDR_VERDICT_PROCEED;
goto Exit;
}
#pragma warning(push)
// SAL annotation for FwpsAcquireClassifyHandle for classify_context is _In_ whereas,
// the SAL for the same parameter in classifyFn callback _In_opt_ which causes a SAL error.
#pragma warning(suppress : 6387)
// Acquire classify handle.
status = FwpsAcquireClassifyHandle((void*)classify_context, 0, &classify_handle);
#pragma warning(pop)
if (!NT_SUCCESS(status)) {
NET_EBPF_EXT_LOG_NTSTATUS_API_FAILURE_UINT64_UINT64(
NET_EBPF_EXT_TRACELOG_KEYWORD_SOCK_ADDR,
"FwpsAcquireClassifyHandle",
status,
filter->filterId,
(uint64_t)sock_addr_ctx->compartment_id);
goto Exit;
}
classify_handle_acquired = TRUE;
if (v4_mapped) {
// Change sock_addr_ctx to using IPv4 address for the eBPF program.
sock_addr_ctx->family = AF_INET;
const uint8_t* v4_ip = IN6_GET_ADDR_V4MAPPED((IN6_ADDR*)&sock_addr_ctx->user_ip6);
uint32_t local_v4_ip = *((uint32_t*)v4_ip);
memset(sock_addr_ctx->user_ip6, 0, 16);
sock_addr_ctx->user_ip4 = local_v4_ip;
}
result = net_ebpf_extension_hook_invoke_program(attached_client, sock_addr_ctx, &verdict);
NET_EBPF_EXT_BAIL_ON_ERROR_RESULT(result);
if (verdict == BPF_SOCK_ADDR_VERDICT_REJECT) {
NET_EBPF_EXT_LOG_MESSAGE(
NET_EBPF_EXT_TRACELOG_LEVEL_WARNING,
NET_EBPF_EXT_TRACELOG_KEYWORD_SOCK_ADDR,
"cgroup_sock_addr eBPF program returned REJECT verdict.");
goto Exit;
}
if (verdict == BPF_SOCK_ADDR_VERDICT_PROCEED) {
if (v4_mapped) {
// Revert back the sock_addr_ctx to v4-mapped v6 address for conenction-redirection processing.
sock_addr_ctx->family = AF_INET6;
IN_ADDR v4_address = *((IN_ADDR*)&sock_addr_ctx->user_ip4);
IN6_SET_ADDR_V4MAPPED((IN6_ADDR*)&sock_addr_ctx->user_ip6, (IN_ADDR*)&v4_address);
}
status = _net_ebpf_ext_process_redirect_verdict(
&sock_addr_ctx_original,
sock_addr_ctx,
filter,
classify_handle,
redirect_handle,
&redirected,
classify_output);
NET_EBPF_EXT_BAIL_ON_ERROR_STATUS(status);
(redirected) ? InterlockedIncrement(&_net_ebpf_ext_statistics.redirect_connection_count)
: InterlockedIncrement(&_net_ebpf_ext_statistics.permit_connection_count);
}
_net_ebpf_ext_log_sock_addr_classify(
"connect_redirect_classify",
incoming_metadata_values->transportEndpointHandle,
&sock_addr_ctx_original,
redirected ? sock_addr_ctx : NULL,
verdict);
Exit:
if (verdict == BPF_SOCK_ADDR_VERDICT_REJECT) {
// Create a blocked connection context and add it to list for the AUTH_CONNECT layer callout to enforce the
// verdict of the program.
// Since the eBPF program turned in a REJECT verdict, there is no need to process
// connection redirection, even if the program modified the destination.
blocked_connection_context = (net_ebpf_extension_connection_context_t*)ExAllocatePoolUninitialized(
NonPagedPoolNx, sizeof(net_ebpf_extension_connection_context_t), NET_EBPF_EXTENSION_POOL_TAG);
NET_EBPF_EXT_BAIL_ON_ALLOC_FAILURE_STATUS(blocked_connection_context, "blocked_connection", status);
memset(blocked_connection_context, 0, sizeof(net_ebpf_extension_connection_context_t));
_net_ebpf_extension_connection_context_initialize(
sock_addr_ctx,
incoming_metadata_values->transportEndpointHandle,
CONNECTION_CONTEXT_INITIALIZATION_SET_TIMESTAMP,
blocked_connection_context);
_net_ebpf_ext_insert_connection_context_to_list(blocked_connection_context);
InterlockedIncrement(&_net_ebpf_ext_statistics.block_connection_count);
}
// Callout at CONNECT_REDIRECT layer always returns WFP action PERMIT.
// If the eBPF program was invoked and it returned a REJECT verdict, it would be enforced by the callout at
// AUTH_CONNECT layer further downstream.
classify_output->actionType = FWP_ACTION_PERMIT;
if (classify_handle_acquired) {
FwpsReleaseClassifyHandle(classify_handle);
}
if (attached_client) {
net_ebpf_extension_hook_client_leave_rundown(attached_client);
}
}
static ebpf_result_t
_ebpf_sock_addr_context_create(
_In_reads_bytes_opt_(data_size_in) const uint8_t* data_in,
size_t data_size_in,
_In_reads_bytes_opt_(context_size_in) const uint8_t* context_in,
size_t context_size_in,
_Outptr_ void** context)
{
NET_EBPF_EXT_LOG_ENTRY();
ebpf_result_t result;
bpf_sock_addr_t* sock_addr_ctx = NULL;
*context = NULL;
// This does not use the data_in parameters.
if (data_size_in != 0 || data_in != NULL) {
NET_EBPF_EXT_LOG_MESSAGE(
NET_EBPF_EXT_TRACELOG_LEVEL_ERROR, NET_EBPF_EXT_TRACELOG_KEYWORD_SOCK_ADDR, "Data is not supported");
result = EBPF_INVALID_ARGUMENT;
goto Exit;
}
// This requires context_in parameters.
if (context_size_in < sizeof(bpf_sock_addr_t) || context_in == NULL) {
NET_EBPF_EXT_LOG_MESSAGE(
NET_EBPF_EXT_TRACELOG_LEVEL_ERROR, NET_EBPF_EXT_TRACELOG_KEYWORD_SOCK_ADDR, "Context is required");
result = EBPF_INVALID_ARGUMENT;
goto Exit;
}
sock_addr_ctx = (bpf_sock_addr_t*)ExAllocatePoolUninitialized(
NonPagedPoolNx, sizeof(bpf_sock_addr_t), NET_EBPF_EXTENSION_POOL_TAG);
NET_EBPF_EXT_BAIL_ON_ALLOC_FAILURE_RESULT(sock_addr_ctx, "sock_addr_ctx", result);
memcpy(sock_addr_ctx, context_in, sizeof(bpf_sock_addr_t));
result = EBPF_SUCCESS;
*context = sock_addr_ctx;
sock_addr_ctx = NULL;
Exit:
if (sock_addr_ctx) {
ExFreePool(sock_addr_ctx);
}
NET_EBPF_EXT_RETURN_RESULT(result);
}
static void
_ebpf_sock_addr_context_destroy(
_In_opt_ void* context,
_Out_writes_bytes_to_(*data_size_out, *data_size_out) uint8_t* data_out,
_Inout_ size_t* data_size_out,
_Out_writes_bytes_to_(*context_size_out, *context_size_out) uint8_t* context_out,
_Inout_ size_t* context_size_out)
{
NET_EBPF_EXT_LOG_ENTRY();
UNREFERENCED_PARAMETER(data_out);
*data_size_out = 0;
if (!context) {
return;
}
if (context_out != NULL && *context_size_out >= sizeof(bpf_sock_addr_t)) {
memcpy(context_out, context, sizeof(bpf_sock_addr_t));
*context_size_out = sizeof(bpf_sock_addr_t);
} else {
*context_size_out = 0;
}
if (context) {
ExFreePool(context);
}
NET_EBPF_EXT_LOG_FUNCTION_SUCCESS();
}