/** * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with this * work for additional information regarding copyright ownership. The ASF * licenses this file to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. */ #include "winutils.h" #include "winutils_msg.h" #include #include #include #include #include #include #include "hadoopwinutilsvc_h.h" #pragma comment(lib, "Rpcrt4.lib") #pragma comment(lib, "advapi32.lib") #pragma comment(lib, "authz.lib") LPCWSTR NM_WSCE_ALLOWED = L"yarn.nodemanager.windows-secure-container-executor.allowed"; LPCWSTR NM_WSCE_JOB_NAME = L"yarn.nodemanager.windows-secure-container-executor.job-name"; LPCWSTR NM_WSCE_LOCAL_DIRS = L"yarn.nodemanager.windows-secure-container-executor.local-dirs"; #define SERVICE_ACCESS_MASK 0x00000001 SERVICE_STATUS gSvcStatus; SERVICE_STATUS_HANDLE gSvcStatusHandle; HANDLE ghSvcStopEvent = INVALID_HANDLE_VALUE; HANDLE ghWaitObject = INVALID_HANDLE_VALUE; HANDLE ghEventLog = INVALID_HANDLE_VALUE; BOOL isListenning = FALSE; PSECURITY_DESCRIPTOR pAllowedSD = NULL; LPWSTR* gLocalDirs = NULL; size_t gLocalDirsCount = 0; int* gCchLocalDir = NULL; LPCWSTR gJobName = NULL; VOID SvcError(DWORD dwError); VOID WINAPI SvcMain(DWORD dwArg, LPTSTR* lpszArgv); DWORD SvcInit(); DWORD RpcInit(); DWORD AuthInit(); VOID ReportSvcStatus( DWORD dwCurrentState, DWORD dwWin32ExitCode, DWORD dwWaitHint); VOID WINAPI SvcCtrlHandler( DWORD dwCtrl ); VOID CALLBACK SvcShutdown( _In_ PVOID lpParameter, _In_ BOOLEAN TimerOrWaitFired); #define CHECK_ERROR_DONE(status, expected, category, message) \ if (status != expected) { \ ReportSvcCheckError( \ EVENTLOG_ERROR_TYPE, \ category, \ status, \ message); \ goto done; \ } else { \ LogDebugMessage(L"%s: OK\n", message); \ } #define CHECK_RPC_STATUS_DONE(status, message) \ CHECK_ERROR_DONE(status, RPC_S_OK, SERVICE_CATEGORY, message) #define CHECK_SVC_STATUS_DONE(status, message) \ CHECK_ERROR_DONE(status, ERROR_SUCCESS, SERVICE_CATEGORY, message) #define CHECK_UNWIND_RPC(rpcCall) { \ unwindStatus = rpcCall; \ if (RPC_S_OK != unwindStatus) { \ ReportSvcCheckError( \ EVENTLOG_WARNING_TYPE, \ SERVICE_CATEGORY, \ unwindStatus, \ L#rpcCall); \ } \ } //---------------------------------------------------------------------------- // Function: ReportSvcCheckError // // Description: // Reports an error with the system event log and to debugger console (if present) // void ReportSvcCheckError(WORD type, WORD category, DWORD dwError, LPCWSTR message) { int len; LPWSTR systemMsg = NULL; LPWSTR appMsg = NULL; DWORD dwReportError; LPWSTR reportMsg = NULL; WCHAR hexError[32]; LPCWSTR inserts[] = {message, NULL, NULL, NULL}; HRESULT hr; hr = StringCbPrintf(hexError, sizeof(hexError), TEXT("%x"), dwError); if (SUCCEEDED(hr)) { inserts[1] = hexError; } else { inserts[1] = L"(Failed to format dwError as string)"; } len = FormatMessageW( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, NULL, dwError, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPWSTR)&systemMsg, 0, NULL); if (len) { inserts[2] = systemMsg; } else { inserts[2] = L"(Failed to get the system error message)"; } LogDebugMessage(L"%s:%d %.*s\n", message, dwError, len, systemMsg); if (INVALID_HANDLE_VALUE != ghEventLog) { if (!ReportEvent(ghEventLog, type, category, MSG_CHECK_ERROR, NULL, // lpUserSid (WORD) 3, // wNumStrings (DWORD) 0, // dwDataSize inserts, // *lpStrings NULL // lpRawData )) { // We tried to report and failed. Send to dbg. dwReportError = GetLastError(); len = FormatMessageW( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, NULL, dwReportError, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPWSTR)&reportMsg, 0, NULL); LogDebugMessage(L"ReportEvent: Error:%d %.*s\n", dwReportError, reportMsg); } }; if (NULL != systemMsg) LocalFree(systemMsg); if (NULL != reportMsg) LocalFree(reportMsg); } VOID ReportSvcMessage(WORD type, WORD category, DWORD msgId) { DWORD dwError; if (INVALID_HANDLE_VALUE != ghEventLog) { if (!ReportEvent(ghEventLog, type, category, msgId, NULL, // lpUserSid (WORD) 0, // wNumStrings (DWORD) 0, // dwDataSize NULL, // *lpStrings NULL // lpRawData )) { // We tried to report and failed but debugger is attached. Send to dbg. dwError = GetLastError(); LogDebugMessage(L"ReportEvent: error %d\n", dwError); } } } //---------------------------------------------------------------------------- // Function: IsSidInList // // Description: // Finds a SID in an array of SID* // BOOL IsSidInList( __in PSID trustee, __in size_t cAllowedSids, __in_ecount(cAllowedSids) PSID* allowedSids) { size_t crtSid = 0; for (crtSid = 0; crtSid < cAllowedSids; ++crtSid) { if (EqualSid(trustee, allowedSids[crtSid])) { return TRUE; } } return FALSE; } //---------------------------------------------------------------------------- // Function: InitLocalDirs // // Description: // Validates that the wsceConfigRelativePath file is only writable by Administrators // DWORD ValidateConfigurationFile() { DWORD dwError = ERROR_SUCCESS; WCHAR xmlPath[MAX_PATH]; PSECURITY_DESCRIPTOR pSd = NULL; BOOL daclPresent = FALSE; BOOL daclDefaulted = FALSE; PACL pDacl = NULL; unsigned int crt = 0, crtSid = 0; WELL_KNOWN_SID_TYPE allowedSidTypes[] = { WinLocalSystemSid, WinBuiltinAdministratorsSid}; ACL_SIZE_INFORMATION aclInfo; DWORD cbSid = SECURITY_MAX_SID_SIZE; PSID* allowedSids = NULL; int cAllowedSids = 0; BOOL isSidDefaulted; PSID sidOwner = NULL; PSID sidGroup = NULL; allowedSids = (PSID*) LocalAlloc( LPTR, sizeof(PSID) * sizeof(allowedSidTypes) / sizeof(WELL_KNOWN_SID_TYPE)); if (NULL == allowedSids) { dwError = ERROR_OUTOFMEMORY; CHECK_SVC_STATUS_DONE(dwError, L"LocalAlloc"); } for(crt = 0; crt < sizeof(allowedSidTypes) / sizeof(WELL_KNOWN_SID_TYPE); ++crt) { allowedSids[crt] = LocalAlloc(LPTR, SECURITY_MAX_SID_SIZE); if (NULL == allowedSids[crt]) { dwError = ERROR_OUTOFMEMORY; CHECK_SVC_STATUS_DONE(dwError, L"LocalAlloc"); } cbSid = SECURITY_MAX_SID_SIZE; if (!CreateWellKnownSid( allowedSidTypes[crt], NULL, allowedSids[crt], &cbSid)) { dwError = GetLastError(); CHECK_SVC_STATUS_DONE(dwError, L"CreateWellKnownSid"); } ++cAllowedSids; } dwError = BuildPathRelativeToModule( wsceConfigRelativePath, sizeof(xmlPath)/sizeof(WCHAR), xmlPath); CHECK_SVC_STATUS_DONE(dwError, L"BuildPathRelativeToModule"); dwError = GetNamedSecurityInfo( xmlPath, SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, NULL, NULL, NULL, NULL, &pSd); CHECK_SVC_STATUS_DONE(dwError, L"GetNamedSecurityInfo"); if (!GetSecurityDescriptorDacl( pSd, &daclPresent, &pDacl, &daclDefaulted)) { dwError = GetLastError(); CHECK_SVC_STATUS_DONE(dwError, L"GetSecurityDescriptorDacl"); } if (!pDacl) { dwError = ERROR_BAD_CONFIGURATION; CHECK_SVC_STATUS_DONE(dwError, L"pDacl"); } ZeroMemory(&aclInfo, sizeof(aclInfo)); if (!GetAclInformation(pDacl, &aclInfo, sizeof(aclInfo), AclSizeInformation)) { dwError = GetLastError(); CHECK_SVC_STATUS_DONE(dwError, L"GetAclInformation"); } // Inspect all ACEs in the file DACL. // Look at all WRITE GRANTs. Make sure the trustee Sid is one of the approved Sid // for(crt = 0; crt < aclInfo.AceCount; ++crt) { ACE_HEADER* aceHdr = NULL; if (!GetAce(pDacl, crt, &aceHdr)) { dwError = GetLastError(); CHECK_SVC_STATUS_DONE(dwError, L"GetAce"); } if (ACCESS_ALLOWED_ACE_TYPE == aceHdr->AceType) { ACCESS_ALLOWED_ACE* pAce = (ACCESS_ALLOWED_ACE*) aceHdr; if (WinMasks[WIN_WRITE] & pAce->Mask) { if (!IsSidInList((PSID) &pAce->SidStart, cAllowedSids, allowedSids)) { dwError = ERROR_BAD_CONFIGURATION; CHECK_SVC_STATUS_DONE(dwError, L"!validSidFound"); } } } } done: if (pSd) LocalFree(pSd); if (allowedSids) { while (cAllowedSids) { LocalFree(allowedSids[cAllowedSids--]); } LocalFree(allowedSids); } return dwError; } //---------------------------------------------------------------------------- // Function: InitJobName // // Description: // Loads the job name to be used for created processes // DWORD InitJobName() { DWORD dwError = ERROR_SUCCESS; size_t len = 0; LPCWSTR value = NULL; int crt = 0; // Services can be restarted if (gJobName) LocalFree(gJobName); gJobName = NULL; dwError = GetConfigValue( wsceConfigRelativePath, NM_WSCE_JOB_NAME, &len, &value); CHECK_SVC_STATUS_DONE(dwError, L"GetConfigValue"); if (len) { gJobName = value; } done: return dwError; } //---------------------------------------------------------------------------- // Function: InitLocalDirs // // Description: // Loads the configured local dirs // DWORD InitLocalDirs() { DWORD dwError = ERROR_SUCCESS; size_t len = 0; LPCWSTR value = NULL; size_t crt = 0; dwError = GetConfigValue( wsceConfigRelativePath, NM_WSCE_LOCAL_DIRS, &len, &value); CHECK_SVC_STATUS_DONE(dwError, L"GetConfigValue"); if (0 == len) { dwError = ERROR_BAD_CONFIGURATION; CHECK_SVC_STATUS_DONE(dwError, NM_WSCE_LOCAL_DIRS); } dwError = SplitStringIgnoreSpaceW(len, value, L',', &gLocalDirsCount, &gLocalDirs); CHECK_SVC_STATUS_DONE(dwError, L"SplitStringIgnoreSpaceW"); if (0 == gLocalDirsCount) { dwError = ERROR_BAD_CONFIGURATION; CHECK_SVC_STATUS_DONE(dwError, NM_WSCE_LOCAL_DIRS); } gCchLocalDir = (int*) LocalAlloc(LPTR, sizeof(int) * gLocalDirsCount); if (NULL == gCchLocalDir) { dwError = ERROR_OUTOFMEMORY; CHECK_SVC_STATUS_DONE(dwError, L"LocalAlloc"); } for (crt = 0; crt < gLocalDirsCount; ++crt) { gCchLocalDir[crt] = (int) wcsnlen(gLocalDirs[crt], MAX_PATH); } done: if (value) LocalFree(value); return dwError; } //---------------------------------------------------------------------------- // Function: ValidateLocalPath // // Description: // Validates that a path is within the contained local dirs // DWORD ValidateLocalPath(LPCWSTR lpszPath) { DWORD dwError = ERROR_SUCCESS; int compareResult = 0; unsigned int crt = 0; int cchLocalBuffer = 0; WCHAR localBuffer[MAX_PATH+1]; BOOLEAN nullFound = FALSE; // Make a copy of the path and replace / with \ in the process while(crt < MAX_PATH && !nullFound) { switch(lpszPath[crt]) { case L'/': localBuffer[crt] = L'\\'; ++crt; break; case L'\0': // NULL terminator nullFound = TRUE; break; default: localBuffer[crt] = lpszPath[crt]; ++crt; break; } } if (FALSE == nullFound) { dwError = ERROR_BUFFER_OVERFLOW; CHECK_SVC_STATUS_DONE(dwError, L"localBuffer"); } localBuffer[crt] = 0; cchLocalBuffer = crt; for(crt = 0; crt < gLocalDirsCount; ++crt) { // use max len gCchLocalDir[crt] to see if it starts with this local dir compareResult = CompareStringEx( LOCALE_NAME_INVARIANT, NORM_IGNORECASE, localBuffer, gCchLocalDir[crt] <= cchLocalBuffer ? gCchLocalDir[crt] : cchLocalBuffer, gLocalDirs[crt], gCchLocalDir[crt], NULL, // lpVersionInformation NULL, // lpReserved NULL); // lParam if (0 == compareResult) { dwError = GetLastError(); CHECK_SVC_STATUS_DONE(dwError, L"CompareStringEx"); } if (CSTR_EQUAL == compareResult) { break; } } if (CSTR_EQUAL != compareResult) { LogDebugMessage(L"ValidateLocalPath bad path: %s\n", lpszPath); dwError = ERROR_BAD_PATHNAME; } done: return dwError; } //---------------------------------------------------------------------------- // Function: RunService // // Description: // Registers with NT SCM and starts the service // // Returns: // ERROR_SUCCESS: On success // Error code otherwise: otherwise DWORD RunService(__in int argc, __in_ecount(argc) wchar_t *argv[]) { DWORD dwError= ERROR_SUCCESS; int argStart = 1; static const SERVICE_TABLE_ENTRY serviceTable[] = { { SVCNAME, (LPSERVICE_MAIN_FUNCTION) SvcMain }, { NULL, NULL } }; ghEventLog = RegisterEventSource(NULL, SVCNAME); if (NULL == ghEventLog) { dwError = GetLastError(); CHECK_SVC_STATUS_DONE(dwError, L"RegisterEventSource") } if (!StartServiceCtrlDispatcher(serviceTable)) { dwError = GetLastError(); CHECK_SVC_STATUS_DONE(dwError, L"StartServiceCtrlDispatcher") } done: return dwError; } //---------------------------------------------------------------------------- // Function: SvcMain // // Description: // Service main entry point. // VOID WINAPI SvcMain() { DWORD dwError = ERROR_SUCCESS; gSvcStatusHandle = RegisterServiceCtrlHandler( SVCNAME, SvcCtrlHandler); if( !gSvcStatusHandle ) { dwError = GetLastError(); CHECK_SVC_STATUS_DONE(dwError, L"RegisterServiceCtrlHandler") } // These SERVICE_STATUS members remain as set here gSvcStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS; gSvcStatus.dwServiceSpecificExitCode = 0; // Report initial status to the SCM ReportSvcStatus( SERVICE_START_PENDING, NO_ERROR, 3000 ); // Perform service-specific initialization and work. dwError = SvcInit(); done: return; } //---------------------------------------------------------------------------- // Function: SvcInit // // Description: // Initializes the service. // DWORD SvcInit() { DWORD dwError = ERROR_SUCCESS; dwError = EnableImpersonatePrivileges(); if( dwError != ERROR_SUCCESS ) { ReportErrorCode(L"EnableImpersonatePrivileges", dwError); goto done; } // The recommended way to shutdown the service is to use an event // and attach a callback with RegisterWaitForSingleObject // ghSvcStopEvent = CreateEvent( NULL, // default security attributes TRUE, // manual reset event FALSE, // not signaled NULL); // no name if ( ghSvcStopEvent == NULL) { dwError = GetLastError(); ReportSvcCheckError(EVENTLOG_ERROR_TYPE, SERVICE_CATEGORY, dwError, L"CreateEvent"); ReportSvcStatus( SERVICE_STOPPED, dwError, 0 ); goto done; } if (!RegisterWaitForSingleObject (&ghWaitObject, ghSvcStopEvent, SvcShutdown, NULL, INFINITE, WT_EXECUTEONLYONCE)) { dwError = GetLastError(); ReportSvcCheckError(EVENTLOG_ERROR_TYPE, SERVICE_CATEGORY, dwError, L"RegisterWaitForSingleObject"); CloseHandle(ghSvcStopEvent); ReportSvcStatus( SERVICE_STOPPED, dwError, 0 ); goto done; } dwError = ValidateConfigurationFile(); if (ERROR_SUCCESS != dwError) { LogDebugMessage(L"ValidateConfigurationFile failed: %d", dwError); SvcError(dwError); goto done; } dwError = AuthInit(); if (ERROR_SUCCESS != dwError) { LogDebugMessage(L"AuthInit failed: %d", dwError); SvcError(dwError); goto done; } dwError = InitLocalDirs(); if (ERROR_SUCCESS != dwError) { LogDebugMessage(L"InitLocalDirs failed: %d", dwError); SvcError(dwError); goto done; } dwError = InitJobName(); if (ERROR_SUCCESS != dwError) { LogDebugMessage(L"InitJobName failed: %d", dwError); SvcError(dwError); goto done; } // Report running status when initialization is complete. ReportSvcStatus( SERVICE_RUNNING, NO_ERROR, 0 ); dwError = RpcInit(); done: return dwError; } //---------------------------------------------------------------------------- // Function: RpcAuthorizeCallback // // Description: // RPC Authorization callback. // // Returns: // RPC_S_OK for access authorized // RPC_S_ACCESS_DENIED for access denied // RPC_STATUS CALLBACK RpcAuthorizeCallback ( RPC_IF_HANDLE hInterface, void* pContext) { RPC_STATUS status, unwindStatus, authStatus = RPC_S_ACCESS_DENIED; DWORD dwError; LUID luidReserved2; AUTHZ_ACCESS_REQUEST request; AUTHZ_ACCESS_REPLY reply; AUTHZ_CLIENT_CONTEXT_HANDLE hClientContext = NULL; DWORD authError = ERROR_SUCCESS; DWORD saclResult = 0; ACCESS_MASK grantedMask = 0; ZeroMemory(&luidReserved2, sizeof(luidReserved2)); ZeroMemory(&request, sizeof(request)); ZeroMemory(&reply, sizeof(reply)); status = RpcGetAuthorizationContextForClient(NULL, FALSE, // ImpersonateOnReturn NULL, // Reserved1 NULL, // pExpirationTime luidReserved2, // Reserved2 0, // Reserved3 NULL, // Reserved4 &hClientContext); CHECK_RPC_STATUS_DONE(status, L"RpcGetAuthorizationContextForClient"); request.DesiredAccess = MAXIMUM_ALLOWED; reply.Error = &authError; reply.SaclEvaluationResults = &saclResult; reply.ResultListLength = 1; reply.GrantedAccessMask = &grantedMask; if (!AuthzAccessCheck( 0, hClientContext, &request, NULL, // AuditEvent pAllowedSD, NULL, // OptionalSecurityDescriptorArray 0, // OptionalSecurityDescriptorCount &reply, NULL // phAccessCheckResults )) { dwError = GetLastError(); CHECK_SVC_STATUS_DONE(dwError, L"AuthzAccessCheck"); } LogDebugMessage(L"AutzAccessCheck: Error:%d sacl:%d access:%d\n", authError, saclResult, grantedMask); if (authError == ERROR_SUCCESS && (grantedMask & SERVICE_ACCESS_MASK)) { authStatus = RPC_S_OK; } done: if (NULL != hClientContext) CHECK_UNWIND_RPC(RpcFreeAuthorizationContext(&hClientContext)); return authStatus; } //---------------------------------------------------------------------------- // Function: AuthInit // // Description: // Initializes the authorization structures (security descriptor). // // Notes: // This is called from RunService solely for debugging purposed // so that it can be tested by wimply running winutil service from CLI (no SCM) // DWORD AuthInit() { DWORD dwError = ERROR_SUCCESS; int count = 0; int crt = 0; size_t len = 0; LPCWSTR value = NULL; WCHAR** tokens = NULL; LPWSTR lpszSD = NULL; ULONG cchSD = 0; DWORD dwBufferSize = 0; int allowedCount = 0; PSID* allowedSids = NULL; dwError = GetConfigValue( wsceConfigRelativePath, NM_WSCE_ALLOWED, &len, &value); CHECK_SVC_STATUS_DONE(dwError, L"GetConfigValue"); if (0 == len) { dwError = ERROR_BAD_CONFIGURATION; CHECK_SVC_STATUS_DONE(dwError, NM_WSCE_ALLOWED); } dwError = SplitStringIgnoreSpaceW(len, value, L',', &count, &tokens); CHECK_SVC_STATUS_DONE(dwError, L"SplitStringIgnoreSpaceW"); allowedSids = (PSID*) LocalAlloc(LPTR, sizeof(PSID) * count); if (NULL == allowedSids) { dwError = ERROR_OUTOFMEMORY; CHECK_SVC_STATUS_DONE(dwError, L"LocalAlloc"); } for (crt = 0; crt < count; ++crt) { dwError = GetSidFromAcctNameW(tokens[crt], &allowedSids[crt]); CHECK_SVC_STATUS_DONE(dwError, L"GetSidFromAcctNameW"); } allowedCount = count; dwError = BuildServiceSecurityDescriptor(SERVICE_ACCESS_MASK, allowedCount, allowedSids, 0, NULL, NULL, &pAllowedSD); CHECK_SVC_STATUS_DONE(dwError, L"BuildServiceSecurityDescriptor"); done: if (lpszSD) LocalFree(lpszSD); if (value) LocalFree(value); if (tokens) LocalFree(tokens); return dwError; } //---------------------------------------------------------------------------- // Function: RpcInit // // Description: // Initializes the RPC infrastructure and starts the RPC listenner. // DWORD RpcInit() { RPC_STATUS status; DWORD dwError; status = RpcServerUseProtseqIf(SVCBINDING, RPC_C_LISTEN_MAX_CALLS_DEFAULT, HadoopWinutilSvc_v1_0_s_ifspec, NULL); if (RPC_S_OK != status) { ReportSvcCheckError(EVENTLOG_ERROR_TYPE, SERVICE_CATEGORY, status, L"RpcServerUseProtseqIf"); SvcError(status); dwError = status; goto done; } status = RpcServerRegisterIfEx(HadoopWinutilSvc_v1_0_s_ifspec, NULL, // MgrTypeUuid NULL, // MgrEpv RPC_IF_ALLOW_LOCAL_ONLY, // Flags RPC_C_LISTEN_MAX_CALLS_DEFAULT, // Max calls RpcAuthorizeCallback); // Auth callback if (RPC_S_OK != status) { ReportSvcCheckError(EVENTLOG_ERROR_TYPE, SERVICE_CATEGORY, status, L"RpcServerRegisterIfEx"); SvcError(status); dwError = status; goto done; } status = RpcServerListen(1, RPC_C_LISTEN_MAX_CALLS_DEFAULT, TRUE); if (RPC_S_ALREADY_LISTENING == status) { ReportSvcCheckError(EVENTLOG_WARNING_TYPE, SERVICE_CATEGORY, status, L"RpcServerListen"); } else if (RPC_S_OK != status) { ReportSvcCheckError(EVENTLOG_ERROR_TYPE, SERVICE_CATEGORY, status, L"RpcServerListen"); SvcError(status); dwError = status; goto done; } isListenning = TRUE; ReportSvcMessage(EVENTLOG_INFORMATION_TYPE, SERVICE_CATEGORY, MSG_RPC_SERVICE_HAS_STARTED); done: return dwError; } //---------------------------------------------------------------------------- // Function: RpcStop // // Description: // Tears down the RPC infrastructure and stops the RPC listenner. // VOID RpcStop() { RPC_STATUS status; if (isListenning) { status = RpcMgmtStopServerListening(NULL); isListenning = FALSE; if (RPC_S_OK != status) { ReportSvcCheckError(EVENTLOG_WARNING_TYPE, SERVICE_CATEGORY, status, L"RpcMgmtStopServerListening"); } ReportSvcMessage(EVENTLOG_INFORMATION_TYPE, SERVICE_CATEGORY, MSG_RPC_SERVICE_HAS_STOPPED); } } //---------------------------------------------------------------------------- // Function: CleanupHandles // // Description: // Cleans up the global service handles. // VOID CleanupHandles() { if (INVALID_HANDLE_VALUE != ghWaitObject) { UnregisterWait(ghWaitObject); ghWaitObject = INVALID_HANDLE_VALUE; } if (INVALID_HANDLE_VALUE != ghSvcStopEvent) { CloseHandle(ghSvcStopEvent); ghSvcStopEvent = INVALID_HANDLE_VALUE; } if (INVALID_HANDLE_VALUE != ghEventLog) { DeregisterEventSource(ghEventLog); ghEventLog = INVALID_HANDLE_VALUE; } } //---------------------------------------------------------------------------- // Function: SvcError // // Description: // Aborts the startup sequence. Reports error, stops RPC, cleans up globals. // VOID SvcError(DWORD dwError) { RpcStop(); CleanupHandles(); ReportSvcStatus( SERVICE_STOPPED, dwError, 0 ); } //---------------------------------------------------------------------------- // Function: SvcShutdown // // Description: // Callback when the shutdown event is signaled. Stops RPC, cleans up globals. // VOID CALLBACK SvcShutdown( _In_ PVOID lpParameter, _In_ BOOLEAN TimerOrWaitFired) { RpcStop(); CleanupHandles(); ReportSvcStatus( SERVICE_STOPPED, NO_ERROR, 0 ); } //---------------------------------------------------------------------------- // Function: SvcCtrlHandler // // Description: // Callback from SCM for for service events (signals). // // Notes: // Shutdown is indirect, we set her the STOP_PENDING state and signal the stop event. // Signaling the event invokes SvcShutdown which completes the shutdown. // This two staged approach allows the SCM handler to complete fast, // not blocking the SCM big fat global lock. // VOID WINAPI SvcCtrlHandler( DWORD dwCtrl ) { // Handle the requested control code. switch(dwCtrl) { case SERVICE_CONTROL_STOP: ReportSvcStatus(SERVICE_STOP_PENDING, NO_ERROR, 0); // Signal the service to stop. SetEvent(ghSvcStopEvent); return; default: break; } } //---------------------------------------------------------------------------- // Function: ReportSvcStatus // // Description: // Updates the service status with the SCM. // VOID ReportSvcStatus( DWORD dwCurrentState, DWORD dwWin32ExitCode, DWORD dwWaitHint) { static DWORD dwCheckPoint = 1; DWORD dwError; // Fill in the SERVICE_STATUS structure. gSvcStatus.dwCurrentState = dwCurrentState; gSvcStatus.dwWin32ExitCode = dwWin32ExitCode; gSvcStatus.dwWaitHint = dwWaitHint; if (dwCurrentState == SERVICE_START_PENDING) gSvcStatus.dwControlsAccepted = 0; else gSvcStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP; if ( (dwCurrentState == SERVICE_RUNNING) || (dwCurrentState == SERVICE_STOPPED) ) gSvcStatus.dwCheckPoint = 0; else gSvcStatus.dwCheckPoint = dwCheckPoint++; // Report the status of the service to the SCM. if (!SetServiceStatus( gSvcStatusHandle, &gSvcStatus)) { dwError = GetLastError(); ReportSvcCheckError(EVENTLOG_WARNING_TYPE, SERVICE_CATEGORY, dwError, L"SetServiceStatus"); }; } //---------------------------------------------------------------------------- // Function: WinutilsCreateProcessAsUser // // Description: // The RPC midl declared function implementation // // Returns: // ERROR_SUCCESS: On success // Error code otherwise: otherwise // // Notes: // This is the entry point when the NodeManager does the RPC call // Note that the RPC call does not do any S4U work. Is simply spawns (suspended) wintutils // using the right command line and the handles over the spwaned process to the NM // The actual S4U work occurs in the spawned process, run and monitored by the NM // error_status_t WinutilsCreateProcessAsUser( /* [in] */ handle_t IDL_handle, /* [in] */ int nmPid, /* [in] */ CREATE_PROCESS_REQUEST *request, /* [out] */ CREATE_PROCESS_RESPONSE **response) { DWORD dwError = ERROR_SUCCESS; LPCWSTR inserts[] = {request->cwd, request->jobName, request->user, request->pidFile, request->cmdLine, NULL}; WCHAR winutilsPath[MAX_PATH]; WCHAR fullCmdLine[32768]; HANDLE taskStdInRd = INVALID_HANDLE_VALUE, taskStdInWr = INVALID_HANDLE_VALUE, taskStdOutRd = INVALID_HANDLE_VALUE, taskStdOutWr = INVALID_HANDLE_VALUE, taskStdErrRd = INVALID_HANDLE_VALUE, taskStdErrWr = INVALID_HANDLE_VALUE, hNmProcess = INVALID_HANDLE_VALUE, hDuplicateProcess = INVALID_HANDLE_VALUE, hDuplicateThread = INVALID_HANDLE_VALUE, hDuplicateStdIn = INVALID_HANDLE_VALUE, hDuplicateStdOut = INVALID_HANDLE_VALUE, hDuplicateStdErr = INVALID_HANDLE_VALUE, hSelfProcess = INVALID_HANDLE_VALUE, hJob = INVALID_HANDLE_VALUE; BOOL fMustCleanupProcess = FALSE; HRESULT hr; STARTUPINFO si; PROCESS_INFORMATION pi; SECURITY_ATTRIBUTES saTaskStdInOutErr; ZeroMemory( &si, sizeof(si) ); si.cb = sizeof(si); ZeroMemory( &pi, sizeof(pi) ); pi.hProcess = INVALID_HANDLE_VALUE; pi.hThread = INVALID_HANDLE_VALUE; ZeroMemory( &saTaskStdInOutErr, sizeof(saTaskStdInOutErr)); if (gJobName) { hJob = OpenJobObject(JOB_OBJECT_ASSIGN_PROCESS, FALSE, gJobName); if (!hJob) { dwError = GetLastError(); ReportSvcCheckError(EVENTLOG_ERROR_TYPE, SERVICE_CATEGORY, dwError, L"OpenJobObject"); goto done; } } // NB: GetCurrentProcess returns a pseudo-handle that just so happens // has the value -1, ie. INVALID_HANDLE_VALUE. It cannot fail. // hSelfProcess = GetCurrentProcess(); hNmProcess = OpenProcess(PROCESS_DUP_HANDLE, FALSE, nmPid); if (NULL == hNmProcess) { dwError = GetLastError(); ReportSvcCheckError(EVENTLOG_ERROR_TYPE, SERVICE_CATEGORY, dwError, L"OpenProcess"); goto done; } GetModuleFileName(NULL, winutilsPath, sizeof(winutilsPath)/sizeof(WCHAR)); dwError = GetLastError(); // Always check after GetModuleFileName for ERROR_INSSUFICIENT_BUFFER if (dwError) { ReportSvcCheckError(EVENTLOG_ERROR_TYPE, SERVICE_CATEGORY, dwError, L"GetModuleFileName"); goto done; } // NB. We can call CreateProcess("wintuls","task create ...") or we can call // CreateProcess(NULL, "winutils task create"). Only the second form passes "task" as // argv[1], as expected by main. First form passes "task" as argv[0] and main fails. hr = StringCbPrintf(fullCmdLine, sizeof(fullCmdLine), L"\"%s\" task createAsUser %ls %ls %ls %ls", winutilsPath, request->jobName, request->user, request->pidFile, request->cmdLine); if (FAILED(hr)) { ReportSvcCheckError(EVENTLOG_ERROR_TYPE, SERVICE_CATEGORY, hr, L"StringCbPrintf:fullCmdLine"); goto done; } LogDebugMessage(L"[%ls]: %ls %ls\n", request->cwd, winutilsPath, fullCmdLine); // stdin/stdout/stderr redirection is handled here // We create 3 anonymous named pipes. // Security attributes are required so that the handles can be inherited. // We assign one end of the pipe to the process (stdin gets a read end, stdout gets a write end) // We then duplicate the other end in the NM process, and we close our own handle // Finally we return the duplicate handle values to the NM // The NM will attach Java file dscriptors to the duplicated handles and // read/write them as ordinary Java InputStream/OutputStream objects si.dwFlags |= STARTF_USESTDHANDLES; saTaskStdInOutErr.nLength = sizeof(SECURITY_ATTRIBUTES); saTaskStdInOutErr.bInheritHandle = TRUE; saTaskStdInOutErr.lpSecurityDescriptor = NULL; if (!CreatePipe(&taskStdInRd, &taskStdInWr, &saTaskStdInOutErr, 0)) { dwError = GetLastError(); goto done; } if (!SetHandleInformation(taskStdInWr, HANDLE_FLAG_INHERIT, FALSE)) { dwError = GetLastError(); goto done; } si.hStdInput = taskStdInRd; if (!CreatePipe(&taskStdOutRd, &taskStdOutWr, &saTaskStdInOutErr, 0)) { dwError = GetLastError(); goto done; } if (!SetHandleInformation(taskStdOutRd, HANDLE_FLAG_INHERIT, FALSE)) { dwError = GetLastError(); goto done; } si.hStdOutput = taskStdOutWr; if (!CreatePipe(&taskStdErrRd, &taskStdErrWr, &saTaskStdInOutErr, 0)) { dwError = GetLastError(); goto done; } if (!SetHandleInformation(taskStdErrRd, HANDLE_FLAG_INHERIT, FALSE)) { dwError = GetLastError(); goto done; } si.hStdError = taskStdErrWr; if (!CreateProcess( NULL, // lpApplicationName, fullCmdLine, // lpCommandLine, NULL, // lpProcessAttributes, NULL, // lpThreadAttributes, TRUE, // bInheritHandles, CREATE_SUSPENDED, // dwCreationFlags, NULL, // lpEnvironment, request->cwd, // lpCurrentDirectory, &si, // lpStartupInfo &pi)) { // lpProcessInformation dwError = GetLastError(); ReportSvcCheckError(EVENTLOG_ERROR_TYPE, SERVICE_CATEGORY, dwError, L"CreateProcess"); goto done; } fMustCleanupProcess = TRUE; LogDebugMessage(L"CreateProcess: pid:%x\n", pi.dwProcessId); if (INVALID_HANDLE_VALUE != hJob) { if (!AssignProcessToJobObject(hJob, pi.hProcess)) { dwError = GetLastError(); goto done; } } // Grant full access to the container user on the 'winutils task createAsUser ...' helper process dwError = AddNodeManagerAndUserACEsToObject(pi.hProcess, request->user, PROCESS_ALL_ACCESS); if (dwError) { LogDebugMessage(L"failed: AddNodeManagerAndUserACEsToObject\n"); goto done; } if (!DuplicateHandle(hSelfProcess, pi.hProcess, hNmProcess, &hDuplicateProcess, 0, FALSE, DUPLICATE_SAME_ACCESS)) { dwError = GetLastError(); LogDebugMessage(L"failed: pi.hProcess\n"); goto done; } if (!DuplicateHandle(hSelfProcess, pi.hThread, hNmProcess, &hDuplicateThread, 0, FALSE, DUPLICATE_SAME_ACCESS)) { dwError = GetLastError(); LogDebugMessage(L"failed: pi.hThread\n"); goto done; } if (!DuplicateHandle(hSelfProcess, taskStdInWr, hNmProcess, &hDuplicateStdIn, 0, FALSE, DUPLICATE_SAME_ACCESS)) { dwError = GetLastError(); LogDebugMessage(L"failed: taskStdInWr\n"); goto done; } if (!DuplicateHandle(hSelfProcess, taskStdOutRd, hNmProcess, &hDuplicateStdOut, 0, FALSE, DUPLICATE_SAME_ACCESS)) { dwError = GetLastError(); LogDebugMessage(L"failed: taskStdOutRd\n"); goto done; } if (!DuplicateHandle(hSelfProcess, taskStdErrRd, hNmProcess, &hDuplicateStdErr, 0, FALSE, DUPLICATE_SAME_ACCESS)) { dwError = GetLastError(); LogDebugMessage(L"failed: taskStdErrRd\n"); goto done; } *response = (CREATE_PROCESS_RESPONSE*) MIDL_user_allocate(sizeof(CREATE_PROCESS_RESPONSE)); if (NULL == *response) { dwError = ERROR_OUTOFMEMORY; LogDebugMessage(L"Failed to allocate CREATE_PROCESS_RESPONSE* response\n"); goto done; } // We're now transfering ownership of the duplicated handles to the caller // If the RPC call fails *after* this point the handles are leaked inside the NM process // Note that there are no more API calls, only assignments. A failure could occur only if // foced (process kill) or hardware error (faulty memory, processort bit flip etc). (*response)->hProcess = hDuplicateProcess; (*response)->hThread = hDuplicateThread; (*response)->hStdIn = hDuplicateStdIn; (*response)->hStdOut = hDuplicateStdOut; (*response)->hStdErr = hDuplicateStdErr; fMustCleanupProcess = FALSE; done: if (fMustCleanupProcess) { LogDebugMessage(L"Cleaning process: %d due to error:%d\n", pi.dwProcessId, dwError); TerminateProcess(pi.hProcess, EXIT_FAILURE); // cleanup the duplicate handles inside the NM. if (INVALID_HANDLE_VALUE != hDuplicateProcess) { DuplicateHandle(hNmProcess, hDuplicateProcess, NULL, NULL, 0, FALSE, DUPLICATE_CLOSE_SOURCE); } if (INVALID_HANDLE_VALUE != hDuplicateThread) { DuplicateHandle(hNmProcess, hDuplicateThread, NULL, NULL, 0, FALSE, DUPLICATE_CLOSE_SOURCE); } if (INVALID_HANDLE_VALUE != hDuplicateStdIn) { DuplicateHandle(hNmProcess, hDuplicateStdIn, NULL, NULL, 0, FALSE, DUPLICATE_CLOSE_SOURCE); } if (INVALID_HANDLE_VALUE != hDuplicateStdOut) { DuplicateHandle(hNmProcess, hDuplicateStdOut, NULL, NULL, 0, FALSE, DUPLICATE_CLOSE_SOURCE); } if (INVALID_HANDLE_VALUE != hDuplicateStdErr) { DuplicateHandle(hNmProcess, hDuplicateStdErr, NULL, NULL, 0, FALSE, DUPLICATE_CLOSE_SOURCE); } } if (INVALID_HANDLE_VALUE != hSelfProcess) CloseHandle(hSelfProcess); if (INVALID_HANDLE_VALUE != hNmProcess) CloseHandle(hNmProcess); if (INVALID_HANDLE_VALUE != taskStdInRd) CloseHandle(taskStdInRd); if (INVALID_HANDLE_VALUE != taskStdInWr) CloseHandle(taskStdInWr); if (INVALID_HANDLE_VALUE != taskStdOutRd) CloseHandle(taskStdOutRd); if (INVALID_HANDLE_VALUE != taskStdOutWr) CloseHandle(taskStdOutWr); if (INVALID_HANDLE_VALUE != taskStdErrRd) CloseHandle(taskStdErrRd); if (INVALID_HANDLE_VALUE != taskStdErrWr) CloseHandle(taskStdErrWr); // This is closing our own process/thread handles. // If the transfer was succesfull the NM has its own duplicates (if any) if (INVALID_HANDLE_VALUE != pi.hThread) CloseHandle(pi.hThread); if (INVALID_HANDLE_VALUE != pi.hProcess) CloseHandle(pi.hProcess); if (hJob) CloseHandle(hJob); return dwError; } error_status_t WinutilsCreateFile( /* [in] */ handle_t IDL_handle, /* [in] */ int nm_pid, /* [in] */ CREATEFILE_REQUEST *request, /* [out] */ CREATEFILE_RESPONSE **response) { DWORD dwError = ERROR_SUCCESS; HANDLE hNmProcess = INVALID_HANDLE_VALUE, hFile = INVALID_HANDLE_VALUE, hDuplicateFile = INVALID_HANDLE_VALUE, hSelfProcess = GetCurrentProcess(); SECURITY_ATTRIBUTES saFile; ZeroMemory( &saFile, sizeof(saFile)); dwError = ValidateLocalPath(request->path); CHECK_SVC_STATUS_DONE(dwError,L"ValidateLocalPath request->path"); saFile.nLength = sizeof(SECURITY_ATTRIBUTES); saFile.bInheritHandle = TRUE; saFile.lpSecurityDescriptor = NULL; hFile = CreateFile( request->path, request->desiredAccess, request->shareMode, &saFile, request->creationDisposition, request->flags, NULL); // hTemplate if (INVALID_HANDLE_VALUE == hFile) { dwError = GetLastError(); goto done; } hNmProcess = OpenProcess(PROCESS_DUP_HANDLE, FALSE, nm_pid); if (NULL == hNmProcess) { dwError = GetLastError(); goto done; } if (!DuplicateHandle(hSelfProcess, hFile, hNmProcess, &hDuplicateFile, 0, FALSE, DUPLICATE_SAME_ACCESS)) { dwError = GetLastError(); goto done; } *response = (CREATEFILE_RESPONSE*) MIDL_user_allocate(sizeof(CREATEFILE_RESPONSE)); if (NULL == *response) { dwError = ERROR_OUTOFMEMORY; goto done; } (*response)->hFile = hDuplicateFile; hDuplicateFile = INVALID_HANDLE_VALUE; done: if (INVALID_HANDLE_VALUE != hFile) CloseHandle(hFile); if (INVALID_HANDLE_VALUE != hDuplicateFile) { DuplicateHandle(hNmProcess, hDuplicateFile, NULL, NULL, 0, FALSE, DUPLICATE_CLOSE_SOURCE); } if (INVALID_HANDLE_VALUE != hNmProcess) CloseHandle(hNmProcess); LogDebugMessage(L"WinutilsCreateFile: %s %d, %d, %d, %d: %d", request->path, request->desiredAccess, request->shareMode, request->creationDisposition, request->flags, dwError); return dwError; } error_status_t WinutilsKillTask( /* [in] */ handle_t IDL_handle, /* [in] */ KILLTASK_REQUEST *request) { DWORD dwError = ERROR_SUCCESS; HRESULT hr; WCHAR bufferName[MAX_PATH]; dwError = GetSecureJobObjectName(request->taskName, MAX_PATH, bufferName); CHECK_SVC_STATUS_DONE(dwError, L"GetSecureJobObjectName"); dwError = KillTask(bufferName); if (ERROR_ACCESS_DENIED == dwError) { // This process runs as LocalSystem with debug privilege enabled // The job has a security descriptor that explictly grants JOB_OBJECT_ALL_ACCESS to us // If we get ACCESS DENIED it means the job is being unwound dwError = ERROR_SUCCESS; } done: LogDebugMessage(L"WinutilsKillTask: %s :%d\n", bufferName, dwError); return dwError; } error_status_t WinutilsDeletePath( /* [in] */ handle_t IDL_handle, /* [in] */ DELETEPATH_REQUEST *request, /* [out] */ DELETEPATH_RESPONSE **response) { DWORD dwError = ERROR_SUCCESS; BOOL deleted = FALSE; dwError = ValidateLocalPath(request->path); CHECK_SVC_STATUS_DONE(dwError,L"ValidateLocalPath request->path"); switch(request->type) { case PATH_IS_DIR: deleted = RemoveDirectory(request->path); if (!deleted) { LogDebugMessage(L"Error %d deleting directory %s\n", GetLastError(), request->path); } break; case PATH_IS_FILE: deleted = DeleteFile(request->path); if (!deleted) { LogDebugMessage(L"Error %d deleting file %s\n", GetLastError(), request->path); } break; default: dwError = ERROR_BAD_ARGUMENTS; CHECK_SVC_STATUS_DONE(dwError, L"request->operation"); } *response = (DELETEPATH_RESPONSE*) MIDL_user_allocate(sizeof(DELETEPATH_RESPONSE)); if (NULL == *response) { dwError = ERROR_OUTOFMEMORY; CHECK_SVC_STATUS_DONE(dwError, L"MIDL_user_allocate"); } (*response)->deleted = deleted; done: LogDebugMessage(L"WinutilsDeletePath: %s %d: %d %d", request->path, request->type, deleted, dwError); return dwError; } error_status_t WinutilsMkDir( /* [in] */ handle_t IDL_handle, /* [in] */ MKDIR_REQUEST *request) { DWORD dwError = ERROR_SUCCESS; dwError = ValidateLocalPath(request->filePath); CHECK_SVC_STATUS_DONE(dwError,L"ValidateLocalPath request->filePath"); if (!CreateDirectory(request->filePath, NULL)) { dwError = GetLastError(); CHECK_SVC_STATUS_DONE(dwError, L"CreateDirectory"); } done: LogDebugMessage(L"WinutilsMkDir: %s :%d\n", request->filePath, dwError); return dwError; } error_status_t WinutilsChown( /* [in] */ handle_t IDL_handle, /* [in] */ CHOWN_REQUEST *request) { DWORD dwError = ERROR_SUCCESS; dwError = ValidateLocalPath(request->filePath); CHECK_SVC_STATUS_DONE(dwError,L"ValidateLocalPath request->filePath"); dwError = ChownImpl(request->ownerName, request->groupName, request->filePath); CHECK_SVC_STATUS_DONE(dwError, L"ChownImpl"); done: LogDebugMessage(L"WinutilsChown: %s %s %s :%d\n", request->ownerName, request->groupName, request->filePath, dwError); return dwError; } error_status_t WinutilsChmod( /* [in] */ handle_t IDL_handle, /* [in] */ CHMOD_REQUEST *request) { DWORD dwError = ERROR_SUCCESS; dwError = ValidateLocalPath(request->filePath); CHECK_SVC_STATUS_DONE(dwError,L"ValidateLocalPath request->filePath"); dwError = ChangeFileModeByMask(request->filePath, request->mode); CHECK_SVC_STATUS_DONE(dwError, L"ChangeFileModeByMask"); done: LogDebugMessage(L"WinutilsChmod: %s %o :%d\n", request->filePath, request->mode, dwError); return dwError; } error_status_t WinutilsMoveFile( /* [in] */ handle_t IDL_handle, /* [in] */ MOVEFILE_REQUEST *request) { DWORD dwError = ERROR_SUCCESS; DWORD flags = 0; dwError = ValidateLocalPath(request->sourcePath); CHECK_SVC_STATUS_DONE(dwError,L"ValidateLocalPath request->sourcePath"); dwError = ValidateLocalPath(request->destinationPath); CHECK_SVC_STATUS_DONE(dwError,L"ValidateLocalPath request->destinationPath"); switch (request->operation) { case MOVE_FILE: flags |= MOVEFILE_COPY_ALLOWED; if (request->replaceExisting) flags |= MOVEFILE_REPLACE_EXISTING; if (!MoveFileEx(request->sourcePath, request->destinationPath, flags)) { dwError = GetLastError(); CHECK_SVC_STATUS_DONE(dwError, L"MoveFileEx"); } break; case COPY_FILE: if (!request->replaceExisting) flags |= COPY_FILE_FAIL_IF_EXISTS; if (!CopyFileEx(request->sourcePath, request->destinationPath, NULL, // lpProgressRoutine NULL, // lpData NULL, // pbCancel flags)) { dwError = GetLastError(); CHECK_SVC_STATUS_DONE(dwError, L"CopyFileEx"); } break; default: dwError = ERROR_BAD_ARGUMENTS; CHECK_SVC_STATUS_DONE(dwError, L"request->operation"); } done: LogDebugMessage(L"WinutilsMoveFile: %d: %s %s :%d\n", request->operation, request->sourcePath, request->destinationPath, dwError); return dwError; } //---------------------------------------------------------------------------- // Function: ServiceUsage // // Description: // Prints the CLI arguments for service command. // void ServiceUsage() { fwprintf(stdout, L"\ Usage: service\n\ Starts the nodemanager Windows Secure Container Executor helper service.\n\ The service must run as a high privileged account (LocalSystem)\n\ and is used by the nodemanager WSCE to spawn secure containers on Windows.\n"); }