Skip to content
Merged
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
22 changes: 22 additions & 0 deletions inc/usersim/fwp_test.h
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,28 @@ USERSIM_API void
usersim_fwp_set_sublayer_guids(
_In_ const GUID& default_sublayer, _In_ const GUID& connect_v4_sublayer, _In_ const GUID& connect_v6_sublayer);

// Test hook to simulate WFP filter-delete failures. Arms the mock to fail the next 'count' FwpmFilterDeleteById
// calls; each returns a failure status while leaving the filter installed and firing no delete notification, so
// a caller can be exercised against a delete that does not take effect. Pass 0 to disarm.
//
// This is deterministic and opt-in, and is separate from the random cxplat fault-injection harness: it stays
// inert until armed (count > 0), consumes one count per failed delete, and does not depend on the harness being
// enabled. A test that arms it should skip itself when cxplat_fault_injection_is_enabled() is true, so random
// injection does not disturb the exact sequence. Example:
// usersim_fwp_set_filter_delete_failure_count(1); // or UINT32_MAX to fail every delete
// ... run the code under test ...
// usersim_fwp_set_filter_delete_failure_count(0);
USERSIM_API void
usersim_fwp_set_filter_delete_failure_count(uint32_t count);

// Test-only: number of WFP filters currently present in the simulated engine.
USERSIM_API uint32_t
usersim_fwp_get_fwpm_filter_count();

// Test-only: remove any WFP filters left in the simulated engine (cleanup after fault-injection tests).
USERSIM_API void
usersim_fwp_clear_fwpm_filters();

USERSIM_API void
usersim_fwp_sock_ops_v4_remove_flow_context(_In_ uint64_t flow_id);

Expand Down
26 changes: 25 additions & 1 deletion src/fwp_um.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -531,9 +531,15 @@ fwp_engine_t::test_cgroup_inet6_listen(_In_ fwp_classify_parameters_t* parameter

_IRQL_requires_max_(PASSIVE_LEVEL) NTSTATUS FwpmFilterDeleteById0(_In_ HANDLE engine_handle, _In_ uint64_t id)
{
// Skip fault injection for this API because return failure status requires to remove filter from the list.
auto& engine = *reinterpret_cast<fwp_engine_t*>(engine_handle);

// Filter-delete failures are injected with a dedicated, caller-armed counter rather than the generic
// cxplat_fault_injection_inject_fault() path, so tests can fail specific deletes deterministically. See
// usersim_fwp_set_filter_delete_failure_count (fwp_test.h) for behavior and usage.
if (engine.consume_filter_delete_failure()) {
Comment thread
mikeagun marked this conversation as resolved.
return (NTSTATUS)STATUS_UNSUCCESSFUL;
}

if (engine.remove_fwpm_filter(id)) {
return STATUS_SUCCESS;
} else {
Expand Down Expand Up @@ -1124,6 +1130,24 @@ usersim_fwp_set_sublayer_guids(
fwp_engine_t::get()->set_sublayer_guids(default_sublayer, connect_v4_sublayer, connect_v6_sublayer);
}

void
usersim_fwp_set_filter_delete_failure_count(uint32_t count)
{
fwp_engine_t::get()->set_filter_delete_failure_count(count);
}

uint32_t
usersim_fwp_get_fwpm_filter_count()
{
return (uint32_t)fwp_engine_t::get()->get_fwpm_filter_count();
}

void
usersim_fwp_clear_fwpm_filters()
{
fwp_engine_t::get()->clear_fwpm_filters();
}

void
usersim_fwp_sock_ops_v4_remove_flow_context(
_In_ uint64_t flow_id)
Expand Down
53 changes: 48 additions & 5 deletions src/fwp_um.h
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ typedef class fwp_engine_t

callout = get_fwps_callout(&filter->action.calloutKey);
CXPLAT_DEBUG_ASSERT(callout != nullptr);
fwps_filter.filterId = id;
fwps_filter.context = filter->rawContext;
}

Expand All @@ -158,8 +159,10 @@ typedef class fwp_engine_t
exclusive_lock_t l(lock);
for (auto& it : fwpm_filters) {
if (it.first == id) {
// May be null if the callout function has already been unregistered (e.g., during driver
// unload); in that case WFP delivers no delete notification (handled below).
callout = get_fwps_callout(&it.second.action.calloutKey);
CXPLAT_DEBUG_ASSERT(callout != nullptr);
fwps_filter.filterId = id;
fwps_filter.context = it.second.rawContext;
break;
}
Expand All @@ -168,14 +171,53 @@ typedef class fwp_engine_t
return_value = fwpm_filters.erase(id) == 1;
}

CXPLAT_DEBUG_ASSERT(callout != nullptr);
__analysis_assume(callout != nullptr);
// Invoke filter delete notification callback.
callout->notifyFn(FWPS_CALLOUT_NOTIFY_DELETE_FILTER, &callout->calloutKey, &fwps_filter);
// If the callout function is still registered, deliver the delete notification as real WFP does. Once the
// callout has been unregistered (e.g., during driver unload), WFP delivers no delete notification.
if (callout != nullptr) {
callout->notifyFn(FWPS_CALLOUT_NOTIFY_DELETE_FILTER, &callout->calloutKey, &fwps_filter);
}

return return_value;
}

// Test-only: remove any WFP filters left in the engine (used to clean up after fault-injection tests that
// intentionally leave filters undeletable). Does not issue notifications.
void
clear_fwpm_filters()
{
exclusive_lock_t l(lock);
fwpm_filters.clear();
}

// Arms the deterministic FwpmFilterDeleteById failure counter (see usersim_fwp_set_filter_delete_failure_count
// in fwp_test.h). Fails the next 'count' deletes; 0 disarms.
void
set_filter_delete_failure_count(uint32_t count)
{
exclusive_lock_t l(lock);
_filter_delete_failure_count = count;
}

// Returns true (and consumes one) if the next FwpmFilterDeleteById call should be failed for fault injection.
bool
consume_filter_delete_failure()
{
exclusive_lock_t l(lock);
if (_filter_delete_failure_count > 0) {
_filter_delete_failure_count--;
return true;
}
return false;
}

// Test-only: number of WFP filters currently present in the engine.
size_t
get_fwpm_filter_count()
{
shared_lock_t l(lock);
return fwpm_filters.size();
}

_Requires_lock_not_held_(this->lock) void add_fwpm_provider(_In_ const FWPM_PROVIDER* provider)
{
UNREFERENCED_PARAMETER(provider);
Expand Down Expand Up @@ -336,6 +378,7 @@ typedef class fwp_engine_t
std::shared_mutex lock;
uint32_t next_id = 1;
uint32_t next_flow_id = 1;
uint32_t _filter_delete_failure_count = 0; // Test-only WFP filter delete fault-injection counter.
std::unordered_map<size_t, FWPS_CALLOUT3> fwps_callouts;
std::unordered_map<size_t, FWPM_CALLOUT0> fwpm_callouts;
std::unordered_map<size_t, FWPM_FILTER0> fwpm_filters;
Expand Down