mirror of
https://github.com/winfsp/winfsp.git
synced 2025-04-23 00:43:00 -05:00
1982 lines
59 KiB
C
1982 lines
59 KiB
C
/**
|
|
* @file launcher/launcher.c
|
|
*
|
|
* @copyright 2015-2020 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 <winfsp/launch.h>
|
|
#include <shared/um/minimal.h>
|
|
#include <aclapi.h>
|
|
#include <sddl.h>
|
|
#include <userenv.h>
|
|
|
|
#define PROGNAME "WinFsp.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))
|
|
{
|
|
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));
|
|
}
|