/* * windivert.c * (C) 2017, all rights reserved, * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #include #include #include #include #include #include #define INITGUID #include #include "windivert_device.h" /* * WDK function declaration cruft. */ DRIVER_INITIALIZE DriverEntry; EVT_WDF_DRIVER_UNLOAD windivert_unload; EVT_WDF_IO_IN_CALLER_CONTEXT windivert_caller_context; EVT_WDF_IO_QUEUE_IO_DEVICE_CONTROL windivert_ioctl; EVT_WDF_DEVICE_FILE_CREATE windivert_create; EVT_WDF_FILE_CLEANUP windivert_cleanup; EVT_WDF_FILE_CLOSE windivert_close; EVT_WDF_OBJECT_CONTEXT_DESTROY windivert_destroy; EVT_WDF_WORKITEM windivert_worker; /* * Debugging macros. */ // #define DEBUG_ON #define DEBUG_BUFSIZE 256 #ifdef DEBUG_ON static void DEBUG(PCCH format, ...) { va_list args; char buf[DEBUG_BUFSIZE+1]; if (KeGetCurrentIrql() != PASSIVE_LEVEL) { return; } va_start(args, format); RtlStringCbVPrintfA(buf, DEBUG_BUFSIZE, format, args); DbgPrint("WINDIVERT: %s\n", buf); va_end(args); } static void DEBUG_ERROR(PCCH format, NTSTATUS status, ...) { va_list args; char buf[DEBUG_BUFSIZE+1]; if (KeGetCurrentIrql() != PASSIVE_LEVEL) { return; } va_start(args, status); RtlStringCbVPrintfA(buf, DEBUG_BUFSIZE, format, args); DbgPrint("WINDIVERT: *** ERROR ***: (status = %x): %s\n", status, buf); va_end(args); } #else // DEBUG_ON #define DEBUG(format, ...) #define DEBUG_ERROR(format, status, ...) #endif #define WINDIVERT_TAG 'viDW' /* * WinDivert packet filter. */ struct filter_s { UINT8 protocol:4; // field's protocol UINT8 test:4; // Filter test UINT8 field; // Field of interest UINT16 success; // Success continuation UINT16 failure; // Fail continuation UINT32 arg[4]; // Comparison argument }; typedef struct filter_s *filter_t; #define WINDIVERT_FILTER_PROTOCOL_NONE 0 #define WINDIVERT_FILTER_PROTOCOL_IP 1 #define WINDIVERT_FILTER_PROTOCOL_IPV6 2 #define WINDIVERT_FILTER_PROTOCOL_ICMP 3 #define WINDIVERT_FILTER_PROTOCOL_ICMPV6 4 #define WINDIVERT_FILTER_PROTOCOL_TCP 5 #define WINDIVERT_FILTER_PROTOCOL_UDP 6 /* * WinDivert context information. */ #define WINDIVERT_CONTEXT_SIZE (sizeof(struct context_s)) #define WINDIVERT_CONTEXT_MAXLAYERS 4 #define WINDIVERT_CONTEXT_MAXWORKERS 1 #define WINDIVERT_CONTEXT_OUTBOUND_IPV4_LAYER 0 #define WINDIVERT_CONTEXT_INBOUND_IPV4_LAYER 1 #define WINDIVERT_CONTEXT_OUTBOUND_IPV6_LAYER 2 #define WINDIVERT_CONTEXT_INBOUND_IPV6_LAYER 3 typedef enum { WINDIVERT_CONTEXT_STATE_OPENING = 0xA0, // Context is opening. WINDIVERT_CONTEXT_STATE_OPEN = 0xB1, // Context is open. WINDIVERT_CONTEXT_STATE_CLOSING = 0xC2, // Context is closing. WINDIVERT_CONTEXT_STATE_CLOSED = 0xD3, // Context is closed. WINDIVERT_CONTEXT_STATE_INVALID = 0xE4 // Context is invalid. } context_state_t; struct context_s { context_state_t state; // Context's state. KSPIN_LOCK lock; // Context-wide lock. WDFDEVICE device; // Context's device. WDFFILEOBJECT object; // Context's parent object. LIST_ENTRY work_queue; // Work queue. ULONG work_queue_length; // Work queue length. LIST_ENTRY packet_queue; // Packet queue. ULONG packet_queue_length; // Packet queue length. ULONG packet_queue_maxlength; // Packet queue max length. ULONG packet_queue_size; // Packet queue size (in bytes). ULONG packet_queue_maxsize; // Packet queue max size. ULONGLONG packet_queue_maxcounts; // Packet queue max counts. ULONG packet_queue_maxtime; // Packet queue max time. WDFQUEUE read_queue; // Read queue. WDFWORKITEM workers[WINDIVERT_CONTEXT_MAXWORKERS]; // Read workers. UINT8 worker_curr; // Current read worker. UINT8 layer; // Context's layer. UINT64 flags; // Context's flags. UINT32 priority; // Context's priority. GUID callout_guid[WINDIVERT_CONTEXT_MAXLAYERS]; // Callout GUIDs. GUID filter_guid[WINDIVERT_CONTEXT_MAXLAYERS]; // Filter GUIDs. BOOL installed[WINDIVERT_CONTEXT_MAXLAYERS]; // What is installed? BOOL on; // Is filtering on? HANDLE engine_handle; // WFP engine handle. filter_t filter; // Packet filter. }; typedef struct context_s context_s; typedef struct context_s *context_t; WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(context_s, windivert_context_get); /* * WinDivert Layer information. */ typedef void (*windivert_callout_t)( IN const FWPS_INCOMING_VALUES0 *fixed_vals, IN const FWPS_INCOMING_METADATA_VALUES0 *meta_vals, IN OUT void *data, const FWPS_FILTER0 *filter, IN UINT64 flow_context, OUT FWPS_CLASSIFY_OUT0 *result); struct layer_s { wchar_t *sublayer_name; // Sub-layer name. wchar_t *sublayer_desc; // Sub-layer description. wchar_t *callout_name; // Call-out name. wchar_t *callout_desc; // Call-out description. wchar_t *filter_name; // Filter name. wchar_t *filter_desc; // Filter description. GUID layer_guid; // WFP layer GUID. GUID sublayer_guid; // Sub-layer GUID. windivert_callout_t callout; // Call-out. }; typedef struct layer_s *layer_t; /* * WinDivert request context. */ struct req_context_s { struct windivert_addr_s *addr; // Pointer to address structure. }; typedef struct req_context_s req_context_s; typedef struct req_context_s *req_context_t; WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(req_context_s, windivert_req_context_get); /* * WinDivert work structure. */ #define WINDIVERT_WORK_QUEUE_LEN_MAX 4096 struct work_s { LIST_ENTRY entry; // Entry for queue. PNET_BUFFER_LIST buffers; // Packet list. PNET_BUFFER buffer; // First matching packet. UINT advance; // Bytes to retreat/advance. BOOL is_ipv4; // Is IPv4? BOOL hop; // Decrement TTL? UINT8 checksums; // Which checksums are valid. UINT8 direction; // Packet direction. UINT32 if_idx; // Interface index. UINT32 sub_if_idx; // Sub-interface index. UINT32 priority; // WinDivert priority. ULONGLONG timestamp; // Packet timestamp. }; typedef struct work_s *work_t; /* * WinDivert packet structure. */ #define WINDIVERT_IP_CHECKSUM 0x01 #define WINDIVERT_TCP_CHECKSUM 0x02 #define WINDIVERT_UDP_CHECKSUM 0x04 #define WINDIVERT_ALL_CHECKSUMS \ (WINDIVERT_IP_CHECKSUM | WINDIVERT_TCP_CHECKSUM | WINDIVERT_UDP_CHECKSUM) struct packet_s { LIST_ENTRY entry; // Entry for queue. UINT8 checksums; // Which checksums are valid. UINT8 direction; // Packet direction. BOOL hop; // Decrement TTL? UINT32 if_idx; // Interface index. UINT32 sub_if_idx; // Sub-interface index. ULONGLONG timestamp; // Packet timestamp. size_t data_len; // Length of `data'. char *data; // Packet data. }; typedef struct packet_s *packet_t; /* * WinDivert address definition. */ struct windivert_addr_s { UINT32 IfIdx; UINT32 SubIfIdx; UINT8 Direction; }; typedef struct windivert_addr_s *windivert_addr_t; /* * Header definitions. */ struct iphdr { UINT8 HdrLength:4; UINT8 Version:4; UINT8 TOS; UINT16 Length; UINT16 Id; UINT16 FragOff0; UINT8 TTL; UINT8 Protocol; UINT16 Checksum; UINT32 SrcAddr; UINT32 DstAddr; }; struct ipv6hdr { UINT8 TrafficClass0:4; UINT8 Version:4; UINT8 FlowLabel0:4; UINT8 TrafficClass1:4; UINT16 FlowLabel1; UINT16 Length; UINT8 NextHdr; UINT8 HopLimit; UINT32 SrcAddr[4]; UINT32 DstAddr[4]; }; struct icmphdr { UINT8 Type; UINT8 Code; UINT16 Checksum; UINT32 Body; }; struct icmpv6hdr { UINT8 Type; UINT8 Code; UINT16 Checksum; UINT32 Body; }; struct tcphdr { UINT16 SrcPort; UINT16 DstPort; UINT32 SeqNum; UINT32 AckNum; UINT16 Reserved1:4; UINT16 HdrLength:4; UINT16 Fin:1; UINT16 Syn:1; UINT16 Rst:1; UINT16 Psh:1; UINT16 Ack:1; UINT16 Urg:1; UINT16 Reserved2:2; UINT16 Window; UINT16 Checksum; UINT16 UrgPtr; }; struct udphdr { UINT16 SrcPort; UINT16 DstPort; UINT16 Length; UINT16 Checksum; }; #define IPHDR_GET_FRAGOFF(hdr) (((hdr)->FragOff0) & 0xFF1F) #define IPHDR_GET_MF(hdr) (((hdr)->FragOff0) & 0x0020) #define IPHDR_GET_DF(hdr) (((hdr)->FragOff0) & 0x0040) #define IPV6HDR_GET_TRAFFICCLASS(hdr) \ ((((hdr)->TrafficClass0) << 4) | ((hdr)->TrafficClass1)) #define IPV6HDR_GET_FLOWLABEL(hdr) \ ((((UINT32)(hdr)->FlowLabel0) << 16) | ((UINT32)(hdr)->FlowLabel1)) /* * Misc. */ #define UINT8_MAX 0xFF #define UINT16_MAX 0xFFFF #define UINT32_MAX 0xFFFFFFFF /* * Global state. */ static HANDLE inject_handle = NULL; static HANDLE injectv6_handle = NULL; static NDIS_HANDLE nbl_pool_handle = NULL; static NDIS_HANDLE nb_pool_handle = NULL; static HANDLE engine_handle = NULL; static LONG priority_counter = 0; static ULONGLONG counts_per_ms = 0; static POOL_TYPE non_paged_pool = NonPagedPool; /* * Priorities. */ #define WINDIVERT_CONTEXT_PRIORITY(priority0) \ windivert_context_priority(priority0) static UINT32 windivert_context_priority(UINT32 priority0) { UINT16 priority1 = (UINT16)InterlockedIncrement(&priority_counter); priority0 -= WINDIVERT_PRIORITY_MIN; return ((priority0 << 16) | ((UINT32)priority1 & 0x0000FFFF)); } #define WINDIVERT_FILTER_WEIGHT(priority) \ ((UINT64)(UINT32_MAX - (priority))) /* * Prototypes. */ static void windivert_driver_unload(void); extern VOID windivert_ioctl(IN WDFQUEUE queue, IN WDFREQUEST request, IN size_t in_length, IN size_t out_len, IN ULONG code); static NTSTATUS windivert_read(context_t context, WDFREQUEST request); extern VOID windivert_worker(IN WDFWORKITEM item); static void windivert_read_service(context_t context); extern VOID windivert_create(IN WDFDEVICE device, IN WDFREQUEST request, IN WDFFILEOBJECT object); static NTSTATUS windivert_install_sublayer(layer_t layer); static NTSTATUS windivert_install_callouts(context_t context, UINT8 layer, BOOL is_inbound, BOOL is_outbound, BOOL is_ipv4, BOOL is_ipv6); static NTSTATUS windivert_install_callout(context_t context, UINT idx, layer_t layer); static void windivert_uninstall_callouts(context_t context, context_state_t state); extern VOID windivert_cleanup(IN WDFFILEOBJECT object); extern VOID windivert_close(IN WDFFILEOBJECT object); extern VOID windivert_destroy(IN WDFOBJECT object); extern NTSTATUS windivert_write(context_t context, WDFREQUEST request, windivert_addr_t addr); extern void NTAPI windivert_inject_complete(VOID *context, NET_BUFFER_LIST *packets, BOOLEAN dispatch_level); static NTSTATUS windivert_notify_callout(IN FWPS_CALLOUT_NOTIFY_TYPE type, IN const GUID *filter_key, IN const FWPS_FILTER0 *filter); static void windivert_classify_outbound_network_v4_callout( IN const FWPS_INCOMING_VALUES0 *fixed_vals, IN const FWPS_INCOMING_METADATA_VALUES0 *meta_vals, IN OUT void *data, const FWPS_FILTER0 *filter, IN UINT64 flow_context, OUT FWPS_CLASSIFY_OUT0 *result); static void windivert_classify_inbound_network_v4_callout( IN const FWPS_INCOMING_VALUES0 *fixed_vals, IN const FWPS_INCOMING_METADATA_VALUES0 *meta_vals, IN OUT void *data, const FWPS_FILTER0 *filter, IN UINT64 flow_context, OUT FWPS_CLASSIFY_OUT0 *result); static void windivert_classify_outbound_network_v6_callout( IN const FWPS_INCOMING_VALUES0 *fixed_vals, IN const FWPS_INCOMING_METADATA_VALUES0 *meta_vals, IN OUT void *data, const FWPS_FILTER0 *filter, IN UINT64 flow_context, OUT FWPS_CLASSIFY_OUT0 *result); static void windivert_classify_inbound_network_v6_callout( IN const FWPS_INCOMING_VALUES0 *fixed_vals, IN const FWPS_INCOMING_METADATA_VALUES0 *meta_vals, IN OUT void *data, const FWPS_FILTER0 *filter, IN UINT64 flow_context, OUT FWPS_CLASSIFY_OUT0 *result); static void windivert_classify_forward_network_v4_callout( IN const FWPS_INCOMING_VALUES0 *fixed_vals, IN const FWPS_INCOMING_METADATA_VALUES0 *meta_vals, IN OUT void *data, const FWPS_FILTER0 *filter, IN UINT64 flow_context, OUT FWPS_CLASSIFY_OUT0 *result); static void windivert_classify_forward_network_v6_callout( IN const FWPS_INCOMING_VALUES0 *fixed_vals, IN const FWPS_INCOMING_METADATA_VALUES0 *meta_vals, IN OUT void *data, const FWPS_FILTER0 *filter, IN UINT64 flow_context, OUT FWPS_CLASSIFY_OUT0 *result); static void windivert_classify_callout(context_t context, IN UINT8 direction, IN UINT32 if_idx, IN UINT32 sub_if_idx, IN BOOL isipv4, IN BOOL isloopback, IN UINT advance, IN OUT void *data, IN UINT64 flow_context, OUT FWPS_CLASSIFY_OUT0 *result); static BOOL windivert_queue_packet(context_t context, PNET_BUFFER buffer, UINT8 direction, UINT32 if_idx, UINT32 sub_if_idx, BOOL hop, UINT8 checksums, ULONGLONG timestamp); static BOOL windivert_reinject_packet(BOOL sniff_mode, BOOL foward, UINT8 direction, BOOL isipv4, UINT32 if_idx, UINT32 sub_if_idx, UINT32 priority, PNET_BUFFER_LIST buffers, PNET_BUFFER buffer); static void NTAPI windivert_reinject_complete(VOID *context, NET_BUFFER_LIST *buffers, BOOLEAN dispatch_level); static void NTAPI windivert_reinject_clone_complete(VOID *context, NET_BUFFER_LIST *buffers_cpy, BOOLEAN dispatch_level); static void windivert_free_packet(packet_t packet); static UINT8 windivert_skip_headers(UINT8 proto, UINT8 **header, size_t *len); static int windivert_big_num_compare(const UINT32 *a, const UINT32 *b); static BOOL windivert_filter(PNET_BUFFER buffer, UINT32 if_idx, UINT32 sub_if_idx, BOOL outbound, BOOL isipv4, BOOL hop, UINT8 checksums, filter_t filter); static NTSTATUS windivert_finalize_packet(void *header, size_t len, BOOL hop, UINT8 checksums); static filter_t windivert_filter_compile(windivert_ioctl_filter_t ioctl_filter, size_t ioctl_filter_len); static void windivert_filter_analyze(filter_t filter, BOOL *is_inbound, BOOL *is_outbound, BOOL *ip_ipv4, BOOL *is_ipv6); static BOOL windivert_filter_test(filter_t filter, UINT16 ip, UINT8 protocol, UINT8 field, UINT32 arg); /* * WinDivert sublayer GUIDs */ DEFINE_GUID(WINDIVERT_SUBLAYER_INBOUND_IPV4_GUID, 0x8DB169DF, 0xFA88, 0x42FC, 0x8E, 0xB3, 0x17, 0x10, 0x5F, 0x62, 0x5B, 0x31); DEFINE_GUID(WINDIVERT_SUBLAYER_OUTBOUND_IPV4_GUID, 0x8E9B9512, 0x6D8D, 0x4846, 0xB8, 0x85, 0x6b, 0x70, 0xc0, 0xE1, 0x15, 0x43); DEFINE_GUID(WINDIVERT_SUBLAYER_INBOUND_IPV6_GUID, 0xC5CC9A51, 0xD4E7, 0x4D92, 0x9C, 0xF0, 0xEF, 0xC8, 0xE1, 0x84, 0x00, 0x23); DEFINE_GUID(WINDIVERT_SUBLAYER_OUTBOUND_IPV6_GUID, 0xE4ED7583, 0x7F64, 0x4678, 0x9E, 0xCB, 0x2A, 0x05, 0x94, 0xA7, 0x53, 0x91); DEFINE_GUID(WINDIVERT_SUBLAYER_FORWARD_IPV4_GUID, 0x33507872, 0x278D, 0x42F7, 0xBD, 0x23, 0x8A, 0x60, 0xA2, 0x6A, 0x3D, 0x44); DEFINE_GUID(WINDIVERT_SUBLAYER_FORWARD_IPV6_GUID, 0xB51E5EB5, 0xCC2B, 0x42ED, 0x9A, 0xCD, 0x94, 0x28, 0xE4, 0x41, 0x09, 0x4A); /* * WinDivert supported layers. */ static struct layer_s layer_inbound_network_ipv4_0 = { L"" WINDIVERT_DEVICE_NAME L"_SubLayerInboundNetworkIPv4", L"" WINDIVERT_DEVICE_NAME L" sublayer network (inbound IPv4)", L"" WINDIVERT_DEVICE_NAME L"_CalloutInboundNetworkIPv4", L"" WINDIVERT_DEVICE_NAME L" callout network (inbound IPv4)", L"" WINDIVERT_DEVICE_NAME L"_FilterInboundNetworkIPv4", L"" WINDIVERT_DEVICE_NAME L" filter network (inbound IPv4)", {0}, {0}, windivert_classify_inbound_network_v4_callout, }; static layer_t layer_inbound_network_ipv4 = &layer_inbound_network_ipv4_0; static struct layer_s layer_outbound_network_ipv4_0 = { L"" WINDIVERT_DEVICE_NAME L"_SubLayerOutboundNetworkIPv4", L"" WINDIVERT_DEVICE_NAME L" sublayer network (outbound IPv4)", L"" WINDIVERT_DEVICE_NAME L"_CalloutOutboundNetworkIPv4", L"" WINDIVERT_DEVICE_NAME L" callout network (outbound IPv4)", L"" WINDIVERT_DEVICE_NAME L"_FilterOutboundNetworkIPv4", L"" WINDIVERT_DEVICE_NAME L" filter network (outbound IPv4)", {0}, {0}, windivert_classify_outbound_network_v4_callout, }; static layer_t layer_outbound_network_ipv4 = &layer_outbound_network_ipv4_0; static struct layer_s layer_inbound_network_ipv6_0 = { L"" WINDIVERT_DEVICE_NAME L"_SubLayerInboundNetworkIPv6", L"" WINDIVERT_DEVICE_NAME L" sublayer network (inbound IPv6)", L"" WINDIVERT_DEVICE_NAME L"_CalloutInboundNetworkIPv6", L"" WINDIVERT_DEVICE_NAME L" callout network (inbound IPv6)", L"" WINDIVERT_DEVICE_NAME L"_FilterInboundNetworkIPv6", L"" WINDIVERT_DEVICE_NAME L" filter network (inbound IPv6)", {0}, {0}, windivert_classify_inbound_network_v6_callout, }; static layer_t layer_inbound_network_ipv6 = &layer_inbound_network_ipv6_0; static struct layer_s layer_outbound_network_ipv6_0 = { L"" WINDIVERT_DEVICE_NAME L"_SubLayerOutboundNetworkIPv6", L"" WINDIVERT_DEVICE_NAME L" sublayer network (outbound IPv6)", L"" WINDIVERT_DEVICE_NAME L"_CalloutOutboundNetworkIPv6", L"" WINDIVERT_DEVICE_NAME L" callout network (outbound IPv6)", L"" WINDIVERT_DEVICE_NAME L"_FilterOutboundNetworkIPv6", L"" WINDIVERT_DEVICE_NAME L" filter network (outbound IPv6)", {0}, {0}, windivert_classify_outbound_network_v6_callout, }; static layer_t layer_outbound_network_ipv6 = &layer_outbound_network_ipv6_0; static struct layer_s layer_forward_network_ipv4_0 = { L"" WINDIVERT_DEVICE_NAME L"_SubLayerForwardNetworkIPv4", L"" WINDIVERT_DEVICE_NAME L" sublayer network (forward IPv4)", L"" WINDIVERT_DEVICE_NAME L"_CalloutForwardNetworkIPv4", L"" WINDIVERT_DEVICE_NAME L" callout network (forward IPv4)", L"" WINDIVERT_DEVICE_NAME L"_FilterForwardNetworkIPv4", L"" WINDIVERT_DEVICE_NAME L" filter network (forward IPv4)", {0}, {0}, windivert_classify_forward_network_v4_callout, }; static layer_t layer_forward_network_ipv4 = &layer_forward_network_ipv4_0; static struct layer_s layer_forward_network_ipv6_0 = { L"" WINDIVERT_DEVICE_NAME L"_SubLayerForwardNetworkIPv6", L"" WINDIVERT_DEVICE_NAME L" sublayer network (forward IPv6)", L"" WINDIVERT_DEVICE_NAME L"_CalloutForwardNetworkIPv6", L"" WINDIVERT_DEVICE_NAME L" callout network (forward IPv6)", L"" WINDIVERT_DEVICE_NAME L"_FilterForwardNetworkIPv6", L"" WINDIVERT_DEVICE_NAME L" filter network (forward IPv6)", {0}, {0}, windivert_classify_forward_network_v6_callout, }; static layer_t layer_forward_network_ipv6 = &layer_forward_network_ipv6_0; /* * WinDivert malloc/free. */ static PVOID windivert_malloc(SIZE_T size, BOOL paged) { POOL_TYPE pool = (paged? PagedPool: non_paged_pool); return ExAllocatePoolWithTag(pool, size, WINDIVERT_TAG); } static VOID windivert_free(PVOID ptr) { if (ptr != NULL) { ExFreePoolWithTag(ptr, WINDIVERT_TAG); } } /* * WinDivert driver entry routine. */ extern NTSTATUS DriverEntry(IN PDRIVER_OBJECT driver_obj, IN PUNICODE_STRING reg_path) { WDF_DRIVER_CONFIG config; WDFDRIVER driver; PWDFDEVICE_INIT device_init; WDFDEVICE device; WDF_FILEOBJECT_CONFIG file_config; WDF_IO_QUEUE_CONFIG queue_config; WDFQUEUE queue; WDF_OBJECT_ATTRIBUTES obj_attrs; NET_BUFFER_LIST_POOL_PARAMETERS nbl_pool_params; NET_BUFFER_POOL_PARAMETERS nb_pool_params; RTL_OSVERSIONINFOW version; LARGE_INTEGER freq; NTSTATUS status; DECLARE_CONST_UNICODE_STRING(device_name, L"\\Device\\" WINDIVERT_DEVICE_NAME); DECLARE_CONST_UNICODE_STRING(dos_device_name, L"\\??\\" WINDIVERT_DEVICE_NAME); DEBUG("LOAD: loading WinDivert driver"); // Use the "no execute" pool if available: status = RtlGetVersion(&version); if (NT_SUCCESS(status)) { if (version.dwMajorVersion > 6 || (version.dwMajorVersion == 6 && version.dwMinorVersion >= 2)) { non_paged_pool = (POOL_TYPE)512; // NonPagedPoolNx (documented) } } // Initialize timer info. KeQueryPerformanceCounter(&freq); counts_per_ms = (ULONGLONG)freq.QuadPart / 1000; counts_per_ms = (counts_per_ms == 0? 1: counts_per_ms); // Initialize the layers. layer_inbound_network_ipv4->layer_guid = FWPM_LAYER_INBOUND_IPPACKET_V4; layer_outbound_network_ipv4->layer_guid = FWPM_LAYER_OUTBOUND_IPPACKET_V4; layer_inbound_network_ipv6->layer_guid = FWPM_LAYER_INBOUND_IPPACKET_V6; layer_outbound_network_ipv6->layer_guid = FWPM_LAYER_OUTBOUND_IPPACKET_V6; layer_forward_network_ipv4->layer_guid = FWPM_LAYER_IPFORWARD_V4; layer_forward_network_ipv6->layer_guid = FWPM_LAYER_IPFORWARD_V6; layer_inbound_network_ipv4->sublayer_guid = WINDIVERT_SUBLAYER_INBOUND_IPV4_GUID; layer_outbound_network_ipv4->sublayer_guid = WINDIVERT_SUBLAYER_OUTBOUND_IPV4_GUID; layer_inbound_network_ipv6->sublayer_guid = WINDIVERT_SUBLAYER_INBOUND_IPV6_GUID; layer_outbound_network_ipv6->sublayer_guid = WINDIVERT_SUBLAYER_OUTBOUND_IPV6_GUID; layer_forward_network_ipv4->sublayer_guid = WINDIVERT_SUBLAYER_FORWARD_IPV4_GUID; layer_forward_network_ipv6->sublayer_guid = WINDIVERT_SUBLAYER_FORWARD_IPV6_GUID; // Configure ourself as a non-PnP driver: WDF_DRIVER_CONFIG_INIT(&config, WDF_NO_EVENT_CALLBACK); config.DriverInitFlags |= WdfDriverInitNonPnpDriver; config.EvtDriverUnload = windivert_unload; status = WdfDriverCreate(driver_obj, reg_path, WDF_NO_OBJECT_ATTRIBUTES, &config, &driver); if (!NT_SUCCESS(status)) { DEBUG_ERROR("failed to create WDF driver", status); goto driver_entry_exit; } device_init = WdfControlDeviceInitAllocate(driver, &SDDL_DEVOBJ_SYS_ALL_ADM_ALL); if (device_init == NULL) { status = STATUS_INSUFFICIENT_RESOURCES; DEBUG_ERROR("failed to allocate WDF control device init structure", status); goto driver_entry_exit; } WdfDeviceInitSetDeviceType(device_init, FILE_DEVICE_NETWORK); WdfDeviceInitSetIoType(device_init, WdfDeviceIoDirect); status = WdfDeviceInitAssignName(device_init, &device_name); if (!NT_SUCCESS(status)) { DEBUG_ERROR("failed to create WDF device name", status); WdfDeviceInitFree(device_init); goto driver_entry_exit; } WDF_FILEOBJECT_CONFIG_INIT(&file_config, windivert_create, windivert_close, windivert_cleanup); WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&obj_attrs, context_s); obj_attrs.ExecutionLevel = WdfExecutionLevelPassive; obj_attrs.SynchronizationScope = WdfSynchronizationScopeNone; obj_attrs.EvtDestroyCallback = windivert_destroy; WdfDeviceInitSetFileObjectConfig(device_init, &file_config, &obj_attrs); WdfDeviceInitSetIoInCallerContextCallback(device_init, windivert_caller_context); WDF_OBJECT_ATTRIBUTES_INIT(&obj_attrs); status = WdfDeviceCreate(&device_init, &obj_attrs, &device); if (!NT_SUCCESS(status)) { DEBUG_ERROR("failed to create WDF control device", status); WdfDeviceInitFree(device_init); goto driver_entry_exit; } WDF_IO_QUEUE_CONFIG_INIT_DEFAULT_QUEUE(&queue_config, WdfIoQueueDispatchParallel); queue_config.EvtIoRead = NULL; queue_config.EvtIoWrite = NULL; queue_config.EvtIoDeviceControl = windivert_ioctl; WDF_OBJECT_ATTRIBUTES_INIT(&obj_attrs); obj_attrs.ExecutionLevel = WdfExecutionLevelPassive; obj_attrs.SynchronizationScope = WdfSynchronizationScopeNone; status = WdfIoQueueCreate(device, &queue_config, &obj_attrs, &queue); if (!NT_SUCCESS(status)) { DEBUG_ERROR("failed to create default WDF queue", status); goto driver_entry_exit; } status = WdfDeviceCreateSymbolicLink(device, &dos_device_name); if (!NT_SUCCESS(status)) { DEBUG_ERROR("failed to create device symbolic link", status); goto driver_entry_exit; } WdfControlFinishInitializing(device); // Create the packet injection handles. status = FwpsInjectionHandleCreate0(AF_INET, FWPS_INJECTION_TYPE_NETWORK | FWPS_INJECTION_TYPE_FORWARD, &inject_handle); if (!NT_SUCCESS(status)) { DEBUG_ERROR("failed to create WFP packet injection handle", status); goto driver_entry_exit; } status = FwpsInjectionHandleCreate0(AF_INET6, FWPS_INJECTION_TYPE_NETWORK | FWPS_INJECTION_TYPE_FORWARD, &injectv6_handle); if (!NT_SUCCESS(status)) { DEBUG_ERROR("failed to create WFP ipv6 packet injection handle", status); goto driver_entry_exit; } // Create a NET_BUFFER_LIST pool handle. RtlZeroMemory(&nbl_pool_params, sizeof(nbl_pool_params)); nbl_pool_params.Header.Type = NDIS_OBJECT_TYPE_DEFAULT; nbl_pool_params.Header.Revision = NET_BUFFER_LIST_POOL_PARAMETERS_REVISION_1; nbl_pool_params.Header.Size = sizeof(nbl_pool_params); nbl_pool_params.fAllocateNetBuffer = TRUE; nbl_pool_params.PoolTag = WINDIVERT_TAG; nbl_pool_params.DataSize = 0; nbl_pool_handle = NdisAllocateNetBufferListPool(NULL, &nbl_pool_params); if (nbl_pool_handle == NULL) { status = STATUS_INSUFFICIENT_RESOURCES; DEBUG_ERROR("failed to allocate net buffer list pool", status); goto driver_entry_exit; } // Create a NET_BUFFER pool handle. RtlZeroMemory(&nb_pool_params, sizeof(nb_pool_params)); nb_pool_params.Header.Type = NDIS_OBJECT_TYPE_DEFAULT; nb_pool_params.Header.Revision = NET_BUFFER_POOL_PARAMETERS_REVISION_1; nb_pool_params.Header.Size = NDIS_SIZEOF_NET_BUFFER_POOL_PARAMETERS_REVISION_1; nb_pool_params.PoolTag = WINDIVERT_TAG; nb_pool_params.DataSize = 0; nb_pool_handle = NdisAllocateNetBufferPool(NULL, &nb_pool_params); if (nb_pool_handle == NULL) { status = STATUS_INSUFFICIENT_RESOURCES; DEBUG_ERROR("failed to allocate net buffer pool", status); goto driver_entry_exit; } // Open a handle to the filter engine: status = FwpmEngineOpen0(NULL, RPC_C_AUTHN_DEFAULT, NULL, NULL, &engine_handle); if (!NT_SUCCESS(status)) { DEBUG_ERROR("failed to create WFP engine handle", status); goto driver_entry_exit; } // Register WFP sub-layers: status = FwpmTransactionBegin0(engine_handle, 0); if (!NT_SUCCESS(status)) { DEBUG_ERROR("failed to begin WFP transaction", status); goto driver_entry_exit; } status = windivert_install_sublayer(layer_inbound_network_ipv4); if (!NT_SUCCESS(status)) { driver_entry_sublayer_error: DEBUG_ERROR("failed to install WFP sub-layer", status); FwpmTransactionAbort0(engine_handle); goto driver_entry_exit; } status = windivert_install_sublayer(layer_outbound_network_ipv4); if (!NT_SUCCESS(status)) { goto driver_entry_sublayer_error; } status = windivert_install_sublayer(layer_inbound_network_ipv6); if (!NT_SUCCESS(status)) { goto driver_entry_sublayer_error; } status = windivert_install_sublayer(layer_outbound_network_ipv6); if (!NT_SUCCESS(status)) { goto driver_entry_sublayer_error; } status = windivert_install_sublayer(layer_forward_network_ipv4); if (!NT_SUCCESS(status)) { goto driver_entry_sublayer_error; } status = windivert_install_sublayer(layer_forward_network_ipv6); if (!NT_SUCCESS(status)) { goto driver_entry_sublayer_error; } status = FwpmTransactionCommit0(engine_handle); if (!NT_SUCCESS(status)) { DEBUG_ERROR("failed to commit WFP transaction", status); goto driver_entry_exit; } driver_entry_exit: if (!NT_SUCCESS(status)) { windivert_driver_unload(); } return status; } /* * WinDivert driver unload routine. */ extern VOID windivert_unload(IN WDFDRIVER Driver) { windivert_driver_unload(); } /* * WinDivert driver unload. */ static void windivert_driver_unload(void) { NTSTATUS status; DEBUG("UNLOAD: unloading the WinDivert driver"); if (inject_handle != NULL) { FwpsInjectionHandleDestroy0(inject_handle); } if (injectv6_handle != NULL) { FwpsInjectionHandleDestroy0(injectv6_handle); } if (nbl_pool_handle != NULL) { NdisFreeNetBufferListPool(nbl_pool_handle); } if (nb_pool_handle != NULL) { NdisFreeNetBufferPool(nb_pool_handle); } if (engine_handle != NULL) { status = FwpmTransactionBegin0(engine_handle, 0); if (!NT_SUCCESS(status)) { DEBUG_ERROR("failed to begin WFP transaction", status); FwpmEngineClose0(engine_handle); return; } FwpmSubLayerDeleteByKey0(engine_handle, &layer_inbound_network_ipv4->sublayer_guid); FwpmSubLayerDeleteByKey0(engine_handle, &layer_outbound_network_ipv4->sublayer_guid); FwpmSubLayerDeleteByKey0(engine_handle, &layer_inbound_network_ipv6->sublayer_guid); FwpmSubLayerDeleteByKey0(engine_handle, &layer_outbound_network_ipv6->sublayer_guid); FwpmSubLayerDeleteByKey0(engine_handle, &layer_forward_network_ipv4->sublayer_guid); FwpmSubLayerDeleteByKey0(engine_handle, &layer_forward_network_ipv6->sublayer_guid); status = FwpmTransactionCommit0(engine_handle); if (!NT_SUCCESS(status)) { DEBUG_ERROR("failed to commit WFP transaction", status); } FwpmEngineClose0(engine_handle); } } /* * Register a sub-layer. */ static NTSTATUS windivert_install_sublayer(layer_t layer) { FWPM_SUBLAYER0 sublayer; NTSTATUS status; RtlZeroMemory(&sublayer, sizeof(sublayer)); sublayer.subLayerKey = layer->sublayer_guid; sublayer.displayData.name = layer->sublayer_name; sublayer.displayData.description = layer->sublayer_desc; sublayer.weight = UINT16_MAX; status = FwpmSubLayerAdd0(engine_handle, &sublayer, NULL); if (!NT_SUCCESS(status)) { DEBUG_ERROR("failed to add WFP sub-layer", status); } return status; } /* * WinDivert create routine. */ extern VOID windivert_create(IN WDFDEVICE device, IN WDFREQUEST request, IN WDFFILEOBJECT object) { WDF_IO_QUEUE_CONFIG queue_config; WDF_WORKITEM_CONFIG item_config; WDF_OBJECT_ATTRIBUTES obj_attrs; FWPM_SESSION0 session; NTSTATUS status = STATUS_SUCCESS; UINT8 i; context_t context = windivert_context_get(object); DEBUG("CREATE: creating a new WinDivert context (context=%p)", context); // Initialise the new context: context->state = WINDIVERT_CONTEXT_STATE_OPENING; context->device = device; context->object = object; context->work_queue_length = 0; context->packet_queue_length = 0; context->packet_queue_maxlength = WINDIVERT_PARAM_QUEUE_LEN_DEFAULT; context->packet_queue_size = 0; context->packet_queue_maxsize = WINDIVERT_PARAM_QUEUE_SIZE_DEFAULT; context->packet_queue_maxcounts = WINDIVERT_PARAM_QUEUE_TIME_DEFAULT * counts_per_ms; context->packet_queue_maxtime = WINDIVERT_PARAM_QUEUE_TIME_DEFAULT; context->layer = WINDIVERT_LAYER_DEFAULT; context->flags = 0; context->priority = WINDIVERT_CONTEXT_PRIORITY(WINDIVERT_PRIORITY_DEFAULT); context->filter = NULL; for (i = 0; i < WINDIVERT_CONTEXT_MAXWORKERS; i++) { context->workers[i] = NULL; } context->worker_curr = 0; for (i = 0; i < WINDIVERT_CONTEXT_MAXLAYERS; i++) { context->installed[i] = FALSE; } context->on = FALSE; KeInitializeSpinLock(&context->lock); InitializeListHead(&context->work_queue); InitializeListHead(&context->packet_queue); for (i = 0; i < WINDIVERT_CONTEXT_MAXLAYERS; i++) { status = ExUuidCreate(&context->callout_guid[i]); if (!NT_SUCCESS(status)) { DEBUG_ERROR("failed to create callout GUID", status); goto windivert_create_exit; } status = ExUuidCreate(&context->filter_guid[i]); if (!NT_SUCCESS(status)) { DEBUG_ERROR("failed to create filter GUID", status); goto windivert_create_exit; } } WDF_IO_QUEUE_CONFIG_INIT(&queue_config, WdfIoQueueDispatchManual); status = WdfIoQueueCreate(device, &queue_config, WDF_NO_OBJECT_ATTRIBUTES, &context->read_queue); if (!NT_SUCCESS(status)) { DEBUG_ERROR("failed to create I/O read queue", status); goto windivert_create_exit; } WDF_WORKITEM_CONFIG_INIT(&item_config, windivert_worker); item_config.AutomaticSerialization = FALSE; WDF_OBJECT_ATTRIBUTES_INIT(&obj_attrs); obj_attrs.ParentObject = (WDFOBJECT)object; for (i = 0; i < WINDIVERT_CONTEXT_MAXWORKERS; i++) { status = WdfWorkItemCreate(&item_config, &obj_attrs, context->workers + i); if (!NT_SUCCESS(status)) { DEBUG_ERROR("failed to create read service work item", status); goto windivert_create_exit; } } RtlZeroMemory(&session, sizeof(session)); session.flags |= FWPM_SESSION_FLAG_DYNAMIC; status = FwpmEngineOpen0(NULL, RPC_C_AUTHN_DEFAULT, NULL, &session, &context->engine_handle); if (!NT_SUCCESS(status)) { DEBUG_ERROR("failed to create WFP engine handle", status); goto windivert_create_exit; } context->state = WINDIVERT_CONTEXT_STATE_OPEN; windivert_create_exit: // Clean-up on error: if (!NT_SUCCESS(status)) { context->state = WINDIVERT_CONTEXT_STATE_INVALID; if (context->read_queue != NULL) { WdfObjectDelete(context->read_queue); } for (i = 0; i < WINDIVERT_CONTEXT_MAXWORKERS; i++) { if (context->workers[i] != NULL) { WdfObjectDelete(context->workers[i]); } } if (context->engine_handle != NULL) { FwpmEngineClose0(context->engine_handle); } } WdfRequestComplete(request, status); } /* * Register all WFP callouts. */ static NTSTATUS windivert_install_callouts(context_t context, UINT8 layer, BOOL is_inbound, BOOL is_outbound, BOOL is_ipv4, BOOL is_ipv6) { UINT8 i, j; layer_t layers[WINDIVERT_CONTEXT_MAXLAYERS]; NTSTATUS status; i = 0; switch (layer) { case WINDIVERT_LAYER_NETWORK: if (is_inbound && is_ipv4) { layers[i++] = layer_inbound_network_ipv4; } if (is_outbound && is_ipv4) { layers[i++] = layer_outbound_network_ipv4; } if (is_inbound && is_ipv6) { layers[i++] = layer_inbound_network_ipv6; } if (is_outbound && is_ipv6) { layers[i++] = layer_outbound_network_ipv6; } break; case WINDIVERT_LAYER_NETWORK_FORWARD: if (is_ipv4) { layers[i++] = layer_forward_network_ipv4; } if (is_ipv6) { layers[i++] = layer_forward_network_ipv6; } break; default: return STATUS_INVALID_PARAMETER; } for (j = 0; j < i; j++) { status = windivert_install_callout(context, j, layers[j]); if (!NT_SUCCESS(status)) { goto windivert_install_callouts_exit; } } windivert_install_callouts_exit: if (!NT_SUCCESS(status)) { windivert_uninstall_callouts(context, WINDIVERT_CONTEXT_STATE_OPEN); } return status; } /* * Register a WFP callout. */ static NTSTATUS windivert_install_callout(context_t context, UINT idx, layer_t layer) { KLOCK_QUEUE_HANDLE lock_handle; FWPS_CALLOUT0 scallout; FWPM_CALLOUT0 mcallout; FWPM_FILTER0 filter; UINT64 weight; UINT32 priority; GUID callout_guid, filter_guid; WDFDEVICE device; HANDLE engine_handle; NTSTATUS status; KeAcquireInStackQueuedSpinLock(&context->lock, &lock_handle); if (context->state != WINDIVERT_CONTEXT_STATE_OPEN) { KeReleaseInStackQueuedSpinLock(&lock_handle); status = STATUS_INVALID_DEVICE_STATE; return status; } priority = context->priority; callout_guid = context->callout_guid[idx]; filter_guid = context->filter_guid[idx]; device = context->device; engine_handle = context->engine_handle; KeReleaseInStackQueuedSpinLock(&lock_handle); weight = WINDIVERT_FILTER_WEIGHT(priority); RtlZeroMemory(&scallout, sizeof(scallout)); scallout.calloutKey = callout_guid; scallout.classifyFn = layer->callout; scallout.notifyFn = windivert_notify_callout; scallout.flowDeleteFn = NULL; RtlZeroMemory(&mcallout, sizeof(mcallout)); mcallout.calloutKey = callout_guid; mcallout.displayData.name = layer->callout_name; mcallout.displayData.description = layer->callout_desc; mcallout.applicableLayer = layer->layer_guid; RtlZeroMemory(&filter, sizeof(filter)); filter.filterKey = filter_guid; filter.layerKey = layer->layer_guid; filter.displayData.name = layer->filter_name; filter.displayData.description = layer->filter_desc; filter.action.type = FWP_ACTION_CALLOUT_UNKNOWN; filter.action.calloutKey = callout_guid; filter.subLayerKey = layer->sublayer_guid; filter.weight.type = FWP_UINT64; filter.weight.uint64 = &weight; filter.rawContext = (UINT64)context; status = FwpsCalloutRegister0(WdfDeviceWdmGetDeviceObject(device), &scallout, NULL); if (!NT_SUCCESS(status)) { DEBUG_ERROR("failed to install WFP callout", status); return status; } status = FwpmTransactionBegin0(engine_handle, 0); if (!NT_SUCCESS(status)) { DEBUG_ERROR("failed to begin WFP transaction", status); FwpsCalloutUnregisterByKey0(&callout_guid); return status; } status = FwpmCalloutAdd0(engine_handle, &mcallout, NULL, NULL); if (!NT_SUCCESS(status)) { DEBUG_ERROR("failed to add WFP callout", status); goto windivert_install_callout_error; } status = FwpmFilterAdd0(engine_handle, &filter, NULL, NULL); if (!NT_SUCCESS(status)) { DEBUG_ERROR("failed to add WFP filter", status); goto windivert_install_callout_error; } status = FwpmTransactionCommit0(engine_handle); if (!NT_SUCCESS(status)) { DEBUG_ERROR("failed to commit WFP transaction", status); FwpsCalloutUnregisterByKey0(&callout_guid); return status; } KeAcquireInStackQueuedSpinLock(&context->lock, &lock_handle); if (context->state != WINDIVERT_CONTEXT_STATE_OPEN) { KeReleaseInStackQueuedSpinLock(&lock_handle); FwpsCalloutUnregisterByKey0(&callout_guid); status = STATUS_INVALID_DEVICE_STATE; return status; } context->installed[idx] = TRUE; KeReleaseInStackQueuedSpinLock(&lock_handle); return STATUS_SUCCESS; windivert_install_callout_error: FwpmTransactionAbort0(engine_handle); FwpsCalloutUnregisterByKey0(&callout_guid); return status; } /* * WinDivert uninstall callouts routine. */ static void windivert_uninstall_callouts(context_t context, context_state_t state) { KLOCK_QUEUE_HANDLE lock_handle; UINT i; HANDLE engine_handle; BOOL installed; GUID callout_guid, filter_guid; NTSTATUS status; KeAcquireInStackQueuedSpinLock(&context->lock, &lock_handle); if (context->state != state) { windivert_uninstall_callouts_error: KeReleaseInStackQueuedSpinLock(&lock_handle); status = STATUS_INVALID_DEVICE_STATE; DEBUG_ERROR("failed to delete filters and callouts", status); return; } engine_handle = context->engine_handle; KeReleaseInStackQueuedSpinLock(&lock_handle); status = FwpmTransactionBegin0(engine_handle, 0); if (!NT_SUCCESS(status)) { // If the userspace app closes without closing the handle to // WinDivert, any actions on engine_handle fail because the // RPC handle was closed first. So, this path is "normal" if // the user's app crashed or never closed the WinDivert handle. DEBUG_ERROR("failed to begin WFP transaction", status); goto unregister_callouts; } for (i = 0; i < WINDIVERT_CONTEXT_MAXLAYERS; i++) { KeAcquireInStackQueuedSpinLock(&context->lock, &lock_handle); if (context->state != state) { KeReleaseInStackQueuedSpinLock(&lock_handle); FwpmTransactionAbort0(engine_handle); status = STATUS_INVALID_DEVICE_STATE; DEBUG_ERROR("failed to delete filters and callouts", status); return; } installed = context->installed[i]; callout_guid = context->callout_guid[i]; filter_guid = context->filter_guid[i]; KeReleaseInStackQueuedSpinLock(&lock_handle); if (!installed) { continue; } status = FwpmFilterDeleteByKey0(engine_handle, &filter_guid); if (!NT_SUCCESS(status)) { DEBUG_ERROR("failed to delete filter", status); break; } status = FwpmCalloutDeleteByKey0(engine_handle, &callout_guid); if (!NT_SUCCESS(status)) { DEBUG_ERROR("failed to delete callout", status); break; } } if (!NT_SUCCESS(status)) { FwpmTransactionAbort0(engine_handle); goto unregister_callouts; } status = FwpmTransactionCommit0(engine_handle); if (!NT_SUCCESS(status)) { DEBUG_ERROR("failed to commit WFP transaction", status); //fallthrough } unregister_callouts: for (i = 0; i < WINDIVERT_CONTEXT_MAXLAYERS; i++) { KeAcquireInStackQueuedSpinLock(&context->lock, &lock_handle); if (context->state != state) { goto windivert_uninstall_callouts_error; } installed = context->installed[i]; callout_guid = context->callout_guid[i]; context->installed[i] = FALSE; KeReleaseInStackQueuedSpinLock(&lock_handle); if (!installed) { continue; } FwpsCalloutUnregisterByKey0(&callout_guid); } } /* * Divert cleanup routine. */ extern VOID windivert_cleanup(IN WDFFILEOBJECT object) { KLOCK_QUEUE_HANDLE lock_handle; PLIST_ENTRY entry; UINT i; context_t context = windivert_context_get(object); work_t work; packet_t packet; WDFQUEUE read_queue; WDFWORKITEM worker; NTSTATUS status; DEBUG("CLEANUP: cleaning up WinDivert context (context=%p)", context); KeAcquireInStackQueuedSpinLock(&context->lock, &lock_handle); if (context->state != WINDIVERT_CONTEXT_STATE_OPEN) { windivert_cleanup_error: KeReleaseInStackQueuedSpinLock(&lock_handle); status = STATUS_INVALID_DEVICE_STATE; DEBUG_ERROR("failed to verify state for cleanup routine", status); return; } context->state = WINDIVERT_CONTEXT_STATE_CLOSING; while (!IsListEmpty(&context->work_queue)) { entry = RemoveHeadList(&context->work_queue); context->work_queue_length--; KeReleaseInStackQueuedSpinLock(&lock_handle); work = CONTAINING_RECORD(entry, struct work_s, entry); FwpsDereferenceNetBufferList(work->buffers, FALSE); windivert_free(work); KeAcquireInStackQueuedSpinLock(&context->lock, &lock_handle); if (context->state != WINDIVERT_CONTEXT_STATE_CLOSING) { goto windivert_cleanup_error; } } while (!IsListEmpty(&context->packet_queue)) { entry = RemoveHeadList(&context->packet_queue); packet = CONTAINING_RECORD(entry, struct packet_s, entry); context->packet_queue_length--; context->packet_queue_size -= packet->data_len; KeReleaseInStackQueuedSpinLock(&lock_handle); windivert_free_packet(packet); KeAcquireInStackQueuedSpinLock(&context->lock, &lock_handle); if (context->state != WINDIVERT_CONTEXT_STATE_CLOSING) { goto windivert_cleanup_error; } } read_queue = context->read_queue; KeReleaseInStackQueuedSpinLock(&lock_handle); WdfIoQueuePurge(read_queue, NULL, NULL); WdfObjectDelete(read_queue); for (i = 0; i < WINDIVERT_CONTEXT_MAXWORKERS; i++) { KeAcquireInStackQueuedSpinLock(&context->lock, &lock_handle); if (context->state != WINDIVERT_CONTEXT_STATE_CLOSING) { goto windivert_cleanup_error; } worker = context->workers[i]; KeReleaseInStackQueuedSpinLock(&lock_handle); WdfWorkItemFlush(worker); WdfObjectDelete(worker); } windivert_uninstall_callouts(context, WINDIVERT_CONTEXT_STATE_CLOSING); FwpmEngineClose0(context->engine_handle); } /* * WinDivert close routine. */ extern VOID windivert_close(IN WDFFILEOBJECT object) { KLOCK_QUEUE_HANDLE lock_handle; context_t context = windivert_context_get(object); NTSTATUS status; DEBUG("CLOSE: closing WinDivert context (context=%p)", context); KeAcquireInStackQueuedSpinLock(&context->lock, &lock_handle); if (context->state != WINDIVERT_CONTEXT_STATE_CLOSING) { KeReleaseInStackQueuedSpinLock(&lock_handle); status = STATUS_INVALID_DEVICE_STATE; DEBUG_ERROR("failed to verify state for close routine", status); return; } context->state = WINDIVERT_CONTEXT_STATE_CLOSED; KeReleaseInStackQueuedSpinLock(&lock_handle); } /* * WinDivert destroy routine. */ extern VOID windivert_destroy(IN WDFOBJECT object) { KLOCK_QUEUE_HANDLE lock_handle; context_t context = windivert_context_get((WDFFILEOBJECT)object); filter_t filter; NTSTATUS status; DEBUG("DESTROY: destroying WinDivert context (context=%p)", context); KeAcquireInStackQueuedSpinLock(&context->lock, &lock_handle); if (context->state != WINDIVERT_CONTEXT_STATE_CLOSED) { KeReleaseInStackQueuedSpinLock(&lock_handle); status = STATUS_INVALID_DEVICE_STATE; DEBUG_ERROR("failed to verify state for destroy routine", status); return; } filter = context->filter; KeReleaseInStackQueuedSpinLock(&lock_handle); windivert_free(filter); } /* * WinDivert read routine. */ static NTSTATUS windivert_read(context_t context, WDFREQUEST request) { NTSTATUS status = STATUS_SUCCESS; DEBUG("READ: reading diverted packet (context=%p, request=%p)", context, request); // Forward the request to the pending read queue: status = WdfRequestForwardToIoQueue(request, context->read_queue); if (!NT_SUCCESS(status)) { DEBUG_ERROR("failed to forward I/O request to read queue", status); return status; } // Service the read request: windivert_read_service(context); return STATUS_SUCCESS; } /* * WinDivert service a single read request. */ static void windivert_read_service_request(packet_t packet, PNET_BUFFER buffer, UINT8 direction, UINT32 if_idx, UINT32 sub_if_idx, BOOL hop, UINT8 checksums, WDFREQUEST request) { PMDL dst_mdl; PVOID dst, src; ULONG dst_len, src_len; NTSTATUS status; req_context_t req_context; windivert_addr_t addr; DEBUG("SERVICE: servicing read request (request=%p)", request); status = WdfRequestRetrieveOutputWdmMdl(request, &dst_mdl); if (!NT_SUCCESS(status)) { DEBUG_ERROR("failed to retrieve output MDL", status); goto windivert_read_service_request_exit; } dst = MmGetSystemAddressForMdlSafe(dst_mdl, NormalPagePriority); if (dst == NULL) { status = STATUS_INSUFFICIENT_RESOURCES; DEBUG_ERROR("failed to get address of output MDL", status); goto windivert_read_service_request_exit; } dst_len = MmGetMdlByteCount(dst_mdl); if (packet != NULL) { src_len = packet->data_len; dst_len = (src_len < dst_len? src_len: dst_len); src = packet->data; RtlCopyMemory(dst, src, dst_len); } else { src_len = NET_BUFFER_DATA_LENGTH(buffer); dst_len = (src_len < dst_len? src_len: dst_len); src = NdisGetDataBuffer(buffer, dst_len, NULL, 1, 0); if (src == NULL) { NdisGetDataBuffer(buffer, dst_len, dst, 1, 0); } else { RtlCopyMemory(dst, src, dst_len); } } // Write the address information. req_context = windivert_req_context_get(request); addr = req_context->addr; if (addr != NULL) { addr->IfIdx = if_idx; addr->SubIfIdx = sub_if_idx; addr->Direction = direction; } // Zero the IP/TCP/UDP checksums and/or decrement the TTL (if required). status = windivert_finalize_packet(dst, dst_len, hop, checksums); windivert_read_service_request_exit: if (NT_SUCCESS(status)) { WdfRequestCompleteWithInformation(request, status, dst_len); } else { WdfRequestComplete(request, status); } } /* * WinDivert read request service. */ static void windivert_read_service(context_t context) { KLOCK_QUEUE_HANDLE lock_handle; WDFREQUEST request; PLIST_ENTRY entry; PMDL dst_mdl; PVOID dst, src; ULONG dst_len, src_len; ULONGLONG timestamp; BOOL timeout; NTSTATUS status; packet_t packet; req_context_t req_context; windivert_addr_t addr; timestamp = (ULONGLONG)KeQueryPerformanceCounter(NULL).QuadPart; KeAcquireInStackQueuedSpinLock(&context->lock, &lock_handle); while (context->state == WINDIVERT_CONTEXT_STATE_OPEN && !IsListEmpty(&context->packet_queue)) { entry = RemoveHeadList(&context->packet_queue); packet = CONTAINING_RECORD(entry, struct packet_s, entry); timeout = (timestamp - packet->timestamp > context->packet_queue_maxcounts); request = NULL; if (!timeout) { status = WdfIoQueueRetrieveNextRequest(context->read_queue, &request); if (!NT_SUCCESS(status)) { InsertHeadList(&context->packet_queue, entry); break; } } context->packet_queue_length--; context->packet_queue_size -= packet->data_len; KeReleaseInStackQueuedSpinLock(&lock_handle); if (!timeout) { windivert_read_service_request(packet, NULL, packet->direction, packet->if_idx, packet->sub_if_idx, packet->hop, packet->checksums, request); } windivert_free_packet(packet); timestamp = (ULONGLONG)KeQueryPerformanceCounter(NULL).QuadPart; KeAcquireInStackQueuedSpinLock(&context->lock, &lock_handle); } KeReleaseInStackQueuedSpinLock(&lock_handle); } /* * WinDivert write routine. */ static NTSTATUS windivert_write(context_t context, WDFREQUEST request, windivert_addr_t addr) { KLOCK_QUEUE_HANDLE lock_handle; PMDL mdl = NULL, mdl_copy = NULL; PVOID data, data_copy = NULL; UINT data_len; struct iphdr *ip_header; struct ipv6hdr *ipv6_header; BOOL isipv4; UINT8 layer; UINT32 priority; HANDLE handle; PNET_BUFFER_LIST buffers = NULL; NTSTATUS status = STATUS_SUCCESS; DEBUG("WRITE: writing/injecting a packet (context=%p, request=%p)", context, request); if (addr->Direction != WINDIVERT_DIRECTION_INBOUND && addr->Direction != WINDIVERT_DIRECTION_OUTBOUND) { status = STATUS_INVALID_PARAMETER; DEBUG_ERROR("failed to inject packet; invalid direction", status); goto windivert_write_exit; } status = WdfRequestRetrieveOutputWdmMdl(request, &mdl); if (!NT_SUCCESS(status)) { DEBUG_ERROR("failed to retrieve input MDL", status); goto windivert_write_exit; } data = MmGetSystemAddressForMdlSafe(mdl, NormalPagePriority); if (data == NULL) { status = STATUS_INSUFFICIENT_RESOURCES; DEBUG_ERROR("failed to get MDL address", status); goto windivert_write_exit; } data_len = MmGetMdlByteCount(mdl); if (data_len > UINT16_MAX || data_len < sizeof(struct iphdr)) { windivert_write_bad_packet: status = STATUS_INVALID_PARAMETER; DEBUG_ERROR("failed to inject a bad packet", status); goto windivert_write_exit; } data_copy = windivert_malloc(data_len, FALSE); if (data_copy == NULL) { status = STATUS_INSUFFICIENT_RESOURCES; DEBUG_ERROR("failed to allocate memory for injected packet data", status); goto windivert_write_exit; } RtlCopyMemory(data_copy, data, sizeof(struct iphdr)); ip_header = (struct iphdr *)data_copy; switch (ip_header->Version) { case 4: if (data_len != RtlUshortByteSwap(ip_header->Length)) goto windivert_write_bad_packet; isipv4 = TRUE; break; case 6: if (data_len < sizeof(struct ipv6hdr)) goto windivert_write_bad_packet; ipv6_header = (struct ipv6hdr *)data_copy; if (data_len != RtlUshortByteSwap(ipv6_header->Length) + sizeof(struct ipv6hdr)) goto windivert_write_bad_packet; isipv4 = FALSE; break; default: goto windivert_write_bad_packet; } if (data_len > sizeof(struct iphdr)) { RtlCopyMemory((char *)data_copy + sizeof(struct iphdr), (char *)data + sizeof(struct iphdr), data_len - sizeof(struct iphdr)); } mdl_copy = IoAllocateMdl(data_copy, data_len, FALSE, FALSE, NULL); if (mdl_copy == NULL) { status = STATUS_INSUFFICIENT_RESOURCES; DEBUG_ERROR("failed to allocate MDL for injected packet", status); goto windivert_write_exit; } MmBuildMdlForNonPagedPool(mdl_copy); status = FwpsAllocateNetBufferAndNetBufferList0(nbl_pool_handle, 0, 0, mdl_copy, 0, data_len, &buffers); if (!NT_SUCCESS(status)) { DEBUG_ERROR("failed to create NET_BUFFER_LIST for injected packet", status); goto windivert_write_exit; } KeAcquireInStackQueuedSpinLock(&context->lock, &lock_handle); if (context->state != WINDIVERT_CONTEXT_STATE_OPEN) { KeReleaseInStackQueuedSpinLock(&lock_handle); status = STATUS_INVALID_DEVICE_STATE; goto windivert_write_exit; } layer = context->layer; priority = context->priority; KeReleaseInStackQueuedSpinLock(&lock_handle); handle = (isipv4? inject_handle: injectv6_handle); if (layer == WINDIVERT_LAYER_NETWORK_FORWARD) { status = FwpsInjectForwardAsync0(handle, (HANDLE)priority, 0, (isipv4? AF_INET: AF_INET6), UNSPECIFIED_COMPARTMENT_ID, addr->IfIdx, buffers, windivert_inject_complete, NULL); } else if (addr->Direction == WINDIVERT_DIRECTION_OUTBOUND) { status = FwpsInjectNetworkSendAsync0(handle, (HANDLE)priority, 0, UNSPECIFIED_COMPARTMENT_ID, buffers, windivert_inject_complete, NULL); } else { status = FwpsInjectNetworkReceiveAsync0(handle, (HANDLE)priority, 0, UNSPECIFIED_COMPARTMENT_ID, addr->IfIdx, addr->SubIfIdx, buffers, windivert_inject_complete, NULL); } windivert_write_exit: if (NT_SUCCESS(status)) { WdfRequestCompleteWithInformation(request, status, data_len); } else { DEBUG_ERROR("failed to inject packet", status); if (buffers != NULL) { FwpsFreeNetBufferList0(buffers); } if (mdl_copy != NULL) { IoFreeMdl(mdl_copy); } windivert_free(data_copy); } return status; } /* * WinDivert inject complete routine. */ static void NTAPI windivert_inject_complete(VOID *context, NET_BUFFER_LIST *buffers, BOOLEAN dispatch_level) { PMDL mdl; PVOID data; PNET_BUFFER buffer; UNREFERENCED_PARAMETER(context); UNREFERENCED_PARAMETER(dispatch_level); buffer = NET_BUFFER_LIST_FIRST_NB(buffers); mdl = NET_BUFFER_FIRST_MDL(buffer); data = MmGetSystemAddressForMdlSafe(mdl, NormalPagePriority); windivert_free(data); IoFreeMdl(mdl); FwpsFreeNetBufferList0(buffers); } /* * WinDivert caller context preprocessing. */ VOID windivert_caller_context(IN WDFDEVICE device, IN WDFREQUEST request) { PCHAR inbuf; size_t inbuflen; WDF_REQUEST_PARAMETERS params; WDFMEMORY memobj; windivert_addr_t addr = NULL; windivert_ioctl_t ioctl; WDF_OBJECT_ATTRIBUTES attributes; req_context_t req_context = NULL; NTSTATUS status; WDF_REQUEST_PARAMETERS_INIT(¶ms); WdfRequestGetParameters(request, ¶ms); if (params.Type != WdfRequestTypeDeviceControl) { goto windivert_caller_context_exit; } // Get and verify the input buffer. status = WdfRequestRetrieveInputBuffer(request, 0, &inbuf, &inbuflen); if (!NT_SUCCESS(status)) { DEBUG_ERROR("failed to retrieve input buffer", status); goto windivert_caller_context_error; } if (inbuflen != sizeof(struct windivert_ioctl_s)) { status = STATUS_INVALID_PARAMETER; DEBUG_ERROR("input buffer not an ioctl message header", status); goto windivert_caller_context_error; } ioctl = (windivert_ioctl_t)inbuf; if (ioctl->version != WINDIVERT_IOCTL_VERSION || ioctl->magic != WINDIVERT_IOCTL_MAGIC) { status = STATUS_INVALID_PARAMETER; DEBUG_ERROR("input buffer contained a bad ioctl message header", status); goto windivert_caller_context_error; } // Probe and lock user buffers here (if required). WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, req_context_s); status = WdfObjectAllocateContext(request, &attributes, &req_context); if (!NT_SUCCESS(status)) { DEBUG_ERROR("failed to allocate request context for ioctl", status); goto windivert_caller_context_error; } switch (params.Parameters.DeviceIoControl.IoControlCode) { case IOCTL_WINDIVERT_RECV: if ((PVOID)ioctl->arg == NULL) { status = STATUS_INVALID_PARAMETER; DEBUG_ERROR("null arg pointer for RECV ioctl", status); goto windivert_caller_context_error; } status = WdfRequestProbeAndLockUserBufferForWrite(request, (PVOID)ioctl->arg, sizeof(struct windivert_addr_s), &memobj); if (!NT_SUCCESS(status)) { DEBUG_ERROR("invalid arg pointer for RECV ioctl", status); goto windivert_caller_context_error; } addr = (windivert_addr_t)WdfMemoryGetBuffer(memobj, NULL); break; case IOCTL_WINDIVERT_SEND: if ((PVOID)ioctl->arg == NULL) { status = STATUS_INVALID_PARAMETER; DEBUG_ERROR("null arg pointer for SEND ioctl", status); goto windivert_caller_context_error; } status = WdfRequestProbeAndLockUserBufferForRead(request, (PVOID)ioctl->arg, sizeof(struct windivert_addr_s), &memobj); if (!NT_SUCCESS(status)) { DEBUG_ERROR("invalid arg pointer for SEND ioctl", status); goto windivert_caller_context_error; } addr = (windivert_addr_t)WdfMemoryGetBuffer(memobj, NULL); break; case IOCTL_WINDIVERT_START_FILTER: case IOCTL_WINDIVERT_SET_LAYER: case IOCTL_WINDIVERT_SET_PRIORITY: case IOCTL_WINDIVERT_SET_FLAGS: case IOCTL_WINDIVERT_SET_PARAM: case IOCTL_WINDIVERT_GET_PARAM: break; default: status = STATUS_INVALID_DEVICE_REQUEST; DEBUG_ERROR("failed to complete I/O control; invalid request", status); goto windivert_caller_context_error; } req_context->addr = addr; windivert_caller_context_exit: status = WdfDeviceEnqueueRequest(device, request); windivert_caller_context_error: if (!NT_SUCCESS(status)) { DEBUG_ERROR("failed to enqueue request", status); WdfRequestComplete(request, status); } } /* * WinDivert I/O control. */ extern VOID windivert_ioctl(IN WDFQUEUE queue, IN WDFREQUEST request, IN size_t out_length, IN size_t in_length, IN ULONG code) { KLOCK_QUEUE_HANDLE lock_handle; PCHAR inbuf, outbuf; size_t inbuflen, outbuflen, filter0_len; windivert_ioctl_t ioctl; windivert_ioctl_filter_t filter0; filter_t filter; UINT8 layer; UINT32 priority; UINT64 flags; windivert_addr_t addr; req_context_t req_context; NTSTATUS status = STATUS_SUCCESS; context_t context = windivert_context_get(WdfRequestGetFileObject(request)); UINT64 value, *valptr; UNREFERENCED_PARAMETER(queue); DEBUG("IOCTL: I/O control request (context=%p)", context); // Get the buffers and do sanity checks. status = WdfRequestRetrieveInputBuffer(request, 0, &inbuf, &inbuflen); if (!NT_SUCCESS(status)) { DEBUG_ERROR("failed to retrieve input buffer", status); goto windivert_ioctl_exit; } switch (code) { case IOCTL_WINDIVERT_START_FILTER: case IOCTL_WINDIVERT_GET_PARAM: status = WdfRequestRetrieveOutputBuffer(request, 0, &outbuf, &outbuflen); if (!NT_SUCCESS(status)) { DEBUG_ERROR("failed to retrieve output buffer", status); goto windivert_ioctl_exit; } break; default: outbuf = NULL; outbuflen = 0; break; } // Handle the ioctl: switch (code) { case IOCTL_WINDIVERT_RECV: status = windivert_read(context, request); if (NT_SUCCESS(status)) { return; } break; case IOCTL_WINDIVERT_SEND: req_context = windivert_req_context_get(request); addr = req_context->addr; status = windivert_write(context, request, addr); if (NT_SUCCESS(status)) { return; } break; case IOCTL_WINDIVERT_START_FILTER: { BOOL is_inbound, is_outbound, is_ipv4, is_ipv6; filter0 = (windivert_ioctl_filter_t)outbuf; filter0_len = outbuflen; filter = windivert_filter_compile(filter0, filter0_len); if (filter == NULL) { status = STATUS_INVALID_PARAMETER; DEBUG_ERROR("failed to compile filter", status); goto windivert_ioctl_exit; } KeAcquireInStackQueuedSpinLock(&context->lock, &lock_handle); if (context->state != WINDIVERT_CONTEXT_STATE_OPEN || context->on) { KeReleaseInStackQueuedSpinLock(&lock_handle); windivert_free(filter); status = STATUS_INVALID_DEVICE_STATE; goto windivert_ioctl_exit; } context->on = TRUE; context->filter = filter; layer = context->layer; KeReleaseInStackQueuedSpinLock(&lock_handle); windivert_filter_analyze(filter, &is_inbound, &is_outbound, &is_ipv4, &is_ipv6); status = windivert_install_callouts(context, layer, is_inbound, is_outbound, is_ipv4, is_ipv6); break; } case IOCTL_WINDIVERT_SET_LAYER: ioctl = (windivert_ioctl_t)inbuf; if (ioctl->arg > WINDIVERT_LAYER_MAX) { status = STATUS_INVALID_PARAMETER; DEBUG_ERROR("failed to set layer; value too big", status); goto windivert_ioctl_exit; } layer = (UINT8)ioctl->arg; KeAcquireInStackQueuedSpinLock(&context->lock, &lock_handle); if (context->state != WINDIVERT_CONTEXT_STATE_OPEN || context->on) { KeReleaseInStackQueuedSpinLock(&lock_handle); status = STATUS_INVALID_DEVICE_STATE; goto windivert_ioctl_exit; } context->layer = layer; KeReleaseInStackQueuedSpinLock(&lock_handle); break; case IOCTL_WINDIVERT_SET_PRIORITY: ioctl = (windivert_ioctl_t)inbuf; if (ioctl->arg < WINDIVERT_PRIORITY_MIN || ioctl->arg > WINDIVERT_PRIORITY_MAX) { status = STATUS_INVALID_PARAMETER; DEBUG_ERROR("failed to set priority; value out of range", status); goto windivert_ioctl_exit; } priority = WINDIVERT_CONTEXT_PRIORITY((UINT32)ioctl->arg); KeAcquireInStackQueuedSpinLock(&context->lock, &lock_handle); if (context->state != WINDIVERT_CONTEXT_STATE_OPEN || context->on) { KeReleaseInStackQueuedSpinLock(&lock_handle); status = STATUS_INVALID_DEVICE_STATE; goto windivert_ioctl_exit; } context->priority = priority; KeReleaseInStackQueuedSpinLock(&lock_handle); break; case IOCTL_WINDIVERT_SET_FLAGS: ioctl = (windivert_ioctl_t)inbuf; if (!WINDIVERT_FLAGS_VALID(ioctl->arg)) { status = STATUS_INVALID_PARAMETER; DEBUG_ERROR("failed to set flags; invalid flags value", status); goto windivert_ioctl_exit; } flags = ioctl->arg; KeAcquireInStackQueuedSpinLock(&context->lock, &lock_handle); if (context->state != WINDIVERT_CONTEXT_STATE_OPEN || context->on) { KeReleaseInStackQueuedSpinLock(&lock_handle); status = STATUS_INVALID_DEVICE_STATE; goto windivert_ioctl_exit; } context->flags = flags; KeReleaseInStackQueuedSpinLock(&lock_handle); break; case IOCTL_WINDIVERT_SET_PARAM: ioctl = (windivert_ioctl_t)inbuf; value = ioctl->arg; KeAcquireInStackQueuedSpinLock(&context->lock, &lock_handle); if (context->state != WINDIVERT_CONTEXT_STATE_OPEN) { KeReleaseInStackQueuedSpinLock(&lock_handle); status = STATUS_INVALID_DEVICE_STATE; goto windivert_ioctl_exit; } switch ((WINDIVERT_PARAM)ioctl->arg8) { case WINDIVERT_PARAM_QUEUE_LEN: if (value < WINDIVERT_PARAM_QUEUE_LEN_MIN || value > WINDIVERT_PARAM_QUEUE_LEN_MAX) { KeReleaseInStackQueuedSpinLock(&lock_handle); status = STATUS_INVALID_PARAMETER; DEBUG_ERROR("failed to set queue length; invalid " "value", status); goto windivert_ioctl_exit; } context->packet_queue_maxlength = (ULONG)value; break; case WINDIVERT_PARAM_QUEUE_TIME: if (value < WINDIVERT_PARAM_QUEUE_TIME_MIN || value > WINDIVERT_PARAM_QUEUE_TIME_MAX) { KeReleaseInStackQueuedSpinLock(&lock_handle); status = STATUS_INVALID_PARAMETER; DEBUG_ERROR("failed to set queue time; invalid " "value", status); goto windivert_ioctl_exit; } context->packet_queue_maxcounts = (ULONGLONG)value * counts_per_ms; context->packet_queue_maxtime = (ULONG)value; break; case WINDIVERT_PARAM_QUEUE_SIZE: if (value < WINDIVERT_PARAM_QUEUE_SIZE_MIN || value > WINDIVERT_PARAM_QUEUE_SIZE_MAX) { KeReleaseInStackQueuedSpinLock(&lock_handle); status = STATUS_INVALID_PARAMETER; DEBUG_ERROR("failed to set queue size; invalid " "value", status); goto windivert_ioctl_exit; } context->packet_queue_maxsize = (ULONG)value; break; default: KeReleaseInStackQueuedSpinLock(&lock_handle); status = STATUS_INVALID_PARAMETER; DEBUG_ERROR("failed to set parameter; invalid parameter", status); goto windivert_ioctl_exit; } KeReleaseInStackQueuedSpinLock(&lock_handle); break; case IOCTL_WINDIVERT_GET_PARAM: ioctl = (windivert_ioctl_t)inbuf; if (outbuflen != sizeof(UINT64)) { status = STATUS_INVALID_PARAMETER; DEBUG_ERROR("failed to get parameter; invalid output " "buffer size", status); goto windivert_ioctl_exit; } valptr = (UINT64 *)outbuf; KeAcquireInStackQueuedSpinLock(&context->lock, &lock_handle); if (context->state != WINDIVERT_CONTEXT_STATE_OPEN) { KeReleaseInStackQueuedSpinLock(&lock_handle); status = STATUS_INVALID_DEVICE_STATE; goto windivert_ioctl_exit; } switch ((WINDIVERT_PARAM)ioctl->arg8) { case WINDIVERT_PARAM_QUEUE_LEN: *valptr = context->packet_queue_maxlength; break; case WINDIVERT_PARAM_QUEUE_TIME: *valptr = context->packet_queue_maxtime; break; case WINDIVERT_PARAM_QUEUE_SIZE: *valptr = context->packet_queue_maxsize; break; default: KeReleaseInStackQueuedSpinLock(&lock_handle); status = STATUS_INVALID_PARAMETER; DEBUG_ERROR("failed to get parameter; invalid parameter", status); goto windivert_ioctl_exit; } KeReleaseInStackQueuedSpinLock(&lock_handle); break; default: status = STATUS_INVALID_DEVICE_REQUEST; DEBUG_ERROR("failed to complete I/O control; invalid request", status); break; } windivert_ioctl_exit: WdfRequestComplete(request, status); } /* * WinDivert notify callout. */ static NTSTATUS windivert_notify_callout(IN FWPS_CALLOUT_NOTIFY_TYPE type, IN const GUID *filter_key, IN const FWPS_FILTER0 *filter) { UNREFERENCED_PARAMETER(type); UNREFERENCED_PARAMETER(filter_key); UNREFERENCED_PARAMETER(filter); return STATUS_SUCCESS; } /* * WinDivert classify outbound IPv4 callout. */ static void windivert_classify_outbound_network_v4_callout( IN const FWPS_INCOMING_VALUES0 *fixed_vals, IN const FWPS_INCOMING_METADATA_VALUES0 *meta_vals, IN OUT void *data, const FWPS_FILTER0 *filter, IN UINT64 flow_context, OUT FWPS_CLASSIFY_OUT0 *result) { windivert_classify_callout((context_t)filter->context, WINDIVERT_DIRECTION_OUTBOUND, fixed_vals->incomingValue[ FWPS_FIELD_OUTBOUND_IPPACKET_V4_INTERFACE_INDEX].value.uint32, fixed_vals->incomingValue[ FWPS_FIELD_OUTBOUND_IPPACKET_V4_SUB_INTERFACE_INDEX].value.uint32, TRUE, (fixed_vals->incomingValue[ FWPS_FIELD_OUTBOUND_IPPACKET_V4_FLAGS].value.uint32 & FWP_CONDITION_FLAG_IS_LOOPBACK) != 0, 0, data, flow_context, result); } /* * WinDivert classify outbound IPv6 callout. */ static void windivert_classify_outbound_network_v6_callout( IN const FWPS_INCOMING_VALUES0 *fixed_vals, IN const FWPS_INCOMING_METADATA_VALUES0 *meta_vals, IN OUT void *data, const FWPS_FILTER0 *filter, IN UINT64 flow_context, OUT FWPS_CLASSIFY_OUT0 *result) { windivert_classify_callout((context_t)filter->context, WINDIVERT_DIRECTION_OUTBOUND, fixed_vals->incomingValue[ FWPS_FIELD_OUTBOUND_IPPACKET_V6_INTERFACE_INDEX].value.uint32, fixed_vals->incomingValue[ FWPS_FIELD_OUTBOUND_IPPACKET_V6_SUB_INTERFACE_INDEX].value.uint32, FALSE, (fixed_vals->incomingValue[ FWPS_FIELD_OUTBOUND_IPPACKET_V6_FLAGS].value.uint32 & FWP_CONDITION_FLAG_IS_LOOPBACK) != 0, 0, data, flow_context, result); } /* * WinDivert classify inbound IPv4 callout. */ static void windivert_classify_inbound_network_v4_callout( IN const FWPS_INCOMING_VALUES0 *fixed_vals, IN const FWPS_INCOMING_METADATA_VALUES0 *meta_vals, IN OUT void *data, const FWPS_FILTER0 *filter, IN UINT64 flow_context, OUT FWPS_CLASSIFY_OUT0 *result) { UINT advance = meta_vals->ipHeaderSize; windivert_classify_callout((context_t)filter->context, WINDIVERT_DIRECTION_INBOUND, fixed_vals->incomingValue[ FWPS_FIELD_INBOUND_IPPACKET_V4_INTERFACE_INDEX].value.uint32, fixed_vals->incomingValue[ FWPS_FIELD_INBOUND_IPPACKET_V4_SUB_INTERFACE_INDEX].value.uint32, TRUE, (fixed_vals->incomingValue[ FWPS_FIELD_INBOUND_IPPACKET_V4_FLAGS].value.uint32 & FWP_CONDITION_FLAG_IS_LOOPBACK) != 0, advance, data, flow_context, result); } /* * WinDivert classify inbound IPv6 callout. */ static void windivert_classify_inbound_network_v6_callout( IN const FWPS_INCOMING_VALUES0 *fixed_vals, IN const FWPS_INCOMING_METADATA_VALUES0 *meta_vals, IN OUT void *data, const FWPS_FILTER0 *filter, IN UINT64 flow_context, OUT FWPS_CLASSIFY_OUT0 *result) { UINT advance = meta_vals->ipHeaderSize; windivert_classify_callout((context_t)filter->context, WINDIVERT_DIRECTION_INBOUND, fixed_vals->incomingValue[ FWPS_FIELD_INBOUND_IPPACKET_V6_INTERFACE_INDEX].value.uint32, fixed_vals->incomingValue[ FWPS_FIELD_INBOUND_IPPACKET_V6_SUB_INTERFACE_INDEX].value.uint32, FALSE, (fixed_vals->incomingValue[ FWPS_FIELD_INBOUND_IPPACKET_V6_FLAGS].value.uint32 & FWP_CONDITION_FLAG_IS_LOOPBACK) != 0, advance, data, flow_context, result); } /* * WinDivert classify forward IPv4 callout. */ static void windivert_classify_forward_network_v4_callout( IN const FWPS_INCOMING_VALUES0 *fixed_vals, IN const FWPS_INCOMING_METADATA_VALUES0 *meta_vals, IN OUT void *data, const FWPS_FILTER0 *filter, IN UINT64 flow_context, OUT FWPS_CLASSIFY_OUT0 *result) { windivert_classify_callout((context_t)filter->context, WINDIVERT_DIRECTION_OUTBOUND, fixed_vals->incomingValue[ FWPS_FIELD_IPFORWARD_V4_DESTINATION_INTERFACE_INDEX].value.uint32, 0, TRUE, FALSE, 0, data, flow_context, result); } /* * WinDivert classify forward IPv6 callout. */ static void windivert_classify_forward_network_v6_callout( IN const FWPS_INCOMING_VALUES0 *fixed_vals, IN const FWPS_INCOMING_METADATA_VALUES0 *meta_vals, IN OUT void *data, const FWPS_FILTER0 *filter, IN UINT64 flow_context, OUT FWPS_CLASSIFY_OUT0 *result) { windivert_classify_callout((context_t)filter->context, WINDIVERT_DIRECTION_OUTBOUND, fixed_vals->incomingValue[ FWPS_FIELD_IPFORWARD_V6_DESTINATION_INTERFACE_INDEX].value.uint32, 0, FALSE, FALSE, 0, data, flow_context, result); } /* * WinDivert classify callout. */ static void windivert_classify_callout(context_t context, IN UINT8 direction, IN UINT32 if_idx, IN UINT32 sub_if_idx, IN BOOL isipv4, IN BOOL isloopback, IN UINT advance, IN OUT void *data, IN UINT64 flow_context, OUT FWPS_CLASSIFY_OUT0 *result) { KLOCK_QUEUE_HANDLE lock_handle; FWPS_PACKET_INJECTION_STATE packet_state; HANDLE packet_context; UINT32 priority, packet_priority; PNET_BUFFER_LIST buffers; PNET_BUFFER buffer, buffer_fst, buffer_itr; NDIS_TCP_IP_CHECKSUM_NET_BUFFER_LIST_INFO checksums_info; UINT8 checksums; BOOL outbound, hop; WDFOBJECT object; work_t work; PLIST_ENTRY old_entry; filter_t filter; ULONGLONG timestamp; NTSTATUS status; // Basic checks: if (!(result->rights & FWPS_RIGHT_ACTION_WRITE) || data == NULL) { return; } buffers = (PNET_BUFFER_LIST)data; buffer = NET_BUFFER_LIST_FIRST_NB(buffers); if (NET_BUFFER_LIST_NEXT_NBL(buffers) != NULL) { // This is a fragment group. This can be ignored since each fragment // should have already been indicated. result->actionType = FWP_ACTION_CONTINUE; return; } if (isipv4) { packet_state = FwpsQueryPacketInjectionState0(inject_handle, buffers, &packet_context); } else { packet_state = FwpsQueryPacketInjectionState0(injectv6_handle, buffers, &packet_context); } KeAcquireInStackQueuedSpinLock(&context->lock, &lock_handle); if (context->state != WINDIVERT_CONTEXT_STATE_OPEN) { KeReleaseInStackQueuedSpinLock(&lock_handle); result->actionType = FWP_ACTION_CONTINUE; return; } priority = context->priority; filter = context->filter; object = (WDFOBJECT)context->object; WdfObjectReference(object); KeReleaseInStackQueuedSpinLock(&lock_handle); hop = FALSE; if (packet_state == FWPS_PACKET_INJECTED_BY_SELF || packet_state == FWPS_PACKET_PREVIOUSLY_INJECTED_BY_SELF) { packet_priority = (UINT32)packet_context; if (packet_priority >= priority) { WdfObjectDereference(object); result->actionType = FWP_ACTION_CONTINUE; return; } } else if (packet_state == FWPS_PACKET_INJECTED_BY_OTHER) { // This is a packet injected by another driver, possibly an older // version of WinDivert. To prevent block-clone-reinject infinite // loops, we consider this capture to be a "hop". hop = TRUE; } timestamp = (ULONGLONG)KeQueryPerformanceCounter(NULL).QuadPart; timestamp = (timestamp > 0? timestamp-1: timestamp); // Determine which checksum fields are present or not. if (isloopback) { // Loopback packets appear to have bogus checksums, so do not trust. checksums = 0; } else if (direction == WINDIVERT_DIRECTION_OUTBOUND) { checksums_info.Value = NET_BUFFER_LIST_INFO(buffers, TcpIpChecksumNetBufferListInfo); checksums = (isipv4? 0: WINDIVERT_IP_CHECKSUM) | (checksums_info.Transmit.TcpChecksum? 0: WINDIVERT_TCP_CHECKSUM) | (checksums_info.Transmit.UdpChecksum? 0: WINDIVERT_UDP_CHECKSUM); } else { checksums = WINDIVERT_ALL_CHECKSUMS; } if (hop && isipv4) { checksums &= ~WINDIVERT_IP_CHECKSUM; } // Retreat the NET_BUFFER to the IP header, if necessary. // If (advance != 0) then this must be in the inbound path, and the // NET_BUFFER_LIST must contain exactly one NET_BUFFER. if (advance != 0) { status = NdisRetreatNetBufferDataStart(buffer, advance, 0, NULL); if (!NT_SUCCESS(status)) { WdfObjectDereference(object); result->actionType = FWP_ACTION_CONTINUE; return; } } /* * This code is complicated by the fact the a single NET_BUFFER_LIST * may contain several NET_BUFFER structures. Each NET_BUFFER needs to * be filtered independently. To achieve this we do the following: * 1) First check if any NET_BUFFER passes the filter. * 2) If no, then CONTINUE the entire NET_BUFFER_LIST. * 3) Else, split the NET_BUFFER_LIST into individual NET_BUFFERs; and * either queue or re-inject based on the filter. This step is done * out-of-band, see windivert_worker(). */ // Find the first NET_BUFFER we need to queue: buffer_fst = buffer; outbound = (direction == WINDIVERT_DIRECTION_OUTBOUND); do { BOOL match = windivert_filter(buffer_fst, if_idx, sub_if_idx, outbound, isipv4, hop, checksums, filter); if (match) { break; } buffer_fst = NET_BUFFER_NEXT_NB(buffer_fst); } while (buffer_fst != NULL); if (advance != 0) { NdisAdvanceNetBufferDataStart(buffer, advance, FALSE, NULL); } if (buffer_fst == NULL) { // No packet matches the filter; continue the entire NET_BUFFER_LIST. WdfObjectDereference(object); result->actionType = FWP_ACTION_CONTINUE; return; } // At least one packet matches the filter. Delay all further processing // until windivert_worker() at IRQL=PASSIVE_LEVEL. work = (work_t)windivert_malloc(sizeof(struct work_s), FALSE); if (work == NULL) { goto windivert_classify_callout_exit; } FwpsReferenceNetBufferList(buffers, TRUE); work->buffers = buffers; work->buffer = buffer_fst; work->advance = advance; work->is_ipv4 = isipv4; work->hop = hop; work->checksums = checksums; work->direction = direction; work->if_idx = if_idx; work->sub_if_idx = sub_if_idx; work->priority = priority; work->timestamp = timestamp; old_entry = NULL; KeAcquireInStackQueuedSpinLock(&context->lock, &lock_handle); if (context->state != WINDIVERT_CONTEXT_STATE_OPEN) { KeReleaseInStackQueuedSpinLock(&lock_handle); WdfObjectDereference(object); FwpsDereferenceNetBufferList(buffers, FALSE); windivert_free(work); result->actionType = FWP_ACTION_CONTINUE; return; } context->work_queue_length++; if (context->work_queue_length > WINDIVERT_WORK_QUEUE_LEN_MAX) { old_entry = RemoveHeadList(&context->work_queue); context->work_queue_length--; } InsertTailList(&context->work_queue, &work->entry); WdfWorkItemEnqueue(context->workers[context->worker_curr]); context->worker_curr = (context->worker_curr + 1) % WINDIVERT_CONTEXT_MAXWORKERS; KeReleaseInStackQueuedSpinLock(&lock_handle); if (old_entry != NULL) { work = CONTAINING_RECORD(old_entry, struct work_s, entry); FwpsDereferenceNetBufferList(work->buffers, FALSE); windivert_free(work); } windivert_classify_callout_exit: WdfObjectDereference(object); result->actionType = FWP_ACTION_BLOCK; result->flags |= FWPS_CLASSIFY_OUT_FLAG_ABSORB; result->rights &= ~FWPS_RIGHT_ACTION_WRITE; } /* * WinDivert work item routine for out-of-band filtering. */ VOID windivert_worker(IN WDFWORKITEM item) { KLOCK_QUEUE_HANDLE lock_handle; WDFFILEOBJECT object = (WDFFILEOBJECT)WdfWorkItemGetParentObject(item); PLIST_ENTRY entry; work_t work; context_t context = windivert_context_get(object); PNET_BUFFER_LIST buffers_clone; PNET_BUFFER buffer_itr, buffer_fst; UINT advance; BOOL match, ok, outbound, sniff_mode, forward; filter_t filter; NTSTATUS status; KeAcquireInStackQueuedSpinLock(&context->lock, &lock_handle); if (context->state == WINDIVERT_CONTEXT_STATE_OPEN) { sniff_mode = ((context->flags & WINDIVERT_FLAG_SNIFF) != 0); forward = (context->layer == WINDIVERT_LAYER_NETWORK_FORWARD); filter = context->filter; } while (context->state == WINDIVERT_CONTEXT_STATE_OPEN && !IsListEmpty(&context->work_queue)) { entry = RemoveHeadList(&context->work_queue); context->work_queue_length--; KeReleaseInStackQueuedSpinLock(&lock_handle); work = CONTAINING_RECORD(entry, struct work_s, entry); advance = work->advance; buffer_fst = work->buffer; if (advance != 0) { status = NdisRetreatNetBufferDataStart(buffer_fst, advance, 0, NULL); if (!NT_SUCCESS(status)) { advance = 0; goto windivert_worker_complete; } } if (!sniff_mode) { // In non-SNIFF mode, reinject all non-matching packets up to the // first matching packet. buffer_itr = NET_BUFFER_LIST_FIRST_NB(work->buffers); while (buffer_itr != buffer_fst) { ok = windivert_reinject_packet(sniff_mode, forward, work->direction, work->is_ipv4, work->if_idx, work->sub_if_idx, work->priority, work->buffers, buffer_itr); if (!ok) { goto windivert_worker_complete; } buffer_itr = NET_BUFFER_NEXT_NB(buffer_itr); } } else { // In SNIFF mode, reinject the entire NET_BUFFER_LIST. ok = windivert_reinject_packet(sniff_mode, forward, work->direction, work->is_ipv4, work->if_idx, work->sub_if_idx, work->priority, work->buffers, NULL); if (!ok) { goto windivert_worker_complete; } buffer_itr = buffer_fst; } // Queue the first matching packet. ok = windivert_queue_packet(context, buffer_itr, work->direction, work->if_idx, work->sub_if_idx, work->hop, work->checksums, work->timestamp); if (!ok) { goto windivert_worker_complete; } buffer_itr = NET_BUFFER_NEXT_NB(buffer_itr); // Queue or re-inject all remaining packets. outbound = (work->direction == WINDIVERT_DIRECTION_OUTBOUND); while (buffer_itr != NULL) { match = windivert_filter(buffer_itr, work->if_idx, work->sub_if_idx, outbound, work->is_ipv4, work->hop, work->checksums, filter); if (match) { ok = windivert_queue_packet(context, buffer_itr, work->direction, work->if_idx, work->sub_if_idx, work->hop, work->checksums, work->timestamp); } else { ok = windivert_reinject_packet(sniff_mode, forward, work->direction, work->is_ipv4, work->if_idx, work->sub_if_idx, work->priority, work->buffers, buffer_itr); } if (!ok) { goto windivert_worker_complete; } buffer_itr = NET_BUFFER_NEXT_NB(buffer_itr); } windivert_worker_complete: if (advance != 0) { NdisAdvanceNetBufferDataStart(work->buffer, advance, 0, 0); } FwpsDereferenceNetBufferList(work->buffers, FALSE); windivert_free(work); KeAcquireInStackQueuedSpinLock(&context->lock, &lock_handle); } KeReleaseInStackQueuedSpinLock(&lock_handle); } /* * Queue a NET_BUFFER. */ static BOOL windivert_queue_packet(context_t context, PNET_BUFFER buffer, UINT8 direction, UINT32 if_idx, UINT32 sub_if_idx, BOOL hop, UINT8 checksums, ULONGLONG timestamp0) { KLOCK_QUEUE_HANDLE lock_handle; PVOID data; WDFREQUEST request; PLIST_ENTRY entry, old_entry; packet_t packet, old_packet; UINT data_len; ULONGLONG timestamp; BOOL timeout; NTSTATUS status; // First we attempt to immediately service a read request directly without // queuing the packet. This helps reduce overhead where possible. timeout = FALSE; request = NULL; timestamp = (ULONGLONG)KeQueryPerformanceCounter(NULL).QuadPart; KeAcquireInStackQueuedSpinLock(&context->lock, &lock_handle); if (context->state != WINDIVERT_CONTEXT_STATE_OPEN) { KeReleaseInStackQueuedSpinLock(&lock_handle); return FALSE; } if ((context->flags & WINDIVERT_FLAG_DROP) != 0) { KeReleaseInStackQueuedSpinLock(&lock_handle); return TRUE; } timeout = (timestamp - timestamp0 > context->packet_queue_maxcounts); if (!timeout && IsListEmpty(&context->packet_queue)) { status = WdfIoQueueRetrieveNextRequest(context->read_queue, &request); if (!NT_SUCCESS(status)) { request = NULL; } } KeReleaseInStackQueuedSpinLock(&lock_handle); if (timeout) { return TRUE; } if (request != NULL) { // FAST PATH: Service an I/O request without queueing the packet. windivert_read_service_request(NULL, buffer, direction, if_idx, sub_if_idx, hop, checksums, request); return TRUE; } // SLOW PATH: queue the packet. data_len = NET_BUFFER_DATA_LENGTH(buffer); packet = (packet_t)windivert_malloc(sizeof(struct packet_s), FALSE); if (packet == NULL) { return FALSE; } packet->data = windivert_malloc(data_len, TRUE); if (packet->data == NULL) { windivert_free_packet(packet); return FALSE; } packet->data_len = data_len; data = NdisGetDataBuffer(buffer, data_len, NULL, 1, 0); if (data == NULL) { NdisGetDataBuffer(buffer, data_len, packet->data, 1, 0); } else { RtlCopyMemory(packet->data, data, data_len); } packet->hop = hop; packet->checksums = checksums; packet->direction = direction; packet->if_idx = if_idx; packet->sub_if_idx = sub_if_idx; packet->timestamp = timestamp0; entry = &packet->entry; timestamp = (ULONGLONG)KeQueryPerformanceCounter(NULL).QuadPart; KeAcquireInStackQueuedSpinLock(&context->lock, &lock_handle); while (TRUE) { if (context->state != WINDIVERT_CONTEXT_STATE_OPEN) { KeReleaseInStackQueuedSpinLock(&lock_handle); windivert_free_packet(packet); return FALSE; } if (data_len > context->packet_queue_maxsize) { // (Corner case) the packet is larger than the max queue size: KeReleaseInStackQueuedSpinLock(&lock_handle); windivert_free_packet(packet); return TRUE; } timeout = (timestamp - packet->timestamp > context->packet_queue_maxcounts); if (timeout) { // (Corner case) the packet has already expired: KeReleaseInStackQueuedSpinLock(&lock_handle); windivert_free_packet(packet); return TRUE; } if (context->packet_queue_size + data_len > context->packet_queue_maxsize || context->packet_queue_length + 1 > context->packet_queue_maxlength) { // The queue is full; drop a packet & try again: old_entry = RemoveHeadList(&context->packet_queue); old_packet = CONTAINING_RECORD(old_entry, struct packet_s, entry); context->packet_queue_length--; context->packet_queue_size -= old_packet->data_len; KeReleaseInStackQueuedSpinLock(&lock_handle); DEBUG("DROP: packet queue is full, dropping packet"); windivert_free_packet(old_packet); timestamp = (ULONGLONG)KeQueryPerformanceCounter(NULL).QuadPart; KeAcquireInStackQueuedSpinLock(&context->lock, &lock_handle); continue; } else { // Queue the packet: InsertTailList(&context->packet_queue, entry); context->packet_queue_length++; context->packet_queue_size += data_len; break; } } KeReleaseInStackQueuedSpinLock(&lock_handle); DEBUG("PACKET: diverting packet (packet=%p)", packet); // Service any pending I/O request. windivert_read_service(context); return TRUE; } /* * Re-inject a packet or packets. */ static BOOL windivert_reinject_packet(BOOL sniff_mode, BOOL forward, UINT8 direction, BOOL isipv4, UINT32 if_idx, UINT32 sub_if_idx, UINT32 priority, PNET_BUFFER_LIST buffers, PNET_BUFFER buffer) { PNET_BUFFER_LIST buffers_cpy; HANDLE handle; BOOL clone = FALSE; FWPS_INJECT_COMPLETE0 completion = windivert_reinject_complete; NTSTATUS status; if (buffer != NULL) { // Re-inject a specific packet. Not applicable for SNIFF mode. if (sniff_mode) { return TRUE; } status = FwpsAllocateNetBufferAndNetBufferList0(nbl_pool_handle, 0, 0, NET_BUFFER_FIRST_MDL(buffer), NET_BUFFER_DATA_OFFSET(buffer), NET_BUFFER_DATA_LENGTH(buffer), &buffers_cpy); if (!NT_SUCCESS(status)) { DEBUG_ERROR("failed to create NET_BUFFER_LIST for injected packet", status); return FALSE; } if (forward || direction == WINDIVERT_DIRECTION_OUTBOUND) { NdisCopySendNetBufferListInfo(buffers_cpy, buffers); } else { NdisCopyReceiveNetBufferListInfo(buffers_cpy, buffers); } } else { // Re-inject all packets for SNIFF mode. status = FwpsAllocateCloneNetBufferList0(buffers, nbl_pool_handle, nb_pool_handle, 0, &buffers_cpy); if (!NT_SUCCESS(status)) { DEBUG_ERROR("failed to clone NET_BUFFER_LIST for injected packets", status); return FALSE; } clone = TRUE; completion = windivert_reinject_clone_complete; } handle = (isipv4? inject_handle: injectv6_handle); FwpsReferenceNetBufferList(buffers, TRUE); if (forward) { status = FwpsInjectForwardAsync0(handle, (HANDLE)priority, 0, (isipv4? AF_INET: AF_INET6), UNSPECIFIED_COMPARTMENT_ID, if_idx, buffers_cpy, completion, (HANDLE)buffers); } else if (direction == WINDIVERT_DIRECTION_OUTBOUND) { status = FwpsInjectNetworkSendAsync0(handle, (HANDLE)priority, 0, UNSPECIFIED_COMPARTMENT_ID, buffers_cpy, completion, (HANDLE)buffers); } else { status = FwpsInjectNetworkReceiveAsync0(handle, (HANDLE)priority, 0, UNSPECIFIED_COMPARTMENT_ID, if_idx, sub_if_idx, buffers_cpy, completion, (HANDLE)buffers); } if (!NT_SUCCESS(status)) { DEBUG_ERROR("failed to (re)inject packet(s)", status); if (clone) { FwpsFreeNetBufferList0(buffers_cpy); } else { FwpsFreeCloneNetBufferList0(buffers_cpy, 0); } FwpsDereferenceNetBufferList(buffers, FALSE); } return TRUE; } /* * WinDivert (re)inject complete. */ static void NTAPI windivert_reinject_complete(VOID *context, NET_BUFFER_LIST *buffers_cpy, BOOLEAN dispatch_level) { PNET_BUFFER_LIST buffers = (PNET_BUFFER_LIST)context; FwpsFreeNetBufferList0(buffers_cpy); FwpsDereferenceNetBufferList(buffers, dispatch_level); } /* * WinDivert (re)inject complete. */ static void NTAPI windivert_reinject_clone_complete(VOID *context, NET_BUFFER_LIST *buffers_cpy, BOOLEAN dispatch_level) { PNET_BUFFER_LIST buffers = (PNET_BUFFER_LIST)context; FwpsFreeCloneNetBufferList0(buffers_cpy, 0); FwpsDereferenceNetBufferList(buffers, dispatch_level); } /* * Free a packet. */ static void windivert_free_packet(packet_t packet) { windivert_free(packet->data); windivert_free(packet); } /* * Skip well-known IPv6 extension headers. */ static UINT8 windivert_skip_headers(UINT8 proto, UINT8 **header, size_t *len) { size_t hdrlen; while (TRUE) { if (*len <= 2) { return IPPROTO_NONE; } hdrlen = (size_t)*(*header + 1); switch (proto) { case IPPROTO_FRAGMENT: hdrlen = 8; break; case IPPROTO_AH: hdrlen += 2; hdrlen *= 4; break; case IPPROTO_HOPOPTS: case IPPROTO_DSTOPTS: case IPPROTO_ROUTING: hdrlen++; hdrlen *= 8; break; case IPPROTO_NONE: return proto; default: return proto; } if (hdrlen >= *len) { return IPPROTO_NONE; } proto = **header; *header += hdrlen; *len -= hdrlen; } } /* * Zero the IP/TCP/UDP checksums and/or decrement the TTL (if required) */ static NTSTATUS windivert_finalize_packet(void *header, size_t len, BOOL hop, UINT8 checksums) { struct iphdr *ip_header = (struct iphdr *)header; struct ipv6hdr *ipv6_header = (struct ipv6hdr *)header; size_t ip_header_len, trans_len; void *trans_header; struct tcphdr *tcp_header; struct udphdr *udp_header; UINT8 proto; NTSTATUS status = STATUS_SUCCESS; if (checksums == WINDIVERT_ALL_CHECKSUMS && !hop) { return status; } if (len < sizeof(struct iphdr)) { return status; } switch (ip_header->Version) { case 4: ip_header_len = ip_header->HdrLength*sizeof(UINT32); if (len < ip_header_len) { return status; } if (hop) { if (ip_header->TTL <= 1) { status = STATUS_HOPLIMIT_EXCEEDED; } if (ip_header->TTL != 0) { ip_header->TTL--; } } if ((checksums & WINDIVERT_IP_CHECKSUM) != 0) { ip_header->Checksum = 0; } proto = ip_header->Protocol; trans_len = len - ip_header_len; trans_header = (UINT8 *)ip_header + ip_header_len; break; case 6: if (len < sizeof(struct ipv6hdr)) { return status; } if (hop) { if (ipv6_header->HopLimit <= 1) { status = STATUS_HOPLIMIT_EXCEEDED; } if (ipv6_header->HopLimit != 0) { ipv6_header->HopLimit--; } } trans_len = len - sizeof(struct ipv6hdr); trans_header = (UINT8 *)(ipv6_header + 1); // Skip extension headers: proto = windivert_skip_headers(ipv6_header->NextHdr, (UINT8 **)&trans_header, &trans_len); break; default: return status; } switch (proto) { case IPPROTO_TCP: if ((checksums & WINDIVERT_TCP_CHECKSUM) != 0) { return status; } tcp_header = (struct tcphdr *)trans_header; if (trans_len < sizeof(struct tcphdr)) { return status; } tcp_header->Checksum = 0; break; case IPPROTO_UDP: if ((checksums & WINDIVERT_UDP_CHECKSUM) != 0) { return status; } udp_header = (struct udphdr *)trans_header; if (trans_len < sizeof(struct udphdr)) { return status; } udp_header->Checksum = 0; break; } return status; } /* * Big number comparison. */ static int windivert_big_num_compare(const UINT32 *a, const UINT32 *b) { if (a[3] < b[3]) { return -1; } if (a[3] > b[3]) { return 1; } if (a[2] < b[2]) { return -1; } if (a[2] > b[2]) { return 1; } if (a[1] < b[1]) { return -1; } if (a[1] > b[1]) { return 1; } if (a[0] < b[0]) { return -1; } if (a[0] > b[0]) { return 1; } return 0; } /* * Checks if the given packet is of interest. */ static BOOL windivert_filter(PNET_BUFFER buffer, UINT32 if_idx, UINT32 sub_if_idx, BOOL outbound, BOOL isipv4, BOOL hop, UINT8 checksums, filter_t filter) { size_t tot_len, ip_header_len; struct iphdr *ip_header = NULL; struct ipv6hdr *ipv6_header = NULL; struct icmphdr *icmp_header = NULL; struct icmpv6hdr *icmpv6_header = NULL; struct tcphdr *tcp_header = NULL; struct udphdr *udp_header = NULL; UINT16 ip, ttl; UINT8 proto; NTSTATUS status; // Parse the headers: tot_len = NET_BUFFER_DATA_LENGTH(buffer); if (tot_len < sizeof(struct iphdr)) { DEBUG("FILTER: REJECT (packet length too small)"); return FALSE; } // Get the IP header. if (isipv4) { // IPv4: if (tot_len < sizeof(struct iphdr)) { DEBUG("FILTER: REJECT (packet length too small)"); return FALSE; } ip_header = (struct iphdr *)NdisGetDataBuffer(buffer, sizeof(struct iphdr), NULL, 1, 0); if (ip_header == NULL) { DEBUG("FILTER: REJECT (failed to get IPv4 header)"); return FALSE; } ip_header_len = ip_header->HdrLength*sizeof(UINT32); if (ip_header->Version != 4 || RtlUshortByteSwap(ip_header->Length) != tot_len || ip_header->HdrLength < 5 || ip_header_len > tot_len) { DEBUG("FILTER: REJECT (bad IPv4 packet)"); return FALSE; } proto = ip_header->Protocol; NdisAdvanceNetBufferDataStart(buffer, ip_header_len, FALSE, NULL); } else { // IPv6: if (tot_len < sizeof(struct ipv6hdr)) { DEBUG("FILTER: REJECT (packet length too small)"); return FALSE; } ipv6_header = (struct ipv6hdr *)NdisGetDataBuffer(buffer, sizeof(struct ipv6hdr), NULL, 1, 0); if (ipv6_header == NULL) { DEBUG("FILTER: REJECT (failed to get IPv6 header)"); return FALSE; } ip_header_len = sizeof(struct ipv6hdr); if (ipv6_header->Version != 6 || ip_header_len > tot_len || RtlUshortByteSwap(ipv6_header->Length) + sizeof(struct ipv6hdr) != tot_len) { DEBUG("FILTER: REJECT (bad IPv6 packet)"); return FALSE; } proto = ipv6_header->NextHdr; NdisAdvanceNetBufferDataStart(buffer, ip_header_len, FALSE, NULL); // Skip extension headers: while (TRUE) { UINT8 *ext_header; size_t ext_header_len; BOOL isexthdr = TRUE; ext_header = (UINT8 *)NdisGetDataBuffer(buffer, 2, NULL, 1, 0); if (ext_header == NULL) { break; } ext_header_len = (size_t)ext_header[1]; switch (proto) { case IPPROTO_FRAGMENT: ext_header_len = 8; break; case IPPROTO_AH: ext_header_len += 2; ext_header_len *= 4; break; case IPPROTO_HOPOPTS: case IPPROTO_DSTOPTS: case IPPROTO_ROUTING: ext_header_len++; ext_header_len *= 8; break; default: isexthdr = FALSE; break; } if (!isexthdr) { break; } proto = ext_header[0]; ip_header_len += ext_header_len; NdisAdvanceNetBufferDataStart(buffer, ext_header_len, FALSE, NULL); } } switch (proto) { case IPPROTO_ICMP: icmp_header = (struct icmphdr *)NdisGetDataBuffer(buffer, sizeof(struct icmphdr), NULL, 1, 0); break; case IPPROTO_ICMPV6: icmpv6_header = (struct icmpv6hdr *)NdisGetDataBuffer(buffer, sizeof(struct icmpv6hdr), NULL, 1, 0); break; case IPPROTO_TCP: tcp_header = (struct tcphdr *)NdisGetDataBuffer(buffer, sizeof(struct tcphdr), NULL, 1, 0); break; case IPPROTO_UDP: udp_header = (struct udphdr *)NdisGetDataBuffer(buffer, sizeof(struct udphdr), NULL, 1, 0); break; default: break; } status = NdisRetreatNetBufferDataStart(buffer, ip_header_len, 0, NULL); if (!NT_SUCCESS(status)) { // Should never occur. DEBUG("FILTER: REJECT (failed to retreat buffer)"); return FALSE; } // Execute the filter: ip = 0; ttl = WINDIVERT_FILTER_MAXLEN+1; // Additional safety while (ttl-- != 0) { BOOL result; int cmp; UINT32 field[4]; field[1] = 0; field[2] = 0; field[3] = 0; switch (filter[ip].protocol) { case WINDIVERT_FILTER_PROTOCOL_NONE: result = TRUE; break; case WINDIVERT_FILTER_PROTOCOL_IP: result = (ip_header != NULL); break; case WINDIVERT_FILTER_PROTOCOL_IPV6: result = (ipv6_header != NULL); break; case WINDIVERT_FILTER_PROTOCOL_ICMP: result = (icmp_header != NULL); break; case WINDIVERT_FILTER_PROTOCOL_ICMPV6: result = (icmpv6_header != NULL); break; case WINDIVERT_FILTER_PROTOCOL_TCP: result = (tcp_header != NULL); break; case WINDIVERT_FILTER_PROTOCOL_UDP: result = (udp_header != NULL); break; default: result = FALSE; break; } if (result) { switch (filter[ip].field) { case WINDIVERT_FILTER_FIELD_ZERO: field[0] = 0; break; case WINDIVERT_FILTER_FIELD_INBOUND: field[0] = (UINT32)(!outbound); break; case WINDIVERT_FILTER_FIELD_OUTBOUND: field[0] = (UINT32)outbound; break; case WINDIVERT_FILTER_FIELD_IFIDX: field[0] = (UINT32)if_idx; break; case WINDIVERT_FILTER_FIELD_SUBIFIDX: field[0] = (UINT32)sub_if_idx; break; case WINDIVERT_FILTER_FIELD_IP: field[0] = (UINT32)(ip_header != NULL); break; case WINDIVERT_FILTER_FIELD_IPV6: field[0] = (UINT32)(ipv6_header != NULL); break; case WINDIVERT_FILTER_FIELD_ICMP: field[0] = (UINT32)(icmp_header != NULL); break; case WINDIVERT_FILTER_FIELD_ICMPV6: field[0] = (UINT32)(icmpv6_header != NULL); break; case WINDIVERT_FILTER_FIELD_TCP: field[0] = (UINT32)(tcp_header != NULL); break; case WINDIVERT_FILTER_FIELD_UDP: field[0] = (UINT32)(udp_header != NULL); break; case WINDIVERT_FILTER_FIELD_IP_HDRLENGTH: field[0] = (UINT32)ip_header->HdrLength; break; case WINDIVERT_FILTER_FIELD_IP_TOS: field[0] = (UINT32)ip_header->TOS; break; case WINDIVERT_FILTER_FIELD_IP_LENGTH: field[0] = (UINT32)RtlUshortByteSwap(ip_header->Length); break; case WINDIVERT_FILTER_FIELD_IP_ID: field[0] = (UINT32)RtlUshortByteSwap(ip_header->Id); break; case WINDIVERT_FILTER_FIELD_IP_DF: field[0] = (UINT32)IPHDR_GET_DF(ip_header); break; case WINDIVERT_FILTER_FIELD_IP_MF: field[0] = (UINT32)IPHDR_GET_MF(ip_header); break; case WINDIVERT_FILTER_FIELD_IP_FRAGOFF: field[0] = (UINT32)RtlUshortByteSwap( IPHDR_GET_FRAGOFF(ip_header)); break; case WINDIVERT_FILTER_FIELD_IP_TTL: field[0] = (UINT32)ip_header->TTL; if (hop) { field[0] = (field[0] == 0? 0: field[0]-1); } break; case WINDIVERT_FILTER_FIELD_IP_PROTOCOL: field[0] = (UINT32)ip_header->Protocol; break; case WINDIVERT_FILTER_FIELD_IP_CHECKSUM: if ((checksums & WINDIVERT_IP_CHECKSUM) != 0) { field[0] = (UINT32)RtlUshortByteSwap(ip_header->Checksum); } else { field[0] = 0; } break; case WINDIVERT_FILTER_FIELD_IP_SRCADDR: field[0] = (UINT32)RtlUlongByteSwap(ip_header->SrcAddr); break; case WINDIVERT_FILTER_FIELD_IP_DSTADDR: field[0] = (UINT32)RtlUlongByteSwap(ip_header->DstAddr); break; case WINDIVERT_FILTER_FIELD_IPV6_TRAFFICCLASS: field[0] = (UINT32)IPV6HDR_GET_TRAFFICCLASS(ipv6_header); break; case WINDIVERT_FILTER_FIELD_IPV6_FLOWLABEL: field[0] = (UINT32)RtlUlongByteSwap( IPV6HDR_GET_FLOWLABEL(ipv6_header)); break; case WINDIVERT_FILTER_FIELD_IPV6_LENGTH: field[0] = (UINT32)RtlUshortByteSwap(ipv6_header->Length); break; case WINDIVERT_FILTER_FIELD_IPV6_NEXTHDR: field[0] = (UINT32)ipv6_header->NextHdr; break; case WINDIVERT_FILTER_FIELD_IPV6_HOPLIMIT: field[0] = (UINT32)ipv6_header->HopLimit; if (hop) { field[0] = (field[0] == 0? 0: field[0]-1); } break; case WINDIVERT_FILTER_FIELD_IPV6_SRCADDR: field[3] = (UINT32)RtlUlongByteSwap(ipv6_header->SrcAddr[0]); field[2] = (UINT32)RtlUlongByteSwap(ipv6_header->SrcAddr[1]); field[1] = (UINT32)RtlUlongByteSwap(ipv6_header->SrcAddr[2]); field[0] = (UINT32)RtlUlongByteSwap(ipv6_header->SrcAddr[3]); break; case WINDIVERT_FILTER_FIELD_IPV6_DSTADDR: field[3] = (UINT32)RtlUlongByteSwap(ipv6_header->DstAddr[0]); field[2] = (UINT32)RtlUlongByteSwap(ipv6_header->DstAddr[1]); field[1] = (UINT32)RtlUlongByteSwap(ipv6_header->DstAddr[2]); field[0] = (UINT32)RtlUlongByteSwap(ipv6_header->DstAddr[3]); break; case WINDIVERT_FILTER_FIELD_ICMP_TYPE: field[0] = (UINT32)icmp_header->Type; break; case WINDIVERT_FILTER_FIELD_ICMP_CODE: field[0] = (UINT32)icmp_header->Code; break; case WINDIVERT_FILTER_FIELD_ICMP_CHECKSUM: field[0] = (UINT32)RtlUshortByteSwap(icmp_header->Checksum); break; case WINDIVERT_FILTER_FIELD_ICMP_BODY: field[0] = (UINT32)RtlUlongByteSwap(icmp_header->Body); break; case WINDIVERT_FILTER_FIELD_ICMPV6_TYPE: field[0] = (UINT32)icmpv6_header->Type; break; case WINDIVERT_FILTER_FIELD_ICMPV6_CODE: field[0] = (UINT32)icmpv6_header->Code; break; case WINDIVERT_FILTER_FIELD_ICMPV6_CHECKSUM: field[0] = (UINT32)RtlUshortByteSwap(icmpv6_header->Checksum); break; case WINDIVERT_FILTER_FIELD_ICMPV6_BODY: field[0] = (UINT32)RtlUlongByteSwap(icmpv6_header->Body); break; case WINDIVERT_FILTER_FIELD_TCP_SRCPORT: field[0] = (UINT32)RtlUshortByteSwap(tcp_header->SrcPort); break; case WINDIVERT_FILTER_FIELD_TCP_DSTPORT: field[0] = (UINT32)RtlUshortByteSwap(tcp_header->DstPort); break; case WINDIVERT_FILTER_FIELD_TCP_SEQNUM: field[0] = (UINT32)RtlUlongByteSwap(tcp_header->SeqNum); break; case WINDIVERT_FILTER_FIELD_TCP_ACKNUM: field[0] = (UINT32)RtlUlongByteSwap(tcp_header->AckNum); break; case WINDIVERT_FILTER_FIELD_TCP_HDRLENGTH: field[0] = (UINT32)tcp_header->HdrLength; break; case WINDIVERT_FILTER_FIELD_TCP_URG: field[0] = (UINT32)tcp_header->Urg; break; case WINDIVERT_FILTER_FIELD_TCP_ACK: field[0] = (UINT32)tcp_header->Ack; break; case WINDIVERT_FILTER_FIELD_TCP_PSH: field[0] = (UINT32)tcp_header->Psh; break; case WINDIVERT_FILTER_FIELD_TCP_RST: field[0] = (UINT32)tcp_header->Rst; break; case WINDIVERT_FILTER_FIELD_TCP_SYN: field[0] = (UINT32)tcp_header->Syn; break; case WINDIVERT_FILTER_FIELD_TCP_FIN: field[0] = (UINT32)tcp_header->Fin; break; case WINDIVERT_FILTER_FIELD_TCP_WINDOW: field[0] = (UINT32)RtlUshortByteSwap(tcp_header->Window); break; case WINDIVERT_FILTER_FIELD_TCP_CHECKSUM: if ((checksums & WINDIVERT_TCP_CHECKSUM) != 0) { field[0] = (UINT32)RtlUshortByteSwap(tcp_header->Checksum); } else { field[0] = 0; } break; case WINDIVERT_FILTER_FIELD_TCP_URGPTR: field[0] = (UINT32)RtlUshortByteSwap(tcp_header->UrgPtr); break; case WINDIVERT_FILTER_FIELD_TCP_PAYLOADLENGTH: field[0] = (UINT32)(tot_len - ip_header_len - tcp_header->HdrLength*sizeof(UINT32)); break; case WINDIVERT_FILTER_FIELD_UDP_SRCPORT: field[0] = (UINT32)RtlUshortByteSwap(udp_header->SrcPort); break; case WINDIVERT_FILTER_FIELD_UDP_DSTPORT: field[0] = (UINT32)RtlUshortByteSwap(udp_header->DstPort); break; case WINDIVERT_FILTER_FIELD_UDP_LENGTH: field[0] = (UINT32)RtlUshortByteSwap(udp_header->Length); break; case WINDIVERT_FILTER_FIELD_UDP_CHECKSUM: if ((checksums & WINDIVERT_UDP_CHECKSUM) != 0) { field[0] = (UINT32)RtlUshortByteSwap(udp_header->Checksum); } else { field[0] = 0; } break; case WINDIVERT_FILTER_FIELD_UDP_PAYLOADLENGTH: field[0] = (UINT32)(tot_len - ip_header_len - sizeof(struct udphdr)); break; default: field[0] = 0; break; } cmp = windivert_big_num_compare(field, filter[ip].arg); switch (filter[ip].test) { case WINDIVERT_FILTER_TEST_EQ: result = (cmp == 0); break; case WINDIVERT_FILTER_TEST_NEQ: result = (cmp != 0); break; case WINDIVERT_FILTER_TEST_LT: result = (cmp < 0); break; case WINDIVERT_FILTER_TEST_LEQ: result = (cmp <= 0); break; case WINDIVERT_FILTER_TEST_GT: result = (cmp > 0); break; case WINDIVERT_FILTER_TEST_GEQ: result = (cmp >= 0); break; default: result = FALSE; break; } } ip = (result? filter[ip].success: filter[ip].failure); if (ip == WINDIVERT_FILTER_RESULT_ACCEPT) { return TRUE; } if (ip == WINDIVERT_FILTER_RESULT_REJECT) { return FALSE; } } DEBUG("FILTER: REJECT (filter TTL exceeded)"); return FALSE; } /* * Analyze the given filter. */ static void windivert_filter_analyze(filter_t filter, BOOL *is_inbound, BOOL *is_outbound, BOOL *is_ipv4, BOOL *is_ipv6) { BOOL result; // False filter? result = windivert_filter_test(filter, 0, WINDIVERT_FILTER_PROTOCOL_NONE, WINDIVERT_FILTER_FIELD_ZERO, 0); if (!result) { *is_inbound = FALSE; *is_outbound = FALSE; *is_ipv4 = FALSE; *is_ipv6 = FALSE; return; } // Inbound? result = windivert_filter_test(filter, 0, WINDIVERT_FILTER_PROTOCOL_NONE, WINDIVERT_FILTER_FIELD_INBOUND, 1); if (result) { result = windivert_filter_test(filter, 0, WINDIVERT_FILTER_PROTOCOL_NONE, WINDIVERT_FILTER_FIELD_OUTBOUND, 0); } *is_inbound = result; // Outbound? result = windivert_filter_test(filter, 0, WINDIVERT_FILTER_PROTOCOL_NONE, WINDIVERT_FILTER_FIELD_OUTBOUND, 1); if (result) { result = windivert_filter_test(filter, 0, WINDIVERT_FILTER_PROTOCOL_NONE, WINDIVERT_FILTER_FIELD_INBOUND, 0); } *is_outbound = result; // IPv4? result = windivert_filter_test(filter, 0, WINDIVERT_FILTER_PROTOCOL_NONE, WINDIVERT_FILTER_FIELD_IP, 1); if (result) { result = windivert_filter_test(filter, 0, WINDIVERT_FILTER_PROTOCOL_NONE, WINDIVERT_FILTER_FIELD_IPV6, 0); } *is_ipv4 = result; // Ipv6? result = windivert_filter_test(filter, 0, WINDIVERT_FILTER_PROTOCOL_NONE, WINDIVERT_FILTER_FIELD_IPV6, 1); if (result) { result = windivert_filter_test(filter, 0, WINDIVERT_FILTER_PROTOCOL_NONE, WINDIVERT_FILTER_FIELD_IP, 0); } *is_ipv6 = result; } /* * Test a filter for any packet where field = arg. */ static BOOL windivert_filter_test(filter_t filter, UINT16 ip, UINT8 protocol, UINT8 field, UINT32 arg) { BOOL known = FALSE; BOOL result = FALSE; if (ip == WINDIVERT_FILTER_RESULT_ACCEPT) { return TRUE; } if (ip == WINDIVERT_FILTER_RESULT_REJECT) { return FALSE; } if (ip > WINDIVERT_FILTER_MAXLEN) { return FALSE; } if (filter[ip].protocol == protocol && filter[ip].field == field) { known = TRUE; switch (filter[ip].test) { case WINDIVERT_FILTER_TEST_EQ: result = (arg == filter[ip].arg[0]); break; case WINDIVERT_FILTER_TEST_NEQ: result = (arg != filter[ip].arg[0]); break; case WINDIVERT_FILTER_TEST_LT: result = (arg < filter[ip].arg[0]); break; case WINDIVERT_FILTER_TEST_LEQ: result = (arg <= filter[ip].arg[0]); break; case WINDIVERT_FILTER_TEST_GT: result = (arg > filter[ip].arg[0]); break; case WINDIVERT_FILTER_TEST_GEQ: result = (arg >= filter[ip].arg[0]); break; default: result = FALSE; break; } } if (!known) { result = windivert_filter_test(filter, filter[ip].success, protocol, field, arg); if (result) { return TRUE; } return windivert_filter_test(filter, filter[ip].failure, protocol, field, arg); } else { ip = (result? filter[ip].success: filter[ip].failure); return windivert_filter_test(filter, ip, protocol, field, arg); } } /* * Compile a WinDivert filter from an IOCTL. */ static filter_t windivert_filter_compile(windivert_ioctl_filter_t ioctl_filter, size_t ioctl_filter_len) { filter_t filter0 = NULL, result = NULL; UINT16 i; size_t length; if (ioctl_filter_len % sizeof(struct windivert_ioctl_filter_s) != 0) { goto windivert_filter_compile_exit; } length = ioctl_filter_len / sizeof(struct windivert_ioctl_filter_s); if (length >= WINDIVERT_FILTER_MAXLEN) { goto windivert_filter_compile_exit; } // Do NOT use the stack (size = 12Kb on x86) for filter0. filter0 = (filter_t)windivert_malloc( WINDIVERT_FILTER_MAXLEN*sizeof(struct filter_s), TRUE); if (filter0 == NULL) { goto windivert_filter_compile_exit; } for (i = 0; i < length; i++) { if (ioctl_filter[i].field > WINDIVERT_FILTER_FIELD_MAX || ioctl_filter[i].test > WINDIVERT_FILTER_TEST_MAX) { goto windivert_filter_compile_exit; } switch (ioctl_filter[i].success) { case WINDIVERT_FILTER_RESULT_ACCEPT: case WINDIVERT_FILTER_RESULT_REJECT: break; default: if (ioctl_filter[i].success <= i || ioctl_filter[i].success >= length) { goto windivert_filter_compile_exit; } break; } switch (ioctl_filter[i].failure) { case WINDIVERT_FILTER_RESULT_ACCEPT: case WINDIVERT_FILTER_RESULT_REJECT: break; default: if (ioctl_filter[i].failure <= i || ioctl_filter[i].failure >= length) { goto windivert_filter_compile_exit; } break; } // Enforce size limits: if (ioctl_filter[i].field != WINDIVERT_FILTER_FIELD_IPV6_SRCADDR && ioctl_filter[i].field != WINDIVERT_FILTER_FIELD_IPV6_DSTADDR) { if (ioctl_filter[i].arg[1] != 0 || ioctl_filter[i].arg[2] != 0 || ioctl_filter[i].arg[3] != 0) { goto windivert_filter_compile_exit; } } switch (ioctl_filter[i].field) { case WINDIVERT_FILTER_FIELD_ZERO: case WINDIVERT_FILTER_FIELD_INBOUND: case WINDIVERT_FILTER_FIELD_OUTBOUND: case WINDIVERT_FILTER_FIELD_IP: case WINDIVERT_FILTER_FIELD_IPV6: case WINDIVERT_FILTER_FIELD_ICMP: case WINDIVERT_FILTER_FIELD_ICMPV6: case WINDIVERT_FILTER_FIELD_TCP: case WINDIVERT_FILTER_FIELD_UDP: case WINDIVERT_FILTER_FIELD_IP_DF: case WINDIVERT_FILTER_FIELD_IP_MF: case WINDIVERT_FILTER_FIELD_TCP_URG: case WINDIVERT_FILTER_FIELD_TCP_ACK: case WINDIVERT_FILTER_FIELD_TCP_PSH: case WINDIVERT_FILTER_FIELD_TCP_RST: case WINDIVERT_FILTER_FIELD_TCP_SYN: case WINDIVERT_FILTER_FIELD_TCP_FIN: if (ioctl_filter[i].arg[0] > 1) { goto windivert_filter_compile_exit; } break; case WINDIVERT_FILTER_FIELD_IP_HDRLENGTH: case WINDIVERT_FILTER_FIELD_TCP_HDRLENGTH: if (ioctl_filter[i].arg[0] > 0x0F) { goto windivert_filter_compile_exit; } break; case WINDIVERT_FILTER_FIELD_IP_TOS: case WINDIVERT_FILTER_FIELD_IP_TTL: case WINDIVERT_FILTER_FIELD_IP_PROTOCOL: case WINDIVERT_FILTER_FIELD_IPV6_TRAFFICCLASS: case WINDIVERT_FILTER_FIELD_IPV6_NEXTHDR: case WINDIVERT_FILTER_FIELD_IPV6_HOPLIMIT: case WINDIVERT_FILTER_FIELD_ICMP_TYPE: case WINDIVERT_FILTER_FIELD_ICMP_CODE: case WINDIVERT_FILTER_FIELD_ICMPV6_TYPE: case WINDIVERT_FILTER_FIELD_ICMPV6_CODE: if (ioctl_filter[i].arg[0] > UINT8_MAX) { goto windivert_filter_compile_exit; } break; case WINDIVERT_FILTER_FIELD_IP_FRAGOFF: if (ioctl_filter[i].arg[0] > 0x1FFF) { goto windivert_filter_compile_exit; } break; case WINDIVERT_FILTER_FIELD_IP_LENGTH: case WINDIVERT_FILTER_FIELD_IP_ID: case WINDIVERT_FILTER_FIELD_IP_CHECKSUM: case WINDIVERT_FILTER_FIELD_IPV6_LENGTH: case WINDIVERT_FILTER_FIELD_ICMP_CHECKSUM: case WINDIVERT_FILTER_FIELD_ICMPV6_CHECKSUM: case WINDIVERT_FILTER_FIELD_TCP_SRCPORT: case WINDIVERT_FILTER_FIELD_TCP_DSTPORT: case WINDIVERT_FILTER_FIELD_TCP_WINDOW: case WINDIVERT_FILTER_FIELD_TCP_CHECKSUM: case WINDIVERT_FILTER_FIELD_TCP_URGPTR: case WINDIVERT_FILTER_FIELD_TCP_PAYLOADLENGTH: case WINDIVERT_FILTER_FIELD_UDP_SRCPORT: case WINDIVERT_FILTER_FIELD_UDP_DSTPORT: case WINDIVERT_FILTER_FIELD_UDP_LENGTH: case WINDIVERT_FILTER_FIELD_UDP_CHECKSUM: case WINDIVERT_FILTER_FIELD_UDP_PAYLOADLENGTH: if (ioctl_filter[i].arg[0] > UINT16_MAX) { goto windivert_filter_compile_exit; } break; case WINDIVERT_FILTER_FIELD_IPV6_FLOWLABEL: if (ioctl_filter[i].arg[0] > 0x000FFFFF) { goto windivert_filter_compile_exit; } break; default: break; } filter0[i].field = ioctl_filter[i].field; filter0[i].test = ioctl_filter[i].test; filter0[i].success = ioctl_filter[i].success; filter0[i].failure = ioctl_filter[i].failure; filter0[i].arg[0] = ioctl_filter[i].arg[0]; filter0[i].arg[1] = ioctl_filter[i].arg[1]; filter0[i].arg[2] = ioctl_filter[i].arg[2]; filter0[i].arg[3] = ioctl_filter[i].arg[3]; // Protocol selection: switch (ioctl_filter[i].field) { case WINDIVERT_FILTER_FIELD_ZERO: case WINDIVERT_FILTER_FIELD_INBOUND: case WINDIVERT_FILTER_FIELD_OUTBOUND: case WINDIVERT_FILTER_FIELD_IFIDX: case WINDIVERT_FILTER_FIELD_SUBIFIDX: case WINDIVERT_FILTER_FIELD_IP: case WINDIVERT_FILTER_FIELD_IPV6: case WINDIVERT_FILTER_FIELD_ICMP: case WINDIVERT_FILTER_FIELD_ICMPV6: case WINDIVERT_FILTER_FIELD_TCP: case WINDIVERT_FILTER_FIELD_UDP: filter0[i].protocol = WINDIVERT_FILTER_PROTOCOL_NONE; break; case WINDIVERT_FILTER_FIELD_IP_HDRLENGTH: case WINDIVERT_FILTER_FIELD_IP_TOS: case WINDIVERT_FILTER_FIELD_IP_LENGTH: case WINDIVERT_FILTER_FIELD_IP_ID: case WINDIVERT_FILTER_FIELD_IP_DF: case WINDIVERT_FILTER_FIELD_IP_MF: case WINDIVERT_FILTER_FIELD_IP_FRAGOFF: case WINDIVERT_FILTER_FIELD_IP_TTL: case WINDIVERT_FILTER_FIELD_IP_PROTOCOL: case WINDIVERT_FILTER_FIELD_IP_CHECKSUM: case WINDIVERT_FILTER_FIELD_IP_SRCADDR: case WINDIVERT_FILTER_FIELD_IP_DSTADDR: filter0[i].protocol = WINDIVERT_FILTER_PROTOCOL_IP; break; case WINDIVERT_FILTER_FIELD_IPV6_TRAFFICCLASS: case WINDIVERT_FILTER_FIELD_IPV6_FLOWLABEL: case WINDIVERT_FILTER_FIELD_IPV6_LENGTH: case WINDIVERT_FILTER_FIELD_IPV6_NEXTHDR: case WINDIVERT_FILTER_FIELD_IPV6_HOPLIMIT: case WINDIVERT_FILTER_FIELD_IPV6_SRCADDR: case WINDIVERT_FILTER_FIELD_IPV6_DSTADDR: filter0[i].protocol = WINDIVERT_FILTER_PROTOCOL_IPV6; break; case WINDIVERT_FILTER_FIELD_ICMP_TYPE: case WINDIVERT_FILTER_FIELD_ICMP_CODE: case WINDIVERT_FILTER_FIELD_ICMP_CHECKSUM: case WINDIVERT_FILTER_FIELD_ICMP_BODY: filter0[i].protocol = WINDIVERT_FILTER_PROTOCOL_ICMP; break; case WINDIVERT_FILTER_FIELD_ICMPV6_TYPE: case WINDIVERT_FILTER_FIELD_ICMPV6_CODE: case WINDIVERT_FILTER_FIELD_ICMPV6_CHECKSUM: case WINDIVERT_FILTER_FIELD_ICMPV6_BODY: filter0[i].protocol = WINDIVERT_FILTER_PROTOCOL_ICMPV6; break; case WINDIVERT_FILTER_FIELD_TCP_SRCPORT: case WINDIVERT_FILTER_FIELD_TCP_DSTPORT: case WINDIVERT_FILTER_FIELD_TCP_SEQNUM: case WINDIVERT_FILTER_FIELD_TCP_ACKNUM: case WINDIVERT_FILTER_FIELD_TCP_HDRLENGTH: case WINDIVERT_FILTER_FIELD_TCP_URG: case WINDIVERT_FILTER_FIELD_TCP_ACK: case WINDIVERT_FILTER_FIELD_TCP_PSH: case WINDIVERT_FILTER_FIELD_TCP_RST: case WINDIVERT_FILTER_FIELD_TCP_SYN: case WINDIVERT_FILTER_FIELD_TCP_FIN: case WINDIVERT_FILTER_FIELD_TCP_WINDOW: case WINDIVERT_FILTER_FIELD_TCP_CHECKSUM: case WINDIVERT_FILTER_FIELD_TCP_URGPTR: case WINDIVERT_FILTER_FIELD_TCP_PAYLOADLENGTH: filter0[i].protocol = WINDIVERT_FILTER_PROTOCOL_TCP; break; case WINDIVERT_FILTER_FIELD_UDP_SRCPORT: case WINDIVERT_FILTER_FIELD_UDP_DSTPORT: case WINDIVERT_FILTER_FIELD_UDP_LENGTH: case WINDIVERT_FILTER_FIELD_UDP_CHECKSUM: case WINDIVERT_FILTER_FIELD_UDP_PAYLOADLENGTH: filter0[i].protocol = WINDIVERT_FILTER_PROTOCOL_UDP; break; default: goto windivert_filter_compile_exit; } } result = (filter_t)windivert_malloc(i*sizeof(struct filter_s), FALSE); if (result != NULL) { RtlMoveMemory(result, filter0, i*sizeof(struct filter_s)); } windivert_filter_compile_exit: windivert_free(filter0); return result; }