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");
|
||||
|
||||
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)
|
||||
{
|
||||
|
131
api/elevate.c
131
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;
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user