diff options
author | Jason A. Donenfeld <Jason@zx2c4.com> | 2021-05-04 18:46:59 +0200 |
---|---|---|
committer | Jason A. Donenfeld <Jason@zx2c4.com> | 2021-05-05 11:17:39 +0200 |
commit | 747ba7121d1d94dac982b9148076d7006e2c170f (patch) | |
tree | 960900a770c9887c16613f7d02e8479451c12883 | |
parent | api: don't pass bogus previous buffer size argument (diff) | |
download | wintun-747ba7121d1d94dac982b9148076d7006e2c170f.tar.xz wintun-747ba7121d1d94dac982b9148076d7006e2c170f.zip |
api: clean up NetSetup2 GUIDs
Recent versions of Windows fail to tidy up, causing issues when reusing
GUIDs. Check to see if a GUID might be orphaned, and forcibly clear out
the registry state if so.
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
-rw-r--r-- | api/adapter.c | 50 | ||||
-rw-r--r-- | api/elevate.c | 131 | ||||
-rw-r--r-- | api/elevate.h | 4 | ||||
-rw-r--r-- | api/registry.c | 71 | ||||
-rw-r--r-- | api/registry.h | 12 |
5 files changed, 268 insertions, 0 deletions
diff --git a/api/adapter.c b/api/adapter.c index 0043ae3..a85d17b 100644 --- a/api/adapter.c +++ b/api/adapter.c @@ -1418,6 +1418,56 @@ static _Return_type_success_(return != NULL) WINTUN_ADAPTER *CreateAdapter( { LOG(WINTUN_LOG_INFO, L"Creating adapter"); + if (RequestedGUID) + { + WCHAR RegPath[MAX_REG_PATH]; + WCHAR RequestedGUIDStr[MAX_GUID_STRING_LEN]; + int GuidStrLen = StringFromGUID2(RequestedGUID, RequestedGUIDStr, _countof(RequestedGUIDStr)) * sizeof(WCHAR); + if (_snwprintf_s( + RegPath, + MAX_REG_PATH, + _TRUNCATE, + L"SYSTEM\\CurrentControlSet\\Control\\Network\\{4D36E972-E325-11CE-BFC1-08002BE10318}\\%.*s\\Connection", + GuidStrLen, + RequestedGUIDStr) == -1) + goto guidIsFresh; + HKEY Key; + if (RegOpenKeyExW(HKEY_LOCAL_MACHINE, RegPath, 0, KEY_QUERY_VALUE, &Key) != ERROR_SUCCESS) + goto guidIsFresh; + WCHAR *InstanceID = RegistryQueryString(Key, L"PnPInstanceId", FALSE); + RegCloseKey(Key); + if (!InstanceID) + goto guidIsFresh; + int Ret = _snwprintf_s(RegPath, MAX_REG_PATH, _TRUNCATE, L"SYSTEM\\CurrentControlSet\\Enum\\%s", InstanceID); + Free(InstanceID); + if (Ret == -1) + goto guidIsFresh; + if (RegOpenKeyExW(HKEY_LOCAL_MACHINE, RegPath, 0, KEY_QUERY_VALUE, &Key) == ERROR_SUCCESS) + { + RegCloseKey(Key); + SetLastError(LOG_ERROR(ERROR_ALREADY_EXISTS, L"Requested GUID is already in use: %s", RequestedGUIDStr)); + return NULL; + } + LOG(WINTUN_LOG_WARN, L"Requested GUID %s has leftover residue", RequestedGUIDStr); + HANDLE OriginalToken; + if (!ImpersonateService(L"NetSetupSvc", &OriginalToken)) + { + LOG_LAST_ERROR(L"Unable to impersonate NetSetupSvc"); + goto guidIsFresh; // non-fatal + } + if (_snwprintf_s( + RegPath, + MAX_REG_PATH, + _TRUNCATE, + L"SYSTEM\\CurrentControlSet\\Control\\NetworkSetup2\\Interfaces\\%.*s", + GuidStrLen, + RequestedGUIDStr) == -1 || + !RegistryDeleteKeyRecursive(HKEY_LOCAL_MACHINE, RegPath)) + LOG_LAST_ERROR(L"Unable to delete NetworkSetup2 registry key"); // non-fatal + RestoreToken(OriginalToken); + guidIsFresh:; + } + HDEVINFO DevInfo = SetupDiCreateDeviceInfoListExW(&GUID_DEVCLASS_NET, NULL, NULL, NULL); if (DevInfo == INVALID_HANDLE_VALUE) { diff --git a/api/elevate.c b/api/elevate.c index 0ba6c09..2daed6b 100644 --- a/api/elevate.c +++ b/api/elevate.c @@ -196,3 +196,134 @@ cleanup: SetLastError(LastError); return NULL; } + +_Return_type_success_(return != FALSE) BOOL ImpersonateService(_In_z_ WCHAR *ServiceName, _In_ HANDLE *OriginalToken) +{ + HANDLE ThreadToken, ServiceProcess, ServiceToken, DuplicatedToken; + SC_HANDLE Scm, ServiceHandle; + DWORD LastError = ERROR_SUCCESS; + TOKEN_PRIVILEGES Privileges = { .PrivilegeCount = 1, .Privileges = { { .Attributes = SE_PRIVILEGE_ENABLED } } }; + SERVICE_STATUS_PROCESS ServiceStatus; + DWORD RequiredBytes; + BOOL Ret = FALSE; + + *OriginalToken = NULL; + if (!OpenThreadToken(GetCurrentThread(), TOKEN_IMPERSONATE, FALSE, OriginalToken) && + GetLastError() != ERROR_NO_TOKEN) + return FALSE; + + if (!LookupPrivilegeValueW(NULL, SE_DEBUG_NAME, &Privileges.Privileges[0].Luid)) + { + LastError = LOG_LAST_ERROR(L"Failed to lookup privilege value"); + goto cleanup; + } + if (!*OriginalToken) + { + RevertToSelf(); + if (!ImpersonateSelf(SecurityImpersonation)) + { + LastError = LOG_LAST_ERROR(L"Failed to impersonate self"); + goto cleanup; + } + } + if (!OpenThreadToken(GetCurrentThread(), TOKEN_ADJUST_PRIVILEGES, FALSE, &ThreadToken)) + { + LastError = LOG_LAST_ERROR(L"Failed to open thread token"); + goto cleanup; + } + if (!AdjustTokenPrivileges(ThreadToken, FALSE, &Privileges, 0, NULL, NULL)) + { + LastError = LOG_LAST_ERROR(L"Failed to enable SE_DEBUG_NAME"); + goto cleanupThreadToken; + } + + Scm = OpenSCManagerW(NULL, NULL, SC_MANAGER_CONNECT); + if (!Scm) + { + LastError = LOG_LAST_ERROR(L"Failed to open SCM"); + goto cleanupThreadToken; + } + ServiceHandle = OpenServiceW(Scm, ServiceName, SERVICE_START | SERVICE_QUERY_STATUS); + if (!ServiceHandle) + { + LastError = LOG_LAST_ERROR(L"Failed to open service %s", ServiceName); + goto cleanupScm; + } + if (!StartServiceW(ServiceHandle, 0, NULL) && GetLastError() != ERROR_SERVICE_ALREADY_RUNNING) + { + LastError = LOG_LAST_ERROR(L"Failed to start service %s", ServiceName); + goto cleanupService; + } + for (int i = 0; i < 1000; ++i) + { + if (!QueryServiceStatusEx( + ServiceHandle, SC_STATUS_PROCESS_INFO, (BYTE *)&ServiceStatus, sizeof(ServiceStatus), &RequiredBytes)) + { + LastError = LOG_LAST_ERROR(L"Failed to query service %s", ServiceName); + goto cleanupService; + } + if (ServiceStatus.dwProcessId) + break; + + if (i != 999) + Sleep(4); + } + ServiceProcess = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, ServiceStatus.dwProcessId); + if (!ServiceProcess) + { + LastError = LOG_LAST_ERROR(L"Failed to open service %s process %u", ServiceName, ServiceStatus.dwProcessId); + goto cleanupService; + } + if (!OpenProcessToken(ServiceProcess, TOKEN_IMPERSONATE | TOKEN_DUPLICATE, &ServiceToken)) + { + LastError = + LOG_LAST_ERROR(L"Failed to open token of service %s process %u", ServiceName, ServiceStatus.dwProcessId); + goto cleanupServiceProcess; + } + if (!DuplicateToken(ServiceToken, SecurityImpersonation, &DuplicatedToken)) + { + LastError = LOG_LAST_ERROR( + L"Failed to duplicate token of service %s process %u", ServiceName, ServiceStatus.dwProcessId); + goto cleanupServiceToken; + } + if (!SetThreadToken(NULL, DuplicatedToken)) + { + LastError = LOG_LAST_ERROR( + L"Failed to set thread token to service %s process %u token", ServiceName, ServiceStatus.dwProcessId); + goto cleanupDuplicatedToken; + } + Ret = TRUE; + +cleanupDuplicatedToken: + CloseHandle(DuplicatedToken); +cleanupServiceToken: + CloseHandle(ServiceToken); +cleanupServiceProcess: + CloseHandle(ServiceProcess); +cleanupService: + CloseServiceHandle(ServiceHandle); +cleanupScm: + CloseServiceHandle(Scm); +cleanupThreadToken: + CloseHandle(ThreadToken); +cleanup: + if (!Ret) + { + RestoreToken(*OriginalToken); + *OriginalToken = NULL; + } + SetLastError(LastError); + return Ret; +} + +_Return_type_success_(return != FALSE) BOOL RestoreToken(_In_ HANDLE OriginalToken) +{ + RevertToSelf(); + if (!OriginalToken) + return TRUE; + BOOL Ret = SetThreadToken(NULL, OriginalToken); + DWORD LastError = Ret ? ERROR_SUCCESS : LOG_LAST_ERROR(L"Failed to restore original token"); + CloseHandle(OriginalToken); + SetLastError(LastError); + return Ret; +} diff --git a/api/elevate.h b/api/elevate.h index 2ad8eb8..511f23d 100644 --- a/api/elevate.h +++ b/api/elevate.h @@ -10,3 +10,7 @@ _Return_type_success_(return != FALSE) BOOL ElevateToSystem(void); _Return_type_success_(return != NULL) HANDLE GetPrimarySystemTokenFromThread(void); + +_Return_type_success_(return != FALSE) BOOL ImpersonateService(_In_z_ WCHAR *ServiceName, _In_ HANDLE *OriginalToken); + +_Return_type_success_(return != FALSE) BOOL RestoreToken(_In_ HANDLE OriginalToken); diff --git a/api/registry.c b/api/registry.c index 94ae8d4..7cb7cf2 100644 --- a/api/registry.c +++ b/api/registry.c @@ -8,6 +8,7 @@ #include "registry.h" #include <Windows.h> #include <wchar.h> +#include <strsafe.h> static _Return_type_success_(return != NULL) HKEY OpenKeyWait(_In_ HKEY Key, _Inout_z_ WCHAR *Path, _In_ DWORD Access, _In_ ULONGLONG Deadline) @@ -397,3 +398,73 @@ _Return_type_success_(return != FALSE) BOOL SetLastError(LastError); return FALSE; } + +_Return_type_success_(return != FALSE) static BOOL + DeleteNodeRecurse(_In_ HKEY Key, _In_z_ WCHAR *Name) +{ + LSTATUS Ret; + DWORD Size; + SIZE_T Len; + WCHAR SubName[MAX_REG_PATH], *End; + HKEY SubKey; + + Len = wcslen(Name); + if (Len >= MAX_REG_PATH || !Len) + return TRUE; + + if (RegDeleteKeyW(Key, Name) == ERROR_SUCCESS) + return TRUE; + + Ret = RegOpenKeyEx(Key, Name, 0, KEY_READ, &SubKey); + if (Ret != ERROR_SUCCESS) + { + if (Ret == ERROR_FILE_NOT_FOUND) + return TRUE; + SetLastError(Ret); + return FALSE; + } + + End = Name + Len; + if (End[-1] != L'\\') + { + *(End++) = L'\\'; + *End = L'\0'; + } + Size = MAX_REG_PATH; + Ret = RegEnumKeyEx(SubKey, 0, SubName, &Size, NULL, NULL, NULL, NULL); + if (Ret == ERROR_SUCCESS) + { + do + { + End[0] = L'\0'; + StringCchCatW(Name, MAX_REG_PATH * 2, SubName); + if (!DeleteNodeRecurse(Key, Name)) + break; + Size = MAX_REG_PATH; + Ret = RegEnumKeyEx(SubKey, 0, SubName, &Size, NULL, NULL, NULL, NULL); + } while (Ret == ERROR_SUCCESS); + } + else + { + SetLastError(Ret); + *(--End) = L'\0'; + RegCloseKey(SubKey); + return FALSE; + } + *(--End) = L'\0'; + RegCloseKey(SubKey); + + Ret = RegDeleteKey(Key, Name); + if (Ret == ERROR_SUCCESS) + return TRUE; + SetLastError(Ret); + return FALSE; +} + +_Return_type_success_(return != FALSE) BOOL +RegistryDeleteKeyRecursive(_In_ HKEY Key, _In_z_ const WCHAR *Name) +{ + WCHAR NameBuf[(MAX_REG_PATH + 2) * 2] = { 0 }; + StringCchCopyW(NameBuf, MAX_REG_PATH * 2, Name); + return DeleteNodeRecurse(Key, NameBuf); +}
\ No newline at end of file diff --git a/api/registry.h b/api/registry.h index 2a85a06..106a79d 100644 --- a/api/registry.h +++ b/api/registry.h @@ -141,3 +141,15 @@ _Return_type_success_(return != FALSE) BOOL */ _Return_type_success_(return != FALSE) BOOL RegistryQueryDWORDWait(_In_ HKEY Key, _In_opt_z_ const WCHAR *Name, _In_ DWORD Timeout, _Out_ DWORD *Value); + +/** + * Deletes the entire registry key subtree recursively. + * + * @param Key Handle of the registry key to at which the subtree is rooted. + * + * @param Name Name of the subtree to delete. + * + * @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 RegistryDeleteKeyRecursive(_In_ HKEY Key, _In_z_ const WCHAR *Name); |