diff options
Diffstat (limited to 'api')
-rw-r--r-- | api/adapter.c | 950 | ||||
-rw-r--r-- | api/adapter.h | 137 | ||||
-rw-r--r-- | api/adapter_win7.h | 351 | ||||
-rw-r--r-- | api/api.vcxproj | 95 | ||||
-rw-r--r-- | api/api.vcxproj.filters | 97 | ||||
-rw-r--r-- | api/driver.c | 519 | ||||
-rw-r--r-- | api/driver.h | 34 | ||||
-rw-r--r-- | api/exports.def | 16 | ||||
-rw-r--r-- | api/extract-driverver.js | 17 | ||||
-rw-r--r-- | api/logger.c | 134 | ||||
-rw-r--r-- | api/logger.h | 175 | ||||
-rw-r--r-- | api/main.c | 134 | ||||
-rw-r--r-- | api/main.h | 38 | ||||
-rw-r--r-- | api/namespace.c | 154 | ||||
-rw-r--r-- | api/namespace.h | 30 | ||||
-rw-r--r-- | api/nci.def | 4 | ||||
-rw-r--r-- | api/nci.h | 31 | ||||
-rw-r--r-- | api/ntdll.h | 62 | ||||
-rw-r--r-- | api/registry.c | 157 | ||||
-rw-r--r-- | api/registry.h | 79 | ||||
-rw-r--r-- | api/resource.c | 129 | ||||
-rw-r--r-- | api/resource.h | 50 | ||||
-rw-r--r-- | api/resources.rc | 65 | ||||
-rw-r--r-- | api/rundll32.c | 398 | ||||
-rw-r--r-- | api/rundll32.h | 26 | ||||
-rw-r--r-- | api/session.c | 309 | ||||
-rw-r--r-- | api/wintun.h | 274 |
27 files changed, 4465 insertions, 0 deletions
diff --git a/api/adapter.c b/api/adapter.c new file mode 100644 index 0000000..0dd8c42 --- /dev/null +++ b/api/adapter.c @@ -0,0 +1,950 @@ +/* SPDX-License-Identifier: GPL-2.0 + * + * Copyright (C) 2018-2021 WireGuard LLC. All Rights Reserved. + */ + +#include <Windows.h> +#include <winternl.h> +#include <cfgmgr32.h> +#include <devguid.h> +#include <iphlpapi.h> +#include <objbase.h> +#include <ndisguid.h> +#include <SetupAPI.h> +#include <Shlwapi.h> +#include <devioctl.h> +#include <wchar.h> +#include <initguid.h> /* Keep these two at bottom in this order, so that we only generate extra GUIDs for devpkey. The other keys we'll get from uuid.lib like usual. */ +#include <devpkey.h> + +/* We pretend we're Windows 8, and then hack around the limitation in Windows 7 below. */ +#if NTDDI_VERSION == NTDDI_WIN7 +# undef NTDDI_VERSION +# define NTDDI_VERSION NTDDI_WIN8 +# include <devquery.h> +# include <swdevice.h> +# undef NTDDI_VERSION +# define NTDDI_VERSION NTDDI_WIN7 +#else +# include <devquery.h> +# include <swdevice.h> +#endif + +#include "adapter.h" +#include "driver.h" +#include "logger.h" +#include "main.h" +#include "namespace.h" +#include "nci.h" +#include "ntdll.h" +#include "rundll32.h" +#include "registry.h" +#include "adapter_win7.h" + +#pragma warning(disable : 4221) /* nonstandard: address of automatic in initializer */ + +const DEVPROPKEY DEVPKEY_Wintun_Name = { + { 0x3361c968, 0x2f2e, 0x4660, { 0xb4, 0x7e, 0x69, 0x9c, 0xdc, 0x4c, 0x32, 0xb9 } }, + DEVPROPID_FIRST_USABLE + 1 +}; + +_Must_inspect_result_ +static _Return_type_success_(return != FALSE) +BOOL +PopulateAdapterData(_Inout_ WINTUN_ADAPTER *Adapter) +{ + DWORD LastError = ERROR_SUCCESS; + + /* Open HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Class\<class>\<id> registry key. */ + HKEY Key = + SetupDiOpenDevRegKey(Adapter->DevInfo, &Adapter->DevInfoData, DICS_FLAG_GLOBAL, 0, DIREG_DRV, KEY_QUERY_VALUE); + if (Key == INVALID_HANDLE_VALUE) + { + LOG_LAST_ERROR(L"Failed to open adapter device registry key"); + return FALSE; + } + + LPWSTR ValueStr = RegistryQueryString(Key, L"NetCfgInstanceId", TRUE); + if (!ValueStr) + { + WCHAR RegPath[MAX_REG_PATH]; + LoggerGetRegistryKeyPath(Key, RegPath); + LastError = LOG(WINTUN_LOG_ERR, L"Failed to get %.*s\\NetCfgInstanceId", MAX_REG_PATH, RegPath); + goto cleanupKey; + } + if (FAILED(CLSIDFromString(ValueStr, &Adapter->CfgInstanceID))) + { + WCHAR RegPath[MAX_REG_PATH]; + LoggerGetRegistryKeyPath(Key, RegPath); + LastError = LOG(WINTUN_LOG_ERR, L"%.*s\\NetCfgInstanceId is not a GUID: %s", MAX_REG_PATH, RegPath, ValueStr); + Free(ValueStr); + goto cleanupKey; + } + Free(ValueStr); + + if (!RegistryQueryDWORD(Key, L"NetLuidIndex", &Adapter->LuidIndex, TRUE)) + { + WCHAR RegPath[MAX_REG_PATH]; + LoggerGetRegistryKeyPath(Key, RegPath); + LastError = LOG(WINTUN_LOG_ERR, L"Failed to get %.*s\\NetLuidIndex", MAX_REG_PATH, RegPath); + goto cleanupKey; + } + + if (!RegistryQueryDWORD(Key, L"*IfType", &Adapter->IfType, TRUE)) + { + WCHAR RegPath[MAX_REG_PATH]; + LoggerGetRegistryKeyPath(Key, RegPath); + LastError = LOG(WINTUN_LOG_ERR, L"Failed to get %.*s\\*IfType", MAX_REG_PATH, RegPath); + goto cleanupKey; + } + + Adapter->InterfaceFilename = AdapterGetDeviceObjectFileName(Adapter->DevInstanceID); + if (!Adapter->InterfaceFilename) + { + LastError = LOG_LAST_ERROR(L"Unable to determine device object file name"); + goto cleanupKey; + } + +cleanupKey: + RegCloseKey(Key); + return RET_ERROR(TRUE, LastError); +} + +static volatile LONG OrphanThreadIsWorking = FALSE; + +static DWORD +DoOrphanedDeviceCleanup(_In_opt_ LPVOID Ctx) +{ + AdapterCleanupOrphanedDevices(); + OrphanThreadIsWorking = FALSE; + return 0; +} + +static VOID QueueUpOrphanedDeviceCleanupRoutine(VOID) +{ + if (InterlockedCompareExchange(&OrphanThreadIsWorking, TRUE, FALSE) == FALSE) + QueueUserWorkItem(DoOrphanedDeviceCleanup, NULL, 0); +} + +VOID AdapterCleanupOrphanedDevices(VOID) +{ + HANDLE DeviceInstallationMutex = NamespaceTakeDeviceInstallationMutex(); + if (!DeviceInstallationMutex) + { + LOG_LAST_ERROR(L"Failed to take device installation mutex"); + return; + } + + if (IsWindows7) + { + AdapterCleanupOrphanedDevicesWin7(); + goto cleanupDeviceInstallationMutex; + } + + HDEVINFO DevInfo = SetupDiGetClassDevsExW(&GUID_DEVCLASS_NET, WINTUN_ENUMERATOR, NULL, 0, NULL, NULL, NULL); + if (DevInfo == INVALID_HANDLE_VALUE) + { + LOG_LAST_ERROR(L"Failed to get adapters"); + goto cleanupDeviceInstallationMutex; + } + + SP_DEVINFO_DATA DevInfoData = { .cbSize = sizeof(DevInfoData) }; + for (DWORD EnumIndex = 0;; ++EnumIndex) + { + if (!SetupDiEnumDeviceInfo(DevInfo, EnumIndex, &DevInfoData)) + { + if (GetLastError() == ERROR_NO_MORE_ITEMS) + break; + continue; + } + ULONG Status, Code; + if (CM_Get_DevNode_Status(&Status, &Code, DevInfoData.DevInst, 0) == CR_SUCCESS && !(Status & DN_HAS_PROBLEM)) + continue; + + DEVPROPTYPE PropType; + WCHAR Name[MAX_ADAPTER_NAME] = L"<unknown>"; + SetupDiGetDevicePropertyW( + DevInfo, + &DevInfoData, + &DEVPKEY_Wintun_Name, + &PropType, + (PBYTE)Name, + MAX_ADAPTER_NAME * sizeof(Name[0]), + NULL, + 0); + if (!AdapterRemoveInstance(DevInfo, &DevInfoData)) + { + LOG_LAST_ERROR(L"Failed to remove orphaned adapter \"%s\"", Name); + continue; + } + LOG(WINTUN_LOG_INFO, L"Removed orphaned adapter \"%s\"", Name); + } + SetupDiDestroyDeviceInfoList(DevInfo); +cleanupDeviceInstallationMutex: + NamespaceReleaseMutex(DeviceInstallationMutex); +} + +_Use_decl_annotations_ +VOID WINAPI +WintunCloseAdapter(WINTUN_ADAPTER *Adapter) +{ + if (!Adapter) + return; + Free(Adapter->InterfaceFilename); + if (Adapter->SwDevice) + SwDeviceClose(Adapter->SwDevice); + if (Adapter->DevInfo) + { + if (!AdapterRemoveInstance(Adapter->DevInfo, &Adapter->DevInfoData)) + LOG_LAST_ERROR(L"Failed to remove adapter when closing"); + SetupDiDestroyDeviceInfoList(Adapter->DevInfo); + } + Free(Adapter); + QueueUpOrphanedDeviceCleanupRoutine(); +} + +static _Return_type_success_(return != FALSE) +BOOL +RenameByNetGUID(_In_ GUID *Guid, _In_reads_or_z_(MAX_ADAPTER_NAME) LPCWSTR Name) +{ + DWORD LastError = ERROR_NOT_FOUND; + HDEVINFO DevInfo = SetupDiGetClassDevsExW(&GUID_DEVCLASS_NET, WINTUN_ENUMERATOR, NULL, 0, NULL, NULL, NULL); + if (DevInfo == INVALID_HANDLE_VALUE) + { + LastError = GetLastError(); + goto cleanup; + } + + SP_DEVINFO_DATA DevInfoData = { .cbSize = sizeof(DevInfoData) }; + for (DWORD EnumIndex = 0;; ++EnumIndex) + { + if (!SetupDiEnumDeviceInfo(DevInfo, EnumIndex, &DevInfoData)) + { + if (GetLastError() == ERROR_NO_MORE_ITEMS) + break; + continue; + } + + HKEY Key = SetupDiOpenDevRegKey(DevInfo, &DevInfoData, DICS_FLAG_GLOBAL, 0, DIREG_DRV, KEY_QUERY_VALUE); + if (Key == INVALID_HANDLE_VALUE) + continue; + LPWSTR ValueStr = RegistryQueryString(Key, L"NetCfgInstanceId", TRUE); + RegCloseKey(Key); + if (!ValueStr) + continue; + GUID Guid2; + HRESULT HRet = CLSIDFromString(ValueStr, &Guid2); + Free(ValueStr); + if (FAILED(HRet) || memcmp(Guid, &Guid2, sizeof(*Guid))) + continue; + LastError = SetupDiSetDevicePropertyW( + DevInfo, + &DevInfoData, + &DEVPKEY_Wintun_Name, + DEVPROP_TYPE_STRING, + (PBYTE)Name, + (DWORD)((wcslen(Name) + 1) * sizeof(Name[0])), + 0) + ? ERROR_SUCCESS + : GetLastError(); + break; + } + SetupDiDestroyDeviceInfoList(DevInfo); +cleanup: + return RET_ERROR(TRUE, LastError); +} + +_Must_inspect_result_ +static _Return_type_success_(return != FALSE) +BOOL +ConvertInterfaceAliasToGuid(_In_z_ LPCWSTR Name, _Out_ GUID *Guid) +{ + NET_LUID Luid; + DWORD LastError = ConvertInterfaceAliasToLuid(Name, &Luid); + if (LastError != NO_ERROR) + { + SetLastError(LOG_ERROR(LastError, L"Failed convert interface %s name to the locally unique identifier", Name)); + return FALSE; + } + LastError = ConvertInterfaceLuidToGuid(&Luid, Guid); + if (LastError != NO_ERROR) + { + SetLastError(LOG_ERROR(LastError, L"Failed to convert interface %s LUID (%I64u) to GUID", Name, Luid.Value)); + return FALSE; + } + return TRUE; +} + +static _Return_type_success_(return != FALSE) +BOOL +NciSetAdapterName(_In_ GUID *Guid, _In_reads_or_z_(MAX_ADAPTER_NAME) LPCWSTR Name) +{ + const int MaxSuffix = 1000; + WCHAR AvailableName[MAX_ADAPTER_NAME]; + if (wcsncpy_s(AvailableName, _countof(AvailableName), Name, _TRUNCATE) == STRUNCATE) + { + SetLastError(ERROR_BUFFER_OVERFLOW); + return FALSE; + } + for (int i = 0;; ++i) + { + DWORD LastError = NciSetConnectionName(Guid, AvailableName); + if (LastError == ERROR_DUP_NAME) + { + GUID Guid2; + if (ConvertInterfaceAliasToGuid(AvailableName, &Guid2)) + { + for (int j = 0; j < MaxSuffix; ++j) + { + WCHAR Proposal[MAX_ADAPTER_NAME]; + if (_snwprintf_s(Proposal, _countof(Proposal), _TRUNCATE, L"%s %d", Name, j + 1) == -1) + { + SetLastError(ERROR_BUFFER_OVERFLOW); + return FALSE; + } + if (_wcsnicmp(Proposal, AvailableName, MAX_ADAPTER_NAME) == 0) + continue; + DWORD LastError2 = NciSetConnectionName(&Guid2, Proposal); + if (LastError2 == ERROR_DUP_NAME) + continue; + if (!RenameByNetGUID(&Guid2, Proposal)) + LOG_LAST_ERROR(L"Failed to set foreign adapter name to \"%s\"", Proposal); + if (LastError2 == ERROR_SUCCESS) + { + LastError = NciSetConnectionName(Guid, AvailableName); + if (LastError == ERROR_SUCCESS) + break; + } + break; + } + } + } + if (LastError == ERROR_SUCCESS) + break; + if (i >= MaxSuffix || LastError != ERROR_DUP_NAME) + { + SetLastError(LastError); + return FALSE; + } + if (_snwprintf_s(AvailableName, _countof(AvailableName), _TRUNCATE, L"%s %d", Name, i + 1) == -1) + { + SetLastError(ERROR_BUFFER_OVERFLOW); + return FALSE; + } + } + return TRUE; +} + +_Use_decl_annotations_ +VOID WINAPI +WintunGetAdapterLUID(WINTUN_ADAPTER *Adapter, NET_LUID *Luid) +{ + Luid->Info.Reserved = 0; + Luid->Info.NetLuidIndex = Adapter->LuidIndex; + Luid->Info.IfType = Adapter->IfType; +} + +_Use_decl_annotations_ +HANDLE WINAPI +AdapterOpenDeviceObject(const WINTUN_ADAPTER *Adapter) +{ + HANDLE Handle = CreateFileW( + Adapter->InterfaceFilename, + GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + NULL, + OPEN_EXISTING, + 0, + NULL); + if (Handle == INVALID_HANDLE_VALUE) + LOG_LAST_ERROR(L"Failed to connect to adapter interface %s", Adapter->InterfaceFilename); + return Handle; +} + +_Use_decl_annotations_ +LPWSTR +AdapterGetDeviceObjectFileName(LPCWSTR InstanceId) +{ + ULONG InterfacesLen; + DWORD LastError = CM_MapCrToWin32Err( + CM_Get_Device_Interface_List_SizeW( + &InterfacesLen, + (GUID *)&GUID_DEVINTERFACE_NET, + (DEVINSTID_W)InstanceId, + CM_GET_DEVICE_INTERFACE_LIST_PRESENT), + ERROR_GEN_FAILURE); + if (LastError != ERROR_SUCCESS) + { + SetLastError(LOG_ERROR(LastError, L"Failed to query adapter %s associated instances size", InstanceId)); + return NULL; + } + LPWSTR Interfaces = AllocArray(InterfacesLen, sizeof(*Interfaces)); + if (!Interfaces) + return NULL; + LastError = CM_MapCrToWin32Err( + CM_Get_Device_Interface_ListW( + (GUID *)&GUID_DEVINTERFACE_NET, + (DEVINSTID_W)InstanceId, + Interfaces, + InterfacesLen, + CM_GET_DEVICE_INTERFACE_LIST_PRESENT), + ERROR_GEN_FAILURE); + if (LastError != ERROR_SUCCESS) + { + LOG_ERROR(LastError, L"Failed to get adapter %s associated instances", InstanceId); + Free(Interfaces); + SetLastError(LastError); + return NULL; + } + if (!Interfaces[0]) + { + Free(Interfaces); + SetLastError(ERROR_DEVICE_NOT_AVAILABLE); + return NULL; + } + return Interfaces; +} + +typedef struct _WAIT_FOR_INTERFACE_CTX +{ + HANDLE Event; + DWORD LastError; +} WAIT_FOR_INTERFACE_CTX; + +static VOID WINAPI +WaitForInterfaceCallback( + _In_ HDEVQUERY DevQuery, + _Inout_ PVOID Context, + _In_ const DEV_QUERY_RESULT_ACTION_DATA *ActionData) +{ + WAIT_FOR_INTERFACE_CTX *Ctx = Context; + DWORD Ret = ERROR_SUCCESS; + switch (ActionData->Action) + { + case DevQueryResultStateChange: + if (ActionData->Data.State != DevQueryStateAborted) + return; + Ret = ERROR_DEVICE_NOT_AVAILABLE; + case DevQueryResultAdd: + case DevQueryResultUpdate: + break; + default: + return; + } + Ctx->LastError = Ret; + SetEvent(Ctx->Event); +} + +_Must_inspect_result_ +static _Return_type_success_(return != FALSE) +BOOL +WaitForInterface(_In_ WCHAR *InstanceId) +{ + if (IsWindows7) + return TRUE; + + DWORD LastError = ERROR_SUCCESS; + static const DEVPROP_BOOLEAN DevPropTrue = DEVPROP_TRUE; + const DEVPROP_FILTER_EXPRESSION Filters[] = { { .Operator = DEVPROP_OPERATOR_EQUALS_IGNORE_CASE, + .Property.CompKey.Key = DEVPKEY_Device_InstanceId, + .Property.CompKey.Store = DEVPROP_STORE_SYSTEM, + .Property.Type = DEVPROP_TYPE_STRING, + .Property.Buffer = InstanceId, + .Property.BufferSize = + (ULONG)((wcslen(InstanceId) + 1) * sizeof(InstanceId[0])) }, + { .Operator = DEVPROP_OPERATOR_EQUALS, + .Property.CompKey.Key = DEVPKEY_DeviceInterface_Enabled, + .Property.CompKey.Store = DEVPROP_STORE_SYSTEM, + .Property.Type = DEVPROP_TYPE_BOOLEAN, + .Property.Buffer = (PVOID)&DevPropTrue, + .Property.BufferSize = sizeof(DevPropTrue) }, + { .Operator = DEVPROP_OPERATOR_EQUALS, + .Property.CompKey.Key = DEVPKEY_DeviceInterface_ClassGuid, + .Property.CompKey.Store = DEVPROP_STORE_SYSTEM, + .Property.Type = DEVPROP_TYPE_GUID, + .Property.Buffer = (PVOID)&GUID_DEVINTERFACE_NET, + .Property.BufferSize = sizeof(GUID_DEVINTERFACE_NET) } }; + WAIT_FOR_INTERFACE_CTX Ctx = { .Event = CreateEventW(NULL, FALSE, FALSE, NULL) }; + if (!Ctx.Event) + { + LastError = LOG_LAST_ERROR(L"Failed to create event"); + goto cleanup; + } + HDEVQUERY Query; + HRESULT HRet = DevCreateObjectQuery( + DevObjectTypeDeviceInterface, + DevQueryFlagUpdateResults, + 0, + NULL, + _countof(Filters), + Filters, + WaitForInterfaceCallback, + &Ctx, + &Query); + if (FAILED(HRet)) + { + LastError = LOG_ERROR(HRet, L"Failed to create device query"); + goto cleanupEvent; + } + LastError = WaitForSingleObject(Ctx.Event, 15000); + if (LastError != WAIT_OBJECT_0) + { + if (LastError == WAIT_FAILED) + LastError = LOG_LAST_ERROR(L"Failed to wait for device query"); + else + LastError = LOG_ERROR(LastError, L"Timed out waiting for device query"); + goto cleanupQuery; + } + LastError = Ctx.LastError; + if (LastError != ERROR_SUCCESS) + LastError = LOG_ERROR(LastError, L"Failed to get enabled device"); +cleanupQuery: + DevCloseObjectQuery(Query); +cleanupEvent: + CloseHandle(Ctx.Event); +cleanup: + return RET_ERROR(TRUE, LastError); +} + +typedef struct _SW_DEVICE_CREATE_CTX +{ + HRESULT CreateResult; + WCHAR *DeviceInstanceId; + HANDLE Triggered; +} SW_DEVICE_CREATE_CTX; + +static VOID +DeviceCreateCallback( + _In_ HSWDEVICE SwDevice, + _In_ HRESULT CreateResult, + _In_ VOID *Context, + _In_opt_ PCWSTR DeviceInstanceId) +{ + SW_DEVICE_CREATE_CTX *Ctx = Context; + Ctx->CreateResult = CreateResult; + if (DeviceInstanceId) + wcsncpy_s(Ctx->DeviceInstanceId, MAX_DEVICE_ID_LEN, DeviceInstanceId, _TRUNCATE); + SetEvent(Ctx->Triggered); +} + +_Use_decl_annotations_ +WINTUN_ADAPTER_HANDLE WINAPI +WintunCreateAdapter(LPCWSTR Name, LPCWSTR TunnelType, const GUID *RequestedGUID) +{ + DWORD LastError = ERROR_SUCCESS; + WINTUN_ADAPTER *Adapter = NULL; + + HANDLE DeviceInstallationMutex = NamespaceTakeDeviceInstallationMutex(); + if (!DeviceInstallationMutex) + { + LastError = LOG_LAST_ERROR(L"Failed to take device installation mutex"); + goto cleanup; + } + + HDEVINFO DevInfoExistingAdapters; + SP_DEVINFO_DATA_LIST *ExistingAdapters; + if (!DriverInstall(&DevInfoExistingAdapters, &ExistingAdapters)) + { + LastError = GetLastError(); + goto cleanupDeviceInstallationMutex; + } + + LOG(WINTUN_LOG_INFO, L"Creating adapter"); + + Adapter = Zalloc(sizeof(*Adapter)); + if (!Adapter) + goto cleanupDriverInstall; + + WCHAR TunnelTypeName[MAX_ADAPTER_NAME + 8]; + if (_snwprintf_s(TunnelTypeName, _countof(TunnelTypeName), _TRUNCATE, L"%s Tunnel", TunnelType) == -1) + { + LastError = ERROR_BUFFER_OVERFLOW; + goto cleanupAdapter; + } + + DEVINST RootNode; + WCHAR RootNodeName[200 /* rasmans.dll uses 200 hard coded instead of calling CM_Get_Device_ID_Size. */]; + CONFIGRET ConfigRet; + if ((ConfigRet = CM_Locate_DevNodeW(&RootNode, NULL, CM_LOCATE_DEVNODE_NORMAL)) != CR_SUCCESS || + (ConfigRet = CM_Get_Device_IDW(RootNode, RootNodeName, _countof(RootNodeName), 0)) != CR_SUCCESS) + { + LastError = LOG_ERROR(CM_MapCrToWin32Err(ConfigRet, ERROR_GEN_FAILURE), L"Failed to get root node name"); + goto cleanupAdapter; + } + + GUID InstanceId; + HRESULT HRet = S_OK; + if (RequestedGUID) + memcpy(&InstanceId, RequestedGUID, sizeof(InstanceId)); + else + HRet = CoCreateGuid(&InstanceId); + WCHAR InstanceIdStr[MAX_GUID_STRING_LEN]; + if (FAILED(HRet) || !StringFromGUID2(&InstanceId, InstanceIdStr, _countof(InstanceIdStr))) + { + LastError = LOG_ERROR(HRet, L"Failed to convert GUID"); + goto cleanupAdapter; + } + SW_DEVICE_CREATE_CTX CreateContext = { .DeviceInstanceId = Adapter->DevInstanceID, + .Triggered = CreateEventW(NULL, FALSE, FALSE, NULL) }; + if (!CreateContext.Triggered) + { + LastError = LOG_LAST_ERROR(L"Failed to create event trigger"); + goto cleanupAdapter; + } + + if (IsWindows7) + { + if (!CreateAdapterWin7(Adapter, Name, TunnelTypeName)) + { + LastError = GetLastError(); + goto cleanupCreateContext; + } + goto skipSwDevice; + } + if (!IsWindows10) + goto skipStub; + + SW_DEVICE_CREATE_INFO StubCreateInfo = { .cbSize = sizeof(StubCreateInfo), + .pszInstanceId = InstanceIdStr, + .pszzHardwareIds = L"", + .CapabilityFlags = + SWDeviceCapabilitiesSilentInstall | SWDeviceCapabilitiesDriverRequired, + .pszDeviceDescription = TunnelTypeName }; + DEVPROPERTY StubDeviceProperties[] = { { .CompKey = { .Key = DEVPKEY_Device_ClassGuid, + .Store = DEVPROP_STORE_SYSTEM }, + .Type = DEVPROP_TYPE_GUID, + .Buffer = (PVOID)&GUID_DEVCLASS_NET, + .BufferSize = sizeof(GUID_DEVCLASS_NET) } }; + HRet = SwDeviceCreate( + WINTUN_HWID, + RootNodeName, + &StubCreateInfo, + _countof(StubDeviceProperties), + StubDeviceProperties, + DeviceCreateCallback, + &CreateContext, + &Adapter->SwDevice); + if (FAILED(HRet)) + { + LastError = LOG_ERROR(HRet, L"Failed to initiate stub device creation"); + goto cleanupCreateContext; + } + if (WaitForSingleObject(CreateContext.Triggered, INFINITE) != WAIT_OBJECT_0) + { + LastError = LOG_LAST_ERROR(L"Failed to wait for stub device creation trigger"); + goto cleanupCreateContext; + } + if (FAILED(CreateContext.CreateResult)) + { + LastError = LOG_ERROR(CreateContext.CreateResult, L"Failed to create stub device"); + goto cleanupCreateContext; + } + DEVINST DevInst; + CONFIGRET CRet = CM_Locate_DevNodeW(&DevInst, Adapter->DevInstanceID, CM_LOCATE_DEVNODE_PHANTOM); + if (CRet != CR_SUCCESS) + { + LastError = + LOG_ERROR(CM_MapCrToWin32Err(CRet, ERROR_DEVICE_ENUMERATION_ERROR), L"Failed to make stub device list"); + goto cleanupCreateContext; + } + HKEY DriverKey; + CRet = CM_Open_DevNode_Key(DevInst, KEY_SET_VALUE, 0, RegDisposition_OpenAlways, &DriverKey, CM_REGISTRY_SOFTWARE); + if (CRet != CR_SUCCESS) + { + LastError = + LOG_ERROR(CM_MapCrToWin32Err(CRet, ERROR_PNP_REGISTRY_ERROR), L"Failed to create software registry key"); + goto cleanupCreateContext; + } + LastError = + RegSetValueExW(DriverKey, L"SuggestedInstanceId", 0, REG_BINARY, (const BYTE *)&InstanceId, sizeof(InstanceId)); + RegCloseKey(DriverKey); + if (LastError != ERROR_SUCCESS) + { + LastError = LOG_ERROR(LastError, L"Failed to set SuggestedInstanceId to %s", InstanceIdStr); + goto cleanupCreateContext; + } + SwDeviceClose(Adapter->SwDevice); + Adapter->SwDevice = NULL; + +skipStub:; + static const WCHAR Hwids[_countof(WINTUN_HWID) + 1 /*Multi-string terminator*/] = WINTUN_HWID; + SW_DEVICE_CREATE_INFO CreateInfo = { .cbSize = sizeof(CreateInfo), + .pszInstanceId = InstanceIdStr, + .pszzHardwareIds = Hwids, + .CapabilityFlags = + SWDeviceCapabilitiesSilentInstall | SWDeviceCapabilitiesDriverRequired, + .pszDeviceDescription = TunnelTypeName }; + DEVPROPERTY DeviceProperties[] = { + { .CompKey = { .Key = DEVPKEY_Wintun_Name, .Store = DEVPROP_STORE_SYSTEM }, + .Type = DEVPROP_TYPE_STRING, + .Buffer = (WCHAR *)Name, + .BufferSize = (ULONG)((wcslen(Name) + 1) * sizeof(*Name)) }, + { .CompKey = { .Key = DEVPKEY_Device_FriendlyName, .Store = DEVPROP_STORE_SYSTEM }, + .Type = DEVPROP_TYPE_STRING, + .Buffer = TunnelTypeName, + .BufferSize = (ULONG)((wcslen(TunnelTypeName) + 1) * sizeof(*TunnelTypeName)) }, + { .CompKey = { .Key = DEVPKEY_Device_DeviceDesc, .Store = DEVPROP_STORE_SYSTEM }, + .Type = DEVPROP_TYPE_STRING, + .Buffer = TunnelTypeName, + .BufferSize = (ULONG)((wcslen(TunnelTypeName) + 1) * sizeof(*TunnelTypeName)) } + }; + + HRet = SwDeviceCreate( + WINTUN_HWID, + RootNodeName, + &CreateInfo, + _countof(DeviceProperties), + DeviceProperties, + DeviceCreateCallback, + &CreateContext, + &Adapter->SwDevice); + if (FAILED(HRet)) + { + LastError = LOG_ERROR(HRet, L"Failed to initiate device creation"); + goto cleanupCreateContext; + } + if (WaitForSingleObject(CreateContext.Triggered, INFINITE) != WAIT_OBJECT_0) + { + LastError = LOG_LAST_ERROR(L"Failed to wait for device creation trigger"); + goto cleanupCreateContext; + } + if (FAILED(CreateContext.CreateResult)) + { + LastError = LOG_ERROR(CreateContext.CreateResult, L"Failed to create device"); + goto cleanupCreateContext; + } + + if (!WaitForInterface(Adapter->DevInstanceID)) + { + LastError = GetLastError(); + DEVPROPTYPE PropertyType = 0; + NTSTATUS NtStatus = 0; + INT32 ProblemCode = 0; + Adapter->DevInfo = SetupDiCreateDeviceInfoListExW(NULL, NULL, NULL, NULL); + if (Adapter->DevInfo == INVALID_HANDLE_VALUE) + { + Adapter->DevInfo = NULL; + goto cleanupCreateContext; + } + Adapter->DevInfoData.cbSize = sizeof(Adapter->DevInfoData); + if (!SetupDiOpenDeviceInfoW( + Adapter->DevInfo, Adapter->DevInstanceID, NULL, DIOD_INHERIT_CLASSDRVS, &Adapter->DevInfoData)) + { + SetupDiDestroyDeviceInfoList(Adapter->DevInfo); + Adapter->DevInfo = NULL; + goto cleanupCreateContext; + } + if (!SetupDiGetDevicePropertyW( + Adapter->DevInfo, + &Adapter->DevInfoData, + &DEVPKEY_Device_ProblemStatus, + &PropertyType, + (PBYTE)&NtStatus, + sizeof(NtStatus), + NULL, + 0) || + PropertyType != DEVPROP_TYPE_NTSTATUS) + NtStatus = 0; + if (!SetupDiGetDevicePropertyW( + Adapter->DevInfo, + &Adapter->DevInfoData, + &DEVPKEY_Device_ProblemCode, + &PropertyType, + (PBYTE)&ProblemCode, + sizeof(ProblemCode), + NULL, + 0) || + (PropertyType != DEVPROP_TYPE_INT32 && PropertyType != DEVPROP_TYPE_UINT32)) + ProblemCode = 0; + LastError = RtlNtStatusToDosError(NtStatus); + if (LastError == ERROR_SUCCESS) + LastError = ERROR_DEVICE_NOT_AVAILABLE; + LOG_ERROR(LastError, L"Failed to setup adapter (problem code: 0x%X, ntstatus: 0x%X)", ProblemCode, NtStatus); + goto cleanupCreateContext; + } + +skipSwDevice: + Adapter->DevInfo = SetupDiCreateDeviceInfoListExW(&GUID_DEVCLASS_NET, NULL, NULL, NULL); + if (Adapter->DevInfo == INVALID_HANDLE_VALUE) + { + Adapter->DevInfo = NULL; + LastError = LOG_LAST_ERROR(L"Failed to make device list"); + goto cleanupCreateContext; + } + Adapter->DevInfoData.cbSize = sizeof(Adapter->DevInfoData); + if (!SetupDiOpenDeviceInfoW( + Adapter->DevInfo, Adapter->DevInstanceID, NULL, DIOD_INHERIT_CLASSDRVS, &Adapter->DevInfoData)) + { + LastError = LOG_LAST_ERROR(L"Failed to open device instance ID %s", Adapter->DevInstanceID); + SetupDiDestroyDeviceInfoList(Adapter->DevInfo); + Adapter->DevInfo = NULL; + goto cleanupCreateContext; + } + + if (!PopulateAdapterData(Adapter)) + { + LastError = LOG(WINTUN_LOG_ERR, L"Failed to populate adapter data"); + goto cleanupCreateContext; + } + + if (!NciSetAdapterName(&Adapter->CfgInstanceID, Name)) + { + LastError = LOG(WINTUN_LOG_ERR, L"Failed to set adapter name \"%s\"", Name); + goto cleanupCreateContext; + } + + if (IsWindows7) + CreateAdapterPostWin7(Adapter, TunnelTypeName); + +cleanupCreateContext: + CloseHandle(CreateContext.Triggered); +cleanupAdapter: + if (LastError != ERROR_SUCCESS) + { + WintunCloseAdapter(Adapter); + Adapter = NULL; + } +cleanupDriverInstall: + DriverInstallDeferredCleanup(DevInfoExistingAdapters, ExistingAdapters); +cleanupDeviceInstallationMutex: + NamespaceReleaseMutex(DeviceInstallationMutex); +cleanup: + QueueUpOrphanedDeviceCleanupRoutine(); + return RET_ERROR(Adapter, LastError); +} + +_Use_decl_annotations_ +WINTUN_ADAPTER_HANDLE WINAPI +WintunOpenAdapter(LPCWSTR Name) +{ + DWORD LastError = ERROR_SUCCESS; + WINTUN_ADAPTER *Adapter = NULL; + + HANDLE DeviceInstallationMutex = NamespaceTakeDeviceInstallationMutex(); + if (!DeviceInstallationMutex) + { + LastError = LOG_LAST_ERROR(L"Failed to take device installation mutex"); + goto cleanup; + } + + Adapter = Zalloc(sizeof(*Adapter)); + if (!Adapter) + goto cleanupDeviceInstallationMutex; + + HDEVINFO DevInfo = + SetupDiGetClassDevsExW(&GUID_DEVCLASS_NET, WINTUN_ENUMERATOR, NULL, DIGCF_PRESENT, NULL, NULL, NULL); + if (DevInfo == INVALID_HANDLE_VALUE) + { + LastError = LOG_LAST_ERROR(L"Failed to get present adapters"); + goto cleanupAdapter; + } + + SP_DEVINFO_DATA DevInfoData = { .cbSize = sizeof(DevInfoData) }; + BOOL Found = FALSE; + for (DWORD EnumIndex = 0; !Found; ++EnumIndex) + { + if (!SetupDiEnumDeviceInfo(DevInfo, EnumIndex, &DevInfoData)) + { + if (GetLastError() == ERROR_NO_MORE_ITEMS) + break; + continue; + } + + DEVPROPTYPE PropType; + WCHAR OtherName[MAX_ADAPTER_NAME]; + Found = SetupDiGetDevicePropertyW( + DevInfo, + &DevInfoData, + &DEVPKEY_Wintun_Name, + &PropType, + (PBYTE)OtherName, + MAX_ADAPTER_NAME * sizeof(OtherName[0]), + NULL, + 0) && + PropType == DEVPROP_TYPE_STRING && !_wcsicmp(Name, OtherName); + } + if (!Found) + { + LastError = LOG_ERROR(ERROR_NOT_FOUND, L"Failed to find matching adapter name"); + goto cleanupDevInfo; + } + DWORD RequiredChars = _countof(Adapter->DevInstanceID); + if (!SetupDiGetDeviceInstanceIdW(DevInfo, &DevInfoData, Adapter->DevInstanceID, RequiredChars, &RequiredChars)) + { + LastError = LOG_LAST_ERROR(L"Failed to get adapter instance ID"); + goto cleanupDevInfo; + } + Adapter->DevInfo = DevInfo; + Adapter->DevInfoData = DevInfoData; + BOOL Ret = WaitForInterface(Adapter->DevInstanceID) && PopulateAdapterData(Adapter); + Adapter->DevInfo = NULL; + if (!Ret) + { + LastError = LOG_LAST_ERROR(L"Failed to populate adapter"); + goto cleanupDevInfo; + } + +cleanupDevInfo: + SetupDiDestroyDeviceInfoList(DevInfo); +cleanupAdapter: + if (LastError != ERROR_SUCCESS) + { + WintunCloseAdapter(Adapter); + Adapter = NULL; + } +cleanupDeviceInstallationMutex: + NamespaceReleaseMutex(DeviceInstallationMutex); +cleanup: + QueueUpOrphanedDeviceCleanupRoutine(); + return RET_ERROR(Adapter, LastError); +} + +_Use_decl_annotations_ +BOOL +AdapterRemoveInstance(HDEVINFO DevInfo, SP_DEVINFO_DATA *DevInfoData) +{ +#ifdef MAYBE_WOW64 + if (NativeMachine != IMAGE_FILE_PROCESS) + return RemoveInstanceViaRundll32(DevInfo, DevInfoData); +#endif + + SP_REMOVEDEVICE_PARAMS RemoveDeviceParams = { .ClassInstallHeader = { .cbSize = sizeof(SP_CLASSINSTALL_HEADER), + .InstallFunction = DIF_REMOVE }, + .Scope = DI_REMOVEDEVICE_GLOBAL }; + return SetupDiSetClassInstallParamsW( + DevInfo, DevInfoData, &RemoveDeviceParams.ClassInstallHeader, sizeof(RemoveDeviceParams)) && + SetupDiCallClassInstaller(DIF_REMOVE, DevInfo, DevInfoData); +} + +_Use_decl_annotations_ +BOOL +AdapterEnableInstance(HDEVINFO DevInfo, SP_DEVINFO_DATA *DevInfoData) +{ +#ifdef MAYBE_WOW64 + if (NativeMachine != IMAGE_FILE_PROCESS) + return EnableInstanceViaRundll32(DevInfo, DevInfoData); +#endif + + SP_PROPCHANGE_PARAMS Params = { .ClassInstallHeader = { .cbSize = sizeof(SP_CLASSINSTALL_HEADER), + .InstallFunction = DIF_PROPERTYCHANGE }, + .StateChange = DICS_ENABLE, + .Scope = DICS_FLAG_GLOBAL }; + return SetupDiSetClassInstallParamsW(DevInfo, DevInfoData, &Params.ClassInstallHeader, sizeof(Params)) && + SetupDiCallClassInstaller(DIF_PROPERTYCHANGE, DevInfo, DevInfoData); +} + +_Use_decl_annotations_ +BOOL +AdapterDisableInstance(HDEVINFO DevInfo, SP_DEVINFO_DATA *DevInfoData) +{ +#ifdef MAYBE_WOW64 + if (NativeMachine != IMAGE_FILE_PROCESS) + return DisableInstanceViaRundll32(DevInfo, DevInfoData); +#endif + SP_PROPCHANGE_PARAMS Params = { .ClassInstallHeader = { .cbSize = sizeof(SP_CLASSINSTALL_HEADER), + .InstallFunction = DIF_PROPERTYCHANGE }, + .StateChange = DICS_DISABLE, + .Scope = DICS_FLAG_GLOBAL }; + return SetupDiSetClassInstallParamsW(DevInfo, DevInfoData, &Params.ClassInstallHeader, sizeof(Params)) && + SetupDiCallClassInstaller(DIF_PROPERTYCHANGE, DevInfo, DevInfoData); +} diff --git a/api/adapter.h b/api/adapter.h new file mode 100644 index 0000000..cfa22fd --- /dev/null +++ b/api/adapter.h @@ -0,0 +1,137 @@ +/* SPDX-License-Identifier: GPL-2.0 + * + * Copyright (C) 2018-2021 WireGuard LLC. All Rights Reserved. + */ + +#pragma once + +#include "wintun.h" +#include <IPExport.h> +#include <SetupAPI.h> +#include <cfgmgr32.h> +#include <Windows.h> + +#define WINTUN_HWID L"Wintun" +#define WINTUN_ENUMERATOR (IsWindows7 ? L"ROOT\\" WINTUN_HWID : L"SWD\\" WINTUN_HWID) + +extern const DEVPROPKEY DEVPKEY_Wintun_Name; + +typedef struct HSWDEVICE__ *HSWDEVICE; + +/** + * Wintun adapter descriptor. + */ +typedef struct _WINTUN_ADAPTER +{ + HSWDEVICE SwDevice; + HDEVINFO DevInfo; + SP_DEVINFO_DATA DevInfoData; + WCHAR *InterfaceFilename; + GUID CfgInstanceID; + WCHAR DevInstanceID[MAX_DEVICE_ID_LEN]; + DWORD LuidIndex; + DWORD IfType; + DWORD IfIndex; +} WINTUN_ADAPTER; +/** + * @copydoc WINTUN_CREATE_ADAPTER_FUNC + */ +WINTUN_CREATE_ADAPTER_FUNC WintunCreateAdapter; + +/** + * @copydoc WINTUN_OPEN_ADAPTER_FUNC + */ +WINTUN_OPEN_ADAPTER_FUNC WintunOpenAdapter; + +/** + * @copydoc WINTUN_CLOSE_ADAPTER_FUNC + */ +WINTUN_CLOSE_ADAPTER_FUNC WintunCloseAdapter; + +/** + * @copydoc WINTUN_GET_ADAPTER_LUID_FUNC + */ +WINTUN_GET_ADAPTER_LUID_FUNC WintunGetAdapterLUID; + +/** + * Returns a handle to the adapter device object. + * + * @param Adapter Adapter handle obtained with WintunOpenAdapter or WintunCreateAdapter. + * + * @return If the function succeeds, the return value is adapter device object handle. + * If the function fails, the return value is INVALID_HANDLE_VALUE. To get extended error + * information, call GetLastError. + */ +_Return_type_success_(return != INVALID_HANDLE_VALUE) +HANDLE WINAPI +AdapterOpenDeviceObject(_In_ const WINTUN_ADAPTER *Adapter); + +/** + * Returns the device object file name for an adapter instance ID. + * + * @param InstanceID The device instance ID of the adapter. + * + * @return If the function succeeds, the return value is the filename of the device object, which + * must be freed with Free(). If the function fails, the return value is INVALID_HANDLE_VALUE. + * To get extended error information, call GetLastError. + */ +_Must_inspect_result_ +_Return_type_success_(return != NULL) +_Post_maybenull_ +LPWSTR +AdapterGetDeviceObjectFileName(_In_z_ LPCWSTR InstanceId); + +/** + * Cleans up adapters with no attached process. + */ +VOID AdapterCleanupOrphanedDevices(VOID); + +/** + * Cleans up adapters that use the old enumerator. + */ +VOID AdapterCleanupLegacyDevices(VOID); + +/** + * Removes the specified device instance. + * + * @param DevInfo Device info handle from SetupAPI. + * @param DevInfoData Device info data specifying which device. + * + * @return If the function succeeds, the return value is TRUE. If the + * function fails, the return value is FALSE. To get extended + * error information, call GetLastError. + */ + +_Return_type_success_(return != FALSE) +BOOL +AdapterRemoveInstance(_In_ HDEVINFO DevInfo, _In_ SP_DEVINFO_DATA *DevInfoData); + +/** + * Enables the specified device instance. + * + * @param DevInfo Device info handle from SetupAPI. + * @param DevInfoData Device info data specifying which device. + * + * @return If the function succeeds, the return value is TRUE. If the + * function fails, the return value is FALSE. To get extended + * error information, call GetLastError. + */ + +_Return_type_success_(return != FALSE) +BOOL +AdapterEnableInstance(_In_ HDEVINFO DevInfo, _In_ SP_DEVINFO_DATA *DevInfoData); + +/** + * Disables the specified device instance. + * + * @param DevInfo Device info handle from SetupAPI. + * @param DevInfoData Device info data specifying which device. + * + * @return If the function succeeds, the return value is TRUE. If the + * function fails, the return value is FALSE. To get extended + * error information, call GetLastError. + */ + +_Return_type_success_(return != FALSE) +BOOL +AdapterDisableInstance(_In_ HDEVINFO DevInfo, _In_ SP_DEVINFO_DATA *DevInfoData); diff --git a/api/adapter_win7.h b/api/adapter_win7.h new file mode 100644 index 0000000..affbd09 --- /dev/null +++ b/api/adapter_win7.h @@ -0,0 +1,351 @@ +/* SPDX-License-Identifier: GPL-2.0 + * + * Copyright (C) 2018-2021 WireGuard LLC. All Rights Reserved. + */ + +static const DEVPROPKEY DEVPKEY_Wintun_OwningProcess = { + { 0x3361c968, 0x2f2e, 0x4660, { 0xb4, 0x7e, 0x69, 0x9c, 0xdc, 0x4c, 0x32, 0xb9 } }, + DEVPROPID_FIRST_USABLE + 3 +}; + +typedef struct _OWNING_PROCESS +{ + DWORD ProcessId; + FILETIME CreationTime; +} OWNING_PROCESS; + +_Must_inspect_result_ +static _Return_type_success_(return != FALSE) +BOOL +WaitForInterfaceWin7(_In_ HDEVINFO DevInfo, _In_ SP_DEVINFO_DATA *DevInfoData, _In_ LPCWSTR DevInstanceId) +{ + ULONG Status, Number; + DWORD ValType, Zero; + WCHAR *FileName = NULL; + HKEY Key = INVALID_HANDLE_VALUE; + HANDLE FileHandle = INVALID_HANDLE_VALUE; + BOOLEAN Ret = FALSE; + for (DWORD Tries = 0; Tries < 1500; ++Tries) + { + if (Tries) + Sleep(10); + if (Key == INVALID_HANDLE_VALUE) + Key = SetupDiOpenDevRegKey(DevInfo, DevInfoData, DICS_FLAG_GLOBAL, 0, DIREG_DRV, KEY_QUERY_VALUE); + if (!FileName) + FileName = AdapterGetDeviceObjectFileName(DevInstanceId); + if (FileName && FileHandle == INVALID_HANDLE_VALUE) + FileHandle = CreateFileW( + FileName, + GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + NULL, + OPEN_EXISTING, + 0, + NULL); + Zero = 0; + if (FileName && FileHandle != INVALID_HANDLE_VALUE && Key != INVALID_HANDLE_VALUE && Key && + RegQueryValueExW(Key, L"NetCfgInstanceId", NULL, &ValType, NULL, &Zero) != ERROR_MORE_DATA && + CM_Get_DevNode_Status(&Status, &Number, DevInfoData->DevInst, 0) == CR_SUCCESS && + !(Status & DN_HAS_PROBLEM) && !Number) + { + Ret = TRUE; + break; + } + } + if (Key != INVALID_HANDLE_VALUE && Key) + RegCloseKey(Key); + if (FileHandle != INVALID_HANDLE_VALUE) + CloseHandle(FileHandle); + Free(FileName); + return Ret; +} + +_Must_inspect_result_ +static _Return_type_success_(return != FALSE) +BOOL +CreateAdapterWin7(_Inout_ WINTUN_ADAPTER *Adapter, _In_z_ LPCWSTR Name, _In_z_ LPCWSTR TunnelTypeName) +{ + DWORD LastError = ERROR_SUCCESS; + + HDEVINFO DevInfo = SetupDiCreateDeviceInfoListExW(&GUID_DEVCLASS_NET, NULL, NULL, NULL); + if (DevInfo == INVALID_HANDLE_VALUE) + { + LastError = LOG_LAST_ERROR(L"Failed to create empty device information set"); + goto cleanup; + } + SP_DEVINFO_DATA DevInfoData = { .cbSize = sizeof(DevInfoData) }; + +#ifdef MAYBE_WOW64 + if (NativeMachine != IMAGE_FILE_PROCESS) + { + if (!CreateInstanceWin7ViaRundll32(Adapter->DevInstanceID)) + { + LastError = LOG_LAST_ERROR(L"Failed to create device instance"); + goto cleanup; + } + if (!SetupDiOpenDeviceInfoW(DevInfo, Adapter->DevInstanceID, NULL, DIOD_INHERIT_CLASSDRVS, &DevInfoData)) + { + LastError = GetLastError(); + goto cleanupDevInfo; + } + goto resumeAfterInstance; + } +#endif + + if (!SetupDiCreateDeviceInfoW( + DevInfo, WINTUN_HWID, &GUID_DEVCLASS_NET, TunnelTypeName, NULL, DICD_GENERATE_ID, &DevInfoData)) + { + LastError = LOG_LAST_ERROR(L"Failed to create new device information element"); + goto cleanupDevInfo; + } + SP_DEVINSTALL_PARAMS_W DevInstallParams = { .cbSize = sizeof(DevInstallParams) }; + if (!SetupDiGetDeviceInstallParamsW(DevInfo, &DevInfoData, &DevInstallParams)) + { + LastError = LOG_LAST_ERROR(L"Failed to retrieve adapter device installation parameters"); + goto cleanupDevInfo; + } + DevInstallParams.Flags |= DI_QUIETINSTALL; + if (!SetupDiSetDeviceInstallParamsW(DevInfo, &DevInfoData, &DevInstallParams)) + { + LastError = LOG_LAST_ERROR(L"Failed to set adapter device installation parameters"); + goto cleanupDevInfo; + } + if (!SetupDiSetSelectedDevice(DevInfo, &DevInfoData)) + { + LastError = LOG_LAST_ERROR(L"Failed to select adapter device"); + goto cleanupDevInfo; + } + static const WCHAR Hwids[_countof(WINTUN_HWID) + 1 /*Multi-string terminator*/] = WINTUN_HWID; + if (!SetupDiSetDeviceRegistryPropertyW(DevInfo, &DevInfoData, SPDRP_HARDWAREID, (const BYTE *)Hwids, sizeof(Hwids))) + { + LastError = LOG_LAST_ERROR(L"Failed to set adapter hardware ID"); + goto cleanupDevInfo; + } + if (!SetupDiBuildDriverInfoList(DevInfo, &DevInfoData, SPDIT_COMPATDRIVER)) + { + LastError = LOG_LAST_ERROR(L"Failed building adapter driver info list"); + goto cleanupDevInfo; + } + SP_DRVINFO_DATA_W DrvInfoData = { .cbSize = sizeof(SP_DRVINFO_DATA_W) }; + if (!SetupDiEnumDriverInfoW(DevInfo, &DevInfoData, SPDIT_COMPATDRIVER, 0, &DrvInfoData) || + !SetupDiSetSelectedDriverW(DevInfo, &DevInfoData, &DrvInfoData)) + { + LastError = LOG_ERROR(ERROR_DRIVER_INSTALL_BLOCKED, L"Failed to select a driver"); + goto cleanupDriverInfo; + } + + if (!SetupDiCallClassInstaller(DIF_REGISTERDEVICE, DevInfo, &DevInfoData)) + { + LastError = LOG_LAST_ERROR(L"Failed to register adapter device"); + goto cleanupDevInfo; + } + if (!SetupDiCallClassInstaller(DIF_REGISTER_COINSTALLERS, DevInfo, &DevInfoData)) + LOG_LAST_ERROR(L"Failed to register adapter coinstallers"); + if (!SetupDiCallClassInstaller(DIF_INSTALLINTERFACES, DevInfo, &DevInfoData)) + LOG_LAST_ERROR(L"Failed to install adapter interfaces"); + if (!SetupDiCallClassInstaller(DIF_INSTALLDEVICE, DevInfo, &DevInfoData)) + { + LastError = LOG_LAST_ERROR(L"Failed to install adapter device"); + goto cleanupDevice; + } + +#ifdef MAYBE_WOW64 +resumeAfterInstance:; +#endif + + OWNING_PROCESS OwningProcess = { .ProcessId = GetCurrentProcessId() }; + FILETIME Unused; + if (!GetProcessTimes(GetCurrentProcess(), &OwningProcess.CreationTime, &Unused, &Unused, &Unused)) + { + LastError = LOG_LAST_ERROR(L"Failed to get process creation time"); + goto cleanupDevice; + } + + if (!SetupDiSetDeviceRegistryPropertyW( + DevInfo, + &DevInfoData, + SPDRP_FRIENDLYNAME, + (PBYTE)TunnelTypeName, + (DWORD)((wcslen(TunnelTypeName) + 1) * sizeof(TunnelTypeName[0]))) || + !SetupDiSetDeviceRegistryPropertyW( + DevInfo, + &DevInfoData, + SPDRP_DEVICEDESC, + (PBYTE)TunnelTypeName, + (DWORD)((wcslen(TunnelTypeName) + 1) * sizeof(TunnelTypeName[0]))) || + !SetupDiSetDevicePropertyW( + DevInfo, + &DevInfoData, + &DEVPKEY_Wintun_Name, + DEVPROP_TYPE_STRING, + (PBYTE)Name, + (DWORD)((wcslen(Name) + 1) * sizeof(Name[0])), + 0) || + !SetupDiSetDevicePropertyW( + DevInfo, + &DevInfoData, + &DEVPKEY_Wintun_OwningProcess, + DEVPROP_TYPE_BINARY, + (PBYTE)&OwningProcess, + sizeof(OwningProcess), + 0)) + { + LastError = LOG_LAST_ERROR(L"Failed to set device properties"); + goto cleanupDevice; + } + + DWORD RequiredChars = _countof(Adapter->DevInstanceID); + if (!SetupDiGetDeviceInstanceIdW(DevInfo, &DevInfoData, Adapter->DevInstanceID, RequiredChars, &RequiredChars)) + { + LastError = LOG_LAST_ERROR(L"Failed to get adapter instance ID"); + goto cleanupDevice; + } + + if (!WaitForInterfaceWin7(DevInfo, &DevInfoData, Adapter->DevInstanceID)) + { + DEVPROPTYPE PropertyType = 0; + INT32 ProblemCode = 0; + if (!SetupDiGetDevicePropertyW( + DevInfo, + &DevInfoData, + &DEVPKEY_Device_ProblemCode, + &PropertyType, + (PBYTE)&ProblemCode, + sizeof(ProblemCode), + NULL, + 0) || + (PropertyType != DEVPROP_TYPE_INT32 && PropertyType != DEVPROP_TYPE_UINT32)) + ProblemCode = 0; + LastError = LOG_ERROR( + ERROR_DEVICE_REINITIALIZATION_NEEDED, L"Failed to setup adapter (problem code: 0x%x)", ProblemCode); + goto cleanupDevice; + } + +cleanupDevice: + if (LastError != ERROR_SUCCESS) + AdapterRemoveInstance(DevInfo, &DevInfoData); +cleanupDriverInfo: + SetupDiDestroyDriverInfoList(DevInfo, &DevInfoData, SPDIT_COMPATDRIVER); +cleanupDevInfo: + SetupDiDestroyDeviceInfoList(DevInfo); +cleanup: + return RET_ERROR(TRUE, LastError); +} + +static VOID +CreateAdapterPostWin7(_Inout_ WINTUN_ADAPTER *Adapter, _In_z_ LPCWSTR TunnelTypeName) +{ + SetupDiSetDeviceRegistryPropertyW( + Adapter->DevInfo, + &Adapter->DevInfoData, + SPDRP_FRIENDLYNAME, + (PBYTE)TunnelTypeName, + (DWORD)((wcslen(TunnelTypeName) + 1) * sizeof(TunnelTypeName[0]))); + SetupDiSetDeviceRegistryPropertyW( + Adapter->DevInfo, + &Adapter->DevInfoData, + SPDRP_DEVICEDESC, + (PBYTE)TunnelTypeName, + (DWORD)((wcslen(TunnelTypeName) + 1) * sizeof(TunnelTypeName[0]))); +} + +static BOOL +ProcessIsStale(_In_ OWNING_PROCESS *OwningProcess) +{ + HANDLE Process = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, OwningProcess->ProcessId); + if (!Process) + return TRUE; + FILETIME CreationTime, Unused; + BOOL Ret = GetProcessTimes(Process, &CreationTime, &Unused, &Unused, &Unused); + CloseHandle(Process); + if (!Ret) + return FALSE; + return !!memcmp(&CreationTime, &OwningProcess->CreationTime, sizeof(CreationTime)); +} + +VOID AdapterCleanupOrphanedDevicesWin7(VOID) +{ + HDEVINFO DevInfo = SetupDiGetClassDevsExW(&GUID_DEVCLASS_NET, WINTUN_ENUMERATOR, NULL, 0, NULL, NULL, NULL); + if (DevInfo == INVALID_HANDLE_VALUE) + { + if (GetLastError() != ERROR_INVALID_DATA) + LOG_LAST_ERROR(L"Failed to get adapters"); + return; + } + + SP_DEVINFO_DATA DevInfoData = { .cbSize = sizeof(DevInfoData) }; + for (DWORD EnumIndex = 0;; ++EnumIndex) + { + if (!SetupDiEnumDeviceInfo(DevInfo, EnumIndex, &DevInfoData)) + { + if (GetLastError() == ERROR_NO_MORE_ITEMS) + break; + continue; + } + + OWNING_PROCESS OwningProcess; + DEVPROPTYPE PropType; + if (SetupDiGetDevicePropertyW( + DevInfo, + &DevInfoData, + &DEVPKEY_Wintun_OwningProcess, + &PropType, + (PBYTE)&OwningProcess, + sizeof(OwningProcess), + NULL, + 0) && + PropType == DEVPROP_TYPE_BINARY && !ProcessIsStale(&OwningProcess)) + continue; + + WCHAR Name[MAX_ADAPTER_NAME] = L"<unknown>"; + SetupDiGetDevicePropertyW( + DevInfo, + &DevInfoData, + &DEVPKEY_Wintun_Name, + &PropType, + (PBYTE)Name, + MAX_ADAPTER_NAME * sizeof(Name[0]), + NULL, + 0); + if (!AdapterRemoveInstance(DevInfo, &DevInfoData)) + { + LOG_LAST_ERROR(L"Failed to remove orphaned adapter \"%s\"", Name); + continue; + } + LOG(WINTUN_LOG_INFO, L"Removed orphaned adapter \"%s\"", Name); + } + SetupDiDestroyDeviceInfoList(DevInfo); +} + +VOID AdapterCleanupLegacyDevices(VOID) +{ + HDEVINFO DevInfo = SetupDiGetClassDevsExW(&GUID_DEVCLASS_NET, L"ROOT\\NET", NULL, 0, NULL, NULL, NULL); + if (DevInfo == INVALID_HANDLE_VALUE) + return; + SP_DEVINFO_DATA DevInfoData = { .cbSize = sizeof(DevInfoData) }; + for (DWORD EnumIndex = 0;; ++EnumIndex) + { + if (!SetupDiEnumDeviceInfo(DevInfo, EnumIndex, &DevInfoData)) + { + if (GetLastError() == ERROR_NO_MORE_ITEMS) + break; + continue; + } + WCHAR HardwareIDs[0x400] = { 0 }; + DWORD ValueType, Size = sizeof(HardwareIDs) - sizeof(HardwareIDs[0]); + if (!SetupDiGetDeviceRegistryPropertyW( + DevInfo, &DevInfoData, SPDRP_HARDWAREID, &ValueType, (PBYTE)HardwareIDs, Size, &Size) || + Size > sizeof(HardwareIDs) - sizeof(HardwareIDs[0])) + continue; + Size /= sizeof(HardwareIDs[0]); + for (WCHAR *P = HardwareIDs; P < HardwareIDs + Size; P += wcslen(P) + 1) + { + if (!_wcsicmp(P, WINTUN_HWID)) + { + AdapterRemoveInstance(DevInfo, &DevInfoData); + break; + } + } + } + SetupDiDestroyDeviceInfoList(DevInfo); +}
\ No newline at end of file diff --git a/api/api.vcxproj b/api/api.vcxproj new file mode 100644 index 0000000..d0d0dcb --- /dev/null +++ b/api/api.vcxproj @@ -0,0 +1,95 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <PropertyGroup Label="Globals"> + <ProjectGuid>{897F02E3-3EAA-40AF-A6DC-17EB2376EDAF}</ProjectGuid> + <RootNamespace>api</RootNamespace> + <ProjectName>api</ProjectName> + </PropertyGroup> + <PropertyGroup Label="Configuration"> + <ConfigurationType>DynamicLibrary</ConfigurationType> + <PlatformToolset>WindowsApplicationForDrivers10.0</PlatformToolset> + </PropertyGroup> + <Import Project="..\wintun.props" /> + <PropertyGroup> + <TargetName>wintun</TargetName> + <IgnoreImportLibrary>true</IgnoreImportLibrary> + </PropertyGroup> + <ItemDefinitionGroup> + <ClCompile> + <PreprocessorDefinitions>_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <PreprocessorDefinitions Condition="'$(Platform)'=='Win32'">MAYBE_WOW64;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <PreprocessorDefinitions Condition="'$(Platform)'=='x64'">MAYBE_WOW64;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <PreprocessorDefinitions Condition="'$(Platform)'=='ARM'">MAYBE_WOW64;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <PreprocessorDefinitions Condition="'$(Platform)'=='ARM64'">%(PreprocessorDefinitions)</PreprocessorDefinitions> + <AdditionalOptions>/volatile:iso %(AdditionalOptions)</AdditionalOptions> + <DisableSpecificWarnings>4100;4201;$(DisableSpecificWarnings)</DisableSpecificWarnings> + <AdditionalIncludeDirectories>$(IntDir);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + </ClCompile> + <ResourceCompile> + <AdditionalIncludeDirectories>..\$(Configuration)\$(WintunPlatform);..\$(Configuration);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <PreprocessorDefinitions Condition="Exists('..\$(Configuration)\arm64\driver\wintun.sys') And Exists('..\$(Configuration)\arm64\setupapihost.dll')">BUILT_ARM64_WOW64;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <PreprocessorDefinitions Condition="Exists('..\$(Configuration)\amd64\driver\wintun.sys') And Exists('..\$(Configuration)\amd64\setupapihost.dll')">BUILT_AMD64_WOW64;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <PreprocessorDefinitions Condition="'$(Platform)'=='Win32'">WANT_ARM64_WOW64;WANT_AMD64_WOW64;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <PreprocessorDefinitions Condition="'$(Platform)'=='x64'">WANT_ARM64_WOW64;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <PreprocessorDefinitions Condition="'$(Platform)'=='ARM'">WANT_ARM64_WOW64;%(PreprocessorDefinitions)</PreprocessorDefinitions> + </ResourceCompile> + <Link> + <DelayLoadDLLs>advapi32.dll;api-ms-win-devices-query-l1-1-0.dll;api-ms-win-devices-swdevice-l1-1-0.dll;cfgmgr32.dll;iphlpapi.dll;ole32.dll;nci.dll;setupapi.dll;shlwapi.dll;version.dll</DelayLoadDLLs> + <DelayLoadDLLs Condition="'$(Platform)'!='ARM64'">shell32.dll;%(DelayLoadDLLs)</DelayLoadDLLs> + <AdditionalDependencies>Cfgmgr32.lib;Iphlpapi.lib;onecore.lib;$(IntDir)nci.lib;ntdll.lib;Setupapi.lib;shlwapi.lib;swdevice.lib;version.lib;%(AdditionalDependencies)</AdditionalDependencies> + <ModuleDefinitionFile>exports.def</ModuleDefinitionFile> + <SubSystem>Windows</SubSystem> + </Link> + </ItemDefinitionGroup> + <ItemGroup> + <ResourceCompile Include="resources.rc" /> + </ItemGroup> + <ItemGroup> + <None Include="exports.def" /> + <None Include="nci.def" /> + </ItemGroup> + <ItemGroup> + <ClInclude Include="adapter_win7.h" /> + <ClInclude Include="main.h" /> + <ClInclude Include="adapter.h" /> + <ClInclude Include="driver.h" /> + <ClInclude Include="logger.h" /> + <ClInclude Include="namespace.h" /> + <ClInclude Include="nci.h" /> + <ClInclude Include="ntdll.h" /> + <ClInclude Include="registry.h" /> + <ClInclude Include="resource.h" /> + <ClInclude Include="rundll32.h" /> + <ClInclude Include="wintun.h" /> + </ItemGroup> + <ItemGroup> + <ClCompile Include="main.c" /> + <ClCompile Include="adapter.c" /> + <ClCompile Include="driver.c" /> + <ClCompile Include="logger.c" /> + <ClCompile Include="namespace.c" /> + <ClCompile Include="registry.c" /> + <ClCompile Include="resource.c" /> + <ClCompile Include="session.c" /> + <ClCompile Include="rundll32.c" /> + </ItemGroup> + <Import Project="..\wintun.props.user" Condition="exists('..\wintun.props.user')" /> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" /> + <ImportGroup Label="ExtensionTargets" /> + <PropertyGroup> + <CleanDependsOn>CleanInfVersion;CleanNci;$(CleanDependsOn)</CleanDependsOn> + </PropertyGroup> + <Target Name="BuildInfVersion" BeforeTargets="ClCompile" Inputs="$(OutDir)driver\wintun.inf" Outputs="$(IntDir)wintun-inf.h"> + <Exec Command="cscript.exe /nologo "extract-driverver.js" < "$(OutDir)driver\wintun.inf" > "$(IntDir)wintun-inf.h"" /> + </Target> + <Target Name="CleanInfVersion"> + <Delete Files="$(IntDir)wintun-inf.h" /> + </Target> + <Target Name="BuildNci" BeforeTargets="Link" Inputs="$(ProjectDir)nci.def;$(ProjectDir)nci.h" Outputs="$(IntDir)nci.lib"> + <Exec Command="cl.exe /nologo /DGENERATE_LIB /Ob0 /c /Fo"$(IntDir)nci.obj" /Tc "nci.h"" /> + <Exec Command="lib.exe /def:"$(ProjectDir)nci.def" /out:"$(IntDir)nci.lib" /machine:$(PlatformTarget) /nologo "$(IntDir)nci.obj"" /> + </Target> + <Target Name="CleanNci"> + <Delete Files="$(IntDir)nci.obj;$(IntDir)nci.lib" /> + </Target> +</Project> diff --git a/api/api.vcxproj.filters b/api/api.vcxproj.filters new file mode 100644 index 0000000..5fb8b10 --- /dev/null +++ b/api/api.vcxproj.filters @@ -0,0 +1,97 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <ItemGroup> + <Filter Include="Source Files"> + <UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier> + <Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions> + </Filter> + <Filter Include="Header Files"> + <UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier> + <Extensions>h;hh;hpp;hxx;hm;inl;inc;ipp;xsd</Extensions> + </Filter> + <Filter Include="Resource Files"> + <UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier> + <Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions> + </Filter> + </ItemGroup> + <ItemGroup> + <ResourceCompile Include="resources.rc"> + <Filter>Resource Files</Filter> + </ResourceCompile> + </ItemGroup> + <ItemGroup> + <None Include="exports.def"> + <Filter>Source Files</Filter> + </None> + <None Include="nci.def"> + <Filter>Source Files</Filter> + </None> + </ItemGroup> + <ItemGroup> + <ClInclude Include="nci.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="namespace.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="registry.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="logger.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="resource.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="adapter.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="wintun.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="main.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="ntdll.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="rundll32.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="driver.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="adapter_win7.h"> + <Filter>Header Files</Filter> + </ClInclude> + </ItemGroup> + <ItemGroup> + <ClCompile Include="namespace.c"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="rundll32.c"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="logger.c"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="resource.c"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="adapter.c"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="session.c"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="main.c"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="driver.c"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="registry.c"> + <Filter>Source Files</Filter> + </ClCompile> + </ItemGroup> +</Project> diff --git a/api/driver.c b/api/driver.c new file mode 100644 index 0000000..4ed0d76 --- /dev/null +++ b/api/driver.c @@ -0,0 +1,519 @@ +/* SPDX-License-Identifier: GPL-2.0 + * + * Copyright (C) 2018-2021 WireGuard LLC. All Rights Reserved. + */ + +#include <Windows.h> +#include <winternl.h> +#include <cfgmgr32.h> +#include <SetupAPI.h> +#include <devguid.h> +#include <ndisguid.h> +#include <Shlwapi.h> +#include <shellapi.h> +#include <wchar.h> + +#include "driver.h" +#include "adapter.h" +#include "logger.h" +#include "namespace.h" +#include "resource.h" +#include "registry.h" +#include "ntdll.h" +#include "rundll32.h" +#include "wintun-inf.h" + +#pragma warning(disable : 4221) /* nonstandard: address of automatic in initializer */ + +struct _SP_DEVINFO_DATA_LIST +{ + SP_DEVINFO_DATA Data; + struct _SP_DEVINFO_DATA_LIST *Next; +}; + +static _Return_type_success_(return != FALSE) +BOOL +DisableAllOurAdapters(_In_ HDEVINFO DevInfo, _Inout_ SP_DEVINFO_DATA_LIST **DisabledAdapters) +{ + DWORD LastError = ERROR_SUCCESS; + for (DWORD EnumIndex = 0;; ++EnumIndex) + { + SP_DEVINFO_DATA_LIST *DeviceNode = Zalloc(sizeof(*DeviceNode)); + if (!DeviceNode) + return FALSE; + DeviceNode->Data.cbSize = sizeof(SP_DEVINFO_DATA); + if (!SetupDiEnumDeviceInfo(DevInfo, EnumIndex, &DeviceNode->Data)) + { + if (GetLastError() == ERROR_NO_MORE_ITEMS) + { + Free(DeviceNode); + break; + } + goto cleanupDeviceNode; + } + + DEVPROPTYPE PropType; + WCHAR Name[MAX_ADAPTER_NAME] = L"<unknown>"; + SetupDiGetDevicePropertyW( + DevInfo, + &DeviceNode->Data, + &DEVPKEY_Wintun_Name, + &PropType, + (PBYTE)Name, + MAX_ADAPTER_NAME * sizeof(Name[0]), + NULL, + 0); + + ULONG Status, ProblemCode; + if (CM_Get_DevNode_Status(&Status, &ProblemCode, DeviceNode->Data.DevInst, 0) != CR_SUCCESS || + ((Status & DN_HAS_PROBLEM) && ProblemCode == CM_PROB_DISABLED)) + goto cleanupDeviceNode; + + LOG(WINTUN_LOG_INFO, L"Disabling adapter \"%s\"", Name); + if (!AdapterDisableInstance(DevInfo, &DeviceNode->Data)) + { + LOG_LAST_ERROR(L"Failed to disable adapter \"%s\"", Name); + LastError = LastError != ERROR_SUCCESS ? LastError : GetLastError(); + goto cleanupDeviceNode; + } + + DeviceNode->Next = *DisabledAdapters; + *DisabledAdapters = DeviceNode; + continue; + + cleanupDeviceNode: + Free(DeviceNode); + } + return RET_ERROR(TRUE, LastError); +} + +static _Return_type_success_(return != FALSE) +BOOL +EnableAllOurAdapters(_In_ HDEVINFO DevInfo, _In_ SP_DEVINFO_DATA_LIST *AdaptersToEnable) +{ + DWORD LastError = ERROR_SUCCESS; + for (SP_DEVINFO_DATA_LIST *DeviceNode = AdaptersToEnable; DeviceNode; DeviceNode = DeviceNode->Next) + { + DEVPROPTYPE PropType; + WCHAR Name[MAX_ADAPTER_NAME] = L"<unknown>"; + SetupDiGetDevicePropertyW( + DevInfo, + &DeviceNode->Data, + &DEVPKEY_Wintun_Name, + &PropType, + (PBYTE)Name, + MAX_ADAPTER_NAME * sizeof(Name[0]), + NULL, + 0); + + LOG(WINTUN_LOG_INFO, L"Enabling adapter \"%s\"", Name); + if (!AdapterEnableInstance(DevInfo, &DeviceNode->Data)) + { + LOG_LAST_ERROR(L"Failed to enable adapter \"%s\"", Name); + LastError = LastError != ERROR_SUCCESS ? LastError : GetLastError(); + } + } + return RET_ERROR(TRUE, LastError); +} + +static BOOL +IsNewer( + _In_ const FILETIME *DriverDate1, + _In_ DWORDLONG DriverVersion1, + _In_ const FILETIME *DriverDate2, + _In_ DWORDLONG DriverVersion2) +{ + if (DriverDate1->dwHighDateTime > DriverDate2->dwHighDateTime) + return TRUE; + if (DriverDate1->dwHighDateTime < DriverDate2->dwHighDateTime) + return FALSE; + + if (DriverDate1->dwLowDateTime > DriverDate2->dwLowDateTime) + return TRUE; + if (DriverDate1->dwLowDateTime < DriverDate2->dwLowDateTime) + return FALSE; + + if (DriverVersion1 > DriverVersion2) + return TRUE; + if (DriverVersion1 < DriverVersion2) + return FALSE; + + return FALSE; +} + +static _Return_type_success_(return != 0) +DWORD +VersionOfFile(_In_z_ LPCWSTR Filename) +{ + DWORD Zero; + DWORD Len = GetFileVersionInfoSizeW(Filename, &Zero); + if (!Len) + { + LOG_LAST_ERROR(L"Failed to query %s version info size", Filename); + return 0; + } + VOID *VersionInfo = Alloc(Len); + if (!VersionInfo) + return 0; + DWORD LastError = ERROR_SUCCESS, Version = 0; + VS_FIXEDFILEINFO *FixedInfo; + UINT FixedInfoLen = sizeof(*FixedInfo); + if (!GetFileVersionInfoW(Filename, 0, Len, VersionInfo)) + { + LastError = LOG_LAST_ERROR(L"Failed to get %s version info", Filename); + goto out; + } + if (!VerQueryValueW(VersionInfo, L"\\", &FixedInfo, &FixedInfoLen)) + { + LastError = LOG_LAST_ERROR(L"Failed to get %s version info root", Filename); + goto out; + } + Version = FixedInfo->dwFileVersionMS; + if (!Version) + { + LOG(WINTUN_LOG_WARN, L"Determined version of %s, but was v0.0, so returning failure", Filename); + LastError = ERROR_VERSION_PARSE_ERROR; + } +out: + Free(VersionInfo); + return RET_ERROR(Version, LastError); +} + +static DWORD WINAPI +MaybeGetRunningDriverVersion(BOOL ReturnOneIfRunningInsteadOfVersion) +{ + PRTL_PROCESS_MODULES Modules; + ULONG BufferSize = 128 * 1024; + for (;;) + { + Modules = Alloc(BufferSize); + if (!Modules) + return 0; + NTSTATUS Status = NtQuerySystemInformation(SystemModuleInformation, Modules, BufferSize, &BufferSize); + if (NT_SUCCESS(Status)) + break; + Free(Modules); + if (Status == STATUS_INFO_LENGTH_MISMATCH) + continue; + LOG(WINTUN_LOG_ERR, L"Failed to enumerate drivers (status: 0x%x)", Status); + SetLastError(RtlNtStatusToDosError(Status)); + return 0; + } + DWORD LastError = ERROR_SUCCESS, Version = 0; + for (ULONG i = Modules->NumberOfModules; i-- > 0;) + { + LPCSTR NtPath = (LPCSTR)Modules->Modules[i].FullPathName; + if (!_stricmp(&NtPath[Modules->Modules[i].OffsetToFileName], "wintun.sys")) + { + if (ReturnOneIfRunningInsteadOfVersion) + { + Version = 1; + goto cleanupModules; + } + WCHAR FilePath[MAX_PATH * 3 + 15]; + if (_snwprintf_s(FilePath, _countof(FilePath), _TRUNCATE, L"\\\\?\\GLOBALROOT%S", NtPath) == -1) + continue; + Version = VersionOfFile(FilePath); + if (!Version) + LastError = GetLastError(); + goto cleanupModules; + } + } + LastError = ERROR_FILE_NOT_FOUND; +cleanupModules: + Free(Modules); + return RET_ERROR(Version, LastError); +} + +_Use_decl_annotations_ +DWORD WINAPI WintunGetRunningDriverVersion(VOID) +{ + return MaybeGetRunningDriverVersion(FALSE); +} + +static BOOL EnsureWintunUnloaded(VOID) +{ + BOOL Loaded; + for (DWORD Tries = 0; Tries < 1500; ++Tries) + { + if (Tries) + Sleep(50); + Loaded = MaybeGetRunningDriverVersion(TRUE) != 0; + if (!Loaded) + break; + } + return !Loaded; +} + +_Use_decl_annotations_ +VOID +DriverInstallDeferredCleanup(HDEVINFO DevInfoExistingAdapters, SP_DEVINFO_DATA_LIST *ExistingAdapters) +{ + if (ExistingAdapters) + { + EnableAllOurAdapters(DevInfoExistingAdapters, ExistingAdapters); + while (ExistingAdapters) + { + SP_DEVINFO_DATA_LIST *Next = ExistingAdapters->Next; + Free(ExistingAdapters); + ExistingAdapters = Next; + } + } + if (DevInfoExistingAdapters != INVALID_HANDLE_VALUE) + SetupDiDestroyDeviceInfoList(DevInfoExistingAdapters); +} + +_Use_decl_annotations_ +BOOL +DriverInstall(HDEVINFO *DevInfoExistingAdaptersForCleanup, SP_DEVINFO_DATA_LIST **ExistingAdaptersForCleanup) +{ + static const FILETIME OurDriverDate = WINTUN_INF_FILETIME; + static const DWORDLONG OurDriverVersion = WINTUN_INF_VERSION; + HANDLE DriverInstallationLock = NamespaceTakeDriverInstallationMutex(); + if (!DriverInstallationLock) + { + LOG(WINTUN_LOG_ERR, L"Failed to take driver installation mutex"); + return FALSE; + } + DWORD LastError = ERROR_SUCCESS; + HDEVINFO DevInfo = SetupDiCreateDeviceInfoListExW(&GUID_DEVCLASS_NET, NULL, NULL, NULL); + if (DevInfo == INVALID_HANDLE_VALUE) + { + LastError = LOG_LAST_ERROR(L"Failed to create empty device information set"); + goto cleanupDriverInstallationLock; + } + SP_DEVINFO_DATA DevInfoData = { .cbSize = sizeof(DevInfoData) }; + if (!SetupDiCreateDeviceInfoW(DevInfo, WINTUN_HWID, &GUID_DEVCLASS_NET, NULL, NULL, DICD_GENERATE_ID, &DevInfoData)) + { + LastError = LOG_LAST_ERROR(L"Failed to create new device information element"); + goto cleanupDevInfo; + } + static const WCHAR Hwids[_countof(WINTUN_HWID) + 1 /*Multi-string terminator*/] = WINTUN_HWID; + if (!SetupDiSetDeviceRegistryPropertyW(DevInfo, &DevInfoData, SPDRP_HARDWAREID, (const BYTE *)Hwids, sizeof(Hwids))) + { + LastError = LOG_LAST_ERROR(L"Failed to set adapter hardware ID"); + goto cleanupDevInfo; + } + if (!SetupDiBuildDriverInfoList(DevInfo, &DevInfoData, SPDIT_COMPATDRIVER)) + { + LastError = LOG_LAST_ERROR(L"Failed building adapter driver info list"); + goto cleanupDevInfo; + } + FILETIME DriverDate = { 0 }; + DWORDLONG DriverVersion = 0; + HDEVINFO DevInfoExistingAdapters = INVALID_HANDLE_VALUE; + SP_DEVINFO_DATA_LIST *ExistingAdapters = NULL; + for (DWORD EnumIndex = 0;; ++EnumIndex) + { + SP_DRVINFO_DATA_W DrvInfoData = { .cbSize = sizeof(SP_DRVINFO_DATA_W) }; + if (!SetupDiEnumDriverInfoW(DevInfo, &DevInfoData, SPDIT_COMPATDRIVER, EnumIndex, &DrvInfoData)) + { + if (GetLastError() == ERROR_NO_MORE_ITEMS) + break; + continue; + } + if (IsNewer(&OurDriverDate, OurDriverVersion, &DrvInfoData.DriverDate, DrvInfoData.DriverVersion)) + { + if (DevInfoExistingAdapters == INVALID_HANDLE_VALUE) + { + DevInfoExistingAdapters = SetupDiGetClassDevsExW( + &GUID_DEVCLASS_NET, WINTUN_ENUMERATOR, NULL, DIGCF_PRESENT, NULL, NULL, NULL); + if (DevInfoExistingAdapters == INVALID_HANDLE_VALUE) + { + LastError = LOG_LAST_ERROR(L"Failed to get present adapters"); + SetupDiDestroyDriverInfoList(DevInfo, &DevInfoData, SPDIT_COMPATDRIVER); + goto cleanupExistingAdapters; + } + _Analysis_assume_(DevInfoExistingAdapters != NULL); + DisableAllOurAdapters(DevInfoExistingAdapters, &ExistingAdapters); + LOG(WINTUN_LOG_INFO, L"Waiting for existing driver to unload from kernel"); + if (!EnsureWintunUnloaded()) + LOG(WINTUN_LOG_WARN, + L"Failed to unload existing driver, which means a reboot will likely be required"); + } + LOG(WINTUN_LOG_INFO, + L"Removing existing driver %u.%u", + (DWORD)((DrvInfoData.DriverVersion & 0xffff000000000000) >> 48), + (DWORD)((DrvInfoData.DriverVersion & 0x0000ffff00000000) >> 32)); + BYTE LargeBuffer[0x2000]; + DWORD Size = sizeof(LargeBuffer); + SP_DRVINFO_DETAIL_DATA_W *DrvInfoDetailData = (SP_DRVINFO_DETAIL_DATA_W *)LargeBuffer; + DrvInfoDetailData->cbSize = sizeof(SP_DRVINFO_DETAIL_DATA_W); + if (!SetupDiGetDriverInfoDetailW(DevInfo, &DevInfoData, &DrvInfoData, DrvInfoDetailData, Size, &Size)) + { + LOG(WINTUN_LOG_WARN, L"Failed getting adapter driver info detail"); + continue; + } + LPWSTR InfFileName = PathFindFileNameW(DrvInfoDetailData->InfFileName); + if (!SetupUninstallOEMInfW(InfFileName, SUOI_FORCEDELETE, NULL)) + LOG_LAST_ERROR(L"Unable to remove existing driver %s", InfFileName); + continue; + } + if (!IsNewer(&DrvInfoData.DriverDate, DrvInfoData.DriverVersion, &DriverDate, DriverVersion)) + continue; + DriverDate = DrvInfoData.DriverDate; + DriverVersion = DrvInfoData.DriverVersion; + } + SetupDiDestroyDriverInfoList(DevInfo, &DevInfoData, SPDIT_COMPATDRIVER); + + if (DriverVersion) + { + LOG(WINTUN_LOG_INFO, + L"Using existing driver %u.%u", + (DWORD)((DriverVersion & 0xffff000000000000) >> 48), + (DWORD)((DriverVersion & 0x0000ffff00000000) >> 32)); + LastError = ERROR_SUCCESS; + goto cleanupExistingAdapters; + } + + LOG(WINTUN_LOG_INFO, + L"Installing driver %u.%u", + (DWORD)((OurDriverVersion & 0xffff000000000000) >> 48), + (DWORD)((OurDriverVersion & 0x0000ffff00000000) >> 32)); + WCHAR RandomTempSubDirectory[MAX_PATH]; + if (!ResourceCreateTemporaryDirectory(RandomTempSubDirectory)) + { + LastError = LOG_LAST_ERROR(L"Failed to create temporary folder %s", RandomTempSubDirectory); + goto cleanupExistingAdapters; + } + + WCHAR CatPath[MAX_PATH] = { 0 }; + WCHAR SysPath[MAX_PATH] = { 0 }; + WCHAR InfPath[MAX_PATH] = { 0 }; + if (!PathCombineW(CatPath, RandomTempSubDirectory, L"wintun.cat") || + !PathCombineW(SysPath, RandomTempSubDirectory, L"wintun.sys") || + !PathCombineW(InfPath, RandomTempSubDirectory, L"wintun.inf")) + { + LastError = ERROR_BUFFER_OVERFLOW; + goto cleanupDirectory; + } + + WCHAR *CatSource, *SysSource, *InfSource; + if (NativeMachine == IMAGE_FILE_PROCESS) + { + CatSource = L"wintun.cat"; + SysSource = L"wintun.sys"; + InfSource = L"wintun.inf"; + } + else if (NativeMachine == IMAGE_FILE_MACHINE_AMD64) + { + CatSource = L"wintun-amd64.cat"; + SysSource = L"wintun-amd64.sys"; + InfSource = L"wintun-amd64.inf"; + } + else if (NativeMachine == IMAGE_FILE_MACHINE_ARM64) + { + CatSource = L"wintun-arm64.cat"; + SysSource = L"wintun-arm64.sys"; + InfSource = L"wintun-arm64.inf"; + } + else + { + LastError = LOG_ERROR(ERROR_NOT_SUPPORTED, L"Unsupported platform 0x%x", NativeMachine); + goto cleanupDirectory; + } + + LOG(WINTUN_LOG_INFO, L"Extracting driver"); + if (!ResourceCopyToFile(CatPath, CatSource) || !ResourceCopyToFile(SysPath, SysSource) || + !ResourceCopyToFile(InfPath, InfSource)) + { + LastError = LOG_LAST_ERROR(L"Failed to extract driver"); + goto cleanupDelete; + } + + LOG(WINTUN_LOG_INFO, L"Installing driver"); + if (!SetupCopyOEMInfW(InfPath, NULL, SPOST_NONE, 0, NULL, 0, NULL, NULL)) + LastError = LOG_LAST_ERROR(L"Could not install driver %s to store", InfPath); + +cleanupDelete: + DeleteFileW(CatPath); + DeleteFileW(SysPath); + DeleteFileW(InfPath); +cleanupDirectory: + RemoveDirectoryW(RandomTempSubDirectory); +cleanupExistingAdapters: + if (LastError == ERROR_SUCCESS) + { + *DevInfoExistingAdaptersForCleanup = DevInfoExistingAdapters; + *ExistingAdaptersForCleanup = ExistingAdapters; + } + else + DriverInstallDeferredCleanup(DevInfoExistingAdapters, ExistingAdapters); +cleanupDevInfo: + SetupDiDestroyDeviceInfoList(DevInfo); +cleanupDriverInstallationLock: + NamespaceReleaseMutex(DriverInstallationLock); + return RET_ERROR(TRUE, LastError); +} + +_Use_decl_annotations_ +BOOL WINAPI WintunDeleteDriver(VOID) +{ + DWORD LastError = ERROR_SUCCESS; + + AdapterCleanupOrphanedDevices(); + + HANDLE DriverInstallationLock = NamespaceTakeDriverInstallationMutex(); + if (!DriverInstallationLock) + { + LastError = LOG(WINTUN_LOG_ERR, L"Failed to take driver installation mutex"); + goto cleanup; + } + + HDEVINFO DevInfo = SetupDiCreateDeviceInfoListExW(&GUID_DEVCLASS_NET, NULL, NULL, NULL); + if (DevInfo == INVALID_HANDLE_VALUE) + { + LastError = LOG_LAST_ERROR(L"Failed to create empty device information set"); + goto cleanupDriverInstallationLock; + } + SP_DEVINFO_DATA DevInfoData = { .cbSize = sizeof(DevInfoData) }; + if (!SetupDiCreateDeviceInfoW(DevInfo, WINTUN_HWID, &GUID_DEVCLASS_NET, NULL, NULL, DICD_GENERATE_ID, &DevInfoData)) + { + LastError = LOG_LAST_ERROR(L"Failed to create new device information element"); + goto cleanupDevInfo; + } + static const WCHAR Hwids[_countof(WINTUN_HWID) + 1 /*Multi-string terminator*/] = WINTUN_HWID; + if (!SetupDiSetDeviceRegistryPropertyW(DevInfo, &DevInfoData, SPDRP_HARDWAREID, (const BYTE *)Hwids, sizeof(Hwids))) + { + LastError = LOG_LAST_ERROR(L"Failed to set adapter hardware ID"); + goto cleanupDevInfo; + } + if (!SetupDiBuildDriverInfoList(DevInfo, &DevInfoData, SPDIT_COMPATDRIVER)) + { + LastError = LOG_LAST_ERROR(L"Failed building adapter driver info list"); + goto cleanupDevInfo; + } + for (DWORD EnumIndex = 0;; ++EnumIndex) + { + SP_DRVINFO_DATA_W DrvInfoData = { .cbSize = sizeof(SP_DRVINFO_DATA_W) }; + if (!SetupDiEnumDriverInfoW(DevInfo, &DevInfoData, SPDIT_COMPATDRIVER, EnumIndex, &DrvInfoData)) + { + if (GetLastError() == ERROR_NO_MORE_ITEMS) + break; + continue; + } + BYTE LargeBuffer[0x2000]; + DWORD Size = sizeof(LargeBuffer); + SP_DRVINFO_DETAIL_DATA_W *DrvInfoDetailData = (SP_DRVINFO_DETAIL_DATA_W *)LargeBuffer; + DrvInfoDetailData->cbSize = sizeof(SP_DRVINFO_DETAIL_DATA_W); + if (!SetupDiGetDriverInfoDetailW(DevInfo, &DevInfoData, &DrvInfoData, DrvInfoDetailData, Size, &Size)) + { + LOG(WINTUN_LOG_WARN, L"Failed getting adapter driver info detail"); + continue; + } + LPCWSTR Path = PathFindFileNameW(DrvInfoDetailData->InfFileName); + LOG(WINTUN_LOG_INFO, L"Removing driver %s", Path); + if (!SetupUninstallOEMInfW(Path, 0, NULL)) + { + LOG_LAST_ERROR(L"Unable to remove driver %s", Path); + LastError = LastError != ERROR_SUCCESS ? LastError : GetLastError(); + } + } + SetupDiDestroyDriverInfoList(DevInfo, &DevInfoData, SPDIT_COMPATDRIVER); +cleanupDevInfo: + SetupDiDestroyDeviceInfoList(DevInfo); +cleanupDriverInstallationLock: + NamespaceReleaseMutex(DriverInstallationLock); +cleanup: + return RET_ERROR(TRUE, LastError); +} diff --git a/api/driver.h b/api/driver.h new file mode 100644 index 0000000..dae4e9b --- /dev/null +++ b/api/driver.h @@ -0,0 +1,34 @@ +/* SPDX-License-Identifier: GPL-2.0 + * + * Copyright (C) 2018-2021 WireGuard LLC. All Rights Reserved. + */ + +#pragma once + +#include "wintun.h" +#include <Windows.h> +#include <SetupAPI.h> + +#define WINTUN_HWID L"Wintun" + +typedef struct _SP_DEVINFO_DATA_LIST SP_DEVINFO_DATA_LIST; + +VOID +DriverInstallDeferredCleanup(_In_ HDEVINFO DevInfoExistingAdapters, _In_opt_ SP_DEVINFO_DATA_LIST *ExistingAdapters); + +_Must_inspect_result_ +_Return_type_success_(return != FALSE) +BOOL +DriverInstall( + _Out_ HDEVINFO *DevInfoExistingAdaptersForCleanup, + _Out_ SP_DEVINFO_DATA_LIST **ExistingAdaptersForCleanup); + +/** + * @copydoc WINTUN_DELETE_DRIVER_FUNC + */ +WINTUN_DELETE_DRIVER_FUNC WintunDeleteDriver; + +/** + * @copydoc WINTUN_GET_RUNNING_DRIVER_VERSION_FUNC + */ +WINTUN_GET_RUNNING_DRIVER_VERSION_FUNC WintunGetRunningDriverVersion; diff --git a/api/exports.def b/api/exports.def new file mode 100644 index 0000000..c12db57 --- /dev/null +++ b/api/exports.def @@ -0,0 +1,16 @@ +LIBRARY wintun.dll +EXPORTS + WintunAllocateSendPacket + WintunCreateAdapter + WintunEndSession + WintunOpenAdapter + WintunCloseAdapter + WintunGetAdapterLUID + WintunGetReadWaitEvent + WintunGetRunningDriverVersion + WintunReceivePacket + WintunReleaseReceivePacket + WintunSendPacket + WintunDeleteDriver + WintunSetLogger + WintunStartSession diff --git a/api/extract-driverver.js b/api/extract-driverver.js new file mode 100644 index 0000000..ee60b8a --- /dev/null +++ b/api/extract-driverver.js @@ -0,0 +1,17 @@ +/* SPDX-License-Identifier: GPL-2.0 + * + * Copyright (C) 2018-2021 WireGuard LLC. All Rights Reserved. + */ + +while (!WScript.StdIn.AtEndOfStream) { + var line = WScript.StdIn.ReadLine(); + if (line.substr(0, 12) != "DriverVer = ") + continue; + var val = line.substr(12).split(","); + var date = val[0].split("/"); + var ver = val[1].split("."); + var time = Date.UTC(date[2], date[0] - 1, date[1]).toString() + WScript.Echo("#define WINTUN_INF_FILETIME { (DWORD)((" + time + "0000ULL + 116444736000000000ULL) & 0xffffffffU), (DWORD)((" + time + "0000ULL + 116444736000000000ULL) >> 32) }") + WScript.Echo("#define WINTUN_INF_VERSION ((" + ver[0] + "ULL << 48) | (" + ver[1] + "ULL << 32) | (" + ver[2] + "ULL << 16) | (" + ver[3] + "ULL << 0))") + break; +} diff --git a/api/logger.c b/api/logger.c new file mode 100644 index 0000000..94ce525 --- /dev/null +++ b/api/logger.c @@ -0,0 +1,134 @@ +/* SPDX-License-Identifier: GPL-2.0 + * + * Copyright (C) 2018-2021 WireGuard LLC. All Rights Reserved. + */ + +#include "logger.h" +#include "adapter.h" +#include "ntdll.h" +#include <Windows.h> +#include <iphlpapi.h> +#include <winternl.h> +#include <wchar.h> +#include <stdlib.h> + +static BOOL CALLBACK +NopLogger(_In_ WINTUN_LOGGER_LEVEL Level, _In_ DWORD64 Timestamp, _In_z_ LPCWSTR LogLine) +{ + return TRUE; +} + +WINTUN_LOGGER_CALLBACK Logger = NopLogger; + +static DWORD64 Now(VOID) +{ + LARGE_INTEGER Timestamp; + NtQuerySystemTime(&Timestamp); + return Timestamp.QuadPart; +} + +_Use_decl_annotations_ +VOID WINAPI +WintunSetLogger(WINTUN_LOGGER_CALLBACK NewLogger) +{ + if (!NewLogger) + NewLogger = NopLogger; + Logger = NewLogger; +} + +static VOID +StrTruncate(_Inout_count_(StrChars) LPWSTR Str, _In_ SIZE_T StrChars) +{ + Str[StrChars - 2] = L'\u2026'; /* Horizontal Ellipsis */ + Str[StrChars - 1] = 0; +} + +_Use_decl_annotations_ +DWORD +LoggerLog(WINTUN_LOGGER_LEVEL Level, LPCWSTR LogLine) +{ + DWORD LastError = GetLastError(); + Logger(Level, Now(), LogLine); + SetLastError(LastError); + return LastError; +} + +_Use_decl_annotations_ +DWORD +LoggerLogV(WINTUN_LOGGER_LEVEL Level, LPCWSTR Format, va_list Args) +{ + DWORD LastError = GetLastError(); + WCHAR LogLine[0x400]; + if (_vsnwprintf_s(LogLine, _countof(LogLine), _TRUNCATE, Format, Args) == -1) + StrTruncate(LogLine, _countof(LogLine)); + Logger(Level, Now(), LogLine); + SetLastError(LastError); + return LastError; +} + +_Use_decl_annotations_ +DWORD +LoggerError(DWORD Error, LPCWSTR Prefix) +{ + LPWSTR SystemMessage = NULL, FormattedMessage = NULL; + FormatMessageW( + FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_MAX_WIDTH_MASK, + NULL, + HRESULT_FROM_SETUPAPI(Error), + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (VOID *)&SystemMessage, + 0, + NULL); + FormatMessageW( + FORMAT_MESSAGE_FROM_STRING | FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_ARGUMENT_ARRAY | + FORMAT_MESSAGE_MAX_WIDTH_MASK, + SystemMessage ? L"%1: %3(Code 0x%2!08X!)" : L"%1: Code 0x%2!08X!", + 0, + 0, + (VOID *)&FormattedMessage, + 0, + (va_list *)(DWORD_PTR[]){ (DWORD_PTR)Prefix, (DWORD_PTR)Error, (DWORD_PTR)SystemMessage }); + if (FormattedMessage) + Logger(WINTUN_LOG_ERR, Now(), FormattedMessage); + LocalFree(FormattedMessage); + LocalFree(SystemMessage); + return Error; +} + +_Use_decl_annotations_ +DWORD +LoggerErrorV(DWORD Error, LPCWSTR Format, va_list Args) +{ + WCHAR LogLine[0x400]; + if (_vsnwprintf_s(LogLine, _countof(LogLine), _TRUNCATE, Format, Args) == -1) + StrTruncate(LogLine, _countof(LogLine)); + return LoggerError(Error, LogLine); +} + +_Use_decl_annotations_ +VOID +LoggerGetRegistryKeyPath(HKEY Key, LPWSTR Path) +{ + DWORD LastError = GetLastError(); + if (Key == NULL) + { + wcsncpy_s(Path, MAX_REG_PATH, L"<null>", _TRUNCATE); + goto out; + } + if (_snwprintf_s(Path, MAX_REG_PATH, _TRUNCATE, L"0x%p", Key) == -1) + StrTruncate(Path, MAX_REG_PATH); + union + { + KEY_NAME_INFORMATION KeyNameInfo; + WCHAR Data[offsetof(KEY_NAME_INFORMATION, Name) + MAX_REG_PATH]; + } Buffer; + DWORD Size; + if (!NT_SUCCESS(NtQueryKey(Key, 3, &Buffer, sizeof(Buffer), &Size)) || + Size < offsetof(KEY_NAME_INFORMATION, Name) || Buffer.KeyNameInfo.NameLength >= MAX_REG_PATH * sizeof(WCHAR)) + goto out; + Buffer.KeyNameInfo.NameLength /= sizeof(WCHAR); + wmemcpy_s(Path, MAX_REG_PATH, Buffer.KeyNameInfo.Name, Buffer.KeyNameInfo.NameLength); + Path[Buffer.KeyNameInfo.NameLength] = L'\0'; +out: + SetLastError(LastError); +} diff --git a/api/logger.h b/api/logger.h new file mode 100644 index 0000000..d83839e --- /dev/null +++ b/api/logger.h @@ -0,0 +1,175 @@ +/* SPDX-License-Identifier: GPL-2.0 + * + * Copyright (C) 2018-2021 WireGuard LLC. All Rights Reserved. + */ + +#pragma once + +#include "wintun.h" +#include "main.h" +#include "registry.h" +#include <Windows.h> +#include <intsafe.h> +#include <stdarg.h> +#include <wchar.h> + +extern WINTUN_LOGGER_CALLBACK Logger; + +/** + * @copydoc WINTUN_SET_LOGGER_FUNC + */ +WINTUN_SET_LOGGER_FUNC WintunSetLogger; + +_Post_equals_last_error_ +DWORD +LoggerLog(_In_ WINTUN_LOGGER_LEVEL Level, _In_z_ LPCWSTR LogLine); + +_Post_equals_last_error_ +DWORD +LoggerLogV(_In_ WINTUN_LOGGER_LEVEL Level, _In_z_ _Printf_format_string_ LPCWSTR Format, _In_ va_list Args); + +_Post_equals_last_error_ +static inline DWORD +LoggerLogFmt(_In_ WINTUN_LOGGER_LEVEL Level, _In_z_ _Printf_format_string_ LPCWSTR Format, ...) +{ + va_list Args; + va_start(Args, Format); + DWORD LastError = LoggerLogV(Level, Format, Args); + va_end(Args); + return LastError; +} + +_Post_equals_last_error_ +DWORD +LoggerError(_In_ DWORD Error, _In_z_ LPCWSTR Prefix); + +_Post_equals_last_error_ +DWORD +LoggerErrorV(_In_ DWORD Error, _In_z_ _Printf_format_string_ LPCWSTR Format, _In_ va_list Args); + +_Post_equals_last_error_ +static inline DWORD +LoggerErrorFmt(_In_ DWORD Error, _In_z_ _Printf_format_string_ LPCWSTR Format, ...) +{ + va_list Args; + va_start(Args, Format); + DWORD LastError = LoggerErrorV(Error, Format, Args); + va_end(Args); + return LastError; +} + +_Post_equals_last_error_ +static inline DWORD +LoggerLastErrorV(_In_z_ _Printf_format_string_ LPCWSTR Format, _In_ va_list Args) +{ + DWORD LastError = GetLastError(); + LoggerErrorV(LastError, Format, Args); + SetLastError(LastError); + return LastError; +} + +_Post_equals_last_error_ +static inline DWORD +LoggerLastErrorFmt(_In_z_ _Printf_format_string_ LPCWSTR Format, ...) +{ + va_list Args; + va_start(Args, Format); + DWORD LastError = LoggerLastErrorV(Format, Args); + va_end(Args); + return LastError; +} + +VOID +LoggerGetRegistryKeyPath(_In_ HKEY Key, _Out_writes_z_(MAX_REG_PATH) LPWSTR Path); + +#define LOG(lvl, msg, ...) (LoggerLogFmt((lvl), msg, __VA_ARGS__)) +#define LOG_ERROR(err, msg, ...) (LoggerErrorFmt((err), msg, __VA_ARGS__)) +#define LOG_LAST_ERROR(msg, ...) (LoggerLastErrorFmt(msg, __VA_ARGS__)) + +#define RET_ERROR(Ret, Error) ((Error) == ERROR_SUCCESS ? (Ret) : (SetLastError(Error), 0)) + +_Must_inspect_result_ +DECLSPEC_ALLOCATOR +static inline _Return_type_success_(return != NULL) +_Post_maybenull_ +_Post_writable_byte_size_(Size) +VOID * +LoggerAlloc(_In_z_ LPCWSTR Function, _In_ DWORD Flags, _In_ SIZE_T Size) +{ + VOID *Data = HeapAlloc(ModuleHeap, Flags, Size); + if (!Data) + { + LoggerLogFmt(WINTUN_LOG_ERR, Function, L"Out of memory (flags: 0x%x, requested size: 0x%zx)", Flags, Size); + SetLastError(ERROR_OUTOFMEMORY); + } + return Data; +} +_Must_inspect_result_ +DECLSPEC_ALLOCATOR +static inline _Return_type_success_(return != NULL) +_Post_maybenull_ +_Post_writable_byte_size_(Size) +VOID * +LoggerReAlloc(_In_z_ LPCWSTR Function, _In_ DWORD Flags, _Frees_ptr_opt_ LPVOID Mem, _In_ SIZE_T Size) +{ + VOID *Data = Mem ? HeapReAlloc(ModuleHeap, Flags, Mem, Size) : HeapAlloc(ModuleHeap, Flags, Size); + if (!Data) + { + LoggerLogFmt(WINTUN_LOG_ERR, Function, L"Out of memory (flags: 0x%x, requested size: 0x%zx)", Flags, Size); + SetLastError(ERROR_OUTOFMEMORY); + } + return Data; +} + +#define __L(x) L##x +#define _L(x) __L(x) +#define Alloc(Size) LoggerAlloc(_L(__FUNCTION__), 0, Size) +#define ReAlloc(Mem, Size) LoggerReAlloc(_L(__FUNCTION__), 0, Mem, Size) +#define Zalloc(Size) LoggerAlloc(_L(__FUNCTION__), HEAP_ZERO_MEMORY, Size) +#define ReZalloc(Mem, Size) LoggerReAlloc(_L(__FUNCTION__), HEAP_ZERO_MEMORY, Mem, Size) + +_Must_inspect_result_ +DECLSPEC_ALLOCATOR +static inline _Return_type_success_(return != NULL) +_Post_maybenull_ +_Post_writable_byte_size_((NumberOfElements) * (SizeOfOneElement)) +VOID * +LoggerAllocArray(_In_z_ LPCWSTR Function, _In_ DWORD Flags, _In_ SIZE_T NumberOfElements, _In_ SIZE_T SizeOfOneElement) +{ + SIZE_T Size; + if (FAILED(SIZETMult(NumberOfElements, SizeOfOneElement, &Size))) + return NULL; + return LoggerAlloc(Function, Flags, Size); +} +_Must_inspect_result_ +DECLSPEC_ALLOCATOR +static inline _Return_type_success_(return != NULL) +_Post_maybenull_ +_Post_writable_byte_size_((NumberOfElements) * (SizeOfOneElement)) +VOID * +LoggerReAllocArray( + _In_z_ LPCWSTR Function, + _In_ DWORD Flags, + _Frees_ptr_opt_ LPVOID Mem, + _In_ SIZE_T NumberOfElements, + _In_ SIZE_T SizeOfOneElement) +{ + SIZE_T Size; + if (FAILED(SIZETMult(NumberOfElements, SizeOfOneElement, &Size))) + return NULL; + return LoggerReAlloc(Function, Flags, Mem, Size); +} +#define AllocArray(Count, Size) LoggerAllocArray(_L(__FUNCTION__), 0, Count, Size) +#define ReAllocArray(Mem, Count, Size) LoggerReAllocArray(_L(__FUNCTION__), 0, Mem, Count, Size) +#define ZallocArray(Count, Size) LoggerAllocArray(_L(__FUNCTION__), HEAP_ZERO_MEMORY, Count, Size) +#define ReZallocArray(Mem, Count, Size) LoggerReAllocArray(_L(__FUNCTION__), HEAP_ZERO_MEMORY, Mem, Count, Size) + +static inline VOID +Free(_Frees_ptr_opt_ VOID *Ptr) +{ + if (!Ptr) + return; + DWORD LastError = GetLastError(); + HeapFree(ModuleHeap, 0, Ptr); + SetLastError(LastError); +} diff --git a/api/main.c b/api/main.c new file mode 100644 index 0000000..032225e --- /dev/null +++ b/api/main.c @@ -0,0 +1,134 @@ +/* SPDX-License-Identifier: GPL-2.0 + * + * Copyright (C) 2018-2021 WireGuard LLC. All Rights Reserved. + */ + +#include "logger.h" +#include "adapter.h" +#include "main.h" +#include "namespace.h" +#include "registry.h" +#include "ntdll.h" + +#include <Windows.h> +#include <delayimp.h> +#include <sddl.h> +#include <winefs.h> +#include <stdlib.h> + +HINSTANCE ResourceModule; +HANDLE ModuleHeap; +SECURITY_ATTRIBUTES SecurityAttributes = { .nLength = sizeof(SECURITY_ATTRIBUTES) }; +BOOL IsLocalSystem; +USHORT NativeMachine = IMAGE_FILE_PROCESS; + +#if NTDDI_VERSION == NTDDI_WIN7 +BOOL IsWindows7; +#endif +#if NTDDI_VERSION < NTDDI_WIN10 +BOOL IsWindows10; +#endif + +static FARPROC WINAPI +DelayedLoadLibraryHook(unsigned dliNotify, PDelayLoadInfo pdli) +{ + if (dliNotify != dliNotePreLoadLibrary) + return NULL; + HMODULE Library = LoadLibraryExA(pdli->szDll, NULL, LOAD_LIBRARY_SEARCH_SYSTEM32); + if (!Library) + abort(); + return (FARPROC)Library; +} + +const PfnDliHook __pfnDliNotifyHook2 = DelayedLoadLibraryHook; + +static BOOL InitializeSecurityObjects(VOID) +{ + BYTE LocalSystemSid[MAX_SID_SIZE]; + DWORD RequiredBytes = sizeof(LocalSystemSid); + HANDLE CurrentProcessToken; + struct + { + TOKEN_USER MaybeLocalSystem; + CHAR LargeEnoughForLocalSystem[MAX_SID_SIZE]; + } TokenUserBuffer; + BOOL Ret = FALSE; + + if (!CreateWellKnownSid(WinLocalSystemSid, NULL, LocalSystemSid, &RequiredBytes)) + return FALSE; + + if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &CurrentProcessToken)) + return FALSE; + + if (!GetTokenInformation(CurrentProcessToken, TokenUser, &TokenUserBuffer, sizeof(TokenUserBuffer), &RequiredBytes)) + goto cleanupProcessToken; + + IsLocalSystem = EqualSid(TokenUserBuffer.MaybeLocalSystem.User.Sid, LocalSystemSid); + Ret = ConvertStringSecurityDescriptorToSecurityDescriptorW( + IsLocalSystem ? L"O:SYD:P(A;;GA;;;SY)(A;;GA;;;BA)S:(ML;;NWNRNX;;;HI)" + : L"O:BAD:P(A;;GA;;;SY)(A;;GA;;;BA)S:(ML;;NWNRNX;;;HI)", + SDDL_REVISION_1, + &SecurityAttributes.lpSecurityDescriptor, + NULL); + +cleanupProcessToken: + CloseHandle(CurrentProcessToken); + return Ret; +} + +static void EnvInit(VOID) +{ + DWORD MajorVersion, MinorVersion; + RtlGetNtVersionNumbers(&MajorVersion, &MinorVersion, NULL); + +#if NTDDI_VERSION == NTDDI_WIN7 + IsWindows7 = MajorVersion == 6 && MinorVersion == 1; +#endif +#if NTDDI_VERSION < NTDDI_WIN10 + IsWindows10 = MajorVersion >= 10; +#endif + +#ifdef MAYBE_WOW64 + HANDLE Kernel32; + BOOL(WINAPI * IsWow64Process2) + (_In_ HANDLE Process, _Out_ USHORT * ProcessMachine, _Out_opt_ USHORT * NativeMachine); + USHORT ProcessMachine; + if ((Kernel32 = GetModuleHandleW(L"kernel32.dll")) == NULL || + (*(FARPROC *)&IsWow64Process2 = GetProcAddress(Kernel32, "IsWow64Process2")) == NULL || + !IsWow64Process2(GetCurrentProcess(), &ProcessMachine, &NativeMachine)) + { + BOOL IsWoW64; + NativeMachine = + IsWow64Process(GetCurrentProcess(), &IsWoW64) && IsWoW64 ? IMAGE_FILE_MACHINE_AMD64 : IMAGE_FILE_PROCESS; + } +#endif +} + +BOOL APIENTRY +DllMain(_In_ HINSTANCE hinstDLL, _In_ DWORD fdwReason, _In_ LPVOID lpvReserved) +{ + switch (fdwReason) + { + case DLL_PROCESS_ATTACH: + ResourceModule = hinstDLL; + ModuleHeap = HeapCreate(0, 0, 0); + if (!ModuleHeap) + return FALSE; + if (!InitializeSecurityObjects()) + { + HeapDestroy(ModuleHeap); + return FALSE; + } + EnvInit(); + NamespaceInit(); + AdapterCleanupLegacyDevices(); + break; + + case DLL_PROCESS_DETACH: + NamespaceDone(); + LocalFree(SecurityAttributes.lpSecurityDescriptor); + HeapDestroy(ModuleHeap); + break; + } + return TRUE; +} diff --git a/api/main.h b/api/main.h new file mode 100644 index 0000000..04ba7dd --- /dev/null +++ b/api/main.h @@ -0,0 +1,38 @@ +/* SPDX-License-Identifier: GPL-2.0 + * + * Copyright (C) 2018-2021 WireGuard LLC. All Rights Reserved. + */ + +#pragma once + +#include <Windows.h> + +#if defined(_M_IX86) +# define IMAGE_FILE_PROCESS IMAGE_FILE_MACHINE_I386 +#elif defined(_M_AMD64) +# define IMAGE_FILE_PROCESS IMAGE_FILE_MACHINE_AMD64 +#elif defined(_M_ARM) +# define IMAGE_FILE_PROCESS IMAGE_FILE_MACHINE_ARMNT +#elif defined(_M_ARM64) +# define IMAGE_FILE_PROCESS IMAGE_FILE_MACHINE_ARM64 +#else +# error Unsupported architecture +#endif + +extern HINSTANCE ResourceModule; +extern HANDLE ModuleHeap; +extern SECURITY_ATTRIBUTES SecurityAttributes; +extern BOOL IsLocalSystem; +extern USHORT NativeMachine; + +#if NTDDI_VERSION > NTDDI_WIN7 +# define IsWindows7 FALSE +#else +extern BOOL IsWindows7; +#endif + +#if NTDDI_VERSION >= NTDDI_WIN10 +# define IsWindows10 TRUE +#else +extern BOOL IsWindows10; +#endif
\ No newline at end of file diff --git a/api/namespace.c b/api/namespace.c new file mode 100644 index 0000000..3248edb --- /dev/null +++ b/api/namespace.c @@ -0,0 +1,154 @@ +/* SPDX-License-Identifier: GPL-2.0 + * + * Copyright (C) 2018-2021 WireGuard LLC. All Rights Reserved. + */ + +#include "logger.h" +#include "main.h" +#include "namespace.h" + +#include <Windows.h> +#include <winternl.h> +#include <winefs.h> +#include <wchar.h> +#include <stdlib.h> + +static HANDLE PrivateNamespace = NULL; +static HANDLE BoundaryDescriptor = NULL; +static CRITICAL_SECTION Initializing; + +static _Return_type_success_(return != FALSE) +BOOL NamespaceRuntimeInit(VOID) +{ + DWORD LastError; + + EnterCriticalSection(&Initializing); + if (PrivateNamespace) + { + LeaveCriticalSection(&Initializing); + return TRUE; + } + + BYTE Sid[MAX_SID_SIZE]; + DWORD SidSize = sizeof(Sid); + if (!CreateWellKnownSid(IsLocalSystem ? WinLocalSystemSid : WinBuiltinAdministratorsSid, NULL, Sid, &SidSize)) + { + LastError = LOG_LAST_ERROR(L"Failed to create SID"); + goto cleanupLeaveCriticalSection; + } + + BoundaryDescriptor = CreateBoundaryDescriptorW(L"Wintun", 0); + if (!BoundaryDescriptor) + { + LastError = LOG_LAST_ERROR(L"Failed to create boundary descriptor"); + goto cleanupLeaveCriticalSection; + } + if (!AddSIDToBoundaryDescriptor(&BoundaryDescriptor, Sid)) + { + LastError = LOG_LAST_ERROR(L"Failed to add SID to boundary descriptor"); + goto cleanupBoundaryDescriptor; + } + + for (;;) + { + if ((PrivateNamespace = CreatePrivateNamespaceW(&SecurityAttributes, BoundaryDescriptor, L"Wintun")) != NULL) + break; + if ((LastError = GetLastError()) == ERROR_ALREADY_EXISTS) + { + if ((PrivateNamespace = OpenPrivateNamespaceW(BoundaryDescriptor, L"Wintun")) != NULL) + break; + if ((LastError = GetLastError()) == ERROR_PATH_NOT_FOUND) + continue; + LOG_ERROR(LastError, L"Failed to open private namespace"); + } + else + LOG_ERROR(LastError, L"Failed to create private namespace"); + goto cleanupBoundaryDescriptor; + } + + LeaveCriticalSection(&Initializing); + return TRUE; + +cleanupBoundaryDescriptor: + DeleteBoundaryDescriptor(BoundaryDescriptor); +cleanupLeaveCriticalSection: + LeaveCriticalSection(&Initializing); + SetLastError(LastError); + return FALSE; +} + +_Use_decl_annotations_ +HANDLE +NamespaceTakeDriverInstallationMutex(VOID) +{ + if (!NamespaceRuntimeInit()) + return NULL; + HANDLE Mutex = CreateMutexW(&SecurityAttributes, FALSE, L"Wintun\\Wintun-Driver-Installation-Mutex"); + if (!Mutex) + { + LOG_LAST_ERROR(L"Failed to create mutex"); + return NULL; + } + DWORD Result = WaitForSingleObject(Mutex, INFINITE); + switch (Result) + { + case WAIT_OBJECT_0: + case WAIT_ABANDONED: + return Mutex; + } + LOG(WINTUN_LOG_ERR, L"Failed to get mutex (status: 0x%x)", Result); + CloseHandle(Mutex); + SetLastError(ERROR_GEN_FAILURE); + return NULL; +} + +_Use_decl_annotations_ +HANDLE +NamespaceTakeDeviceInstallationMutex(VOID) +{ + if (!NamespaceRuntimeInit()) + return NULL; + HANDLE Mutex = CreateMutexW(&SecurityAttributes, FALSE, L"Wintun\\Wintun-Device-Installation-Mutex"); + if (!Mutex) + { + LOG_LAST_ERROR(L"Failed to create mutex"); + return NULL; + } + DWORD Result = WaitForSingleObject(Mutex, INFINITE); + switch (Result) + { + case WAIT_OBJECT_0: + case WAIT_ABANDONED: + return Mutex; + } + LOG(WINTUN_LOG_ERR, L"Failed to get mutex (status: 0x%x)", Result); + CloseHandle(Mutex); + SetLastError(ERROR_GEN_FAILURE); + return NULL; +} + +_Use_decl_annotations_ +VOID +NamespaceReleaseMutex(HANDLE Mutex) +{ + ReleaseMutex(Mutex); + CloseHandle(Mutex); +} + +VOID NamespaceInit(VOID) +{ + InitializeCriticalSection(&Initializing); +} + +VOID NamespaceDone(VOID) +{ + EnterCriticalSection(&Initializing); + if (PrivateNamespace) + { + ClosePrivateNamespace(PrivateNamespace, 0); + DeleteBoundaryDescriptor(BoundaryDescriptor); + PrivateNamespace = NULL; + } + LeaveCriticalSection(&Initializing); + DeleteCriticalSection(&Initializing); +} diff --git a/api/namespace.h b/api/namespace.h new file mode 100644 index 0000000..a0397be --- /dev/null +++ b/api/namespace.h @@ -0,0 +1,30 @@ +/* SPDX-License-Identifier: GPL-2.0 + * + * Copyright (C) 2018-2021 WireGuard LLC. All Rights Reserved. + */ + +#pragma once + +#include <Windows.h> + +_Must_inspect_result_ +_Return_type_success_(return != NULL) +_Post_maybenull_ +_Acquires_lock_(_Curr_) +HANDLE +NamespaceTakeDriverInstallationMutex(VOID); + +_Must_inspect_result_ +_Return_type_success_(return != NULL) +_Post_maybenull_ +_Acquires_lock_(_Curr_) +HANDLE +NamespaceTakeDeviceInstallationMutex(VOID); + +_Releases_lock_(Mutex) +VOID +NamespaceReleaseMutex(_In_ HANDLE Mutex); + +VOID NamespaceInit(VOID); + +VOID NamespaceDone(VOID); diff --git a/api/nci.def b/api/nci.def new file mode 100644 index 0000000..db484b7 --- /dev/null +++ b/api/nci.def @@ -0,0 +1,4 @@ +LIBRARY nci.dll +EXPORTS + NciGetConnectionName + NciSetConnectionName diff --git a/api/nci.h b/api/nci.h new file mode 100644 index 0000000..ba99fa6 --- /dev/null +++ b/api/nci.h @@ -0,0 +1,31 @@ +/* SPDX-License-Identifier: GPL-2.0 + * + * Copyright (C) 2018-2021 WireGuard LLC. All Rights Reserved. + */ + +#pragma once + +#include <Windows.h> + +#ifdef GENERATE_LIB +# define DECLSPEC __declspec(dllexport) +# define STUB \ + { \ + return 0; \ + } +#else +# define DECLSPEC __declspec(dllimport) +# define STUB ; +#endif + +EXTERN_C +DECLSPEC DWORD WINAPI +NciSetConnectionName(_In_ const GUID *Guid, _In_z_ LPCWSTR NewName) STUB + + EXTERN_C +DECLSPEC DWORD WINAPI +NciGetConnectionName( + _In_ const GUID *Guid, + _Out_z_bytecap_(InDestNameBytes) LPWSTR Name, + _In_ DWORD InDestNameBytes, + _Out_opt_ DWORD *OutDestNameBytes) STUB
\ No newline at end of file diff --git a/api/ntdll.h b/api/ntdll.h new file mode 100644 index 0000000..2eb2786 --- /dev/null +++ b/api/ntdll.h @@ -0,0 +1,62 @@ +/* SPDX-License-Identifier: GPL-2.0 + * + * Copyright (C) 2018-2021 WireGuard LLC. All Rights Reserved. + */ + +#pragma once + +#include <Windows.h> + +enum +{ + SystemModuleInformation = 11 +}; + +typedef struct _RTL_PROCESS_MODULE_INFORMATION +{ + HANDLE Section; + PVOID MappedBase; + PVOID ImageBase; + ULONG ImageSize; + ULONG Flags; + USHORT LoadOrderIndex; + USHORT InitOrderIndex; + USHORT LoadCount; + USHORT OffsetToFileName; + UCHAR FullPathName[256]; +} RTL_PROCESS_MODULE_INFORMATION, *PRTL_PROCESS_MODULE_INFORMATION; + +typedef struct _RTL_PROCESS_MODULES +{ + ULONG NumberOfModules; + RTL_PROCESS_MODULE_INFORMATION Modules[1]; +} RTL_PROCESS_MODULES, *PRTL_PROCESS_MODULES; + +typedef struct _KEY_NAME_INFORMATION +{ + ULONG NameLength; + WCHAR Name[1]; +} KEY_NAME_INFORMATION, *PKEY_NAME_INFORMATION; + +#define STATUS_INFO_LENGTH_MISMATCH ((NTSTATUS)0xC0000004L) // TODO: #include <ntstatus.h> instead of this + +/* We can't use RtlGetVersion, because appcompat's aclayers.dll shims it to report Vista + * when run from legacy contexts. So, we instead use the undocumented RtlGetNtVersionNumbers. + * + * Another way would be reading from the PEB directly: + * ((DWORD *)NtCurrentTeb()->ProcessEnvironmentBlock)[sizeof(VOID *) == 8 ? 70 : 41] + * Or just read from KUSER_SHARED_DATA the same way on 32-bit and 64-bit: + * *(DWORD *)0x7FFE026C + */ +EXTERN_C +DECLSPEC_IMPORT VOID NTAPI +RtlGetNtVersionNumbers(_Out_opt_ DWORD *MajorVersion, _Out_opt_ DWORD *MinorVersion, _Out_opt_ DWORD *BuildNumber); + +EXTERN_C +DECLSPEC_IMPORT DWORD NTAPI +NtQueryKey( + _In_ HANDLE KeyHandle, + _In_ int KeyInformationClass, + _Out_bytecap_post_bytecount_(Length, *ResultLength) PVOID KeyInformation, + _In_ ULONG Length, + _Out_ PULONG ResultLength); diff --git a/api/registry.c b/api/registry.c new file mode 100644 index 0000000..4f1001c --- /dev/null +++ b/api/registry.c @@ -0,0 +1,157 @@ +/* SPDX-License-Identifier: GPL-2.0 + * + * Copyright (C) 2018-2021 WireGuard LLC. All Rights Reserved. + */ + +#include "logger.h" +#include "registry.h" +#include <Windows.h> +#include <wchar.h> +#include <stdlib.h> +#include <strsafe.h> + +_Use_decl_annotations_ +BOOL +RegistryGetString(LPWSTR *Buf, DWORD Len, DWORD ValueType) +{ + if (wcsnlen(*Buf, Len) >= Len) + { + /* String is missing zero-terminator. */ + LPWSTR BufZ = ReZallocArray(*Buf, (SIZE_T)Len + 1, sizeof(*BufZ)); + if (!BufZ) + return FALSE; + _Analysis_assume_((wmemset(BufZ, L'A', (SIZE_T)Len + 1), TRUE)); + *Buf = BufZ; + } + + if (ValueType != REG_EXPAND_SZ) + return TRUE; + + /* ExpandEnvironmentStringsW() returns strlen on success or 0 on error. Bail out on empty input strings to + * disambiguate. */ + if (!(*Buf)[0]) + return TRUE; + + for (;;) + { + LPWSTR Expanded = AllocArray(Len, sizeof(*Expanded)); + if (!Expanded) + return FALSE; + DWORD Result = ExpandEnvironmentStringsW(*Buf, Expanded, Len); + if (!Result) + { + LOG_LAST_ERROR(L"Failed to expand environment variables: %s", *Buf); + Free(Expanded); + return FALSE; + } + if (Result > Len) + { + Free(Expanded); + Len = Result; + continue; + } + Free(*Buf); + *Buf = Expanded; + return TRUE; + } +} + +_Must_inspect_result_ +static _Return_type_success_(return != NULL) +_Post_maybenull_ +_Post_writable_byte_size_(*BufLen) +VOID * +RegistryQuery(_In_ HKEY Key, _In_opt_z_ LPCWSTR Name, _Out_opt_ DWORD *ValueType, _Inout_ DWORD *BufLen, _In_ BOOL Log) +{ + for (;;) + { + BYTE *p = Alloc(*BufLen); + if (!p) + return NULL; + LSTATUS LastError = RegQueryValueExW(Key, Name, NULL, ValueType, p, BufLen); + if (LastError == ERROR_SUCCESS) + return p; + Free(p); + if (LastError != ERROR_MORE_DATA) + { + if (Log) + { + WCHAR RegPath[MAX_REG_PATH]; + LoggerGetRegistryKeyPath(Key, RegPath); + LOG_ERROR(LastError, L"Failed to query registry value %.*s\\%s", MAX_REG_PATH, RegPath, Name); + } + SetLastError(LastError); + return NULL; + } + } +} + +_Use_decl_annotations_ +LPWSTR +RegistryQueryString(HKEY Key, LPCWSTR Name, BOOL Log) +{ + DWORD LastError, ValueType, Size = 256 * sizeof(WCHAR); + LPWSTR Value = RegistryQuery(Key, Name, &ValueType, &Size, Log); + if (!Value) + return NULL; + switch (ValueType) + { + case REG_SZ: + case REG_EXPAND_SZ: + case REG_MULTI_SZ: + if (RegistryGetString(&Value, Size / sizeof(*Value), ValueType)) + return Value; + LastError = GetLastError(); + break; + default: { + WCHAR RegPath[MAX_REG_PATH]; + LoggerGetRegistryKeyPath(Key, RegPath); + LOG(WINTUN_LOG_ERR, + L"Registry value %.*s\\%s is not a string (type: %u)", + MAX_REG_PATH, + RegPath, + Name, + ValueType); + LastError = ERROR_INVALID_DATATYPE; + } + } + Free(Value); + SetLastError(LastError); + return NULL; +} + +_Use_decl_annotations_ +BOOL +RegistryQueryDWORD(HKEY Key, LPCWSTR Name, DWORD *Value, BOOL Log) +{ + DWORD ValueType, Size = sizeof(DWORD); + DWORD LastError = RegQueryValueExW(Key, Name, NULL, &ValueType, (BYTE *)Value, &Size); + if (LastError != ERROR_SUCCESS) + { + if (Log) + { + WCHAR RegPath[MAX_REG_PATH]; + LoggerGetRegistryKeyPath(Key, RegPath); + LOG_ERROR(LastError, L"Failed to query registry value %.*s\\%s", MAX_REG_PATH, RegPath, Name); + } + SetLastError(LastError); + return FALSE; + } + if (ValueType != REG_DWORD) + { + WCHAR RegPath[MAX_REG_PATH]; + LoggerGetRegistryKeyPath(Key, RegPath); + LOG(WINTUN_LOG_ERR, L"Value %.*s\\%s is not a DWORD (type: %u)", MAX_REG_PATH, RegPath, Name, ValueType); + SetLastError(ERROR_INVALID_DATATYPE); + return FALSE; + } + if (Size != sizeof(DWORD)) + { + WCHAR RegPath[MAX_REG_PATH]; + LoggerGetRegistryKeyPath(Key, RegPath); + LOG(WINTUN_LOG_ERR, L"Value %.*s\\%s size is not 4 bytes (size: %u)", MAX_REG_PATH, RegPath, Name, Size); + SetLastError(ERROR_INVALID_DATA); + return FALSE; + } + return TRUE; +} diff --git a/api/registry.h b/api/registry.h new file mode 100644 index 0000000..6beed2f --- /dev/null +++ b/api/registry.h @@ -0,0 +1,79 @@ +/* SPDX-License-Identifier: GPL-2.0 + * + * Copyright (C) 2018-2021 WireGuard LLC. All Rights Reserved. + */ + +#pragma once + +#include "wintun.h" +#include <Windows.h> + +#define MAX_REG_PATH \ + 256 /* Maximum registry path length \ + https://support.microsoft.com/en-us/help/256986/windows-registry-information-for-advanced-users */ + +/** + * Validates and/or sanitizes string value read from registry. + * + * @param Buf On input, it contains a pointer to pointer where the data is stored. The data must be allocated + * using HeapAlloc(ModuleHeap, 0). On output, it contains a pointer to pointer where the sanitized + * data is stored. It must be released with HeapFree(ModuleHeap, 0, *Buf) after use. + * + * @param Len Length of data string in wide characters. + * + * @param ValueType Type of data. Must be either REG_SZ or REG_EXPAND_SZ. REG_MULTI_SZ is treated like REG_SZ; only + * the first string of a multi-string is to be used. + * + * @return If the function succeeds, the return value is nonzero. If the function fails, the return value is zero. To + * get extended error information, call GetLastError. + */ +_Must_inspect_result_ +_Return_type_success_(return != FALSE) +BOOL +RegistryGetString(_Inout_ LPWSTR *Buf, _In_ DWORD Len, _In_ DWORD ValueType); + +/** + * Reads string value from registry key. + * + * @param Key Handle of the registry key to read from. Must be opened with read access. + * + * @param Name Name of the value to read. + * + * @param Value Pointer to string to retrieve registry value. If the value type is REG_EXPAND_SZ the value is + * expanded using ExpandEnvironmentStrings(). If the value type is REG_MULTI_SZ, only the first + * string from the multi-string is returned. The string must be released with + * HeapFree(ModuleHeap, 0, Value) after use. + * + * @Log Set to TRUE to log all failures; FALSE to skip logging the innermost errors. Skipping innermost + * errors reduces log clutter when we are using RegistryQueryString() from + * RegistryQueryStringWait() and some errors are expected to occur. + * + * @return String with registry value on success; If the function fails, the return value is zero. To get extended error + * information, call GetLastError. + */ +_Must_inspect_result_ +_Return_type_success_(return != NULL) +_Post_maybenull_ +LPWSTR +RegistryQueryString(_In_ HKEY Key, _In_opt_z_ LPCWSTR Name, _In_ BOOL Log); + +/** + * Reads a 32-bit DWORD value from registry key. + * + * @param Key Handle of the registry key to read from. Must be opened with read access. + * + * @param Name Name of the value to read. + * + * @param Value Pointer to DWORD to retrieve registry value. + * + * @Log Set to TRUE to log all failures; FALSE to skip logging the innermost errors. Skipping innermost + * errors reduces log clutter when we are using RegistryQueryDWORD() from + * RegistryQueryDWORDWait() and some errors are expected to occur. + * + * @return If the function succeeds, the return value is nonzero. If the function fails, the return value is zero. To + * get extended error information, call GetLastError. + */ +_Must_inspect_result_ +_Return_type_success_(return != FALSE) +BOOL +RegistryQueryDWORD(_In_ HKEY Key, _In_opt_z_ LPCWSTR Name, _Out_ DWORD *Value, _In_ BOOL Log); diff --git a/api/resource.c b/api/resource.c new file mode 100644 index 0000000..648f568 --- /dev/null +++ b/api/resource.c @@ -0,0 +1,129 @@ +/* SPDX-License-Identifier: GPL-2.0 + * + * Copyright (C) 2018-2021 WireGuard LLC. All Rights Reserved. + */ + +#include "logger.h" +#include "main.h" +#include "resource.h" +#include <Windows.h> +#include <Shlwapi.h> +#include <NTSecAPI.h> + +_Use_decl_annotations_ +const VOID * +ResourceGetAddress(LPCWSTR ResourceName, DWORD *Size) +{ + HRSRC FoundResource = FindResourceW(ResourceModule, ResourceName, RT_RCDATA); + if (!FoundResource) + { + LOG_LAST_ERROR(L"Failed to find resource %s", ResourceName); + return NULL; + } + *Size = SizeofResource(ResourceModule, FoundResource); + if (!*Size) + { + LOG_LAST_ERROR(L"Failed to query resource %s size", ResourceName); + return NULL; + } + HGLOBAL LoadedResource = LoadResource(ResourceModule, FoundResource); + if (!LoadedResource) + { + LOG_LAST_ERROR(L"Failed to load resource %s", ResourceName); + return NULL; + } + BYTE *Address = LockResource(LoadedResource); + if (!Address) + { + LOG(WINTUN_LOG_ERR, L"Failed to lock resource %s", ResourceName); + SetLastError(ERROR_LOCK_FAILED); + return NULL; + } + return Address; +} + +_Use_decl_annotations_ +BOOL +ResourceCopyToFile(LPCWSTR DestinationPath, LPCWSTR ResourceName) +{ + DWORD SizeResource; + const VOID *LockedResource = ResourceGetAddress(ResourceName, &SizeResource); + if (!LockedResource) + { + LOG(WINTUN_LOG_ERR, L"Failed to locate resource %s", ResourceName); + return FALSE; + } + HANDLE DestinationHandle = CreateFileW( + DestinationPath, + GENERIC_WRITE, + 0, + &SecurityAttributes, + CREATE_NEW, + FILE_ATTRIBUTE_NORMAL | FILE_ATTRIBUTE_TEMPORARY, + NULL); + if (DestinationHandle == INVALID_HANDLE_VALUE) + { + LOG_LAST_ERROR(L"Failed to create file %s", DestinationPath); + return FALSE; + } + DWORD BytesWritten; + DWORD LastError; + if (!WriteFile(DestinationHandle, LockedResource, SizeResource, &BytesWritten, NULL)) + { + LastError = LOG_LAST_ERROR(L"Failed to write file %s", DestinationPath); + goto cleanupDestinationHandle; + } + if (BytesWritten != SizeResource) + { + LOG(WINTUN_LOG_ERR, + L"Incomplete write to %s (written: %u, expected: %u)", + DestinationPath, + BytesWritten, + SizeResource); + LastError = ERROR_WRITE_FAULT; + goto cleanupDestinationHandle; + } + LastError = ERROR_SUCCESS; +cleanupDestinationHandle: + CloseHandle(DestinationHandle); + return RET_ERROR(TRUE, LastError); +} + +_Return_type_success_(return != FALSE) +BOOL +ResourceCreateTemporaryDirectory(_Out_writes_z_(MAX_PATH) LPWSTR RandomTempSubDirectory) +{ + WCHAR WindowsDirectory[MAX_PATH]; + if (!GetWindowsDirectoryW(WindowsDirectory, _countof(WindowsDirectory))) + { + LOG_LAST_ERROR(L"Failed to get Windows folder"); + return FALSE; + } + WCHAR WindowsTempDirectory[MAX_PATH]; + if (!PathCombineW(WindowsTempDirectory, WindowsDirectory, L"Temp")) + { + SetLastError(ERROR_BUFFER_OVERFLOW); + return FALSE; + } + UCHAR RandomBytes[32] = { 0 }; + if (!RtlGenRandom(RandomBytes, sizeof(RandomBytes))) + { + LOG(WINTUN_LOG_ERR, L"Failed to generate random"); + SetLastError(ERROR_GEN_FAILURE); + return FALSE; + } + WCHAR RandomSubDirectory[sizeof(RandomBytes) * 2 + 1]; + for (int i = 0; i < sizeof(RandomBytes); ++i) + swprintf_s(&RandomSubDirectory[i * 2], 3, L"%02x", RandomBytes[i]); + if (!PathCombineW(RandomTempSubDirectory, WindowsTempDirectory, RandomSubDirectory)) + { + SetLastError(ERROR_BUFFER_OVERFLOW); + return FALSE; + } + if (!CreateDirectoryW(RandomTempSubDirectory, &SecurityAttributes)) + { + LOG_LAST_ERROR(L"Failed to create temporary folder %s", RandomTempSubDirectory); + return FALSE; + } + return TRUE; +} diff --git a/api/resource.h b/api/resource.h new file mode 100644 index 0000000..ae1210b --- /dev/null +++ b/api/resource.h @@ -0,0 +1,50 @@ +/* SPDX-License-Identifier: GPL-2.0 + * + * Copyright (C) 2018-2021 WireGuard LLC. All Rights Reserved. + */ + +#pragma once + +#include "wintun.h" +#include <Windows.h> + +/** + * Locates RT_RCDATA resource memory address and size. + * + * @param ResourceName Name of the RT_RCDATA resource. Use MAKEINTRESOURCEW to locate resource by ID. + * + * @param Size Pointer to a variable to receive resource size. + * + * @return Resource address on success. If the function fails, the return value is NULL. To get extended error + * information, call GetLastError. + */ +_Must_inspect_result_ +_Return_type_success_(return != NULL) +_Post_maybenull_ +_Post_readable_byte_size_(*Size) const VOID *ResourceGetAddress(_In_z_ LPCWSTR ResourceName, _Out_ DWORD *Size); + +/** + * Copies resource to a file. + * + * @param DestinationPath File path + * + * @param ResourceName Name of the RT_RCDATA resource. Use MAKEINTRESOURCEW to locate resource by ID. + * + * @return If the function succeeds, the return value is nonzero. If the function fails, the return value is zero. To + * get extended error information, call GetLastError. + */ +_Return_type_success_(return != FALSE) +BOOL +ResourceCopyToFile(_In_z_ LPCWSTR DestinationPath, _In_z_ LPCWSTR ResourceName); + +/** + * Creates a temporary directory. + * + * @param RandomTempSubDirectory Name of random temporary directory. + * + * @return If the function succeeds, the return value is nonzero. If the function fails, the return value is zero. To + * get extended error information, call GetLastError. + */ +_Return_type_success_(return != FALSE) +BOOL +ResourceCreateTemporaryDirectory(_Out_writes_z_(MAX_PATH) LPWSTR RandomTempSubDirectory); diff --git a/api/resources.rc b/api/resources.rc new file mode 100644 index 0000000..e7fab9c --- /dev/null +++ b/api/resources.rc @@ -0,0 +1,65 @@ +/* SPDX-License-Identifier: GPL-2.0 + * + * Copyright (C) 2018-2021 WireGuard LLC. All Rights Reserved. + */ + +#include <windows.h> +#include <ntverp.h> + +#pragma code_page(1252) + +wintun.cat RCDATA "driver\\wintun.cat" +wintun.inf RCDATA "driver\\wintun.inf" +wintun.sys RCDATA "driver\\wintun.sys" + +#if defined(WANT_AMD64_WOW64) +# if defined(BUILT_AMD64_WOW64) +wintun-amd64.cat RCDATA "amd64\\driver\\wintun.cat" +wintun-amd64.inf RCDATA "amd64\\driver\\wintun.inf" +wintun-amd64.sys RCDATA "amd64\\driver\\wintun.sys" +setupapihost-amd64.dll RCDATA "amd64\\setupapihost.dll" +# else +# pragma message("AMD64 wintun.sys was not built, so this will not work from WOW64") +# endif +#endif +#if defined(WANT_ARM64_WOW64) +# if defined(BUILT_ARM64_WOW64) +wintun-arm64.cat RCDATA "arm64\\driver\\wintun.cat" +wintun-arm64.inf RCDATA "arm64\\driver\\wintun.inf" +wintun-arm64.sys RCDATA "arm64\\driver\\wintun.sys" +setupapihost-arm64.dll RCDATA "arm64\\setupapihost.dll" +# else +# pragma message("ARM64 wintun.sys was not built, so this will not work from WOW64") +# endif +#endif + +#define STRINGIZE(x) #x +#define EXPAND(x) STRINGIZE(x) + +VS_VERSION_INFO VERSIONINFO +FILEVERSION WINTUN_VERSION_MAJ, WINTUN_VERSION_MIN, WINTUN_VERSION_REL, 0 +PRODUCTVERSION WINTUN_VERSION_MAJ, WINTUN_VERSION_MIN, WINTUN_VERSION_REL, 0 +FILEOS VOS_NT_WINDOWS32 +FILETYPE VFT_DLL +FILESUBTYPE VFT2_UNKNOWN +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" + BEGIN + VALUE "CompanyName", "WireGuard LLC" + VALUE "FileDescription", "Wintun API Library" + VALUE "FileVersion", EXPAND(WINTUN_VERSION) + VALUE "InternalName", "wintun.dll" + VALUE "LegalCopyright", "Copyright \xa9 2018-2021 WireGuard LLC. All Rights Reserved." + VALUE "OriginalFilename", "wintun.dll" + VALUE "ProductName", "Wintun Driver" + VALUE "ProductVersion", EXPAND(WINTUN_VERSION) + VALUE "Comments", "https://www.wintun.net/" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END diff --git a/api/rundll32.c b/api/rundll32.c new file mode 100644 index 0000000..5ea2b15 --- /dev/null +++ b/api/rundll32.c @@ -0,0 +1,398 @@ +/* SPDX-License-Identifier: GPL-2.0 + * + * Copyright (C) 2018-2021 WireGuard LLC. All Rights Reserved. + */ + +#include "rundll32.h" +#include "adapter.h" +#include "main.h" +#include "logger.h" +#include "resource.h" +#include <Windows.h> +#include <shellapi.h> +#include <Shlwapi.h> +#include <cfgmgr32.h> +#include <objbase.h> +#include <assert.h> + +#ifdef MAYBE_WOW64 + +_Return_type_success_(return != FALSE) +static BOOL +AppendToBuffer(_Inout_ LPWSTR *Buffer, _In_ CONST WCHAR Addition, _Inout_ SIZE_T *BufferPos, _Inout_ SIZE_T *BufferLen) +{ + SIZE_T NewPos; + if (FAILED(SIZETAdd(*BufferPos, sizeof(Addition), &NewPos))) + return FALSE; + if (NewPos >= *BufferLen) + { + SIZE_T NewLen; + if (FAILED(SIZETMult(NewPos, 3, &NewLen))) + return FALSE; + LPWSTR NewBuffer = ReZalloc(*Buffer, NewLen); + if (!NewBuffer) + return FALSE; + *Buffer = NewBuffer; + *BufferLen = NewLen; + } + SIZE_T NewIndex = *BufferPos / sizeof(**Buffer); + if (*Buffer + NewIndex < *Buffer) + return FALSE; + (*Buffer)[NewIndex] = Addition; + *BufferPos = NewPos; + return TRUE; +} + +_Must_inspect_result_ +static _Return_type_success_(return != NULL) +_Post_maybenull_ +LPWSTR +ArgvToCommandLineW(_In_ SIZE_T ArgCount, ...) +{ + LPWSTR Output = NULL; + SIZE_T BufferPos = 0, BufferLen = 0; +# define Append(Char) \ + do \ + { \ + if (!AppendToBuffer(&Output, Char, &BufferPos, &BufferLen)) \ + goto cleanupBuffer; \ + } while (0) + + va_list Args; + va_start(Args, ArgCount); + for (SIZE_T i = 0; i < ArgCount; ++i) + { + LPCWSTR Arg = va_arg(Args, LPCWSTR); + SIZE_T ArgLen = wcslen(Arg); + if (ArgLen >= DWORD_MAX >> 3) + goto cleanupBuffer; + if (i) + Append(L' '); + Append(L'"'); + for (SIZE_T j = 0;; ++j) + { + SIZE_T NumberBackslashes = 0; + + while (j < ArgLen && Arg[j] == L'\\') + { + ++j; + ++NumberBackslashes; + } + if (j >= ArgLen) + { + for (SIZE_T k = 0; k < NumberBackslashes * 2; ++k) + Append(L'\\'); + break; + } + else if (Arg[j] == L'"') + { + for (SIZE_T k = 0; k < NumberBackslashes * 2 + 1; ++k) + Append(L'\\'); + Append(Arg[j]); + } + else + { + for (SIZE_T k = 0; k < NumberBackslashes; ++k) + Append(L'\\'); + Append(Arg[j]); + } + } + Append(L'"'); + } + va_end(Args); + return Output; + +cleanupBuffer: + Free(Output); + return NULL; +# undef Append +} + +typedef struct _PROCESS_STDOUT_STATE +{ + HANDLE Stdout; + LPWSTR Response; + DWORD ResponseCapacity; +} PROCESS_STDOUT_STATE; + +_Return_type_success_(return != ERROR_SUCCESS) +static DWORD WINAPI +ProcessStdout(_Inout_ PROCESS_STDOUT_STATE *State) +{ + for (DWORD Offset = 0, MaxLen = State->ResponseCapacity - 1; Offset < MaxLen;) + { + DWORD Size; + if (FAILED(DWordMult(MaxLen - Offset, sizeof(WCHAR), &Size))) + return ERROR_BUFFER_OVERFLOW; + if (!ReadFile(State->Stdout, State->Response + Offset, Size, &Size, NULL)) + return ERROR_SUCCESS; + if (Size % sizeof(WCHAR)) + return ERROR_INVALID_DATA; + Offset += Size / sizeof(WCHAR); + State->Response[Offset] = 0; + } + return ERROR_BUFFER_OVERFLOW; +} + +static DWORD WINAPI +ProcessStderr(_In_ HANDLE Stderr) +{ + WCHAR Msg[0x200], Buf[0x220], LevelRune; + DWORD64 Timestamp; + DWORD SizeRead; + WINTUN_LOGGER_LEVEL Level; + for (;;) + { + if (!ReadFile(Stderr, Buf, sizeof(Buf), &SizeRead, NULL) || !SizeRead) + return ERROR_SUCCESS; + if (SizeRead % sizeof(*Buf)) + return ERROR_INVALID_DATA; + Msg[0] = Buf[SizeRead / sizeof(*Buf) - 1] = L'\0'; + if (swscanf_s(Buf, L"[%c %I64u] %[^\n]", &LevelRune, 1, &Timestamp, Msg, (DWORD)_countof(Msg)) != 3 || !Msg[0]) + return ERROR_INVALID_DATA; + if (!((Level = WINTUN_LOG_INFO, LevelRune == L'+') || (Level = WINTUN_LOG_WARN, LevelRune == L'-') || + (Level = WINTUN_LOG_ERR, LevelRune == L'!'))) + return ERROR_INVALID_DATA; + Logger(Level, Timestamp, Msg); + } +} + +static _Return_type_success_(return != FALSE) +BOOL +ExecuteRunDll32( + _In_z_ LPCWSTR Function, + _In_z_ LPCWSTR Arguments, + _Out_z_cap_c_(ResponseCapacity) LPWSTR Response, + _In_ DWORD ResponseCapacity) +{ + WCHAR WindowsDirectory[MAX_PATH]; + if (!GetWindowsDirectoryW(WindowsDirectory, _countof(WindowsDirectory))) + { + LOG_LAST_ERROR(L"Failed to get Windows folder"); + return FALSE; + } + WCHAR RunDll32Path[MAX_PATH]; + if (!PathCombineW(RunDll32Path, WindowsDirectory, L"Sysnative\\rundll32.exe")) + { + SetLastError(ERROR_BUFFER_OVERFLOW); + return FALSE; + } + + DWORD LastError; + WCHAR RandomTempSubDirectory[MAX_PATH]; + if (!ResourceCreateTemporaryDirectory(RandomTempSubDirectory)) + { + LOG(WINTUN_LOG_ERR, L"Failed to create temporary folder"); + return FALSE; + } + WCHAR DllPath[MAX_PATH] = { 0 }; + if (!PathCombineW(DllPath, RandomTempSubDirectory, L"setupapihost.dll")) + { + LastError = ERROR_BUFFER_OVERFLOW; + goto cleanupDirectory; + } + LPCWSTR WintunDllResourceName; + switch (NativeMachine) + { + case IMAGE_FILE_MACHINE_AMD64: + WintunDllResourceName = L"setupapihost-amd64.dll"; + break; + case IMAGE_FILE_MACHINE_ARM64: + WintunDllResourceName = L"setupapihost-arm64.dll"; + break; + default: + LOG(WINTUN_LOG_ERR, L"Unsupported platform 0x%x", NativeMachine); + LastError = ERROR_NOT_SUPPORTED; + goto cleanupDirectory; + } + if (!ResourceCopyToFile(DllPath, WintunDllResourceName)) + { + LastError = LOG(WINTUN_LOG_ERR, L"Failed to copy resource %s to %s", WintunDllResourceName, DllPath); + goto cleanupDelete; + } + size_t CommandLineLen = 10 + MAX_PATH + 2 + wcslen(Arguments) + 1 + wcslen(Function) + 1; + LPWSTR CommandLine = AllocArray(CommandLineLen, sizeof(*CommandLine)); + if (!CommandLine) + { + LastError = GetLastError(); + goto cleanupDelete; + } + if (_snwprintf_s( + CommandLine, + CommandLineLen, + _TRUNCATE, + L"rundll32 \"%.*s\",%s %s", + MAX_PATH, + DllPath, + Function, + Arguments) == -1) + { + LOG(WINTUN_LOG_ERR, L"Command line too long"); + LastError = ERROR_INVALID_PARAMETER; + goto cleanupDelete; + } + HANDLE StreamRStdout = INVALID_HANDLE_VALUE, StreamRStderr = INVALID_HANDLE_VALUE, + StreamWStdout = INVALID_HANDLE_VALUE, StreamWStderr = INVALID_HANDLE_VALUE; + if (!CreatePipe(&StreamRStdout, &StreamWStdout, &SecurityAttributes, 0) || + !CreatePipe(&StreamRStderr, &StreamWStderr, &SecurityAttributes, 0)) + { + LastError = LOG_LAST_ERROR(L"Failed to create pipes"); + goto cleanupPipes; + } + if (!SetHandleInformation(StreamWStdout, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT) || + !SetHandleInformation(StreamWStderr, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT)) + { + LastError = LOG_LAST_ERROR(L"Failed to set handle info"); + goto cleanupPipes; + } + if (ResponseCapacity) + Response[0] = 0; + PROCESS_STDOUT_STATE ProcessStdoutState = { .Stdout = StreamRStdout, + .Response = Response, + .ResponseCapacity = ResponseCapacity }; + HANDLE ThreadStdout = NULL, ThreadStderr = NULL; + if ((ThreadStdout = CreateThread(NULL, 0, ProcessStdout, &ProcessStdoutState, 0, NULL)) == NULL || + (ThreadStderr = CreateThread(NULL, 0, ProcessStderr, StreamRStderr, 0, NULL)) == NULL) + { + LastError = LOG_LAST_ERROR(L"Failed to spawn readers"); + goto cleanupThreads; + } + STARTUPINFOW si = { .cb = sizeof(STARTUPINFO), + .dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES, + .wShowWindow = SW_HIDE, + .hStdOutput = StreamWStdout, + .hStdError = StreamWStderr }; + PROCESS_INFORMATION pi; + if (!CreateProcessW(RunDll32Path, CommandLine, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi)) + { + LastError = LOG_LAST_ERROR(L"Failed to create process: %s", CommandLine); + goto cleanupThreads; + } + LastError = ERROR_SUCCESS; + WaitForSingleObject(pi.hProcess, INFINITE); + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); +cleanupThreads: + if (ThreadStderr) + { + CloseHandle(StreamWStderr); + StreamWStderr = INVALID_HANDLE_VALUE; + WaitForSingleObject(ThreadStderr, INFINITE); + CloseHandle(ThreadStderr); + } + if (ThreadStdout) + { + CloseHandle(StreamWStdout); + StreamWStdout = INVALID_HANDLE_VALUE; + WaitForSingleObject(ThreadStdout, INFINITE); + DWORD ThreadResult; + if (!GetExitCodeThread(ThreadStdout, &ThreadResult)) + LastError = LOG_LAST_ERROR(L"Failed to retrieve stdout reader result"); + else if (ThreadResult != ERROR_SUCCESS) + LastError = LOG_ERROR(ThreadResult, L"Failed to read process output"); + CloseHandle(ThreadStdout); + } +cleanupPipes: + CloseHandle(StreamRStderr); + CloseHandle(StreamWStderr); + CloseHandle(StreamRStdout); + CloseHandle(StreamWStdout); + Free(CommandLine); +cleanupDelete: + DeleteFileW(DllPath); +cleanupDirectory: + RemoveDirectoryW(RandomTempSubDirectory); + return RET_ERROR(TRUE, LastError); +} + +static _Return_type_success_(return != FALSE) +BOOL +InvokeClassInstaller(_In_ LPCWSTR Action, _In_ LPCWSTR Function, _In_ HDEVINFO DevInfo, _In_ SP_DEVINFO_DATA *DevInfoData) +{ + LOG(WINTUN_LOG_INFO, L"Spawning native process to %s instance", Action); + + WCHAR InstanceId[MAX_DEVICE_ID_LEN]; + DWORD RequiredChars = _countof(InstanceId); + if (!SetupDiGetDeviceInstanceIdW(DevInfo, DevInfoData, InstanceId, RequiredChars, &RequiredChars)) + { + LOG_LAST_ERROR(L"Failed to get adapter instance ID"); + return FALSE; + } + LPWSTR Arguments = ArgvToCommandLineW(1, InstanceId); + if (!Arguments) + { + SetLastError(LOG_ERROR(ERROR_INVALID_PARAMETER, L"Command line too long")); + return FALSE; + } + DWORD LastError; + WCHAR Response[8 + 1]; + if (!ExecuteRunDll32(Function, Arguments, Response, _countof(Response))) + { + LastError = LOG_LAST_ERROR(L"Error executing worker process: %s", Arguments); + goto cleanupArguments; + } + int Argc; + LPWSTR *Argv = CommandLineToArgvW(Response, &Argc); + if (Argc < 1) + { + LastError = LOG_ERROR(ERROR_INVALID_PARAMETER, L"Incomplete response: %s", Response); + goto cleanupArgv; + } + LastError = wcstoul(Argv[0], NULL, 16); +cleanupArgv: + LocalFree(Argv); +cleanupArguments: + Free(Arguments); + return RET_ERROR(TRUE, LastError); +} + +_Use_decl_annotations_ +BOOL +RemoveInstanceViaRundll32(HDEVINFO DevInfo, SP_DEVINFO_DATA *DevInfoData) +{ + return InvokeClassInstaller(L"remove", L"RemoveInstance", DevInfo, DevInfoData); +} + +_Use_decl_annotations_ +BOOL +EnableInstanceViaRundll32(HDEVINFO DevInfo, SP_DEVINFO_DATA *DevInfoData) +{ + return InvokeClassInstaller(L"enable", L"EnableInstance", DevInfo, DevInfoData); +} + +_Use_decl_annotations_ +BOOL +DisableInstanceViaRundll32(HDEVINFO DevInfo, SP_DEVINFO_DATA *DevInfoData) +{ + return InvokeClassInstaller(L"disable", L"DisableInstance", DevInfo, DevInfoData); +} + +_Use_decl_annotations_ +BOOL +CreateInstanceWin7ViaRundll32(LPWSTR InstanceId) +{ + LOG(WINTUN_LOG_INFO, L"Spawning native process to create instance"); + + DWORD LastError; + WCHAR Response[MAX_DEVICE_ID_LEN + 1]; + if (!ExecuteRunDll32(L"CreateInstanceWin7", L"", Response, _countof(Response))) + { + LastError = LOG_LAST_ERROR(L"Error executing worker process"); + goto cleanup; + } + int Argc; + LPWSTR *Argv = CommandLineToArgvW(Response, &Argc); + if (Argc < 2) + { + LastError = LOG_ERROR(ERROR_INVALID_PARAMETER, L"Incomplete response: %s", Response); + goto cleanupArgv; + } + LastError = wcstoul(Argv[0], NULL, 16); + if (LastError == ERROR_SUCCESS) + wcsncpy_s(InstanceId, MAX_DEVICE_ID_LEN, Argv[1], _TRUNCATE); +cleanupArgv: + LocalFree(Argv); +cleanup: + return RET_ERROR(TRUE, LastError); +} +#endif diff --git a/api/rundll32.h b/api/rundll32.h new file mode 100644 index 0000000..762bfcf --- /dev/null +++ b/api/rundll32.h @@ -0,0 +1,26 @@ +/* SPDX-License-Identifier: GPL-2.0 + * + * Copyright (C) 2018-2021 WireGuard LLC. All Rights Reserved. + */ + +#pragma once + +#include <Windows.h> +#include <SetupAPI.h> +#include "adapter.h" + +_Return_type_success_(return != FALSE) +BOOL +RemoveInstanceViaRundll32(_In_ HDEVINFO DevInfo, _In_ SP_DEVINFO_DATA *DevInfoData); + +_Return_type_success_(return != FALSE) +BOOL +EnableInstanceViaRundll32(_In_ HDEVINFO DevInfo, _In_ SP_DEVINFO_DATA *DevInfoData); + +_Return_type_success_(return != FALSE) +BOOL +DisableInstanceViaRundll32(_In_ HDEVINFO DevInfo, _In_ SP_DEVINFO_DATA *DevInfoData); + +_Return_type_success_(return != FALSE) +BOOL +CreateInstanceWin7ViaRundll32(_Out_writes_z_(MAX_DEVICE_ID_LEN) LPWSTR InstanceId);
\ No newline at end of file diff --git a/api/session.c b/api/session.c new file mode 100644 index 0000000..ab96c64 --- /dev/null +++ b/api/session.c @@ -0,0 +1,309 @@ +/* SPDX-License-Identifier: GPL-2.0 + * + * Copyright (C) 2018-2021 WireGuard LLC. All Rights Reserved. + */ + +#include "adapter.h" +#include "logger.h" +#include "main.h" +#include "wintun.h" +#include <Windows.h> +#include <devioctl.h> +#include <stdlib.h> + +#pragma warning(disable : 4200) /* nonstandard: zero-sized array in struct/union */ + +#define TUN_ALIGNMENT sizeof(ULONG) +#define TUN_ALIGN(Size) (((ULONG)(Size) + ((ULONG)TUN_ALIGNMENT - 1)) & ~((ULONG)TUN_ALIGNMENT - 1)) +#define TUN_IS_ALIGNED(Size) (!((ULONG)(Size) & ((ULONG)TUN_ALIGNMENT - 1))) +#define TUN_MAX_PACKET_SIZE TUN_ALIGN(sizeof(TUN_PACKET) + WINTUN_MAX_IP_PACKET_SIZE) +#define TUN_RING_CAPACITY(Size) ((Size) - sizeof(TUN_RING) - (TUN_MAX_PACKET_SIZE - TUN_ALIGNMENT)) +#define TUN_RING_SIZE(Capacity) (sizeof(TUN_RING) + (Capacity) + (TUN_MAX_PACKET_SIZE - TUN_ALIGNMENT)) +#define TUN_RING_WRAP(Value, Capacity) ((Value) & (Capacity - 1)) +#define LOCK_SPIN_COUNT 0x10000 +#define TUN_PACKET_RELEASE ((DWORD)0x80000000) + +typedef struct _TUN_PACKET +{ + ULONG Size; + UCHAR Data[]; +} TUN_PACKET; + +typedef struct _TUN_RING +{ + volatile ULONG Head; + volatile ULONG Tail; + volatile LONG Alertable; + UCHAR Data[]; +} TUN_RING; + +#define TUN_IOCTL_REGISTER_RINGS CTL_CODE(51820U, 0x970U, METHOD_BUFFERED, FILE_READ_DATA | FILE_WRITE_DATA) + +typedef struct _TUN_REGISTER_RINGS +{ + struct + { + ULONG RingSize; + TUN_RING *Ring; + HANDLE TailMoved; + } Send, Receive; +} TUN_REGISTER_RINGS; + +typedef struct _TUN_SESSION +{ + ULONG Capacity; + struct + { + ULONG Tail; + ULONG TailRelease; + ULONG PacketsToRelease; + CRITICAL_SECTION Lock; + } Receive; + struct + { + ULONG Head; + ULONG HeadRelease; + ULONG PacketsToRelease; + CRITICAL_SECTION Lock; + } Send; + TUN_REGISTER_RINGS Descriptor; + HANDLE Handle; +} TUN_SESSION; + +WINTUN_START_SESSION_FUNC WintunStartSession; +_Use_decl_annotations_ +TUN_SESSION *WINAPI +WintunStartSession(WINTUN_ADAPTER *Adapter, DWORD Capacity) +{ + DWORD LastError; + TUN_SESSION *Session = Zalloc(sizeof(TUN_SESSION)); + if (!Session) + { + LastError = GetLastError(); + goto cleanup; + } + const ULONG RingSize = TUN_RING_SIZE(Capacity); + BYTE *AllocatedRegion = VirtualAlloc(0, (size_t)RingSize * 2, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); + if (!AllocatedRegion) + { + LastError = LOG_LAST_ERROR(L"Failed to allocate ring memory (requested size: 0x%zx)", (size_t)RingSize * 2); + goto cleanupRings; + } + Session->Descriptor.Send.RingSize = RingSize; + Session->Descriptor.Send.Ring = (TUN_RING *)AllocatedRegion; + Session->Descriptor.Send.TailMoved = CreateEventW(&SecurityAttributes, FALSE, FALSE, NULL); + if (!Session->Descriptor.Send.TailMoved) + { + LastError = LOG_LAST_ERROR(L"Failed to create send event"); + goto cleanupAllocatedRegion; + } + + Session->Descriptor.Receive.RingSize = RingSize; + Session->Descriptor.Receive.Ring = (TUN_RING *)(AllocatedRegion + RingSize); + Session->Descriptor.Receive.TailMoved = CreateEventW(&SecurityAttributes, FALSE, FALSE, NULL); + if (!Session->Descriptor.Receive.TailMoved) + { + LastError = LOG_LAST_ERROR(L"Failed to create receive event"); + goto cleanupSendTailMoved; + } + + Session->Handle = AdapterOpenDeviceObject(Adapter); + if (Session->Handle == INVALID_HANDLE_VALUE) + { + LastError = LOG(WINTUN_LOG_ERR, L"Failed to open adapter device object"); + goto cleanupReceiveTailMoved; + } + DWORD BytesReturned; + if (!DeviceIoControl( + Session->Handle, + TUN_IOCTL_REGISTER_RINGS, + &Session->Descriptor, + sizeof(TUN_REGISTER_RINGS), + NULL, + 0, + &BytesReturned, + NULL)) + { + LastError = LOG_LAST_ERROR(L"Failed to register rings"); + goto cleanupHandle; + } + Session->Capacity = Capacity; + (VOID) InitializeCriticalSectionAndSpinCount(&Session->Receive.Lock, LOCK_SPIN_COUNT); + (VOID) InitializeCriticalSectionAndSpinCount(&Session->Send.Lock, LOCK_SPIN_COUNT); + return Session; +cleanupHandle: + CloseHandle(Session->Handle); +cleanupReceiveTailMoved: + CloseHandle(Session->Descriptor.Receive.TailMoved); +cleanupSendTailMoved: + CloseHandle(Session->Descriptor.Send.TailMoved); +cleanupAllocatedRegion: + VirtualFree(AllocatedRegion, 0, MEM_RELEASE); +cleanupRings: + Free(Session); +cleanup: + SetLastError(LastError); + return NULL; +} + +WINTUN_END_SESSION_FUNC WintunEndSession; +_Use_decl_annotations_ +VOID WINAPI +WintunEndSession(TUN_SESSION *Session) +{ + DeleteCriticalSection(&Session->Send.Lock); + DeleteCriticalSection(&Session->Receive.Lock); + CloseHandle(Session->Handle); + CloseHandle(Session->Descriptor.Send.TailMoved); + CloseHandle(Session->Descriptor.Receive.TailMoved); + VirtualFree(Session->Descriptor.Send.Ring, 0, MEM_RELEASE); + Free(Session); +} + +WINTUN_GET_READ_WAIT_EVENT_FUNC WintunGetReadWaitEvent; +_Use_decl_annotations_ +HANDLE WINAPI +WintunGetReadWaitEvent(TUN_SESSION *Session) +{ + return Session->Descriptor.Send.TailMoved; +} + +WINTUN_RECEIVE_PACKET_FUNC WintunReceivePacket; +_Use_decl_annotations_ +BYTE *WINAPI +WintunReceivePacket(TUN_SESSION *Session, DWORD *PacketSize) +{ + DWORD LastError; + EnterCriticalSection(&Session->Send.Lock); + if (Session->Send.Head >= Session->Capacity) + { + LastError = ERROR_HANDLE_EOF; + goto cleanup; + } + const ULONG BuffTail = ReadULongAcquire(&Session->Descriptor.Send.Ring->Tail); + if (BuffTail >= Session->Capacity) + { + LastError = ERROR_HANDLE_EOF; + goto cleanup; + } + if (Session->Send.Head == BuffTail) + { + LastError = ERROR_NO_MORE_ITEMS; + goto cleanup; + } + const ULONG BuffContent = TUN_RING_WRAP(BuffTail - Session->Send.Head, Session->Capacity); + if (BuffContent < sizeof(TUN_PACKET)) + { + LastError = ERROR_INVALID_DATA; + goto cleanup; + } + TUN_PACKET *BuffPacket = (TUN_PACKET *)&Session->Descriptor.Send.Ring->Data[Session->Send.Head]; + if (BuffPacket->Size > WINTUN_MAX_IP_PACKET_SIZE) + { + LastError = ERROR_INVALID_DATA; + goto cleanup; + } + const ULONG AlignedPacketSize = TUN_ALIGN(sizeof(TUN_PACKET) + BuffPacket->Size); + if (AlignedPacketSize > BuffContent) + { + LastError = ERROR_INVALID_DATA; + goto cleanup; + } + *PacketSize = BuffPacket->Size; + BYTE *Packet = BuffPacket->Data; + Session->Send.Head = TUN_RING_WRAP(Session->Send.Head + AlignedPacketSize, Session->Capacity); + Session->Send.PacketsToRelease++; + LeaveCriticalSection(&Session->Send.Lock); + return Packet; +cleanup: + LeaveCriticalSection(&Session->Send.Lock); + SetLastError(LastError); + return NULL; +} + +WINTUN_RELEASE_RECEIVE_PACKET_FUNC WintunReleaseReceivePacket; +_Use_decl_annotations_ +VOID WINAPI +WintunReleaseReceivePacket(TUN_SESSION *Session, const BYTE *Packet) +{ + EnterCriticalSection(&Session->Send.Lock); + TUN_PACKET *ReleasedBuffPacket = (TUN_PACKET *)(Packet - offsetof(TUN_PACKET, Data)); + ReleasedBuffPacket->Size |= TUN_PACKET_RELEASE; + while (Session->Send.PacketsToRelease) + { + const TUN_PACKET *BuffPacket = (TUN_PACKET *)&Session->Descriptor.Send.Ring->Data[Session->Send.HeadRelease]; + if ((BuffPacket->Size & TUN_PACKET_RELEASE) == 0) + break; + const ULONG AlignedPacketSize = TUN_ALIGN(sizeof(TUN_PACKET) + (BuffPacket->Size & ~TUN_PACKET_RELEASE)); + Session->Send.HeadRelease = TUN_RING_WRAP(Session->Send.HeadRelease + AlignedPacketSize, Session->Capacity); + Session->Send.PacketsToRelease--; + } + WriteULongRelease(&Session->Descriptor.Send.Ring->Head, Session->Send.HeadRelease); + LeaveCriticalSection(&Session->Send.Lock); +} + +WINTUN_ALLOCATE_SEND_PACKET_FUNC WintunAllocateSendPacket; +_Use_decl_annotations_ +BYTE *WINAPI +WintunAllocateSendPacket(TUN_SESSION *Session, DWORD PacketSize) +{ + DWORD LastError; + EnterCriticalSection(&Session->Receive.Lock); + if (Session->Receive.Tail >= Session->Capacity) + { + LastError = ERROR_HANDLE_EOF; + goto cleanup; + } + const ULONG AlignedPacketSize = TUN_ALIGN(sizeof(TUN_PACKET) + PacketSize); + const ULONG BuffHead = ReadULongAcquire(&Session->Descriptor.Receive.Ring->Head); + if (BuffHead >= Session->Capacity) + { + LastError = ERROR_HANDLE_EOF; + goto cleanup; + } + const ULONG BuffSpace = TUN_RING_WRAP(BuffHead - Session->Receive.Tail - TUN_ALIGNMENT, Session->Capacity); + if (AlignedPacketSize > BuffSpace) + { + LastError = ERROR_BUFFER_OVERFLOW; + goto cleanup; + } + TUN_PACKET *BuffPacket = (TUN_PACKET *)&Session->Descriptor.Receive.Ring->Data[Session->Receive.Tail]; + BuffPacket->Size = PacketSize | TUN_PACKET_RELEASE; + BYTE *Packet = BuffPacket->Data; + Session->Receive.Tail = TUN_RING_WRAP(Session->Receive.Tail + AlignedPacketSize, Session->Capacity); + Session->Receive.PacketsToRelease++; + LeaveCriticalSection(&Session->Receive.Lock); + return Packet; +cleanup: + LeaveCriticalSection(&Session->Receive.Lock); + SetLastError(LastError); + return NULL; +} + +WINTUN_SEND_PACKET_FUNC WintunSendPacket; +_Use_decl_annotations_ +VOID WINAPI +WintunSendPacket(TUN_SESSION *Session, const BYTE *Packet) +{ + EnterCriticalSection(&Session->Receive.Lock); + TUN_PACKET *ReleasedBuffPacket = (TUN_PACKET *)(Packet - offsetof(TUN_PACKET, Data)); + ReleasedBuffPacket->Size &= ~TUN_PACKET_RELEASE; + while (Session->Receive.PacketsToRelease) + { + const TUN_PACKET *BuffPacket = + (TUN_PACKET *)&Session->Descriptor.Receive.Ring->Data[Session->Receive.TailRelease]; + if (BuffPacket->Size & TUN_PACKET_RELEASE) + break; + const ULONG AlignedPacketSize = TUN_ALIGN(sizeof(TUN_PACKET) + BuffPacket->Size); + Session->Receive.TailRelease = + TUN_RING_WRAP(Session->Receive.TailRelease + AlignedPacketSize, Session->Capacity); + Session->Receive.PacketsToRelease--; + } + if (Session->Descriptor.Receive.Ring->Tail != Session->Receive.TailRelease) + { + WriteULongRelease(&Session->Descriptor.Receive.Ring->Tail, Session->Receive.TailRelease); + if (ReadAcquire(&Session->Descriptor.Receive.Ring->Alertable)) + SetEvent(Session->Descriptor.Receive.TailMoved); + } + LeaveCriticalSection(&Session->Receive.Lock); +} diff --git a/api/wintun.h b/api/wintun.h new file mode 100644 index 0000000..bcf5ccc --- /dev/null +++ b/api/wintun.h @@ -0,0 +1,274 @@ +/* SPDX-License-Identifier: GPL-2.0 OR MIT + * + * Copyright (C) 2018-2021 WireGuard LLC. All Rights Reserved. + */ + +#pragma once + +#include <winsock2.h> +#include <windows.h> +#include <ipexport.h> +#include <ifdef.h> +#include <ws2ipdef.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef ALIGNED +# if defined(_MSC_VER) +# define ALIGNED(n) __declspec(align(n)) +# elif defined(__GNUC__) +# define ALIGNED(n) __attribute__((aligned(n))) +# else +# error "Unable to define ALIGNED" +# endif +#endif + +/* MinGW is missing this one, unfortunately. */ +#ifndef _Post_maybenull_ +# define _Post_maybenull_ +#endif + +#if defined(_MSC_VER) +# pragma warning(push) +# pragma warning(disable : 4324) /* structure was padded due to alignment specifier */ +#endif + +/** + * A handle representing Wintun adapter + */ +typedef struct _WINTUN_ADAPTER *WINTUN_ADAPTER_HANDLE; + +/** + * Creates a new Wintun adapter. + * + * @param Name The requested name of the adapter. Zero-terminated string of up to MAX_ADAPTER_NAME-1 + * characters. + * + * @param TunnelType Name of the adapter tunnel type. Zero-terminated string of up to MAX_ADAPTER_NAME-1 + * characters. + * + * @param RequestedGUID The GUID of the created network adapter, which then influences NLA generation deterministically. + * If it is set to NULL, the GUID is chosen by the system at random, and hence a new NLA entry is + * created for each new adapter. It is called "requested" GUID because the API it uses is + * completely undocumented, and so there could be minor interesting complications with its usage. + * + * @return If the function succeeds, the return value is the adapter handle. Must be released with + * WintunCloseAdapter. If the function fails, the return value is NULL. To get extended error information, call + * GetLastError. + */ +typedef _Must_inspect_result_ +_Return_type_success_(return != NULL) +_Post_maybenull_ +WINTUN_ADAPTER_HANDLE(WINAPI WINTUN_CREATE_ADAPTER_FUNC) +(_In_z_ LPCWSTR Name, _In_z_ LPCWSTR TunnelType, _In_opt_ const GUID *RequestedGUID); + +/** + * Opens an existing Wintun adapter. + * + * @param Name The requested name of the adapter. Zero-terminated string of up to MAX_ADAPTER_NAME-1 + * characters. + * + * @return If the function succeeds, the return value is the adapter handle. Must be released with + * WintunCloseAdapter. If the function fails, the return value is NULL. To get extended error information, call + * GetLastError. + */ +typedef _Must_inspect_result_ +_Return_type_success_(return != NULL) +_Post_maybenull_ +WINTUN_ADAPTER_HANDLE(WINAPI WINTUN_OPEN_ADAPTER_FUNC)(_In_z_ LPCWSTR Name); + +/** + * Releases Wintun adapter resources and, if adapter was created with WintunCreateAdapter, removes adapter. + * + * @param Adapter Adapter handle obtained with WintunCreateAdapter or WintunOpenAdapter. + */ +typedef VOID(WINAPI WINTUN_CLOSE_ADAPTER_FUNC)(_In_opt_ WINTUN_ADAPTER_HANDLE Adapter); + +/** + * Deletes the Wintun driver if there are no more adapters in use. + * + * @return If the function succeeds, the return value is nonzero. If the function fails, the return value is zero. To + * get extended error information, call GetLastError. + */ +typedef _Return_type_success_(return != FALSE) +BOOL(WINAPI WINTUN_DELETE_DRIVER_FUNC)(VOID); + +/** + * Returns the LUID of the adapter. + * + * @param Adapter Adapter handle obtained with WintunCreateAdapter or WintunOpenAdapter + * + * @param Luid Pointer to LUID to receive adapter LUID. + */ +typedef VOID(WINAPI WINTUN_GET_ADAPTER_LUID_FUNC)(_In_ WINTUN_ADAPTER_HANDLE Adapter, _Out_ NET_LUID *Luid); + +/** + * Determines the version of the Wintun driver currently loaded. + * + * @return If the function succeeds, the return value is the version number. If the function fails, the return value is + * zero. To get extended error information, call GetLastError. Possible errors include the following: + * ERROR_FILE_NOT_FOUND Wintun not loaded + */ +typedef _Return_type_success_(return != 0) +DWORD(WINAPI WINTUN_GET_RUNNING_DRIVER_VERSION_FUNC)(VOID); + +/** + * Determines the level of logging, passed to WINTUN_LOGGER_CALLBACK. + */ +typedef enum +{ + WINTUN_LOG_INFO, /**< Informational */ + WINTUN_LOG_WARN, /**< Warning */ + WINTUN_LOG_ERR /**< Error */ +} WINTUN_LOGGER_LEVEL; + +/** + * Called by internal logger to report diagnostic messages + * + * @param Level Message level. + * + * @param Timestamp Message timestamp in in 100ns intervals since 1601-01-01 UTC. + * + * @param Message Message text. + */ +typedef VOID(CALLBACK *WINTUN_LOGGER_CALLBACK)( + _In_ WINTUN_LOGGER_LEVEL Level, + _In_ DWORD64 Timestamp, + _In_z_ LPCWSTR Message); + +/** + * Sets logger callback function. + * + * @param NewLogger Pointer to callback function to use as a new global logger. NewLogger may be called from various + * threads concurrently. Should the logging require serialization, you must handle serialization in + * NewLogger. Set to NULL to disable. + */ +typedef VOID(WINAPI WINTUN_SET_LOGGER_FUNC)(_In_ WINTUN_LOGGER_CALLBACK NewLogger); + +/** + * Minimum ring capacity. + */ +#define WINTUN_MIN_RING_CAPACITY 0x20000 /* 128kiB */ + +/** + * Maximum ring capacity. + */ +#define WINTUN_MAX_RING_CAPACITY 0x4000000 /* 64MiB */ + +/** + * A handle representing Wintun session + */ +typedef struct _TUN_SESSION *WINTUN_SESSION_HANDLE; + +/** + * Starts Wintun session. + * + * @param Adapter Adapter handle obtained with WintunOpenAdapter or WintunCreateAdapter + * + * @param Capacity Rings capacity. Must be between WINTUN_MIN_RING_CAPACITY and WINTUN_MAX_RING_CAPACITY (incl.) + * Must be a power of two. + * + * @return Wintun session handle. Must be released with WintunEndSession. If the function fails, the return value is + * NULL. To get extended error information, call GetLastError. + */ +typedef _Must_inspect_result_ +_Return_type_success_(return != NULL) +_Post_maybenull_ +WINTUN_SESSION_HANDLE(WINAPI WINTUN_START_SESSION_FUNC)(_In_ WINTUN_ADAPTER_HANDLE Adapter, _In_ DWORD Capacity); + +/** + * Ends Wintun session. + * + * @param Session Wintun session handle obtained with WintunStartSession + */ +typedef VOID(WINAPI WINTUN_END_SESSION_FUNC)(_In_ WINTUN_SESSION_HANDLE Session); + +/** + * Gets Wintun session's read-wait event handle. + * + * @param Session Wintun session handle obtained with WintunStartSession + * + * @return Pointer to receive event handle to wait for available data when reading. Should + * WintunReceivePackets return ERROR_NO_MORE_ITEMS (after spinning on it for a while under heavy + * load), wait for this event to become signaled before retrying WintunReceivePackets. Do not call + * CloseHandle on this event - it is managed by the session. + */ +typedef HANDLE(WINAPI WINTUN_GET_READ_WAIT_EVENT_FUNC)(_In_ WINTUN_SESSION_HANDLE Session); + +/** + * Maximum IP packet size + */ +#define WINTUN_MAX_IP_PACKET_SIZE 0xFFFF + +/** + * Retrieves one or packet. After the packet content is consumed, call WintunReleaseReceivePacket with Packet returned + * from this function to release internal buffer. This function is thread-safe. + * + * @param Session Wintun session handle obtained with WintunStartSession + * + * @param PacketSize Pointer to receive packet size. + * + * @return Pointer to layer 3 IPv4 or IPv6 packet. Client may modify its content at will. If the function fails, the + * return value is NULL. To get extended error information, call GetLastError. Possible errors include the + * following: + * ERROR_HANDLE_EOF Wintun adapter is terminating; + * ERROR_NO_MORE_ITEMS Wintun buffer is exhausted; + * ERROR_INVALID_DATA Wintun buffer is corrupt + */ +typedef _Must_inspect_result_ +_Return_type_success_(return != NULL) +_Post_maybenull_ +_Post_writable_byte_size_(*PacketSize) +BYTE *(WINAPI WINTUN_RECEIVE_PACKET_FUNC)(_In_ WINTUN_SESSION_HANDLE Session, _Out_ DWORD *PacketSize); + +/** + * Releases internal buffer after the received packet has been processed by the client. This function is thread-safe. + * + * @param Session Wintun session handle obtained with WintunStartSession + * + * @param Packet Packet obtained with WintunReceivePacket + */ +typedef VOID( + WINAPI WINTUN_RELEASE_RECEIVE_PACKET_FUNC)(_In_ WINTUN_SESSION_HANDLE Session, _In_ const BYTE *Packet); + +/** + * Allocates memory for a packet to send. After the memory is filled with packet data, call WintunSendPacket to send + * and release internal buffer. WintunAllocateSendPacket is thread-safe and the WintunAllocateSendPacket order of + * calls define the packet sending order. + * + * @param Session Wintun session handle obtained with WintunStartSession + * + * @param PacketSize Exact packet size. Must be less or equal to WINTUN_MAX_IP_PACKET_SIZE. + * + * @return Returns pointer to memory where to prepare layer 3 IPv4 or IPv6 packet for sending. If the function fails, + * the return value is NULL. To get extended error information, call GetLastError. Possible errors include the + * following: + * ERROR_HANDLE_EOF Wintun adapter is terminating; + * ERROR_BUFFER_OVERFLOW Wintun buffer is full; + */ +typedef _Must_inspect_result_ +_Return_type_success_(return != NULL) +_Post_maybenull_ +_Post_writable_byte_size_(PacketSize) +BYTE *(WINAPI WINTUN_ALLOCATE_SEND_PACKET_FUNC)(_In_ WINTUN_SESSION_HANDLE Session, _In_ DWORD PacketSize); + +/** + * Sends the packet and releases internal buffer. WintunSendPacket is thread-safe, but the WintunAllocateSendPacket + * order of calls define the packet sending order. This means the packet is not guaranteed to be sent in the + * WintunSendPacket yet. + * + * @param Session Wintun session handle obtained with WintunStartSession + * + * @param Packet Packet obtained with WintunAllocateSendPacket + */ +typedef VOID(WINAPI WINTUN_SEND_PACKET_FUNC)(_In_ WINTUN_SESSION_HANDLE Session, _In_ const BYTE *Packet); + +#if defined(_MSC_VER) +# pragma warning(pop) +#endif + +#ifdef __cplusplus +} +#endif |