Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions inc/usersim/fwp_test.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,16 @@ usersim_fwp_bind_ipv4(_In_ fwp_classify_parameters_t* parameters);
USERSIM_API FWP_ACTION_TYPE
usersim_fwp_bind_ipv6(_In_ fwp_classify_parameters_t* parameters);

// Bind-hook classify variants that select the WFP filter bound to a specific callout key. Use these
// to target a specific bind callout (e.g., the CGROUP_SOCK_ADDR bind callout) when multiple callouts
// are registered at the ALE_RESOURCE_ASSIGNMENT layer, instead of relying on layer+sublayer
// first-match ordering.
USERSIM_API FWP_ACTION_TYPE
usersim_fwp_bind_ipv4_by_callout(_In_ fwp_classify_parameters_t* parameters, _In_ const GUID* callout_key);

USERSIM_API FWP_ACTION_TYPE
usersim_fwp_bind_ipv6_by_callout(_In_ fwp_classify_parameters_t* parameters, _In_ const GUID* callout_key);

USERSIM_API FWP_ACTION_TYPE
usersim_fwp_cgroup_inet4_recv_accept(_In_ fwp_classify_parameters_t* parameters);

Expand Down
44 changes: 38 additions & 6 deletions src/fwp_um.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ fwp_engine_t::classify_test_packet(_In_ const GUID* layer_guid, NET_IFINDEX if_i

// This is used to test the bind hook.
FWP_ACTION_TYPE
fwp_engine_t::test_bind_ipv4(_In_ fwp_classify_parameters_t* parameters)
fwp_engine_t::test_bind_ipv4(_In_ fwp_classify_parameters_t* parameters, _In_opt_ const GUID* callout_key)
{
FWPS_INCOMING_VALUE0 incoming_value[FWPS_FIELD_ALE_RESOURCE_ASSIGNMENT_V4_MAX] = {};
incoming_value[FWPS_FIELD_ALE_RESOURCE_ASSIGNMENT_V4_IP_LOCAL_PORT].value.uint16 = parameters->destination_port;
Expand All @@ -148,12 +148,13 @@ fwp_engine_t::test_bind_ipv4(_In_ fwp_classify_parameters_t* parameters)
FWPM_LAYER_ALE_RESOURCE_ASSIGNMENT_V4,
_default_sublayer,
incoming_value,
nullptr);
nullptr,
callout_key);
}

// This is used to test the IPv6 bind hook.
FWP_ACTION_TYPE
fwp_engine_t::test_bind_ipv6(_In_ fwp_classify_parameters_t* parameters)
fwp_engine_t::test_bind_ipv6(_In_ fwp_classify_parameters_t* parameters, _In_opt_ const GUID* callout_key)
{
FWPS_INCOMING_VALUE0 incoming_value[FWPS_FIELD_ALE_RESOURCE_ASSIGNMENT_V6_MAX] = {};
incoming_value[FWPS_FIELD_ALE_RESOURCE_ASSIGNMENT_V6_IP_LOCAL_PORT].value.uint16 = parameters->destination_port;
Expand All @@ -171,15 +172,17 @@ fwp_engine_t::test_bind_ipv6(_In_ fwp_classify_parameters_t* parameters)
FWPM_LAYER_ALE_RESOURCE_ASSIGNMENT_V6,
_default_sublayer,
incoming_value,
nullptr);
nullptr,
callout_key);
}

