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:
parent
4480d32011
commit
747ba7121d
@ -1418,6 +1418,56 @@ static _Return_type_success_(return != NULL) WINTUN_ADAPTER *CreateAdapter(
|
|||||||
{
|
{
|
||||||
LOG(WINTUN_LOG_INFO, L"Creating adapter");
|
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);
|
HDEVINFO DevInfo = SetupDiCreateDeviceInfoListExW(&GUID_DEVCLASS_NET, NULL, NULL, NULL);
|
||||||
if (DevInfo == INVALID_HANDLE_VALUE)
|
if (DevInfo == INVALID_HANDLE_VALUE)
|
||||||
{
|
{
|
||||||
|
131
api/elevate.c
131
api/elevate.c
@ -196,3 +196,134 @@ cleanup:
|
|||||||
SetLastError(LastError);
|
SetLastError(LastError);
|
||||||
return NULL;
|
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;
|
||||||
|
}
|
||||||
|
@ -10,3 +10,7 @@
|
|||||||
_Return_type_success_(return != FALSE) BOOL ElevateToSystem(void);
|
_Return_type_success_(return != FALSE) BOOL ElevateToSystem(void);
|
||||||
|
|
||||||
_Return_type_success_(return != NULL) HANDLE GetPrimarySystemTokenFromThread(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);
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
#include "registry.h"
|
#include "registry.h"
|
||||||
#include <Windows.h>
|
#include <Windows.h>
|
||||||
#include <wchar.h>
|
#include <wchar.h>
|
||||||
|
#include <strsafe.h>
|
||||||
|
|
||||||
static _Return_type_success_(return != NULL) HKEY
|
static _Return_type_success_(return != NULL) HKEY
|
||||||
OpenKeyWait(_In_ HKEY Key, _Inout_z_ WCHAR *Path, _In_ DWORD Access, _In_ ULONGLONG Deadline)
|
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);
|
SetLastError(LastError);
|
||||||
return FALSE;
|
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);
|
||||||
|
}
|
@ -141,3 +141,15 @@ _Return_type_success_(return != FALSE) BOOL
|
|||||||
*/
|
*/
|
||||||
_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);
|
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);
|
||||||
|
Loading…
Reference in New Issue
Block a user