/** * @file launcher/launcher.c * * @copyright 2015-2021 Bill Zissimopoulos */ /* * This file is part of WinFsp. * * You can redistribute it and/or modify it under the terms of the GNU * General Public License version 3 as published by the Free Software * Foundation. * * Licensees holding a valid commercial license may use this software * in accordance with the commercial license agreement provided in * conjunction with the software. The terms and conditions of any such * commercial license agreement shall govern, supersede, and render * ineffective any application of the GPLv3 license to this software, * notwithstanding of any reference thereto in the software or * associated repository. */ #include #include #include #include #include #define PROGNAME FSP_FSCTL_PRODUCT_NAME ".Launcher" static NTSTATUS (NTAPI *SvcNtOpenSymbolicLinkObject)( PHANDLE LinkHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes); static NTSTATUS (NTAPI *SvcNtClose)( HANDLE Handle); static BOOL CreateOverlappedPipe( PHANDLE PReadPipe, PHANDLE PWritePipe, DWORD Size, BOOL ReadInherit, BOOL WriteInherit, DWORD ReadMode, DWORD WriteMode) { RPC_STATUS RpcStatus; UUID Uuid; WCHAR PipeNameBuf[MAX_PATH]; SECURITY_ATTRIBUTES ReadSecurityAttributes = { sizeof(SECURITY_ATTRIBUTES), 0, ReadInherit }; SECURITY_ATTRIBUTES WriteSecurityAttributes = { sizeof(SECURITY_ATTRIBUTES), 0, WriteInherit }; HANDLE ReadPipe, WritePipe; DWORD LastError; RpcStatus = UuidCreate(&Uuid); if (S_OK != RpcStatus && RPC_S_UUID_LOCAL_ONLY != RpcStatus) { SetLastError(ERROR_INTERNAL_ERROR); return FALSE; } wsprintfW(PipeNameBuf, L"\\\\.\\pipe\\" "%08lX-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X", Uuid.Data1, Uuid.Data2, Uuid.Data3, Uuid.Data4[0], Uuid.Data4[1], Uuid.Data4[2], Uuid.Data4[3], Uuid.Data4[4], Uuid.Data4[5], Uuid.Data4[6], Uuid.Data4[7]); ReadPipe = CreateNamedPipeW(PipeNameBuf, PIPE_ACCESS_INBOUND | FILE_FLAG_FIRST_PIPE_INSTANCE | ReadMode, PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT | PIPE_REJECT_REMOTE_CLIENTS, 1, Size, Size, 120 * 1000, &ReadSecurityAttributes); if (INVALID_HANDLE_VALUE == ReadPipe) return FALSE; WritePipe = CreateFileW(PipeNameBuf, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, &WriteSecurityAttributes, OPEN_EXISTING, WriteMode, 0); if (INVALID_HANDLE_VALUE == WritePipe) { LastError = GetLastError(); CloseHandle(ReadPipe); SetLastError(LastError); return FALSE; } *PReadPipe = ReadPipe; *PWritePipe = WritePipe; return TRUE; } static NTSTATUS GetTokenUserName(HANDLE Token, PWSTR *PUserName) { union { TOKEN_USER V; UINT8 B[128]; } UserInfoBuf; PTOKEN_USER UserInfo = &UserInfoBuf.V; WCHAR Name[256], Domn[256]; DWORD UserSize, NameSize, DomnSize; SID_NAME_USE Use; PWSTR P; NTSTATUS Result; *PUserName = 0; if (!GetTokenInformation(Token, TokenUser, UserInfo, sizeof UserInfoBuf, &UserSize)) { if (ERROR_INSUFFICIENT_BUFFER != GetLastError()) { Result = FspNtStatusFromWin32(GetLastError()); goto exit; } UserInfo = MemAlloc(UserSize); if (0 == UserInfo) { Result = STATUS_INSUFFICIENT_RESOURCES; goto exit; } if (!GetTokenInformation(Token, TokenUser, UserInfo, UserSize, &UserSize)) { Result = FspNtStatusFromWin32(GetLastError()); goto exit; } } NameSize = sizeof Name / sizeof Name[0]; DomnSize = sizeof Domn / sizeof Domn[0]; if (!LookupAccountSidW(0, UserInfo->User.Sid, Name, &NameSize, Domn, &DomnSize, &Use)) { Result = FspNtStatusFromWin32(GetLastError()); goto exit; } NameSize = lstrlenW(Name); DomnSize = lstrlenW(Domn); P = *PUserName = MemAlloc((DomnSize + 1 + NameSize + 1) * sizeof(WCHAR)); if (0 == P) { Result = STATUS_INSUFFICIENT_RESOURCES; goto exit; } if (0 < DomnSize) { memcpy(P, Domn, DomnSize * sizeof(WCHAR)); P[DomnSize] = L'\\'; P += DomnSize + 1; } memcpy(P, Name, NameSize * sizeof(WCHAR)); P[NameSize] = L'\0'; Result = STATUS_SUCCESS; exit: if (UserInfo != &UserInfoBuf.V) MemFree(UserInfo); return Result; } static NTSTATUS AddAccessForTokenUser(HANDLE Handle, DWORD Access, HANDLE Token) { union { TOKEN_USER V; UINT8 B[128]; } UserInfoBuf; PTOKEN_USER UserInfo = &UserInfoBuf.V; PSECURITY_DESCRIPTOR SecurityDescriptor = 0; PSECURITY_DESCRIPTOR NewSecurityDescriptor = 0; EXPLICIT_ACCESSW AccessEntry; DWORD Size, LastError; NTSTATUS Result; if (!GetTokenInformation(Token, TokenUser, UserInfo, sizeof UserInfoBuf, &Size)) { if (ERROR_INSUFFICIENT_BUFFER != GetLastError()) { Result = FspNtStatusFromWin32(GetLastError()); goto exit; } UserInfo = MemAlloc(Size); if (0 == UserInfo) { Result = STATUS_INSUFFICIENT_RESOURCES; goto exit; } if (!GetTokenInformation(Token, TokenUser, UserInfo, Size, &Size)) { Result = FspNtStatusFromWin32(GetLastError()); goto exit; } } if (GetKernelObjectSecurity(Handle, DACL_SECURITY_INFORMATION, 0, 0, &Size)) { Result = STATUS_INVALID_PARAMETER; goto exit; } if (ERROR_INSUFFICIENT_BUFFER != GetLastError()) { Result = FspNtStatusFromWin32(GetLastError()); goto exit; } SecurityDescriptor = MemAlloc(Size); if (0 == SecurityDescriptor) { Result = STATUS_INSUFFICIENT_RESOURCES; goto exit; } if (!GetKernelObjectSecurity(Handle, DACL_SECURITY_INFORMATION, SecurityDescriptor, Size, &Size)) { Result = FspNtStatusFromWin32(GetLastError()); goto exit; } AccessEntry.grfAccessPermissions = Access; AccessEntry.grfAccessMode = GRANT_ACCESS; AccessEntry.grfInheritance = NO_INHERITANCE; AccessEntry.Trustee.pMultipleTrustee = 0; AccessEntry.Trustee.MultipleTrusteeOperation = NO_MULTIPLE_TRUSTEE; AccessEntry.Trustee.TrusteeForm = TRUSTEE_IS_SID; AccessEntry.Trustee.TrusteeType = TRUSTEE_IS_UNKNOWN; AccessEntry.Trustee.ptstrName = UserInfo->User.Sid; LastError = BuildSecurityDescriptorW(0, 0, 1, &AccessEntry, 0, 0, SecurityDescriptor, &Size, &NewSecurityDescriptor); if (0 != LastError) { Result = FspNtStatusFromWin32(LastError); goto exit; } if (!SetKernelObjectSecurity(Handle, DACL_SECURITY_INFORMATION, NewSecurityDescriptor)) { Result = FspNtStatusFromWin32(GetLastError()); goto exit; } Result = STATUS_SUCCESS; exit: LocalFree(NewSecurityDescriptor); MemFree(SecurityDescriptor); if (UserInfo != &UserInfoBuf.V) MemFree(UserInfo); return Result; } static BOOL LogonCreateProcess( PWSTR UserName, HANDLE Token, LPCWSTR ApplicationName, LPWSTR CommandLine, LPSECURITY_ATTRIBUTES ProcessAttributes, LPSECURITY_ATTRIBUTES ThreadAttributes, BOOL InheritHandles, DWORD CreationFlags, LPVOID Environment, LPCWSTR CurrentDirectory, LPSTARTUPINFOW StartupInfo, LPPROCESS_INFORMATION ProcessInformation) { PWSTR DomainName = 0; if (0 != UserName) { if (0 == invariant_wcsicmp(UserName, L"LocalSystem")) { UserName = 0; Token = 0; } else if (0 == invariant_wcsicmp(UserName, L"LocalService") || 0 == invariant_wcsicmp(UserName, L"NetworkService")) { DomainName = L"NT AUTHORITY"; Token = 0; } else if (0 == invariant_wcsicmp(UserName, L".")) ; else { SetLastError(ERROR_ACCESS_DENIED); return FALSE; } } if (0 == UserName) /* without a user name go ahead and call CreateProcessW */ return CreateProcessW( ApplicationName, CommandLine, ProcessAttributes, ThreadAttributes, InheritHandles, CreationFlags, Environment, CurrentDirectory, StartupInfo, ProcessInformation); HANDLE LogonToken = 0; PVOID EnvironmentBlock = 0; DWORD SessionId; DWORD LastError; BOOL Success; if (0 == Token) { Success = LogonUserW( UserName, DomainName, 0, LOGON32_LOGON_SERVICE, LOGON32_PROVIDER_DEFAULT, &LogonToken); if (!Success) goto exit; } else { /* convert the impersonation token to a primary token */ Success = DuplicateTokenEx(Token, TOKEN_ALL_ACCESS, 0, SecurityAnonymous, TokenPrimary, &LogonToken); if (!Success) goto exit; if (!ProcessIdToSessionId(GetCurrentProcessId(), &SessionId)) SessionId = 0; /* place the duplicated token in the service session (session 0) */ Success = SetTokenInformation(LogonToken, TokenSessionId, &SessionId, sizeof SessionId); if (!Success) goto exit; } if (0 == Environment) { Success = CreateEnvironmentBlock(&EnvironmentBlock, LogonToken, FALSE); if (!Success) goto exit; CreationFlags |= CREATE_UNICODE_ENVIRONMENT; Environment = EnvironmentBlock; } Success = ImpersonateLoggedOnUser(LogonToken); if (!Success) goto exit; Success = CreateProcessAsUserW( LogonToken, ApplicationName, CommandLine, ProcessAttributes, ThreadAttributes, InheritHandles, CreationFlags, Environment, CurrentDirectory, StartupInfo, ProcessInformation); if (!RevertToSelf()) /* should not happen! */ ExitProcess(GetLastError()); exit: if (!Success) LastError = GetLastError(); if (0 != EnvironmentBlock) DestroyEnvironmentBlock(EnvironmentBlock); if (0 != LogonToken) CloseHandle(LogonToken); if (!Success) SetLastError(LastError); return Success; } typedef struct { HANDLE Process; HANDLE ProcessWait; } KILL_PROCESS_DATA; static VOID CALLBACK KillProcessWait(PVOID Context, BOOLEAN Timeout); static VOID KillProcess(ULONG ProcessId, HANDLE Process, ULONG Timeout) { if (GenerateConsoleCtrlEvent(CTRL_BREAK_EVENT, ProcessId)) { /* * If GenerateConsoleCtrlEvent succeeds, but the child process does not exit * timely we will terminate it with extreme prejudice. This is done by calling * RegisterWaitForSingleObject with timeout on a duplicated process handle. * * If GenerateConsoleCtrlEvent succeeds, but we are not able to successfully call * RegisterWaitForSingleObject, we do NOT terminate the child process forcibly. * This is by design as it is not the child process's fault and the child process * should (we hope in this case) respond to the console control event timely. */ KILL_PROCESS_DATA *KillProcessData; KillProcessData = MemAlloc(sizeof *KillProcessData); if (0 != KillProcessData) { if (DuplicateHandle(GetCurrentProcess(), Process, GetCurrentProcess(), &KillProcessData->Process, 0, FALSE, DUPLICATE_SAME_ACCESS)) { if (RegisterWaitForSingleObject(&KillProcessData->ProcessWait, KillProcessData->Process, KillProcessWait, KillProcessData, Timeout, WT_EXECUTEONLYONCE)) KillProcessData = 0; else CloseHandle(KillProcessData->Process); } MemFree(KillProcessData); } } else TerminateProcess(Process, 0); } static VOID CALLBACK KillProcessWait(PVOID Context, BOOLEAN Timeout) { KILL_PROCESS_DATA *KillProcessData = Context; if (Timeout) TerminateProcess(KillProcessData->Process, 0); UnregisterWaitEx(KillProcessData->ProcessWait, 0); CloseHandle(KillProcessData->Process); MemFree(KillProcessData); } #define LAUNCHER_PIPE_DEFAULT_TIMEOUT (2 * 15000 + 1000) #define LAUNCHER_START_WITH_SECRET_TIMEOUT 15000 #define LAUNCHER_STOP_TIMEOUT 5500 #define LAUNCHER_KILL_TIMEOUT 5000 typedef struct { LONG RefCount; LIST_ENTRY ListEntry; HANDLE ClientToken; PWSTR ClassName; PWSTR InstanceName; PWSTR CommandLine; PSECURITY_DESCRIPTOR SecurityDescriptor; DWORD ProcessId; HANDLE Process; HANDLE ProcessWait; HANDLE StdioHandles[3]; DWORD Recovery; ULONG Argc; PWSTR *Argv; BOOLEAN HasSecret; BOOLEAN Started, Stopped; WCHAR Buffer[]; } SVC_INSTANCE; static HANDLE SvcJob; static CRITICAL_SECTION SvcInstanceLock; static HANDLE SvcInstanceEvent; static LIST_ENTRY SvcInstanceList = { &SvcInstanceList, &SvcInstanceList }; static VOID CALLBACK SvcInstanceTerminated(PVOID Context, BOOLEAN Timeout); NTSTATUS SvcInstanceStart(HANDLE ClientToken, PWSTR ClassName, PWSTR InstanceName, ULONG Argc, PWSTR *Argv, HANDLE Job, BOOLEAN HasSecret); static SVC_INSTANCE *SvcInstanceLookup(PWSTR ClassName, PWSTR InstanceName) { SVC_INSTANCE *SvcInstance; PLIST_ENTRY ListEntry; for (ListEntry = SvcInstanceList.Flink; &SvcInstanceList != ListEntry; ListEntry = ListEntry->Flink) { SvcInstance = CONTAINING_RECORD(ListEntry, SVC_INSTANCE, ListEntry); if (0 == invariant_wcsicmp(ClassName, SvcInstance->ClassName) && 0 == invariant_wcsicmp(InstanceName, SvcInstance->InstanceName)) return SvcInstance; } return 0; } static inline ULONG SvcInstanceArgumentLength(PWSTR Arg, PWSTR Pattern, BOOLEAN Quote) { PWSTR PathTransform(PWSTR Dest, PWSTR Arg, PWSTR Pattern); return (Quote ? 2 : 0) + (ULONG)((UINT_PTR)PathTransform(0, Arg, Pattern) / sizeof(WCHAR)); } static inline PWSTR SvcInstanceArgumentCopy(PWSTR Dest, PWSTR Arg, PWSTR Pattern, BOOLEAN Quote) { PWSTR PathTransform(PWSTR Dest, PWSTR Arg, PWSTR Pattern); if (Quote) *Dest++ = L'"'; Dest = PathTransform(Dest, Arg, Pattern); if (Quote) *Dest++ = L'"'; return Dest; } static NTSTATUS SvcInstanceReplaceArguments(PWSTR String, ULONG Argc, PWSTR *Argv, PWSTR *Varv, BOOLEAN Quote, PWSTR *PNewString) { PWSTR NewString = 0, P, Q; PWSTR EmptyArg = L""; ULONG Length; PWSTR Pattern; *PNewString = 0; Length = 0; for (P = String; *P; P++) { switch (*P) { case L'%': Pattern = 0; P++; if (L'\\' == *P) { Pattern = ++P; while (!(L'\0' == *P || (L'0' <= *P && *P <= L'9') || (L'A' <= *P && *P <= L'Z'))) P++; } if (L'0' <= *P && *P <= L'9') { if (Argc > (ULONG)(*P - L'0')) Length += SvcInstanceArgumentLength(Argv[*P - L'0'], Pattern, Quote); else Length += SvcInstanceArgumentLength(EmptyArg, 0, Quote); } else if (L'A' <= *P && *P <= L'Z') { if (0 != Varv[*P - L'A']) Length += SvcInstanceArgumentLength(Varv[*P - L'A'], Pattern, Quote); else Length += SvcInstanceArgumentLength(EmptyArg, 0, Quote); } else if (*P) Length++; else P--; break; default: Length++; break; } } NewString = MemAlloc((Length + 1) * sizeof(WCHAR)); if (0 == NewString) return STATUS_INSUFFICIENT_RESOURCES; for (P = String, Q = NewString; *P; P++) { switch (*P) { case L'%': Pattern = 0; P++; if (L'\\' == *P) { Pattern = ++P; while (!(L'\0' == *P || (L'0' <= *P && *P <= L'9') || (L'A' <= *P && *P <= L'Z'))) P++; } if (L'0' <= *P && *P <= L'9') { if (Argc > (ULONG)(*P - L'0')) Q = SvcInstanceArgumentCopy(Q, Argv[*P - L'0'], Pattern, Quote); else Q = SvcInstanceArgumentCopy(Q, EmptyArg, 0, Quote); } else if (L'A' <= *P && *P <= L'Z') { if (0 != Varv[*P - L'A']) Q = SvcInstanceArgumentCopy(Q, Varv[*P - L'A'], Pattern, Quote); else Q = SvcInstanceArgumentCopy(Q, EmptyArg, 0, Quote); } else if (*P) *Q++ = *P; else P--; break; default: *Q++ = *P; break; } } *Q = L'\0'; *PNewString = NewString; return STATUS_SUCCESS; } static NTSTATUS SvcInstanceAddUserRights(HANDLE Token, PSECURITY_DESCRIPTOR SecurityDescriptor, PSECURITY_DESCRIPTOR *PNewSecurityDescriptor) { PSECURITY_DESCRIPTOR NewSecurityDescriptor; TOKEN_USER *User = 0; EXPLICIT_ACCESSW AccessEntry; DWORD Size, LastError; NTSTATUS Result; *PNewSecurityDescriptor = 0; if (GetTokenInformation(Token, TokenUser, 0, 0, &Size)) { Result = STATUS_INVALID_PARAMETER; goto exit; } if (ERROR_INSUFFICIENT_BUFFER != GetLastError()) { Result = FspNtStatusFromWin32(GetLastError()); goto exit; } User = MemAlloc(Size); if (0 == User) { Result = STATUS_INSUFFICIENT_RESOURCES; goto exit; } if (!GetTokenInformation(Token, TokenUser, User, Size, &Size)) { Result = FspNtStatusFromWin32(GetLastError()); goto exit; } AccessEntry.grfAccessPermissions = SERVICE_QUERY_STATUS | SERVICE_STOP; AccessEntry.grfAccessMode = GRANT_ACCESS; AccessEntry.grfInheritance = NO_INHERITANCE; AccessEntry.Trustee.pMultipleTrustee = 0; AccessEntry.Trustee.MultipleTrusteeOperation = NO_MULTIPLE_TRUSTEE; AccessEntry.Trustee.TrusteeForm = TRUSTEE_IS_SID; AccessEntry.Trustee.TrusteeType = TRUSTEE_IS_UNKNOWN; AccessEntry.Trustee.ptstrName = User->User.Sid; LastError = BuildSecurityDescriptorW(0, 0, 1, &AccessEntry, 0, 0, SecurityDescriptor, &Size, &NewSecurityDescriptor); if (0 != LastError) { Result = FspNtStatusFromWin32(LastError); goto exit; } *PNewSecurityDescriptor = NewSecurityDescriptor; Result = STATUS_SUCCESS; exit: MemFree(User); return Result; } static NTSTATUS SvcInstanceAccessCheck(HANDLE ClientToken, ULONG DesiredAccess, PSECURITY_DESCRIPTOR SecurityDescriptor) { static GENERIC_MAPPING GenericMapping = { .GenericRead = STANDARD_RIGHTS_READ | SERVICE_QUERY_CONFIG | SERVICE_QUERY_STATUS | SERVICE_INTERROGATE | SERVICE_ENUMERATE_DEPENDENTS, .GenericWrite = STANDARD_RIGHTS_WRITE | SERVICE_CHANGE_CONFIG, .GenericExecute = STANDARD_RIGHTS_EXECUTE | SERVICE_START | SERVICE_STOP | SERVICE_PAUSE_CONTINUE | SERVICE_USER_DEFINED_CONTROL, .GenericAll = SERVICE_ALL_ACCESS, }; UINT8 PrivilegeSetBuf[sizeof(PRIVILEGE_SET) + 15 * sizeof(LUID_AND_ATTRIBUTES)]; PPRIVILEGE_SET PrivilegeSet = (PVOID)PrivilegeSetBuf; DWORD PrivilegeSetLength = sizeof PrivilegeSetBuf; ULONG GrantedAccess; BOOL AccessStatus; NTSTATUS Result; if (AccessCheck(SecurityDescriptor, ClientToken, DesiredAccess, &GenericMapping, PrivilegeSet, &PrivilegeSetLength, &GrantedAccess, &AccessStatus)) Result = AccessStatus ? STATUS_SUCCESS : STATUS_ACCESS_DENIED; else Result = FspNtStatusFromWin32(GetLastError()); return Result; } static NTSTATUS SvcInstanceCreateProcess(PWSTR UserName, HANDLE ClientToken, PWSTR Executable, PWSTR CommandLine, PWSTR WorkDirectory, HANDLE StdioHandles[2], HANDLE StderrHandle, PPROCESS_INFORMATION ProcessInfo) { WCHAR WorkDirectoryBuf[MAX_PATH]; STARTUPINFOEXW StartupInfoEx; HANDLE ChildHandles[3] = { INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE/* NO CLOSE!*/ }; HANDLE ParentHandles[2] = { INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE }; PPROC_THREAD_ATTRIBUTE_LIST AttrList = 0; BOOLEAN InitDoneAttrList = FALSE; SIZE_T Size; NTSTATUS Result; if (0 != WorkDirectory && L'.' == WorkDirectory[0] && L'\0' == WorkDirectory[1]) { PWSTR Backslash = 0, P; if (0 == GetModuleFileNameW(0, WorkDirectoryBuf, MAX_PATH)) return FspNtStatusFromWin32(GetLastError()); for (P = WorkDirectoryBuf; *P; P++) if (L'\\' == *P) Backslash = P; if (0 != Backslash && WorkDirectoryBuf < Backslash && L':' != Backslash[-1]) *Backslash = L'\0'; WorkDirectory = WorkDirectoryBuf; } memset(&StartupInfoEx, 0, sizeof StartupInfoEx); StartupInfoEx.StartupInfo.cb = sizeof StartupInfoEx.StartupInfo; if (0 != StdioHandles || INVALID_HANDLE_VALUE != StderrHandle) { /* * Create child process and redirect stdin/stdout. Do *not* inherit other handles. * * For explanation see: * https://blogs.msdn.microsoft.com/oldnewthing/20111216-00/?p=8873/ */ if (0 != StdioHandles) { /* create stdin read/write ends; make them inheritable */ if (!CreateOverlappedPipe(&ChildHandles[0], &ParentHandles[0], 0, TRUE, FALSE, 0, 0)) { Result = FspNtStatusFromWin32(GetLastError()); goto exit; } } if (0 != StdioHandles) { /* create stdout read/write ends; make them inheritable */ if (!CreateOverlappedPipe(&ParentHandles[1], &ChildHandles[1], 0, FALSE, TRUE, FILE_FLAG_OVERLAPPED, 0)) { Result = FspNtStatusFromWin32(GetLastError()); goto exit; } } if (INVALID_HANDLE_VALUE != StderrHandle) ChildHandles[2] = StderrHandle; Size = 0; if (!InitializeProcThreadAttributeList(0, 1, 0, &Size) && ERROR_INSUFFICIENT_BUFFER != GetLastError()) { Result = FspNtStatusFromWin32(GetLastError()); goto exit; } AttrList = MemAlloc(Size); if (0 == AttrList) { Result = STATUS_INSUFFICIENT_RESOURCES; goto exit; } if (!InitializeProcThreadAttributeList(AttrList, 1, 0, &Size)) { Result = FspNtStatusFromWin32(GetLastError()); goto exit; } InitDoneAttrList = TRUE; /* only the child ends of stdin/stdout/stderr are actually inherited */ if (!UpdateProcThreadAttribute(AttrList, 0, PROC_THREAD_ATTRIBUTE_HANDLE_LIST, 0 != StdioHandles ? ChildHandles : ChildHandles + 2, ((0 != StdioHandles ? 2 : 0) + (INVALID_HANDLE_VALUE != StderrHandle ? 1 : 0)) * sizeof ChildHandles[0], 0, 0)) { Result = FspNtStatusFromWin32(GetLastError()); goto exit; } StartupInfoEx.StartupInfo.cb = sizeof StartupInfoEx; StartupInfoEx.lpAttributeList = AttrList; StartupInfoEx.StartupInfo.dwFlags = STARTF_USESTDHANDLES; StartupInfoEx.StartupInfo.hStdInput = ChildHandles[0]; StartupInfoEx.StartupInfo.hStdOutput = ChildHandles[1]; StartupInfoEx.StartupInfo.hStdError = ChildHandles[2]; if (!LogonCreateProcess(UserName, ClientToken, Executable, CommandLine, 0, 0, TRUE, CREATE_SUSPENDED | CREATE_NEW_PROCESS_GROUP | EXTENDED_STARTUPINFO_PRESENT, 0, WorkDirectory, &StartupInfoEx.StartupInfo, ProcessInfo)) { if (ERROR_NO_SYSTEM_RESOURCES != GetLastError()) { Result = FspNtStatusFromWin32(GetLastError()); goto exit; } /* * On Win7 CreateProcessW with EXTENDED_STARTUPINFO_PRESENT * may fail with ERROR_NO_SYSTEM_RESOURCES. * * In that case go ahead and retry with a CreateProcessW with * bInheritHandles==TRUE, but without EXTENDED_STARTUPINFO_PRESENT. * Not ideal, but... */ StartupInfoEx.StartupInfo.cb = sizeof StartupInfoEx.StartupInfo; if (!LogonCreateProcess(UserName, ClientToken, Executable, CommandLine, 0, 0, TRUE, CREATE_SUSPENDED | CREATE_NEW_PROCESS_GROUP, 0, WorkDirectory, &StartupInfoEx.StartupInfo, ProcessInfo)) { Result = FspNtStatusFromWin32(GetLastError()); goto exit; } } } else { if (!LogonCreateProcess(UserName, ClientToken, Executable, CommandLine, 0, 0, FALSE, CREATE_SUSPENDED | CREATE_NEW_PROCESS_GROUP, 0, WorkDirectory, &StartupInfoEx.StartupInfo, ProcessInfo)) { Result = FspNtStatusFromWin32(GetLastError()); goto exit; } } Result = STATUS_SUCCESS; exit: if (!NT_SUCCESS(Result)) { if (INVALID_HANDLE_VALUE != ParentHandles[0]) CloseHandle(ParentHandles[0]); if (INVALID_HANDLE_VALUE != ParentHandles[1]) CloseHandle(ParentHandles[1]); } else if (0 != StdioHandles) { StdioHandles[0] = ParentHandles[0]; StdioHandles[1] = ParentHandles[1]; } if (INVALID_HANDLE_VALUE != ChildHandles[0]) CloseHandle(ChildHandles[0]); if (INVALID_HANDLE_VALUE != ChildHandles[1]) CloseHandle(ChildHandles[1]); if (InitDoneAttrList) DeleteProcThreadAttributeList(AttrList); MemFree(AttrList); return Result; } NTSTATUS SvcInstanceCreate(HANDLE ClientToken, PWSTR ClassName, PWSTR InstanceName, ULONG Argc, PWSTR *Argv0, HANDLE Job, BOOLEAN RedirectStdio, SVC_INSTANCE **PSvcInstance) { SVC_INSTANCE *SvcInstance = 0; PWSTR Argv[10]; PWSTR Varv[26]; SYSTEMTIME SystemTime; PWSTR ClientUserName = 0, StderrFileName = 0; DWORD ClientTokenInformation = -1; SECURITY_ATTRIBUTES StderrSecurityAttributes = { sizeof(SECURITY_ATTRIBUTES), 0, TRUE }; FSP_LAUNCH_REG_RECORD *Record = 0; WCHAR CurrentTime[32], UserProfileDir[MAX_PATH], CommandLine[512], Security[512]; DWORD Length, ClassNameSize, InstanceNameSize; PSECURITY_DESCRIPTOR SecurityDescriptor = 0, NewSecurityDescriptor; PROCESS_INFORMATION ProcessInfo; NTSTATUS Result; *PSvcInstance = 0; if (Argc > sizeof Argv / sizeof Argv[0] - 1) Argc = sizeof Argv / sizeof Argv[0] - 1; memcpy(Argv + 1, Argv0, Argc * sizeof(PWSTR)); Argv[0] = 0; Argc++; memset(Varv, 0, sizeof Varv); memset(&ProcessInfo, 0, sizeof ProcessInfo); EnterCriticalSection(&SvcInstanceLock); if (0 != SvcInstanceLookup(ClassName, InstanceName)) { Result = STATUS_OBJECT_NAME_COLLISION; goto exit; } GetSystemTime(&SystemTime); wsprintfW(CurrentTime, L"%04hu%02hu%02huT%02hu%02hu%02hu.%03huZ", SystemTime.wYear, SystemTime.wMonth, SystemTime.wDay, SystemTime.wHour, SystemTime.wMinute, SystemTime.wSecond, SystemTime.wMilliseconds); Varv[L'T' - L'A'] = CurrentTime; Result = GetTokenUserName(ClientToken, &ClientUserName); if (!NT_SUCCESS(Result)) goto exit; Varv[L'U' - L'A'] = ClientUserName; Length = MAX_PATH; if (!GetUserProfileDirectoryW(ClientToken, UserProfileDir, &Length)) /* store an invalid filename; any attempt to use it will fail */ lstrcpyW(UserProfileDir, L":INVALID:"); Varv[L'P' - L'A'] = UserProfileDir; Result = FspLaunchRegGetRecord(ClassName, 0, &Record); if (!NT_SUCCESS(Result)) goto exit; if ((!RedirectStdio && 0 != Record->Credentials) || ( RedirectStdio && 0 == Record->Credentials)) { Result = STATUS_DEVICE_CONFIGURATION_ERROR; goto exit; } Argv[0] = Record->Executable; lstrcpyW(CommandLine, L"%0 "); if (0 != Record->CommandLine) { Length = lstrlenW(CommandLine); lstrcpynW(CommandLine + Length, Record->CommandLine, sizeof CommandLine / sizeof(WCHAR) - Length); CommandLine[sizeof CommandLine / sizeof(WCHAR) - 1] = L'\0'; } lstrcpyW(Security, L"O:SYG:SY"); if (0 != Record->Security) { if (L'D' == Record->Security[0] && L':' == Record->Security[1]) Length = lstrlenW(Security); else Length = 0; lstrcpynW(Security + Length, Record->Security, sizeof Security / sizeof(WCHAR) - Length); Security[sizeof Security / sizeof(WCHAR) - 1] = L'\0'; } else { Length = lstrlenW(Security); lstrcpyW(Security + Length, L"" FSP_LAUNCH_SERVICE_DEFAULT_SDDL); } if (!ConvertStringSecurityDescriptorToSecurityDescriptorW(Security, SDDL_REVISION_1, &SecurityDescriptor, 0)) { Result = FspNtStatusFromWin32(GetLastError()); goto exit; } Result = SvcInstanceAccessCheck(ClientToken, SERVICE_START, SecurityDescriptor); if (!NT_SUCCESS(Result)) goto exit; Result = SvcInstanceAddUserRights(ClientToken, SecurityDescriptor, &NewSecurityDescriptor); if (!NT_SUCCESS(Result)) goto exit; LocalFree(SecurityDescriptor); SecurityDescriptor = NewSecurityDescriptor; ClassNameSize = (lstrlenW(ClassName) + 1) * sizeof(WCHAR); InstanceNameSize = (lstrlenW(InstanceName) + 1) * sizeof(WCHAR); SvcInstance = MemAlloc(sizeof *SvcInstance + ClassNameSize + InstanceNameSize); if (0 == SvcInstance) { Result = STATUS_INSUFFICIENT_RESOURCES; goto exit; } /* ClientToken: protect from CloseHandle; do not inherit */ if (!GetHandleInformation(ClientToken, &ClientTokenInformation) || !SetHandleInformation(ClientToken, HANDLE_FLAG_PROTECT_FROM_CLOSE | HANDLE_FLAG_INHERIT, HANDLE_FLAG_PROTECT_FROM_CLOSE | 0)) { ClientTokenInformation = -1; Result = FspNtStatusFromWin32(GetLastError()); goto exit; } memset(SvcInstance, 0, sizeof *SvcInstance); SvcInstance->RefCount = 2; SvcInstance->ClientToken = ClientToken; memcpy(SvcInstance->Buffer, ClassName, ClassNameSize); memcpy(SvcInstance->Buffer + ClassNameSize / sizeof(WCHAR), InstanceName, InstanceNameSize); SvcInstance->ClassName = SvcInstance->Buffer; SvcInstance->InstanceName = SvcInstance->Buffer + ClassNameSize / sizeof(WCHAR); SvcInstance->SecurityDescriptor = SecurityDescriptor; SvcInstance->StdioHandles[0] = INVALID_HANDLE_VALUE; SvcInstance->StdioHandles[1] = INVALID_HANDLE_VALUE; SvcInstance->StdioHandles[2] = INVALID_HANDLE_VALUE; SvcInstance->Recovery = Record->Recovery; Result = SvcInstanceReplaceArguments(CommandLine, Argc, Argv, Varv, TRUE, &SvcInstance->CommandLine); if (!NT_SUCCESS(Result)) goto exit; if (0 != Record->Stderr) { Result = SvcInstanceReplaceArguments(Record->Stderr, Argc, Argv, Varv, FALSE, &StderrFileName); if (!NT_SUCCESS(Result)) goto exit; SvcInstance->StdioHandles[2] = CreateFileW( StderrFileName, FILE_APPEND_DATA, FILE_SHARE_READ | FILE_SHARE_WRITE, &StderrSecurityAttributes, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0); if (INVALID_HANDLE_VALUE == SvcInstance->StdioHandles[2]) FspServiceLog(EVENTLOG_WARNING_TYPE, L"Ignorning error: cannot create stderr file = %ld", GetLastError()); } Result = SvcInstanceCreateProcess( Record->RunAs, ClientToken, Record->Executable, SvcInstance->CommandLine, Record->WorkDirectory, RedirectStdio ? SvcInstance->StdioHandles : 0, SvcInstance->StdioHandles[2], &ProcessInfo); if (!NT_SUCCESS(Result)) goto exit; SvcInstance->ProcessId = ProcessInfo.dwProcessId; SvcInstance->Process = ProcessInfo.hProcess; if (!RegisterWaitForSingleObject(&SvcInstance->ProcessWait, SvcInstance->Process, SvcInstanceTerminated, SvcInstance, INFINITE, WT_EXECUTEONLYONCE)) { Result = FspNtStatusFromWin32(GetLastError()); goto exit; } if (0 != Job && Record->JobControl) { if (!AssignProcessToJobObject(Job, SvcInstance->Process)) FspServiceLog(EVENTLOG_WARNING_TYPE, L"Ignorning error: AssignProcessToJobObject = %ld", GetLastError()); } /* * ONCE THE PROCESS IS RESUMED NO MORE FAILURES ALLOWED! */ ResumeThread(ProcessInfo.hThread); CloseHandle(ProcessInfo.hThread); ProcessInfo.hThread = 0; InsertTailList(&SvcInstanceList, &SvcInstance->ListEntry); ResetEvent(SvcInstanceEvent); *PSvcInstance = SvcInstance; Result = STATUS_SUCCESS; exit: if (!NT_SUCCESS(Result)) { LocalFree(SecurityDescriptor); if (0 != ProcessInfo.hThread) CloseHandle(ProcessInfo.hThread); if (-1 != ClientTokenInformation) SetHandleInformation(ClientToken, HANDLE_FLAG_PROTECT_FROM_CLOSE | HANDLE_FLAG_INHERIT, ClientTokenInformation); if (0 != SvcInstance) { if (INVALID_HANDLE_VALUE != SvcInstance->StdioHandles[0]) CloseHandle(SvcInstance->StdioHandles[0]); if (INVALID_HANDLE_VALUE != SvcInstance->StdioHandles[1]) CloseHandle(SvcInstance->StdioHandles[1]); if (INVALID_HANDLE_VALUE != SvcInstance->StdioHandles[2]) CloseHandle(SvcInstance->StdioHandles[2]); if (0 != SvcInstance->ProcessWait) UnregisterWaitEx(SvcInstance->ProcessWait, 0); if (0 != SvcInstance->Process) { TerminateProcess(SvcInstance->Process, 0); CloseHandle(SvcInstance->Process); } MemFree(SvcInstance->CommandLine); MemFree(SvcInstance); } } if (0 != Record) FspLaunchRegFreeRecord(Record); MemFree(StderrFileName); MemFree(ClientUserName); LeaveCriticalSection(&SvcInstanceLock); FspServiceLog(EVENTLOG_INFORMATION_TYPE, L"create %s\\%s = %lx", ClassName, InstanceName, Result); return Result; } static VOID SvcInstanceRelease(SVC_INSTANCE *SvcInstance) { if (0 != InterlockedDecrement(&SvcInstance->RefCount)) return; EnterCriticalSection(&SvcInstanceLock); if (RemoveEntryList(&SvcInstance->ListEntry)) SetEvent(SvcInstanceEvent); LeaveCriticalSection(&SvcInstanceLock); SetHandleInformation(SvcInstance->ClientToken, HANDLE_FLAG_PROTECT_FROM_CLOSE, 0); if (1 == SvcInstance->Recovery && SvcInstance->Started && !SvcInstance->Stopped) SvcInstanceStart(SvcInstance->ClientToken, SvcInstance->ClassName, SvcInstance->InstanceName, SvcInstance->Argc, SvcInstance->Argv, SvcJob, SvcInstance->HasSecret); MemFree(SvcInstance->Argv); if (INVALID_HANDLE_VALUE != SvcInstance->StdioHandles[0]) CloseHandle(SvcInstance->StdioHandles[0]); if (INVALID_HANDLE_VALUE != SvcInstance->StdioHandles[1]) CloseHandle(SvcInstance->StdioHandles[1]); if (INVALID_HANDLE_VALUE != SvcInstance->StdioHandles[2]) CloseHandle(SvcInstance->StdioHandles[2]); if (0 != SvcInstance->ProcessWait) UnregisterWaitEx(SvcInstance->ProcessWait, 0); if (0 != SvcInstance->Process) CloseHandle(SvcInstance->Process); LocalFree(SvcInstance->SecurityDescriptor); MemFree(SvcInstance->CommandLine); /* * NOTE: * New instances store the ClientToken and protect it from CloseHandle. * This results in an unhandled exception when running under a debugger. * Such exceptions can be ignored. * * See MSDN CloseHandle for details: * https://docs.microsoft.com/en-us/windows/win32/api/handleapi/nf-handleapi-closehandle */ CloseHandle(SvcInstance->ClientToken); MemFree(SvcInstance); } static VOID CALLBACK SvcInstanceTerminated(PVOID Context, BOOLEAN Timeout) { SVC_INSTANCE *SvcInstance = Context; FspServiceLog(EVENTLOG_INFORMATION_TYPE, L"terminated %s\\%s", SvcInstance->ClassName, SvcInstance->InstanceName); SvcInstanceRelease(SvcInstance); } static NTSTATUS SvcInstanceStartWithArgvCopy(HANDLE ClientToken, PWSTR ClassName, PWSTR InstanceName, ULONG Argc, PWSTR *Argv, HANDLE Job, BOOLEAN HasSecret) { SVC_INSTANCE *SvcInstance; NTSTATUS Result; if (HasSecret && (0 == Argc || L'\0' == Argv[Argc - 1][0])) return STATUS_INVALID_PARAMETER; HasSecret = !!HasSecret; Result = SvcInstanceCreate(ClientToken, ClassName, InstanceName, Argc - HasSecret, Argv, Job, HasSecret, &SvcInstance); if (!NT_SUCCESS(Result)) return Result; if (!HasSecret) Result = STATUS_SUCCESS; else { PWSTR Secret = Argv[Argc - 1]; UINT8 ReqBuf[256]; UINT8 RspBuf[2]; DWORD BytesTransferred; OVERLAPPED Overlapped; DWORD WaitResult; if (0 == (BytesTransferred = WideCharToMultiByte(CP_UTF8, 0, Secret, lstrlenW(Secret), ReqBuf, sizeof ReqBuf, 0, 0))) { Result = FspNtStatusFromWin32(GetLastError()); goto exit; } if (!WriteFile(SvcInstance->StdioHandles[0], ReqBuf, BytesTransferred, &BytesTransferred, 0)) { Result = FspNtStatusFromWin32(GetLastError()); goto exit; } CloseHandle(SvcInstance->StdioHandles[0]); SvcInstance->StdioHandles[0] = INVALID_HANDLE_VALUE; memset(&Overlapped, 0, sizeof Overlapped); if (!ReadFile(SvcInstance->StdioHandles[1], RspBuf, sizeof RspBuf, 0, &Overlapped) && ERROR_IO_PENDING != GetLastError()) { Result = FspNtStatusFromWin32(GetLastError()); goto exit; } /* * We need to perform a GetOverlappedResult with a timeout. GetOverlappedResultEx would * be perfect except that it is a Windows 8 and above API. We will therefore replace with * WaitForSingleObject followed by GetOverlappedResult on success. */ WaitResult = WaitForSingleObject(SvcInstance->StdioHandles[1], LAUNCHER_START_WITH_SECRET_TIMEOUT); if (WAIT_OBJECT_0 == WaitResult) Result = GetOverlappedResult(SvcInstance->StdioHandles[1], &Overlapped, &BytesTransferred, TRUE) ? STATUS_SUCCESS : FspNtStatusFromWin32(GetLastError()); else if (WAIT_TIMEOUT == WaitResult) Result = STATUS_TIMEOUT; else Result = FspNtStatusFromWin32(GetLastError()); if (!NT_SUCCESS(Result) || STATUS_TIMEOUT == Result) { CancelIoEx(SvcInstance->StdioHandles[1], &Overlapped); goto exit; } if (sizeof RspBuf <= BytesTransferred && 'O' == RspBuf[0] && 'K' == RspBuf[1]) Result = STATUS_SUCCESS; else Result = STATUS_ACCESS_DENIED; } exit: if (INVALID_HANDLE_VALUE != SvcInstance->StdioHandles[0]) { CloseHandle(SvcInstance->StdioHandles[0]); SvcInstance->StdioHandles[0] = INVALID_HANDLE_VALUE; } if (INVALID_HANDLE_VALUE != SvcInstance->StdioHandles[1]) { CloseHandle(SvcInstance->StdioHandles[1]); SvcInstance->StdioHandles[1] = INVALID_HANDLE_VALUE; } if (INVALID_HANDLE_VALUE != SvcInstance->StdioHandles[2]) { CloseHandle(SvcInstance->StdioHandles[2]); SvcInstance->StdioHandles[2] = INVALID_HANDLE_VALUE; } if (NT_SUCCESS(Result) && STATUS_TIMEOUT != Result) { SvcInstance->Argc = Argc; SvcInstance->Argv = Argv; SvcInstance->HasSecret = HasSecret; SvcInstance->Started = TRUE; } SvcInstanceRelease(SvcInstance); if (STATUS_TIMEOUT == Result) /* convert to an error! */ Result = 0x80070000 | ERROR_TIMEOUT; return Result; } NTSTATUS SvcInstanceStart(HANDLE ClientToken, PWSTR ClassName, PWSTR InstanceName, ULONG Argc, PWSTR *Argv, HANDLE Job, BOOLEAN HasSecret) { DWORD ArgvSize; PWSTR *ArgvCopy; NTSTATUS Result; ArgvSize = 0; for (ULONG I = 0; Argc > I; I++) ArgvSize += (lstrlenW(Argv[I]) + 1) * sizeof(WCHAR); ArgvCopy = MemAlloc(Argc * sizeof(PWSTR) + ArgvSize); if (0 == ArgvCopy) return STATUS_INSUFFICIENT_RESOURCES; ArgvSize = 0; for (ULONG I = 0; Argc > I; I++) { ULONG L = (lstrlenW(Argv[I]) + 1) * sizeof(WCHAR); ArgvCopy[I] = (PWSTR)((PUINT8)ArgvCopy + Argc * sizeof(PWSTR) + ArgvSize); memcpy(ArgvCopy[I], Argv[I], L); ArgvSize += L; } Result = SvcInstanceStartWithArgvCopy(ClientToken, ClassName, InstanceName, Argc, ArgvCopy, Job, HasSecret); if (!NT_SUCCESS(Result)) MemFree(ArgvCopy); return Result; } NTSTATUS SvcInstanceStop(HANDLE ClientToken, PWSTR ClassName, PWSTR InstanceName) { SVC_INSTANCE *SvcInstance; NTSTATUS Result; EnterCriticalSection(&SvcInstanceLock); SvcInstance = SvcInstanceLookup(ClassName, InstanceName); if (0 == SvcInstance) { Result = STATUS_OBJECT_NAME_NOT_FOUND; goto exit; } Result = SvcInstanceAccessCheck(ClientToken, SERVICE_STOP, SvcInstance->SecurityDescriptor); if (!NT_SUCCESS(Result)) goto exit; SvcInstance->Stopped = TRUE; KillProcess(SvcInstance->ProcessId, SvcInstance->Process, LAUNCHER_KILL_TIMEOUT); Result = STATUS_SUCCESS; exit: LeaveCriticalSection(&SvcInstanceLock); return Result; } NTSTATUS SvcInstanceGetInfo(HANDLE ClientToken, PWSTR ClassName, PWSTR InstanceName, PWSTR Buffer, PULONG PSize) { SVC_INSTANCE *SvcInstance; PWSTR P = Buffer; ULONG ClassNameSize, InstanceNameSize, CommandLineSize; NTSTATUS Result; EnterCriticalSection(&SvcInstanceLock); SvcInstance = SvcInstanceLookup(ClassName, InstanceName); if (0 == SvcInstance) { Result = STATUS_OBJECT_NAME_NOT_FOUND; goto exit; } Result = SvcInstanceAccessCheck(ClientToken, SERVICE_QUERY_STATUS, SvcInstance->SecurityDescriptor); if (!NT_SUCCESS(Result)) goto exit; ClassNameSize = lstrlenW(SvcInstance->ClassName) + 1; InstanceNameSize = lstrlenW(SvcInstance->InstanceName) + 1; CommandLineSize = lstrlenW(SvcInstance->CommandLine) + 1; if (*PSize < (ClassNameSize + InstanceNameSize + CommandLineSize) * sizeof(WCHAR)) { Result = STATUS_BUFFER_TOO_SMALL; goto exit; } memcpy(P, SvcInstance->ClassName, ClassNameSize * sizeof(WCHAR)); P += ClassNameSize; memcpy(P, SvcInstance->InstanceName, InstanceNameSize * sizeof(WCHAR)); P += InstanceNameSize; memcpy(P, SvcInstance->CommandLine, CommandLineSize * sizeof(WCHAR)); P += CommandLineSize; *PSize = (ULONG)(P - Buffer) * sizeof(WCHAR); Result = STATUS_SUCCESS; exit: LeaveCriticalSection(&SvcInstanceLock); return Result; } NTSTATUS SvcInstanceGetNameList(HANDLE ClientToken, PWSTR Buffer, PULONG PSize) { SVC_INSTANCE *SvcInstance; PLIST_ENTRY ListEntry; PWSTR P = Buffer, BufferEnd = P + *PSize / sizeof(WCHAR); ULONG ClassNameSize, InstanceNameSize; EnterCriticalSection(&SvcInstanceLock); for (ListEntry = SvcInstanceList.Flink; &SvcInstanceList != ListEntry; ListEntry = ListEntry->Flink) { SvcInstance = CONTAINING_RECORD(ListEntry, SVC_INSTANCE, ListEntry); ClassNameSize = lstrlenW(SvcInstance->ClassName) + 1; InstanceNameSize = lstrlenW(SvcInstance->InstanceName) + 1; if (BufferEnd < P + ClassNameSize + InstanceNameSize) break; memcpy(P, SvcInstance->ClassName, ClassNameSize * sizeof(WCHAR)); P += ClassNameSize; memcpy(P, SvcInstance->InstanceName, InstanceNameSize * sizeof(WCHAR)); P += InstanceNameSize; } LeaveCriticalSection(&SvcInstanceLock); *PSize = (ULONG)(P - Buffer) * sizeof(WCHAR); return STATUS_SUCCESS; } NTSTATUS SvcInstanceStopAndWaitAll(VOID) { SVC_INSTANCE *SvcInstance; PLIST_ENTRY ListEntry; EnterCriticalSection(&SvcInstanceLock); for (ListEntry = SvcInstanceList.Flink; &SvcInstanceList != ListEntry; ListEntry = ListEntry->Flink) { SvcInstance = CONTAINING_RECORD(ListEntry, SVC_INSTANCE, ListEntry); SvcInstance->Stopped = TRUE; KillProcess(SvcInstance->ProcessId, SvcInstance->Process, LAUNCHER_KILL_TIMEOUT); } LeaveCriticalSection(&SvcInstanceLock); WaitForSingleObject(SvcInstanceEvent, LAUNCHER_STOP_TIMEOUT); return STATUS_SUCCESS; } NTSTATUS SvcDefineDosDevice(HANDLE ClientToken, PWSTR DeviceName, PWSTR TargetPath) { NTSTATUS Result; if (L'+' != DeviceName[0] && L'-' != DeviceName[0]) return STATUS_INVALID_PARAMETER; Result = FspServiceContextCheck(ClientToken, 0); if (!NT_SUCCESS(Result)) return Result; if (!DefineDosDeviceW( DDD_RAW_TARGET_PATH | (L'+' == DeviceName[0] ? 0 : DDD_REMOVE_DEFINITION | DDD_EXACT_MATCH_ON_REMOVE), DeviceName + 1, TargetPath)) return FspNtStatusFromWin32(GetLastError()); if (L'+' == DeviceName[0] && 0 != SvcNtOpenSymbolicLinkObject) { /* The drive symlink now exists; add DELETE access to it for the ClientToken. */ WCHAR SymlinkBuf[6]; UNICODE_STRING Symlink; OBJECT_ATTRIBUTES Obja; HANDLE MountHandle; memcpy(SymlinkBuf, L"\\??\\X:", sizeof SymlinkBuf); SymlinkBuf[4] = DeviceName[1]; Symlink.Length = Symlink.MaximumLength = sizeof SymlinkBuf; Symlink.Buffer = SymlinkBuf; memset(&Obja, 0, sizeof Obja); Obja.Length = sizeof Obja; Obja.ObjectName = &Symlink; Obja.Attributes = OBJ_CASE_INSENSITIVE; Result = SvcNtOpenSymbolicLinkObject(&MountHandle, READ_CONTROL | WRITE_DAC, &Obja); if (NT_SUCCESS(Result)) { AddAccessForTokenUser(MountHandle, DELETE, ClientToken); SvcNtClose(MountHandle); } } return STATUS_SUCCESS; } static HANDLE SvcThread, SvcEvent; static DWORD SvcThreadId; static HANDLE SvcPipe = INVALID_HANDLE_VALUE; static OVERLAPPED SvcOverlapped; static DWORD WINAPI SvcPipeServer(PVOID Context); static VOID SvcPipeTransact(HANDLE ClientToken, PWSTR PipeBuf, PULONG PSize); static NTSTATUS SvcStart(FSP_SERVICE *Service, ULONG argc, PWSTR *argv) { SECURITY_ATTRIBUTES SecurityAttributes = { 0 }; /* * Allocate a console in case we are running as a service without one. * This will ensure that we can send console control events to service instances. */ if (AllocConsole()) ShowWindow(GetConsoleWindow(), SW_HIDE); InitializeCriticalSection(&SvcInstanceLock); SecurityAttributes.nLength = sizeof SecurityAttributes; SecurityAttributes.bInheritHandle = FALSE; if (!ConvertStringSecurityDescriptorToSecurityDescriptorW(L"" FSP_LAUNCH_PIPE_SDDL, SDDL_REVISION_1, &SecurityAttributes.lpSecurityDescriptor, 0)) goto fail; //FspDebugLogSD(__FUNCTION__ ": SDDL = %s\n", SecurityAttributes.lpSecurityDescriptor); SvcInstanceEvent = CreateEventW(0, TRUE, TRUE, 0); if (0 == SvcInstanceEvent) goto fail; SvcJob = CreateJobObjectW(0, 0); if (0 != SvcJob) { JOBOBJECT_EXTENDED_LIMIT_INFORMATION LimitInfo; memset(&LimitInfo, 0, sizeof LimitInfo); LimitInfo.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE; if (!SetInformationJobObject(SvcJob, JobObjectExtendedLimitInformation, &LimitInfo, sizeof LimitInfo)) { CloseHandle(SvcJob); SvcJob = 0; } } SvcEvent = CreateEventW(0, TRUE, FALSE, 0); if (0 == SvcEvent) goto fail; SvcOverlapped.hEvent = CreateEventW(0, TRUE, FALSE, 0); if (0 == SvcOverlapped.hEvent) goto fail; SvcPipe = CreateNamedPipeW(L"" FSP_LAUNCH_PIPE_NAME, PIPE_ACCESS_DUPLEX | FILE_FLAG_FIRST_PIPE_INSTANCE | FILE_FLAG_WRITE_THROUGH | FILE_FLAG_OVERLAPPED, PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT | PIPE_REJECT_REMOTE_CLIENTS, 1, FSP_LAUNCH_PIPE_BUFFER_SIZE, FSP_LAUNCH_PIPE_BUFFER_SIZE, LAUNCHER_PIPE_DEFAULT_TIMEOUT, &SecurityAttributes); if (INVALID_HANDLE_VALUE == SvcPipe) goto fail; SvcThread = CreateThread(0, 0, SvcPipeServer, Service, 0, &SvcThreadId); if (0 == SvcThread) goto fail; LocalFree(SecurityAttributes.lpSecurityDescriptor); return STATUS_SUCCESS; fail: DWORD LastError = GetLastError(); /* * The OS will cleanup for us. So there is no need to explicitly release these resources. */ #if 0 if (0 != SvcThread) CloseHandle(SvcThread); if (INVALID_HANDLE_VALUE != SvcPipe) CloseHandle(SvcPipe); if (0 != SvcOverlapped.hEvent) CloseHandle(SvcOverlapped.hEvent); if (0 != SvcEvent) CloseHandle(SvcEvent); if (0 != SvcJob) CloseHandle(SvcJob); if (0 != SvcInstanceEvent) CloseHandle(SvcInstanceEvent); LocalFree(SecurityAttributes.lpSecurityDescriptor); DeleteCriticalSection(&SvcInstanceLock); #endif return FspNtStatusFromWin32(LastError); } static NTSTATUS SvcStop(FSP_SERVICE *Service) { if (GetCurrentThreadId() != SvcThreadId) { SetEvent(SvcEvent); FspServiceRequestTime(Service, LAUNCHER_STOP_TIMEOUT); WaitForSingleObject(SvcThread, LAUNCHER_STOP_TIMEOUT); } /* * The OS will cleanup for us. So there is no need to explicitly release these resources. * * This also protects us from scenarios where not all child processes terminate timely * and KillProcess decides to terminate them forcibly, thus creating racing conditions * with SvcInstanceTerminated. */ #if 0 if (0 != SvcThread) CloseHandle(SvcThread); if (INVALID_HANDLE_VALUE != SvcPipe) CloseHandle(SvcPipe); if (0 != SvcOverlapped.hEvent) CloseHandle(SvcOverlapped.hEvent); if (0 != SvcEvent) CloseHandle(SvcEvent); if (0 != SvcJob) CloseHandle(SvcJob); if (0 != SvcInstanceEvent) CloseHandle(SvcInstanceEvent); DeleteCriticalSection(&SvcInstanceLock); #endif return STATUS_SUCCESS; } static inline DWORD SvcPipeWaitResult(BOOL Success, HANDLE StopEvent, HANDLE Handle, OVERLAPPED *Overlapped, PDWORD PBytesTransferred) { HANDLE WaitObjects[2]; DWORD WaitResult; if (!Success && ERROR_IO_PENDING != GetLastError()) return GetLastError(); WaitObjects[0] = StopEvent; WaitObjects[1] = Overlapped->hEvent; WaitResult = WaitForMultipleObjects(2, WaitObjects, FALSE, INFINITE); if (WAIT_OBJECT_0 == WaitResult) return -1; /* special: stop thread */ else if (WAIT_OBJECT_0 + 1 == WaitResult) { if (!GetOverlappedResult(Handle, Overlapped, PBytesTransferred, TRUE)) return GetLastError(); return 0; } else return GetLastError(); } static DWORD WINAPI SvcPipeServer(PVOID Context) { static PWSTR LoopErrorMessage = L"Error in service main loop (%s = %ld). Exiting..."; static PWSTR LoopWarningMessage = L"Error in service main loop (%s = %ld). Continuing..."; FSP_SERVICE *Service = Context; PWSTR PipeBuf = 0; HANDLE ClientToken; DWORD LastError, BytesTransferred; PipeBuf = MemAlloc(FSP_LAUNCH_PIPE_BUFFER_SIZE); if (0 == PipeBuf) { FspServiceSetExitCode(Service, ERROR_NO_SYSTEM_RESOURCES); goto exit; } for (;;) { LastError = SvcPipeWaitResult( ConnectNamedPipe(SvcPipe, &SvcOverlapped), SvcEvent, SvcPipe, &SvcOverlapped, &BytesTransferred); if (-1 == LastError) break; else if (0 != LastError && ERROR_PIPE_CONNECTED != LastError && ERROR_NO_DATA != LastError) { FspServiceLog(EVENTLOG_WARNING_TYPE, LoopWarningMessage, L"ConnectNamedPipe", LastError); continue; } LastError = SvcPipeWaitResult( ReadFile(SvcPipe, PipeBuf, FSP_LAUNCH_PIPE_BUFFER_SIZE, &BytesTransferred, &SvcOverlapped), SvcEvent, SvcPipe, &SvcOverlapped, &BytesTransferred); if (-1 == LastError) break; else if (0 != LastError || sizeof(WCHAR) > BytesTransferred) { DisconnectNamedPipe(SvcPipe); if (0 != LastError) FspServiceLog(EVENTLOG_WARNING_TYPE, LoopWarningMessage, L"ReadFile", LastError); continue; } ClientToken = 0; if (!ImpersonateNamedPipeClient(SvcPipe) || ( !OpenThreadToken(GetCurrentThread(), TOKEN_QUERY | TOKEN_DUPLICATE, FALSE, &ClientToken) && !OpenThreadToken(GetCurrentThread(), TOKEN_QUERY, FALSE, &ClientToken) ) || !RevertToSelf()) { LastError = GetLastError(); if (0 == ClientToken) { CloseHandle(ClientToken); DisconnectNamedPipe(SvcPipe); FspServiceLog(EVENTLOG_WARNING_TYPE, LoopWarningMessage, L"ImpersonateNamedPipeClient||OpenThreadToken", LastError); continue; } else { CloseHandle(ClientToken); DisconnectNamedPipe(SvcPipe); FspServiceLog(EVENTLOG_ERROR_TYPE, LoopErrorMessage, L"RevertToSelf", LastError); break; } } SvcPipeTransact(ClientToken, PipeBuf, &BytesTransferred); /* * NOTE: * New instances store the ClientToken and protect it from CloseHandle. * This results in an unhandled exception when running under a debugger. * Such exceptions can be ignored. * * See MSDN CloseHandle for details: * https://docs.microsoft.com/en-us/windows/win32/api/handleapi/nf-handleapi-closehandle */ CloseHandle(ClientToken); LastError = SvcPipeWaitResult( WriteFile(SvcPipe, PipeBuf, BytesTransferred, &BytesTransferred, &SvcOverlapped), SvcEvent, SvcPipe, &SvcOverlapped, &BytesTransferred); if (-1 == LastError) break; else if (0 != LastError) { DisconnectNamedPipe(SvcPipe); FspServiceLog(EVENTLOG_WARNING_TYPE, LoopWarningMessage, L"WriteFile", LastError); continue; } DisconnectNamedPipe(SvcPipe); } exit: MemFree(PipeBuf); SvcInstanceStopAndWaitAll(); FspServiceStop(Service); return 0; } static inline PWSTR SvcPipeTransactGetPart(PWSTR *PP, PWSTR PipeBufEnd) { PWSTR PipeBufBeg = *PP, P; for (P = PipeBufBeg; PipeBufEnd > P && *P; P++) ; if (PipeBufEnd > P) { *PP = P + 1; return PipeBufBeg; } else { *PP = P; return 0; } } static inline VOID SvcPipeTransactResult(NTSTATUS Result, PWSTR PipeBuf, PULONG PSize) { if (NT_SUCCESS(Result)) { *PipeBuf = FspLaunchCmdSuccess; *PSize += sizeof(WCHAR); } else *PSize = (wsprintfW(PipeBuf, L"%c%ld", FspLaunchCmdFailure, FspWin32FromNtStatus(Result)) + 1) * sizeof(WCHAR); } static VOID SvcPipeTransact(HANDLE ClientToken, PWSTR PipeBuf, PULONG PSize) { if (sizeof(WCHAR) > *PSize) return; PWSTR P = PipeBuf, PipeBufEnd = PipeBuf + *PSize / sizeof(WCHAR); PWSTR ClassName, InstanceName; PWSTR DeviceName, TargetPath; ULONG Argc; PWSTR Argv[9]; BOOLEAN HasSecret = FALSE; NTSTATUS Result; *PSize = 0; switch (*P++) { case FspLaunchCmdStartWithSecret: HasSecret = TRUE; /* fall through! */ case FspLaunchCmdStart: ClassName = SvcPipeTransactGetPart(&P, PipeBufEnd); InstanceName = SvcPipeTransactGetPart(&P, PipeBufEnd); for (Argc = 0; sizeof Argv / sizeof Argv[0] > Argc; Argc++) if (0 == (Argv[Argc] = SvcPipeTransactGetPart(&P, PipeBufEnd))) break; Result = STATUS_INVALID_PARAMETER; if (0 != ClassName && 0 != InstanceName) Result = SvcInstanceStart(ClientToken, ClassName, InstanceName, Argc, Argv, SvcJob, HasSecret); SvcPipeTransactResult(Result, PipeBuf, PSize); break; case FspLaunchCmdStop: ClassName = SvcPipeTransactGetPart(&P, PipeBufEnd); InstanceName = SvcPipeTransactGetPart(&P, PipeBufEnd); Result = STATUS_INVALID_PARAMETER; if (0 != ClassName && 0 != InstanceName) Result = SvcInstanceStop(ClientToken, ClassName, InstanceName); SvcPipeTransactResult(Result, PipeBuf, PSize); break; case FspLaunchCmdGetInfo: ClassName = SvcPipeTransactGetPart(&P, PipeBufEnd); InstanceName = SvcPipeTransactGetPart(&P, PipeBufEnd); Result = STATUS_INVALID_PARAMETER; if (0 != ClassName && 0 != InstanceName) { *PSize = FSP_LAUNCH_PIPE_BUFFER_SIZE - 1; Result = SvcInstanceGetInfo(ClientToken, ClassName, InstanceName, PipeBuf + 1, PSize); } SvcPipeTransactResult(Result, PipeBuf, PSize); break; case FspLaunchCmdGetNameList: *PSize = FSP_LAUNCH_PIPE_BUFFER_SIZE - 1; Result = SvcInstanceGetNameList(ClientToken, PipeBuf + 1, PSize); SvcPipeTransactResult(Result, PipeBuf, PSize); break; case FspLaunchCmdDefineDosDevice: DeviceName = SvcPipeTransactGetPart(&P, PipeBufEnd); TargetPath = SvcPipeTransactGetPart(&P, PipeBufEnd); Result = STATUS_INVALID_PARAMETER; if (0 != DeviceName && 0 != TargetPath) Result = SvcDefineDosDevice(ClientToken, DeviceName, TargetPath); SvcPipeTransactResult(Result, PipeBuf, PSize); break; #if !defined(NDEBUG) case FspLaunchCmdQuit: SetEvent(SvcEvent); SvcPipeTransactResult(STATUS_SUCCESS, PipeBuf, PSize); break; #endif default: SvcPipeTransactResult(STATUS_INVALID_PARAMETER, PipeBuf, PSize); break; } } int wmain(int argc, wchar_t **argv) { HANDLE Handle = GetModuleHandleW(L"ntdll.dll"); if (0 != Handle) { SvcNtOpenSymbolicLinkObject = (PVOID)GetProcAddress(Handle, "NtOpenSymbolicLinkObject"); SvcNtClose = (PVOID)GetProcAddress(Handle, "NtClose"); if (0 == SvcNtOpenSymbolicLinkObject || 0 == SvcNtClose) { SvcNtOpenSymbolicLinkObject = 0; SvcNtClose = 0; } } return FspServiceRun(L"" PROGNAME, SvcStart, SvcStop, 0); } void wmainCRTStartup(void) { ExitProcess(wmain(0, 0)); }