_Requires_lock_not_held_(this->lock) FWP_ACTION_TYPE fwp_engine_t::test_callout(
uint16_t layer_id,
_In_ const GUID& layer_guid,
_In_ const GUID& sublayer_guid,
_In_ FWPS_INCOMING_VALUE0* incoming_value,
_Out_opt_ uint64_t* flow_id)
_Out_opt_ uint64_t* flow_id,
_In_opt_ const GUID* callout_key)
{
FWPS_INCOMING_VALUES incoming_fixed_values = {.layerId = layer_id, .incomingValue = incoming_value};
FWPS_INCOMING_METADATA_VALUES incoming_metadata_values = {};
Expand All @@ -188,7 +191,12 @@ _Requires_lock_not_held_(this->lock) FWP_ACTION_TYPE fwp_engine_t::test_callout(

{
shared_lock_t l(lock);
const FWPM_FILTER* fwpm_filter = get_fwpm_filter_with_context_under_lock(layer_guid, sublayer_guid);
// When a specific callout key is requested, select the filter bound to that callout so the
// intended callout is exercised even if multiple callouts are registered at this layer and
// sublayer. Otherwise fall back to first-match by layer+sublayer.
const FWPM_FILTER* fwpm_filter =
callout_key ? get_fwpm_filter_by_callout_under_lock(layer_guid, sublayer_guid, *callout_key)
: get_fwpm_filter_with_context_under_lock(layer_guid, sublayer_guid);
if (!fwpm_filter) {
return FWP_ACTION_CALLOUT_UNKNOWN;
}
Expand Down Expand Up @@ -1069,6 +1077,30 @@ usersim_fwp_bind_ipv6(_In_ fwp_classify_parameters_t* parameters)
return fwp_engine_t::get()->test_bind_ipv6(parameters);
}

FWP_ACTION_TYPE
usersim_fwp_bind_ipv4_by_callout(_In_ fwp_classify_parameters_t* parameters, _In_ const GUID* callout_key)
{
CXPLAT_DEBUG_ASSERT(callout_key != nullptr);
if (callout_key == nullptr) {
// Guard release builds where the assert is compiled out: without a callout key the engine would
// silently fall back to layer+sublayer first-match and could exercise the wrong callout.
return FWP_ACTION_CALLOUT_UNKNOWN;
}
return fwp_engine_t::get()->test_bind_ipv4(parameters, callout_key);
}

FWP_ACTION_TYPE
usersim_fwp_bind_ipv6_by_callout(_In_ fwp_classify_parameters_t* parameters, _In_ const GUID* callout_key)
{
CXPLAT_DEBUG_ASSERT(callout_key != nullptr);
if (callout_key == nullptr) {
// Guard release builds where the assert is compiled out: without a callout key the engine would
// silently fall back to layer+sublayer first-match and could exercise the wrong callout.
return FWP_ACTION_CALLOUT_UNKNOWN;
}
return fwp_engine_t::get()->test_bind_ipv6(parameters, callout_key);
}

FWP_ACTION_TYPE
usersim_fwp_cgroup_inet4_recv_accept(_In_ fwp_classify_parameters_t* parameters)
{
Expand Down
28 changes: 25 additions & 3 deletions src/fwp_um.h
Original file line number Diff line number Diff line change
Expand Up @@ -218,10 +218,10 @@ typedef class fwp_engine_t
classify_test_packet(_In_ const GUID* layer_guid, NET_IFINDEX if_index);

FWP_ACTION_TYPE
test_bind_ipv4(_In_ fwp_classify_parameters_t* parameters);
test_bind_ipv4(_In_ fwp_classify_parameters_t* parameters, _In_opt_ const GUID* callout_key = nullptr);

FWP_ACTION_TYPE
test_bind_ipv6(_In_ fwp_classify_parameters_t* parameters);
test_bind_ipv6(_In_ fwp_classify_parameters_t* parameters, _In_opt_ const GUID* callout_key = nullptr);

FWP_ACTION_TYPE
test_cgroup_inet4_recv_accept(_In_ fwp_classify_parameters_t* parameters);
Expand Down Expand Up @@ -268,7 +268,8 @@ typedef class fwp_engine_t
_In_ const GUID& layer_guid,
_In_ const GUID& sublayer_guid,
_In_ FWPS_INCOMING_VALUE0* incoming_value,
_Out_opt_ uint64_t* flow_handle);
_Out_opt_ uint64_t* flow_handle,
_In_opt_ const GUID* callout_key = nullptr);

_Requires_lock_not_held_(this->lock) void test_remove_flow_context(
uint64_t flow_id,
Expand Down Expand Up @@ -298,6 +299,27 @@ typedef class fwp_engine_t
return nullptr;
}

// Select a filter by its bound callout key at the given layer and sublayer. This disambiguates the
// case where multiple callouts (each with its own filter) are registered at the same WFP layer and
// sublayer (e.g., the legacy bind callout and the CGROUP_SOCK_ADDR bind callout both at
// ALE_RESOURCE_ASSIGNMENT). Selecting the filter by its callout key lets a test target a specific
// callout explicitly instead of relying on layer+sublayer first-match ordering. The sublayer is
// still matched (mirroring get_fwpm_filter_with_context_under_lock) so selection stays unambiguous
// even if a callout ever owns filters on more than one sublayer.
_Ret_maybenull_ const FWPM_FILTER*
get_fwpm_filter_by_callout_under_lock(
_In_ const GUID& layer_guid, _In_ const GUID& sublayer_guid, _In_ const GUID& callout_key)
{
for (auto& [first, filter] : fwpm_filters) {
if (memcmp(&filter.layerKey, &layer_guid, sizeof(GUID)) == 0 &&
memcmp(&filter.subLayerKey, &sublayer_guid, sizeof(GUID)) == 0 &&
memcmp(&filter.action.calloutKey, &callout_key, sizeof(GUID)) == 0 && filter.rawContext != 0) {
return &filter;
}
}
return nullptr;
}

_Ret_maybenull_ const GUID*
get_callout_key_from_layer_guid_under_lock(_In_ const GUID* layer_guid)
{
Expand Down