aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJason A. Donenfeld <Jason@zx2c4.com>2021-05-04 18:46:59 +0200
committerJason A. Donenfeld <Jason@zx2c4.com>2021-05-05 11:17:39 +0200
commit747ba7121d1d94dac982b9148076d7006e2c170f (patch)
tree960900a770c9887c16613f7d02e8479451c12883
parentapi: don't pass bogus previous buffer size argument (diff)
downloadwintun-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.c50
-rw-r--r--api/elevate.c131
-rw-r--r--api/elevate.h4
-rw-r--r--api/registry.c71
-rw-r--r--api/registry.h12
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);