/* Copyright (C) 2018 Open Information Security Foundation * * You can copy, redistribute or modify this Program under the terms of * the GNU General Public License version 2 as published by the Free * Software Foundation. * * 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * version 2 along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. */ /** * \file * * \author Jacob Masen-Smith * * WinDivert emulation of netfilter_queue functionality to hook into Suricata's * IPS mode. Supported solely on Windows. * */ #include "suricata-common.h" #include "suricata.h" #include "tm-threads.h" #include "packet.h" #include "util-byte.h" #include "util-debug.h" #include "util-device.h" #include "util-error.h" #include "util-ioctl.h" #include "util-privs.h" #include "util-unittest.h" #include "runmodes.h" #include "queue.h" #include "source-windivert-prototypes.h" #include "source-windivert.h" #ifdef WINDIVERT // clang-format off #include #include #include #include // clang-format on #endif #ifndef WINDIVERT /* Gracefully handle the case where no WinDivert support is compiled in */ TmEcode NoWinDivertSupportExit(ThreadVars *, const void *, void **); void TmModuleReceiveWinDivertRegister(void) { tmm_modules[TMM_RECEIVEWINDIVERT].name = "ReceiveWinDivert"; tmm_modules[TMM_RECEIVEWINDIVERT].ThreadInit = NoWinDivertSupportExit; tmm_modules[TMM_RECEIVEWINDIVERT].flags = TM_FLAG_RECEIVE_TM; } void TmModuleVerdictWinDivertRegister(void) { tmm_modules[TMM_VERDICTWINDIVERT].name = "VerdictWinDivert"; tmm_modules[TMM_VERDICTWINDIVERT].ThreadInit = NoWinDivertSupportExit; } void TmModuleDecodeWinDivertRegister(void) { tmm_modules[TMM_DECODEWINDIVERT].name = "DecodeWinDivert"; tmm_modules[TMM_DECODEWINDIVERT].ThreadInit = NoWinDivertSupportExit; tmm_modules[TMM_DECODEWINDIVERT].flags = TM_FLAG_DECODE_TM; } TmEcode NoWinDivertSupportExit(ThreadVars *tv, const void *initdata, void **data) { SCLogError("Error creating thread %s: you do not have support for WinDivert " "enabled; please recompile with --enable-windivert", tv->name); exit(EXIT_FAILURE); } #else /* implied we do have WinDivert support */ #include "action-globals.h" #include "win32-syscall.h" typedef struct WinDivertThreadVars_ { WinDivertHandle filter_handle; int thread_num; int64_t qpc_start_time; int64_t qpc_start_count; int64_t qpc_freq_usec; TmSlot *slot; bool offload_enabled; TAILQ_HEAD(, LiveDevice_) live_devices; } WinDivertThreadVars; #define WINDIVERT_MAX_QUEUE 16 static WinDivertThreadVars g_wd_tv[WINDIVERT_MAX_QUEUE]; static WinDivertQueueVars g_wd_qv[WINDIVERT_MAX_QUEUE]; static uint16_t g_wd_num = 0; static SCMutex g_wd_init_lock = SCMUTEX_INITIALIZER; void *WinDivertGetThread(int n) { if (n >= g_wd_num) { return NULL; } return (void *)&g_wd_tv[n]; } void *WinDivertGetQueue(int n) { if (n >= g_wd_num) { return NULL; } return (void *)&g_wd_qv[n]; } // not defined in MinGW winerror.h #ifndef ERROR_INVALID_IMAGE_HASH #define ERROR_INVALID_IMAGE_HASH 577L #endif #ifndef ERROR_DATA_NOT_ACCEPTED #define ERROR_DATA_NOT_ACCEPTED 592L #endif /** * \brief return an error description for Win32 error values commonly returned * by WinDivert */ static const char *WinDivertGetErrorString(DWORD error_code) { switch (error_code) { // WinDivertOpen errors case ERROR_FILE_NOT_FOUND: return "The driver files WinDivert32.sys or WinDivert64.sys were " "not found."; case ERROR_ACCESS_DENIED: return "Suricata must be run with Administrator privileges."; case ERROR_INVALID_PARAMETER: return "The WinDivert packet filter string is invalid."; case ERROR_INVALID_IMAGE_HASH: return "The WinDivert32.sys or WinDivert64.sys driver does not " "have a valid digital signature, or your copy of Windows is " "not up-to-date. Windows 7 and Server 2008 users need to " "run Windows Update or install the following patch from " "Microsoft: http://support.microsoft.com/kb/2949927"; case ERROR_DRIVER_BLOCKED: return "This error occurs for various reasons, including: " "attempting to load the 32-bit WinDivert.sys driver on a " "64-bit system (or vice versa); the WinDivert.sys driver is " "blocked by security software; or you are using a " "virtualization environment that does not support " "drivers."; case EPT_S_NOT_REGISTERED: return "This error occurs when the Base Filtering Engine service " "has been disabled."; case ERROR_PROC_NOT_FOUND: return "The error may occur for Windows Vista users. The " "solution is to install the following patch from Microsoft: " "http://support.microsoft.com/kb/2761494."; // WinDivertSend errors case ERROR_HOST_UNREACHABLE: return "This error occurs when an impostor packet (with " "pAddr->Impostor set to 1) is injected and the ip.TTL or " "ipv6.HopLimit field goes to zero. This is a defense of " "last resort against infinite loops caused by impostor " "packets."; case ERROR_DATA_NOT_ACCEPTED: return "This error is returned when the user application attempts " "to inject a malformed packet. It may also be returned for " "valid inbound packets, and the Windows TCP/IP stack " "rejects the packet for some reason."; case ERROR_RETRY: return "The underlying cause of this error is unknown. However, " "this error usually occurs when certain kinds of " "anti-virus/firewall/security software is installed, and " "the error message usually resolves once the offending " "program is uninstalled. This suggests a software " "compatibility problem."; default: return ""; } } /** * \brief logs a WinDivert error at Error level. */ #define WinDivertLogError(err_code) \ do { \ const char *win_err_str = Win32GetErrorString((err_code), NULL); \ SCLogError("WinDivertOpen failed, error %" PRId32 " (0x%08" PRIx32 "): %s %s", \ (uint32_t)(err_code), (uint32_t)(err_code), win_err_str, \ WinDivertGetErrorString(err_code)); \ LocalFree((LPVOID)win_err_str); \ } while (0); /** * \brief initializes QueryPerformanceCounter values so we can get * absolute time from WinDivert timestamps. */ static void WinDivertInitQPCValues(WinDivertThreadVars *wd_tv) { SCTime_t now = TimeGet(); (void)QueryPerformanceCounter((LARGE_INTEGER *)&wd_tv->qpc_start_count); wd_tv->qpc_start_time = (uint64_t)SCTIME_SECS(now) * (1000 * 1000) + (uint64_t)SCTIME_SECS(now); (void)QueryPerformanceFrequency((LARGE_INTEGER *)&wd_tv->qpc_freq_usec); /* \bug: clock drift? */ wd_tv->qpc_freq_usec /= 1000 * 1000; } /** * \brief WinDivert timestamp to a SCTime_t */ static SCTime_t WinDivertTimestampToTimeStamp(WinDivertThreadVars *wd_tv, INT64 timestamp_count) { struct timeval tv; int64_t qpc_delta = (int64_t)timestamp_count - wd_tv->qpc_start_count; int64_t unix_usec = wd_tv->qpc_start_time; if (wd_tv->qpc_freq_usec) { unix_usec += qpc_delta / wd_tv->qpc_freq_usec; } tv.tv_sec = (long)(unix_usec / (1000 * 1000)); tv.tv_usec = (long)(unix_usec - (int64_t)tv.tv_sec * (1000 * 1000)); return SCTIME_FROM_TIMEVAL(&tv); } /** * \brief initialize a WinDivert filter * * \param filter a WinDivert filter string as defined at * https://www.reqrypt.org/windivert-doc.html#filter_language * * \retval 0 on success * \retval -1 on failure */ int WinDivertRegisterQueue(bool forward, char *filter_str) { SCEnter(); int ret = 0; WINDIVERT_LAYER layer = forward ? WINDIVERT_LAYER_NETWORK_FORWARD : WINDIVERT_LAYER_NETWORK; /* validate the filter string */ const char *error_str; uint32_t error_pos; bool valid = WinDivertHelperCheckFilter(filter_str, layer, &error_str, &error_pos); if (!valid) { SCLogWarning("Invalid filter \"%s\" supplied to WinDivert: %s at position " "%" PRId32 "", filter_str, error_str, error_pos); SCReturnInt(-1); } /* initialize the queue */ SCMutexLock(&g_wd_init_lock); if (g_wd_num >= WINDIVERT_MAX_QUEUE) { SCLogError("Too many WinDivert queues specified %" PRId32 "", g_wd_num); ret = -1; goto unlock; } if (g_wd_num == 0) { /* on first registration, zero-initialize all array structs */ memset(&g_wd_tv, 0, sizeof(g_wd_tv)); memset(&g_wd_qv, 0, sizeof(g_wd_qv)); } /* init thread vars */ WinDivertThreadVars *wd_tv = &g_wd_tv[g_wd_num]; wd_tv->thread_num = g_wd_num; /* init queue vars */ WinDivertQueueVars *wd_qv = &g_wd_qv[g_wd_num]; wd_qv->queue_num = g_wd_num; WinDivertInitQPCValues(wd_tv); /* copy filter to persistent storage */ size_t filter_len = strlen(filter_str); size_t copy_len = strlcpy(wd_qv->filter_str, filter_str, sizeof(wd_qv->filter_str)); if (filter_len > copy_len) { SCLogWarning("Queue length exceeds storage by %" PRId32 " bytes", (int32_t)(filter_len - copy_len)); ret = -1; goto unlock; } wd_qv->layer = layer; wd_qv->priority = g_wd_num; /* priority set in the order filters are defined */ wd_qv->flags = 0; /* normal inline function */ SCMutexInit(&wd_qv->filter_init_mutex, NULL); SCMutexInit(&wd_qv->counters_mutex, NULL); g_wd_num++; unlock: SCMutexUnlock(&g_wd_init_lock); if (ret == 0) { // stringify queue index to use as thread name descriptor char wd_num_str[6]; wd_num_str[sizeof(wd_num_str) - 1] = 0; snprintf(wd_num_str, sizeof(wd_num_str), "%" PRId16 "", g_wd_num); LiveRegisterDevice(wd_num_str); SCLogDebug("Queue %" PRId16 " registered", wd_qv->queue_num); } return ret; } /* forward declarations of internal functions */ /* Receive functions */ TmEcode ReceiveWinDivertLoop(ThreadVars *, void *, void *); TmEcode ReceiveWinDivertThreadInit(ThreadVars *, const void *, void **); TmEcode ReceiveWinDivertThreadDeinit(ThreadVars *, void *); void ReceiveWinDivertThreadExitStats(ThreadVars *, void *); /* Verdict functions */ TmEcode VerdictWinDivert(ThreadVars *, Packet *, void *); TmEcode VerdictWinDivertThreadInit(ThreadVars *, const void *, void **); TmEcode VerdictWinDivertThreadDeinit(ThreadVars *, void *); /* Decode functions */ TmEcode DecodeWinDivert(ThreadVars *, Packet *, void *); TmEcode DecodeWinDivertThreadInit(ThreadVars *, const void *, void **); TmEcode DecodeWinDivertThreadDeinit(ThreadVars *, void *); /* internal helper functions */ static TmEcode WinDivertRecvHelper(ThreadVars *tv, WinDivertThreadVars *); static TmEcode WinDivertVerdictHelper(ThreadVars *tv, Packet *p); static TmEcode WinDivertCloseHelper(WinDivertThreadVars *); static TmEcode WinDivertCollectFilterDevices(WinDivertThreadVars *, WinDivertQueueVars *); static bool WinDivertIfaceMatchFilter(const char *filter_string, int if_index); static void WinDivertDisableOffloading(WinDivertThreadVars *); static void WinDivertRestoreOffloading(WinDivertThreadVars *); void TmModuleReceiveWinDivertRegister(void) { TmModule *tm_ptr = &tmm_modules[TMM_RECEIVEWINDIVERT]; tm_ptr->name = "ReceiveWinDivert"; tm_ptr->ThreadInit = ReceiveWinDivertThreadInit; tm_ptr->PktAcqLoop = ReceiveWinDivertLoop; tm_ptr->ThreadExitPrintStats = ReceiveWinDivertThreadExitStats; tm_ptr->ThreadDeinit = ReceiveWinDivertThreadDeinit; tm_ptr->flags = TM_FLAG_RECEIVE_TM; } void TmModuleVerdictWinDivertRegister(void) { TmModule *tm_ptr = &tmm_modules[TMM_VERDICTWINDIVERT]; tm_ptr->name = "VerdictWinDivert"; tm_ptr->ThreadInit = VerdictWinDivertThreadInit; tm_ptr->Func = VerdictWinDivert; tm_ptr->ThreadDeinit = VerdictWinDivertThreadDeinit; } void TmModuleDecodeWinDivertRegister(void) { TmModule *tm_ptr = &tmm_modules[TMM_DECODEWINDIVERT]; tm_ptr->name = "DecodeWinDivert"; tm_ptr->ThreadInit = DecodeWinDivertThreadInit; tm_ptr->Func = DecodeWinDivert; tm_ptr->ThreadDeinit = DecodeWinDivertThreadDeinit; tm_ptr->flags = TM_FLAG_DECODE_TM; } /** * \brief Main WinDivert packet receive pump */ TmEcode ReceiveWinDivertLoop(ThreadVars *tv, void *data, void *slot) { SCEnter(); WinDivertThreadVars *wd_tv = (WinDivertThreadVars *)data; wd_tv->slot = ((TmSlot *)slot)->slot_next; // Indicate that the thread is actually running its application level code (i.e., it can poll // packets) TmThreadsSetFlag(tv, THV_RUNNING); while (true) { if (suricata_ctl_flags & SURICATA_STOP) { SCReturnInt(TM_ECODE_OK); } if (unlikely(WinDivertRecvHelper(tv, wd_tv) != TM_ECODE_OK)) { SCReturnInt(TM_ECODE_FAILED); } StatsSyncCountersIfSignalled(tv); } SCReturnInt(TM_ECODE_OK); } static TmEcode WinDivertRecvHelper(ThreadVars *tv, WinDivertThreadVars *wd_tv) { SCEnter(); #ifdef COUNTERS WinDivertQueueVars *wd_qv = WinDivertGetQueue(wd_tv->thread_num); #endif /* COUNTERS */ /* make sure we have at least one packet in the packet pool, to prevent us * from alloc'ing packets at line rate */ PacketPoolWait(); /* obtain a packet buffer */ Packet *p = PacketGetFromQueueOrAlloc(); if (unlikely(p == NULL)) { SCLogDebug( "PacketGetFromQueueOrAlloc() - failed to obtain Packet buffer"); SCReturnInt(TM_ECODE_FAILED); } PKT_SET_SRC(p, PKT_SRC_WIRE); /* receive packet, depending on offload status. MTU is used as an estimator * for direct data alloc size, and this is meaningless if large segments are * coalesced before they reach WinDivert */ bool success = false; uint32_t pktlen = 0; if (wd_tv->offload_enabled) { /* allocate external, if not already */ PacketCallocExtPkt(p, MAX_PAYLOAD_SIZE); success = WinDivertRecv(wd_tv->filter_handle, p->ext_pkt, MAX_PAYLOAD_SIZE, &p->windivert_v.addr, &pktlen); } else { success = WinDivertRecv(wd_tv->filter_handle, GET_PKT_DIRECT_DATA(p), GET_PKT_DIRECT_MAX_SIZE(p), &p->windivert_v.addr, &pktlen); } SET_PKT_LEN(p, pktlen); if (!success) { #ifdef COUNTERS SCMutexLock(&wd_qv->counters_mutex); wd_qv->errs++; SCMutexUnlock(&wd_qv->counters_mutex); #endif /* COUNTERS */ /* ensure packet length is zero to trigger an error in packet decoding */ SET_PKT_LEN(p, 0); SCLogInfo("WinDivertRecv failed: error %" PRIu32 "", (uint32_t)(GetLastError())); SCReturnInt(TM_ECODE_FAILED); } SCLogDebug("Packet received, length %" PRId32 "", GET_PKT_LEN(p)); p->ts = WinDivertTimestampToTimeStamp(wd_tv, p->windivert_v.addr.Timestamp); p->windivert_v.thread_num = wd_tv->thread_num; #ifdef COUNTERS SCMutexLock(&wd_qv->counters_mutex); wd_qv->pkts++; wd_qv->bytes += GET_PKT_LEN(p); SCMutexUnlock(&wd_qv->counters_mutex); #endif /* COUNTERS */ /* Do the packet processing by calling TmThreadsSlotProcessPkt, this will, * depending on the running mode, pass the packet to the treatment functions * or push it to a packet pool. So processing time can vary. */ if (TmThreadsSlotProcessPkt(tv, wd_tv->slot, p) != TM_ECODE_OK) { SCReturnInt(TM_ECODE_FAILED); } SCReturnInt(TM_ECODE_OK); } /** * \brief Init function for ReceiveWinDivert * * ReceiveWinDivertThreadInit sets up receiving packets via WinDivert. * * \param tv pointer to generic thread vars * \param initdata pointer to the interface passed from the user * \param data out-pointer to the WinDivert-specific thread vars */ TmEcode ReceiveWinDivertThreadInit(ThreadVars *tv, const void *initdata, void **data) { SCEnter(); TmEcode ret = TM_ECODE_OK; WinDivertThreadVars *wd_tv = (WinDivertThreadVars *)initdata; if (wd_tv == NULL) { SCLogError("initdata == NULL"); SCReturnInt(TM_ECODE_FAILED); } WinDivertQueueVars *wd_qv = WinDivertGetQueue(wd_tv->thread_num); if (wd_qv == NULL) { SCLogError("queue == NULL"); SCReturnInt(TM_ECODE_FAILED); } SCMutexLock(&wd_qv->filter_init_mutex); /* does the queue already have an active handle? */ if (wd_qv->filter_handle != NULL && wd_qv->filter_handle != INVALID_HANDLE_VALUE) { goto unlock; } TAILQ_INIT(&wd_tv->live_devices); if (WinDivertCollectFilterDevices(wd_tv, wd_qv) == TM_ECODE_OK) { WinDivertDisableOffloading(wd_tv); } else { SCLogWarning("Failed to obtain network devices for WinDivert filter"); } /* we open now so that we can immediately start handling packets, * instead of losing however many would occur between registering the * queue and starting a receive thread. */ wd_qv->filter_handle = WinDivertOpen(wd_qv->filter_str, wd_qv->layer, wd_qv->priority, wd_qv->flags); if (wd_qv->filter_handle == INVALID_HANDLE_VALUE) { WinDivertLogError(GetLastError()); ret = TM_ECODE_FAILED; goto unlock; } unlock: if (ret == 0) { /* success */ wd_tv->filter_handle = wd_qv->filter_handle; /* set our return context */ *data = wd_tv; } SCMutexUnlock(&wd_qv->filter_init_mutex); SCReturnInt(ret); } /** * \brief collect all devices covered by this filter in the thread vars' * live devices list * * \param wd_tv pointer to WinDivert thread vars * \param wd_qv pointer to WinDivert queue vars */ static TmEcode WinDivertCollectFilterDevices(WinDivertThreadVars *wd_tv, WinDivertQueueVars *wd_qv) { SCEnter(); TmEcode ret = TM_ECODE_OK; IP_ADAPTER_ADDRESSES *if_info_list; DWORD err = (DWORD)Win32GetAdaptersAddresses(&if_info_list); if (err != NO_ERROR) { ret = TM_ECODE_FAILED; goto release; } for (IP_ADAPTER_ADDRESSES *if_info = if_info_list; if_info != NULL; if_info = if_info->Next) { if (WinDivertIfaceMatchFilter(wd_qv->filter_str, if_info->IfIndex)) { SCLogConfig("Found adapter %s matching WinDivert filter %s", if_info->AdapterName, wd_qv->filter_str); LiveDevice *new_ldev = SCCalloc(1, sizeof(LiveDevice)); if (new_ldev == NULL) { ret = TM_ECODE_FAILED; goto release; } new_ldev->dev = SCStrdup(if_info->AdapterName); if (new_ldev->dev == NULL) { ret = TM_ECODE_FAILED; goto release; } TAILQ_INSERT_TAIL(&wd_tv->live_devices, new_ldev, next); } else { SCLogDebug("Adapter %s does not match WinDivert filter %s", if_info->AdapterName, wd_qv->filter_str); } } release: SCFree(if_info_list); SCReturnInt(ret); } /** * \brief test if the specified interface index matches the filter */ static bool WinDivertIfaceMatchFilter(const char *filter_string, int if_index) { bool match = false; WINDIVERT_ADDRESS if_addr = {}; if_addr.IfIdx = if_index; uint8_t dummy[4] = {4, 4, 4, 4}; match = WinDivertHelperEvalFilter(filter_string, WINDIVERT_LAYER_NETWORK, dummy, sizeof(dummy), &if_addr); if (!match) { int err = GetLastError(); if (err != 0) { SCLogWarning("Failed to evaluate filter: 0x%" PRIx32, err); } } return match; } /** * \brief disable offload status on devices for this filter * * \param wd_tv pointer to WinDivert thread vars */ static void WinDivertDisableOffloading(WinDivertThreadVars *wd_tv) { for (LiveDevice *ldev = TAILQ_FIRST(&wd_tv->live_devices); ldev != NULL; ldev = TAILQ_NEXT(ldev, next)) { if (LiveGetOffload() == 0) { if (GetIfaceOffloading(ldev->dev, 1, 1) == 1) { wd_tv->offload_enabled = true; } } else { if (DisableIfaceOffloading(ldev, 1, 1) != 1) { wd_tv->offload_enabled = true; } } } } /** * \brief enable offload status on devices for this filter * * \param wd_tv pointer to WinDivert thread vars */ static void WinDivertRestoreOffloading(WinDivertThreadVars *wd_tv) { for (LiveDevice *ldev = TAILQ_FIRST(&wd_tv->live_devices); ldev != NULL; ldev = TAILQ_NEXT(ldev, next)) { RestoreIfaceOffloading(ldev); } } /** * \brief Deinit function releases resources at exit. * * \param tv pointer to generic thread vars * \param data pointer to WinDivert-specific thread vars */ TmEcode ReceiveWinDivertThreadDeinit(ThreadVars *tv, void *data) { SCEnter(); WinDivertThreadVars *wd_tv = (WinDivertThreadVars *)data; SCReturnCT(WinDivertCloseHelper(wd_tv), "TmEcode"); } /** * \brief ExitStats prints stats to stdout at exit * * * \param tv pointer to generic thread vars * \param data pointer to WinDivert-specific thread vars */ void ReceiveWinDivertThreadExitStats(ThreadVars *tv, void *data) { SCEnter(); WinDivertThreadVars *wd_tv = (WinDivertThreadVars *)data; WinDivertQueueVars *wd_qv = WinDivertGetQueue(wd_tv->thread_num); if (wd_qv == NULL) { SCLogError("queue == NULL"); SCReturn; } SCMutexLock(&wd_qv->counters_mutex); SCLogInfo("(%s) Packets %" PRIu32 ", Bytes %" PRIu64 ", Errors %" PRIu32 "", tv->name, wd_qv->pkts, wd_qv->bytes, wd_qv->errs); SCLogInfo("(%s) Verdict: Accepted %" PRIu32 ", Dropped %" PRIu32 ", Replaced %" PRIu32 "", tv->name, wd_qv->accepted, wd_qv->dropped, wd_qv->replaced); SCMutexUnlock(&wd_qv->counters_mutex); SCReturn; } /** * \brief WinDivert verdict module packet entry function */ TmEcode VerdictWinDivert(ThreadVars *tv, Packet *p, void *data) { SCEnter(); TmEcode ret = TM_ECODE_OK; ret = WinDivertVerdictHelper(tv, p); if (ret != TM_ECODE_OK) { SCReturnInt(ret); } SCReturnInt(TM_ECODE_OK); } /** * \brief internal helper function to do the bulk of verdict work */ static TmEcode WinDivertVerdictHelper(ThreadVars *tv, Packet *p) { SCEnter(); WinDivertThreadVars *wd_tv = WinDivertGetThread(p->windivert_v.thread_num); #ifdef COUNTERS WinDivertQueueVars *wd_qv = WinDivertGetQueue(wd_tv->thread_num); #endif /* COUNTERS */ p->windivert_v.verdicted = true; /* can't verdict a "fake" packet */ if (PKT_IS_PSEUDOPKT(p)) { SCReturnInt(TM_ECODE_OK); } /* the handle has been closed and we can no longer use it */ if (wd_tv->filter_handle == INVALID_HANDLE_VALUE || wd_tv->filter_handle == NULL) { SCReturnInt(TM_ECODE_OK); } /* we can't verdict tunnel packets without ensuring all encapsulated * packets are verdicted */ if (IS_TUNNEL_PKT(p)) { bool finalVerdict = VerdictTunnelPacket(p); if (!finalVerdict) { SCReturnInt(TM_ECODE_OK); } // the action needs to occur on the root packet. if (p->root != NULL) { p = p->root; } } /* DROP simply means we do nothing; the WinDivert driver does the rest. */ if (PacketCheckAction(p, ACTION_DROP)) { #ifdef COUNTERS SCMutexLock(&wd_qv->counters_mutex); wd_qv->dropped++; SCMutexUnlock(&wd_qv->counters_mutex); #endif /* counters */ SCReturnInt(TM_ECODE_OK); } bool success = WinDivertSend(wd_tv->filter_handle, GET_PKT_DATA(p), GET_PKT_LEN(p), &p->windivert_v.addr, NULL); if (unlikely(!success)) { WinDivertLogError(GetLastError()); SCReturnInt(TM_ECODE_FAILED); } #ifdef COUNTERS SCMutexLock(&wd_qv->counters_mutex); wd_qv->accepted++; SCMutexUnlock(&wd_qv->counters_mutex); #endif /* counters */ SCReturnInt(TM_ECODE_OK); } /** * \brief init the verdict thread, which is piggybacked off the receive * thread */ TmEcode VerdictWinDivertThreadInit(ThreadVars *tv, const void *initdata, void **data) { SCEnter(); WinDivertThreadVars *wd_tv = (WinDivertThreadVars *)initdata; *data = wd_tv; SCReturnInt(TM_ECODE_OK); } /** * \brief deinit the verdict thread and shut down the WinDivert driver if * it's still up. */ TmEcode VerdictWinDivertThreadDeinit(ThreadVars *tv, void *data) { SCEnter(); WinDivertThreadVars *wd_tv = (WinDivertThreadVars *)data; SCReturnCT(WinDivertCloseHelper(wd_tv), "TmEcode"); } /** * \brief decode a raw packet submitted to suricata from the WinDivert * driver * * All WinDivert packets are IPv4/v6, but do not include the network layer * to differentiate the two, so instead we must check the version and go * from there. */ TmEcode DecodeWinDivert(ThreadVars *tv, Packet *p, void *data) { SCEnter(); IPV4Hdr *ip4h = (IPV4Hdr *)GET_PKT_DATA(p); IPV6Hdr *ip6h = (IPV6Hdr *)GET_PKT_DATA(p); DecodeThreadVars *d_tv = (DecodeThreadVars *)data; BUG_ON(PKT_IS_PSEUDOPKT(p)); DecodeUpdatePacketCounters(tv, d_tv, p); if (IPV4_GET_RAW_VER(ip4h) == 4) { SCLogDebug("IPv4 packet"); DecodeIPV4(tv, d_tv, p, GET_PKT_DATA(p), GET_PKT_LEN(p)); } else if (IPV6_GET_RAW_VER(ip6h) == 6) { SCLogDebug("IPv6 packet"); DecodeIPV6(tv, d_tv, p, GET_PKT_DATA(p), GET_PKT_LEN(p)); } else { SCLogDebug("packet unsupported by WinDivert, first byte: %02x", *GET_PKT_DATA(p)); } PacketDecodeFinalize(tv, d_tv, p); SCReturnInt(TM_ECODE_OK); } TmEcode DecodeWinDivertThreadInit(ThreadVars *tv, const void *initdata, void **data) { SCEnter(); DecodeThreadVars *d_tv = DecodeThreadVarsAlloc(tv); if (d_tv == NULL) { SCReturnInt(TM_ECODE_FAILED); } DecodeRegisterPerfCounters(d_tv, tv); *data = d_tv; SCReturnInt(TM_ECODE_OK); } TmEcode DecodeWinDivertThreadDeinit(ThreadVars *tv, void *data) { SCEnter(); if (data != NULL) { DecodeThreadVarsFree(tv, data); } SCReturnInt(TM_ECODE_OK); } /** * \brief helper function for use with ThreadDeinit functions */ static TmEcode WinDivertCloseHelper(WinDivertThreadVars *wd_tv) { SCEnter(); TmEcode ret = TM_ECODE_OK; WinDivertQueueVars *wd_qv = WinDivertGetQueue(wd_tv->thread_num); if (wd_qv == NULL) { SCLogDebug("No queue could be found for thread num %" PRId32 "", wd_tv->thread_num); SCReturnInt(TM_ECODE_FAILED); } SCMutexLock(&wd_qv->filter_init_mutex); /* check if there's nothing to close */ if (wd_qv->filter_handle == INVALID_HANDLE_VALUE || wd_qv->filter_handle == NULL) { goto unlock; } if (!WinDivertClose(wd_qv->filter_handle)) { SCLogError("WinDivertClose failed: error %" PRIu32 "", (uint32_t)(GetLastError())); ret = TM_ECODE_FAILED; goto unlock; } (void)WinDivertRestoreOffloading(wd_tv); wd_qv->filter_handle = NULL; unlock: SCMutexUnlock(&wd_qv->filter_init_mutex); if (ret == TM_ECODE_OK) { SCMutexDestroy(&wd_qv->filter_init_mutex); SCMutexDestroy(&wd_qv->counters_mutex); } SCReturnInt(ret); } #ifdef UNITTESTS static int SourceWinDivertTestIfaceMatchFilter(void) { struct testdata { const char *filter; int if_index; bool expected; }; struct testdata tests[] = { {"true", 11, true}, {"ifIdx=11", 11, true}, {"ifIdx==11", 11, true}, {"ifIdx!=11", 1, true}, {"ifIdx!=11", 11, false}, {"ifIdx=3", 4, false}, {"ifIdx=11 || ifIdx=5", 5, true}, {"ifIdx=11 || ifIdx=4", 5, false}, {"ifIdx<3 || ifIdx>7", 8, true}, {"ifIdx<3 || ifIdx>7", 5, false}, {"ifIdx>3 or ifIdx<7", 5, true}, {"ifIdx>3 && ifIdx<7", 5, true}, {"ifIdx>3 && ifIdx<7", 1, false}, {"(ifIdx > 3 && ifIdx < 7) or ifIdx == 11", 11, true}}; size_t count = (sizeof(tests) / sizeof(tests[0])); for (size_t i = 0; i < count; i++) { struct testdata test = tests[i]; bool actual = WinDivertIfaceMatchFilter(test.filter, test.if_index); if (actual != test.expected) { printf("WinDivertIfaceMatchFilter(\"%s\", %d) == %d, expected %d\n", test.filter, test.if_index, actual, test.expected); FAIL; } } PASS; } #endif /** * \brief this function registers unit tests for the WinDivert Source */ void SourceWinDivertRegisterTests(void) { #ifdef UNITTESTS UtRegisterTest("SourceWinDivertTestIfaceMatchFilter", SourceWinDivertTestIfaceMatchFilter); #endif } #endif /* WINDIVERT */