diff --git a/inc/usersim/fwp_test.h b/inc/usersim/fwp_test.h index c9dfbad..a636d8a 100644 --- a/inc/usersim/fwp_test.h +++ b/inc/usersim/fwp_test.h @@ -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); diff --git a/src/fwp_um.cpp b/src/fwp_um.cpp index cdd3142..658f225 100644 --- a/src/fwp_um.cpp +++ b/src/fwp_um.cpp @@ -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(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()) { + return (NTSTATUS)STATUS_UNSUCCESSFUL; + } + if (engine.remove_fwpm_filter(id)) { return STATUS_SUCCESS; } else { @@ -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) diff --git a/src/fwp_um.h b/src/fwp_um.h index 6a1d30f..9558e85 100644 --- a/src/fwp_um.h +++ b/src/fwp_um.h @@ -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; } @@ -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; } @@ -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); @@ -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 fwps_callouts; std::unordered_map fwpm_callouts; std::unordered_map fwpm_filters;