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>
This commit is contained in:
Jason A. Donenfeld 2021-05-04 18:46:59 +02:00
parent 4480d32011
commit 747ba7121d
5 changed files with 268 additions and 0 deletions

View File

@ -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)
{

View File

@ -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;
}

View File

@ -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);

View File

@ -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);
}

View File

@ -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);