/** * 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 #include #include #include #include #ifdef PSAPI_VERSION #undef PSAPI_VERSION #endif #define PSAPI_VERSION 1 #pragma comment(lib, "psapi.lib") #define NM_WSCE_IMPERSONATE_ALLOWED L"yarn.nodemanager.windows-secure-container-executor.impersonate.allowed" #define NM_WSCE_IMPERSONATE_DENIED L"yarn.nodemanager.windows-secure-container-executor.impersonate.denied" // The S4U impersonation access check mask. Arbitrary value (we use 1 for the service access check) #define SERVICE_IMPERSONATE_MASK 0x00000002 // Name for tracking this logon process when registering with LSA static const char *LOGON_PROCESS_NAME="Hadoop Container Executor"; // Name for token source, must be less or eq to TOKEN_SOURCE_LENGTH (currently 8) chars static const char *TOKEN_SOURCE_NAME = "HadoopEx"; // List of different task related command line options supported by // winutils. typedef enum TaskCommandOptionType { TaskInvalid, TaskCreate, TaskCreateAsUser, TaskIsAlive, TaskKill, TaskProcessList } TaskCommandOption; //---------------------------------------------------------------------------- // Function: GetLimit // // Description: // Get the resource limit value in long type given the command line argument. // // Returns: // TRUE: If successfully get the value // FALSE: otherwise static BOOL GetLimit(__in const wchar_t *str, __out long *value) { wchar_t *end = NULL; if (str == NULL || value == NULL) return FALSE; *value = wcstol(str, &end, 10); if (end == NULL || *end != '\0') { *value = -1; return FALSE; } else { return TRUE; } } //---------------------------------------------------------------------------- // Function: ParseCommandLine // // Description: // Parses the given command line. On success, out param 'command' contains // the user specified command. // // Returns: // TRUE: If the command line is valid // FALSE: otherwise static BOOL ParseCommandLine(__in int argc, __in_ecount(argc) wchar_t *argv[], __out TaskCommandOption *command, __out_opt long *memory, __out_opt long *vcore) { *command = TaskInvalid; if (wcscmp(argv[0], L"task") != 0 ) { return FALSE; } if (argc == 3) { if (wcscmp(argv[1], L"isAlive") == 0) { *command = TaskIsAlive; return TRUE; } if (wcscmp(argv[1], L"kill") == 0) { *command = TaskKill; return TRUE; } if (wcscmp(argv[1], L"processList") == 0) { *command = TaskProcessList; return TRUE; } } if (argc >= 4 && argc <= 8) { if (wcscmp(argv[1], L"create") == 0) { int i; for (i = 2; i < argc - 3; i++) { if (wcscmp(argv[i], L"-c") == 0) { if (vcore != NULL && !GetLimit(argv[i + 1], vcore)) { return FALSE; } else { i++; continue; } } else if (wcscmp(argv[i], L"-m") == 0) { if (memory != NULL && !GetLimit(argv[i + 1], memory)) { return FALSE; } else { i++; continue; } } else { break; } } if (argc - i != 2) return FALSE; *command = TaskCreate; return TRUE; } } if (argc >= 6) { if (wcscmp(argv[1], L"createAsUser") == 0) { *command = TaskCreateAsUser; return TRUE; } } return FALSE; } //---------------------------------------------------------------------------- // Function: BuildImpersonateSecurityDescriptor // // Description: // Builds the security descriptor for the S4U impersonation permissions // This describes what users can be impersonated and what not // // Returns: // ERROR_SUCCESS: On success // GetLastError: otherwise // DWORD BuildImpersonateSecurityDescriptor(__out PSECURITY_DESCRIPTOR* ppSD) { DWORD dwError = ERROR_SUCCESS; size_t countAllowed = 0; PSID* allowedSids = NULL; size_t countDenied = 0; PSID* deniedSids = NULL; LPCWSTR value = NULL; WCHAR** tokens = NULL; size_t len = 0; size_t count = 0; size_t crt = 0; PSECURITY_DESCRIPTOR pSD = NULL; dwError = GetConfigValue(wsceConfigRelativePath, NM_WSCE_IMPERSONATE_ALLOWED, &len, &value); if (dwError) { ReportErrorCode(L"GetConfigValue:1", dwError); goto done; } if (0 == len) { dwError = ERROR_BAD_CONFIGURATION; ReportErrorCode(L"GetConfigValue:2", dwError); goto done; } dwError = SplitStringIgnoreSpaceW(len, value, L',', &count, &tokens); if (dwError) { ReportErrorCode(L"SplitStringIgnoreSpaceW:1", dwError); goto done; } allowedSids = LocalAlloc(LPTR, sizeof(PSID) * count); if (NULL == allowedSids) { dwError = GetLastError(); ReportErrorCode(L"LocalAlloc:1", dwError); goto done; } for(crt = 0; crt < count; ++crt) { dwError = GetSidFromAcctNameW(tokens[crt], &allowedSids[crt]); if (dwError) { ReportErrorCode(L"GetSidFromAcctNameW:1", dwError); goto done; } } countAllowed = count; LocalFree(tokens); tokens = NULL; LocalFree((HLOCAL)value); value = NULL; dwError = GetConfigValue(wsceConfigRelativePath, NM_WSCE_IMPERSONATE_DENIED, &len, &value); if (dwError) { ReportErrorCode(L"GetConfigValue:3", dwError); goto done; } if (0 != len) { dwError = SplitStringIgnoreSpaceW(len, value, L',', &count, &tokens); if (dwError) { ReportErrorCode(L"SplitStringIgnoreSpaceW:2", dwError); goto done; } deniedSids = LocalAlloc(LPTR, sizeof(PSID) * count); if (NULL == allowedSids) { dwError = GetLastError(); ReportErrorCode(L"LocalAlloc:2", dwError); goto done; } for(crt = 0; crt < count; ++crt) { dwError = GetSidFromAcctNameW(tokens[crt], &deniedSids[crt]); if (dwError) { ReportErrorCode(L"GetSidFromAcctNameW:2", dwError); goto done; } } countDenied = count; } dwError = BuildServiceSecurityDescriptor( SERVICE_IMPERSONATE_MASK, countAllowed, allowedSids, countDenied, deniedSids, NULL, &pSD); if (dwError) { ReportErrorCode(L"BuildServiceSecurityDescriptor", dwError); goto done; } *ppSD = pSD; pSD = NULL; done: if (pSD) LocalFree(pSD); if (tokens) LocalFree(tokens); if (allowedSids) LocalFree(allowedSids); if (deniedSids) LocalFree(deniedSids); return dwError; } //---------------------------------------------------------------------------- // Function: AddNodeManagerAndUserACEsToObject // // Description: // Adds ACEs to grant NM and user the provided access mask over a given handle // // Returns: // ERROR_SUCCESS: on success // DWORD AddNodeManagerAndUserACEsToObject( __in HANDLE hObject, __in LPCWSTR user, __in ACCESS_MASK accessMask) { DWORD dwError = ERROR_SUCCESS; size_t countTokens = 0; size_t len = 0; LPCWSTR value = NULL; WCHAR** tokens = NULL; DWORD crt = 0; PACL pDacl = NULL; PSECURITY_DESCRIPTOR psdProcess = NULL; LPWSTR lpszOldDacl = NULL, lpszNewDacl = NULL; ULONG daclLen = 0; PACL pNewDacl = NULL; ACL_SIZE_INFORMATION si; DWORD dwNewAclSize = 0; PACE_HEADER pTempAce = NULL; BYTE sidTemp[SECURITY_MAX_SID_SIZE]; DWORD cbSid = SECURITY_MAX_SID_SIZE; PSID tokenSid = NULL; // These hard-coded SIDs are allways added WELL_KNOWN_SID_TYPE forcesSidTypes[] = { WinLocalSystemSid, WinBuiltinAdministratorsSid}; BOOL logSDs = IsDebuggerPresent(); // Check only once to avoid attach-while-running dwError = GetSecurityInfo(hObject, SE_KERNEL_OBJECT, DACL_SECURITY_INFORMATION, NULL, NULL, &pDacl, NULL, &psdProcess); if (dwError) { ReportErrorCode(L"GetSecurityInfo", dwError); goto done; } // This is debug only output for troubleshooting if (logSDs) { if (!ConvertSecurityDescriptorToStringSecurityDescriptor( psdProcess, SDDL_REVISION_1, DACL_SECURITY_INFORMATION, &lpszOldDacl, &daclLen)) { dwError = GetLastError(); ReportErrorCode(L"ConvertSecurityDescriptorToStringSecurityDescriptor", dwError); goto done; } } ZeroMemory(&si, sizeof(si)); if (!GetAclInformation(pDacl, &si, sizeof(si), AclSizeInformation)) { dwError = GetLastError(); ReportErrorCode(L"GetAclInformation", dwError); goto done; } dwError = GetConfigValue(wsceConfigRelativePath, NM_WSCE_ALLOWED, &len, &value); if (ERROR_SUCCESS != dwError) { ReportErrorCode(L"GetConfigValue", dwError); goto done; } if (0 == len) { dwError = ERROR_BAD_CONFIGURATION; ReportErrorCode(L"GetConfigValue", dwError); goto done; } dwError = SplitStringIgnoreSpaceW(len, value, L',', &countTokens, &tokens); if (ERROR_SUCCESS != dwError) { ReportErrorCode(L"SplitStringIgnoreSpaceW", dwError); goto done; } // We're gonna add 1 ACE for each token found, +1 for user and +1 for each forcesSidTypes[] // ACCESS_ALLOWED_ACE struct contains the first DWORD of the SID // dwNewAclSize = si.AclBytesInUse + (DWORD)(countTokens + 1 + sizeof(forcesSidTypes)/sizeof(forcesSidTypes[0])) * (sizeof(ACCESS_ALLOWED_ACE) + SECURITY_MAX_SID_SIZE - sizeof(DWORD)); pNewDacl = (PSID) LocalAlloc(LPTR, dwNewAclSize); if (!pNewDacl) { dwError = ERROR_OUTOFMEMORY; ReportErrorCode(L"LocalAlloc", dwError); goto done; } if (!InitializeAcl(pNewDacl, dwNewAclSize, ACL_REVISION)) { dwError = ERROR_OUTOFMEMORY; ReportErrorCode(L"InitializeAcl", dwError); goto done; } // Copy over old ACEs for (crt = 0; crt < si.AceCount; ++crt) { if (!GetAce(pDacl, crt, &pTempAce)) { dwError = ERROR_OUTOFMEMORY; ReportErrorCode(L"InitializeAcl", dwError); goto done; } if (!AddAce(pNewDacl, ACL_REVISION, MAXDWORD, pTempAce, pTempAce->AceSize)) { dwError = ERROR_OUTOFMEMORY; ReportErrorCode(L"InitializeAcl", dwError); goto done; } } // Add the configured allowed SIDs for (crt = 0; crt < countTokens; ++crt) { dwError = GetSidFromAcctNameW(tokens[crt], &tokenSid); if (ERROR_SUCCESS != dwError) { ReportErrorCode(L"GetSidFromAcctNameW", dwError); goto done; } if (!AddAccessAllowedAceEx( pNewDacl, ACL_REVISION_DS, CONTAINER_INHERIT_ACE | OBJECT_INHERIT_ACE, PROCESS_ALL_ACCESS, tokenSid)) { dwError = GetLastError(); ReportErrorCode(L"AddAccessAllowedAceEx:1", dwError); goto done; } LocalFree(tokenSid); tokenSid = NULL; } // add the forced SIDs ACE for (crt = 0; crt < sizeof(forcesSidTypes)/sizeof(forcesSidTypes[0]); ++crt) { cbSid = SECURITY_MAX_SID_SIZE; if (!CreateWellKnownSid(forcesSidTypes[crt], NULL, &sidTemp, &cbSid)) { dwError = GetLastError(); ReportErrorCode(L"CreateWellKnownSid", dwError); goto done; } if (!AddAccessAllowedAceEx( pNewDacl, ACL_REVISION_DS, CONTAINER_INHERIT_ACE | OBJECT_INHERIT_ACE, accessMask, (PSID) sidTemp)) { dwError = GetLastError(); ReportErrorCode(L"AddAccessAllowedAceEx:2", dwError); goto done; } } // add the user ACE dwError = GetSidFromAcctNameW(user, &tokenSid); if (ERROR_SUCCESS != dwError) { ReportErrorCode(L"GetSidFromAcctNameW:user", dwError); goto done; } if (!AddAccessAllowedAceEx( pNewDacl, ACL_REVISION_DS, CONTAINER_INHERIT_ACE | OBJECT_INHERIT_ACE, PROCESS_ALL_ACCESS, tokenSid)) { dwError = GetLastError(); ReportErrorCode(L"AddAccessAllowedAceEx:3", dwError); goto done; } LocalFree(tokenSid); tokenSid = NULL; dwError = SetSecurityInfo(hObject, SE_KERNEL_OBJECT, DACL_SECURITY_INFORMATION, NULL, NULL, pNewDacl, NULL); if (dwError) { ReportErrorCode(L"SetSecurityInfo", dwError); goto done; } // This is debug only output for troubleshooting if (logSDs) { dwError = GetSecurityInfo(hObject, SE_KERNEL_OBJECT, DACL_SECURITY_INFORMATION, NULL, NULL, &pDacl, NULL, &psdProcess); if (dwError) { ReportErrorCode(L"GetSecurityInfo:2", dwError); goto done; } if (!ConvertSecurityDescriptorToStringSecurityDescriptor( psdProcess, SDDL_REVISION_1, DACL_SECURITY_INFORMATION, &lpszNewDacl, &daclLen)) { dwError = GetLastError(); ReportErrorCode(L"ConvertSecurityDescriptorToStringSecurityDescriptor:2", dwError); goto done; } LogDebugMessage(L"Old DACL: %ls\nNew DACL: %ls\n", lpszOldDacl, lpszNewDacl); } done: if (tokenSid) LocalFree(tokenSid); if (pNewDacl) LocalFree(pNewDacl); if (lpszOldDacl) LocalFree(lpszOldDacl); if (lpszNewDacl) LocalFree(lpszNewDacl); if (psdProcess) LocalFree(psdProcess); return dwError; } //---------------------------------------------------------------------------- // Function: ValidateImpersonateAccessCheck // // Description: // Performs the access check for S4U impersonation // // Returns: // ERROR_SUCCESS: On success // ERROR_ACCESS_DENIED, GetLastError: otherwise // DWORD ValidateImpersonateAccessCheck(__in HANDLE logonHandle) { DWORD dwError = ERROR_SUCCESS; PSECURITY_DESCRIPTOR pSD = NULL; LUID luidUnused; AUTHZ_ACCESS_REQUEST request; AUTHZ_ACCESS_REPLY reply; DWORD authError = ERROR_SUCCESS; DWORD saclResult = 0; ACCESS_MASK grantedMask = 0; AUTHZ_RESOURCE_MANAGER_HANDLE hManager = NULL; AUTHZ_CLIENT_CONTEXT_HANDLE hAuthzToken = NULL; ZeroMemory(&luidUnused, sizeof(luidUnused)); ZeroMemory(&request, sizeof(request)); ZeroMemory(&reply, sizeof(reply)); dwError = BuildImpersonateSecurityDescriptor(&pSD); if (dwError) { ReportErrorCode(L"BuildImpersonateSecurityDescriptor", dwError); goto done; } request.DesiredAccess = MAXIMUM_ALLOWED; reply.Error = &authError; reply.SaclEvaluationResults = &saclResult; reply.ResultListLength = 1; reply.GrantedAccessMask = &grantedMask; if (!AuthzInitializeResourceManager( AUTHZ_RM_FLAG_NO_AUDIT, NULL, // pfnAccessCheck NULL, // pfnComputeDynamicGroups NULL, // pfnFreeDynamicGroups NULL, // szResourceManagerName &hManager)) { dwError = GetLastError(); ReportErrorCode(L"AuthzInitializeResourceManager", dwError); goto done; } if (!AuthzInitializeContextFromToken( 0, logonHandle, hManager, NULL, // expiration time luidUnused, // not used NULL, // callback args &hAuthzToken)) { dwError = GetLastError(); ReportErrorCode(L"AuthzInitializeContextFromToken", dwError); goto done; } if (!AuthzAccessCheck( 0, hAuthzToken, &request, NULL, // AuditEvent pSD, NULL, // OptionalSecurityDescriptorArray 0, // OptionalSecurityDescriptorCount &reply, NULL // phAccessCheckResults )) { dwError = GetLastError(); ReportErrorCode(L"AuthzAccessCheck", dwError); goto done; } LogDebugMessage(L"AutzAccessCheck: Error:%d sacl:%d access:%d\n", authError, saclResult, grantedMask); if (authError != ERROR_SUCCESS) { ReportErrorCode(L"AuthzAccessCheck:REPLY:1", authError); dwError = authError; } else if (!(grantedMask & SERVICE_IMPERSONATE_MASK)) { ReportErrorCode(L"AuthzAccessCheck:REPLY:2", ERROR_ACCESS_DENIED); dwError = ERROR_ACCESS_DENIED; } done: if (hAuthzToken) AuthzFreeContext(hAuthzToken); if (hManager) AuthzFreeResourceManager(hManager); if (pSD) LocalFree(pSD); return dwError; } //---------------------------------------------------------------------------- // Function: CreateTaskImpl // // Description: // Creates a task via a jobobject. Outputs the // appropriate information to stdout on success, or stderr on failure. // logonHandle may be NULL, in this case the current logon will be utilized for the // created process // // Returns: // ERROR_SUCCESS: On success // GetLastError: otherwise DWORD CreateTaskImpl(__in_opt HANDLE logonHandle, __in PCWSTR jobObjName,__in PWSTR cmdLine, __in LPCWSTR userName, __in long memory, __in long cpuRate) { DWORD dwErrorCode = ERROR_SUCCESS; DWORD exitCode = EXIT_FAILURE; DWORD currDirCnt = 0; STARTUPINFO si; PROCESS_INFORMATION pi; HANDLE jobObject = NULL; JOBOBJECT_EXTENDED_LIMIT_INFORMATION jeli = { 0 }; void * envBlock = NULL; WCHAR secureJobNameBuffer[MAX_PATH]; LPCWSTR secureJobName = jobObjName; wchar_t* curr_dir = NULL; FILE *stream = NULL; if (NULL != logonHandle) { dwErrorCode = ValidateImpersonateAccessCheck(logonHandle); if (dwErrorCode) { ReportErrorCode(L"ValidateImpersonateAccessCheck", dwErrorCode); return dwErrorCode; } dwErrorCode = GetSecureJobObjectName(jobObjName, MAX_PATH, secureJobNameBuffer); if (dwErrorCode) { ReportErrorCode(L"GetSecureJobObjectName", dwErrorCode); return dwErrorCode; } secureJobName = secureJobNameBuffer; } // Create un-inheritable job object handle and set job object to terminate // when last handle is closed. So winutils.exe invocation has the only open // job object handle. Exit of winutils.exe ensures termination of job object. // Either a clean exit of winutils or crash or external termination. jobObject = CreateJobObject(NULL, secureJobName); dwErrorCode = GetLastError(); if(jobObject == NULL || dwErrorCode == ERROR_ALREADY_EXISTS) { ReportErrorCode(L"CreateJobObject", dwErrorCode); return dwErrorCode; } jeli.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE; if (memory > 0) { jeli.BasicLimitInformation.LimitFlags |= JOB_OBJECT_LIMIT_JOB_MEMORY; jeli.ProcessMemoryLimit = ((SIZE_T) memory) * 1024 * 1024; jeli.JobMemoryLimit = ((SIZE_T) memory) * 1024 * 1024; } if(SetInformationJobObject(jobObject, JobObjectExtendedLimitInformation, &jeli, sizeof(jeli)) == 0) { dwErrorCode = GetLastError(); ReportErrorCode(L"SetInformationJobObject", dwErrorCode); CloseHandle(jobObject); return dwErrorCode; } #ifdef NTDDI_WIN8 if (cpuRate > 0) { JOBOBJECT_CPU_RATE_CONTROL_INFORMATION jcrci = { 0 }; SYSTEM_INFO sysinfo; GetSystemInfo(&sysinfo); jcrci.ControlFlags = JOB_OBJECT_CPU_RATE_CONTROL_ENABLE | JOB_OBJECT_CPU_RATE_CONTROL_HARD_CAP; jcrci.CpuRate = min(10000, cpuRate); if(SetInformationJobObject(jobObject, JobObjectCpuRateControlInformation, &jcrci, sizeof(jcrci)) == 0) { dwErrorCode = GetLastError(); CloseHandle(jobObject); return dwErrorCode; } } #endif if (logonHandle != NULL) { dwErrorCode = AddNodeManagerAndUserACEsToObject(jobObject, userName, JOB_OBJECT_ALL_ACCESS); if (dwErrorCode) { ReportErrorCode(L"AddNodeManagerAndUserACEsToObject", dwErrorCode); CloseHandle(jobObject); return dwErrorCode; } } if(AssignProcessToJobObject(jobObject, GetCurrentProcess()) == 0) { dwErrorCode = GetLastError(); ReportErrorCode(L"AssignProcessToJobObject", dwErrorCode); CloseHandle(jobObject); return dwErrorCode; } // the child JVM uses this env var to send the task OS process identifier // to the TaskTracker. We pass the job object name. if(SetEnvironmentVariable(L"JVM_PID", jobObjName) == 0) { dwErrorCode = GetLastError(); ReportErrorCode(L"SetEnvironmentVariable", dwErrorCode); // We have to explictly Terminate, passing in the error code // simply closing the job would kill our own process with success exit status TerminateJobObject(jobObject, dwErrorCode); return dwErrorCode; } ZeroMemory( &si, sizeof(si) ); si.cb = sizeof(si); ZeroMemory( &pi, sizeof(pi) ); if( logonHandle != NULL ) { // create user environment for this logon if(!CreateEnvironmentBlock(&envBlock, logonHandle, TRUE )) { dwErrorCode = GetLastError(); ReportErrorCode(L"CreateEnvironmentBlock", dwErrorCode); // We have to explictly Terminate, passing in the error code // simply closing the job would kill our own process with success exit status TerminateJobObject(jobObject, dwErrorCode); return dwErrorCode; } } // Get the required buffer size first currDirCnt = GetCurrentDirectory(0, NULL); if (0 < currDirCnt) { curr_dir = (wchar_t*) alloca(currDirCnt * sizeof(wchar_t)); assert(curr_dir); currDirCnt = GetCurrentDirectory(currDirCnt, curr_dir); } if (0 == currDirCnt) { dwErrorCode = GetLastError(); ReportErrorCode(L"GetCurrentDirectory", dwErrorCode); // We have to explictly Terminate, passing in the error code // simply closing the job would kill our own process with success exit status TerminateJobObject(jobObject, dwErrorCode); return dwErrorCode; } dwErrorCode = ERROR_SUCCESS; if (logonHandle == NULL) { if (!CreateProcess( NULL, // ApplicationName cmdLine, // command line NULL, // process security attributes NULL, // thread security attributes TRUE, // inherit handles 0, // creation flags NULL, // environment curr_dir, // current directory &si, // startup info &pi)) { // process info dwErrorCode = GetLastError(); ReportErrorCode(L"CreateProcess", dwErrorCode); } // task create (w/o createAsUser) does not need the ACEs change on the process goto create_process_done; } // From here on is the secure S4U implementation for CreateProcessAsUser // We desire to grant process access to NM so that it can interogate process status // and resource utilization. Passing in a security descriptor though results in the // S4U privilege checks being done against that SD and CreateProcessAsUser fails. // So instead we create the process suspended and then we add the desired ACEs. // if (!CreateProcessAsUser( logonHandle, // logon token handle NULL, // Application handle cmdLine, // command line NULL, // process security attributes NULL, // thread security attributes FALSE, // inherit handles CREATE_UNICODE_ENVIRONMENT | CREATE_SUSPENDED, // creation flags envBlock, // environment curr_dir, // current directory &si, // startup info &pi)) { // process info dwErrorCode = GetLastError(); ReportErrorCode(L"CreateProcessAsUser", dwErrorCode); goto create_process_done; } dwErrorCode = AddNodeManagerAndUserACEsToObject(pi.hProcess, userName, PROCESS_ALL_ACCESS); if (dwErrorCode) { ReportErrorCode(L"AddNodeManagerAndUserACEsToObject", dwErrorCode); goto create_process_done; } if (-1 == ResumeThread(pi.hThread)) { dwErrorCode = GetLastError(); ReportErrorCode(L"ResumeThread", dwErrorCode); goto create_process_done; } create_process_done: if (dwErrorCode) { if( envBlock != NULL ) { DestroyEnvironmentBlock( envBlock ); envBlock = NULL; } // We have to explictly Terminate, passing in the error code // simply closing the job would kill our own process with success exit status TerminateJobObject(jobObject, dwErrorCode); // This is tehnically dead code, we cannot reach this condition return dwErrorCode; } CloseHandle(pi.hThread); // Wait until child process exits. WaitForSingleObject( pi.hProcess, INFINITE ); if(GetExitCodeProcess(pi.hProcess, &exitCode) == 0) { dwErrorCode = GetLastError(); } CloseHandle( pi.hProcess ); if( envBlock != NULL ) { DestroyEnvironmentBlock( envBlock ); envBlock = NULL; } // Terminate job object so that all spawned processes are also killed. // This is needed because once this process closes the handle to the job // object and none of the spawned objects have the handle open (via // inheritance on creation) then it will not be possible for any other external // program (say winutils task kill) to terminate this job object via its name. if(TerminateJobObject(jobObject, exitCode) == 0) { dwErrorCode = GetLastError(); } // comes here only on failure of TerminateJobObject CloseHandle(jobObject); if(dwErrorCode != ERROR_SUCCESS) { return dwErrorCode; } return exitCode; } //---------------------------------------------------------------------------- // Function: CreateTask // // Description: // Creates a task via a jobobject. Outputs the // appropriate information to stdout on success, or stderr on failure. // // Returns: // ERROR_SUCCESS: On success // GetLastError: otherwise DWORD CreateTask(__in PCWSTR jobObjName,__in PWSTR cmdLine, __in long memory, __in long cpuRate) { // call with null logon in order to create tasks utilizing the current logon return CreateTaskImpl( NULL, jobObjName, cmdLine, NULL, memory, cpuRate); } //---------------------------------------------------------------------------- // Function: CreateTaskAsUser // // Description: // Creates a task via a jobobject. Outputs the // appropriate information to stdout on success, or stderr on failure. // // Returns: // ERROR_SUCCESS: On success // GetLastError: otherwise DWORD CreateTaskAsUser(__in PCWSTR jobObjName, __in PCWSTR user, __in PCWSTR pidFilePath, __in PWSTR cmdLine) { DWORD err = ERROR_SUCCESS; DWORD exitCode = EXIT_FAILURE; ULONG authnPkgId; HANDLE lsaHandle = INVALID_HANDLE_VALUE; PROFILEINFO pi; BOOL profileIsLoaded = FALSE; FILE* pidFile = NULL; DWORD retLen = 0; HANDLE logonHandle = NULL; errno_t pidErrNo = 0; err = EnableImpersonatePrivileges(); if( err != ERROR_SUCCESS ) { ReportErrorCode(L"EnableImpersonatePrivileges", err); goto done; } err = RegisterWithLsa(LOGON_PROCESS_NAME ,&lsaHandle); if( err != ERROR_SUCCESS ) { ReportErrorCode(L"RegisterWithLsa", err); goto done; } err = LookupKerberosAuthenticationPackageId( lsaHandle, &authnPkgId ); if( err != ERROR_SUCCESS ) { ReportErrorCode(L"LookupKerberosAuthenticationPackageId", err); goto done; } err = CreateLogonTokenForUser(lsaHandle, LOGON_PROCESS_NAME, TOKEN_SOURCE_NAME, authnPkgId, user, &logonHandle); if( err != ERROR_SUCCESS ) { ReportErrorCode(L"CreateLogonTokenForUser", err); goto done; } err = LoadUserProfileForLogon(logonHandle, &pi); if( err != ERROR_SUCCESS ) { ReportErrorCode(L"LoadUserProfileForLogon", err); goto done; } profileIsLoaded = TRUE; // Create the PID file pidErrNo = _wfopen_s(&pidFile, pidFilePath, L"w"); if (pidErrNo) { err = GetLastError(); ReportErrorCode(L"_wfopen:pidFilePath", err); goto done; } if (0 > fprintf_s(pidFile, "%ls", jobObjName)) { err = GetLastError(); } fclose(pidFile); if (err != ERROR_SUCCESS) { ReportErrorCode(L"fprintf_s:pidFilePath", err); goto done; } err = CreateTaskImpl(logonHandle, jobObjName, cmdLine, user, -1, -1); done: if( profileIsLoaded ) { UnloadProfileForLogon( logonHandle, &pi ); profileIsLoaded = FALSE; } if( logonHandle != NULL ) { CloseHandle(logonHandle); } if (INVALID_HANDLE_VALUE != lsaHandle) { UnregisterWithLsa(lsaHandle); } return err; } //---------------------------------------------------------------------------- // Function: IsTaskAlive // // Description: // Checks if a task is alive via a jobobject. Outputs the // appropriate information to stdout on success, or stderr on failure. // // Returns: // ERROR_SUCCESS: On success // GetLastError: otherwise DWORD IsTaskAlive(const WCHAR* jobObjName, int* isAlive, int* procsInJob) { PJOBOBJECT_BASIC_PROCESS_ID_LIST procList; HANDLE jobObject = NULL; int numProcs = 100; WCHAR secureJobNameBuffer[MAX_PATH]; *isAlive = FALSE; jobObject = OpenJobObject(JOB_OBJECT_QUERY, FALSE, jobObjName); if(jobObject == NULL) { // Try Global\... DWORD err = GetSecureJobObjectName(jobObjName, MAX_PATH, secureJobNameBuffer); if (err) { ReportErrorCode(L"GetSecureJobObjectName", err); return err; } jobObject = OpenJobObject(JOB_OBJECT_QUERY, FALSE, secureJobNameBuffer); } if(jobObject == NULL) { DWORD err = GetLastError(); return err; } if(jobObject == NULL) { DWORD err = GetLastError(); if(err == ERROR_FILE_NOT_FOUND) { // job object does not exist. assume its not alive return ERROR_SUCCESS; } return err; } procList = (PJOBOBJECT_BASIC_PROCESS_ID_LIST) LocalAlloc(LPTR, sizeof (JOBOBJECT_BASIC_PROCESS_ID_LIST) + numProcs*32); if (!procList) { DWORD err = GetLastError(); CloseHandle(jobObject); return err; } if(QueryInformationJobObject(jobObject, JobObjectBasicProcessIdList, procList, sizeof(JOBOBJECT_BASIC_PROCESS_ID_LIST)+numProcs*32, NULL) == 0) { DWORD err = GetLastError(); if(err != ERROR_MORE_DATA) { CloseHandle(jobObject); LocalFree(procList); return err; } } if(procList->NumberOfAssignedProcesses > 0) { *isAlive = TRUE; *procsInJob = procList->NumberOfAssignedProcesses; } LocalFree(procList); return ERROR_SUCCESS; } //---------------------------------------------------------------------------- // Function: PrintTaskProcessList // // Description: // Prints resource usage of all processes in the task jobobject // // Returns: // ERROR_SUCCESS: On success // GetLastError: otherwise DWORD PrintTaskProcessList(const WCHAR* jobObjName) { DWORD i; PJOBOBJECT_BASIC_PROCESS_ID_LIST procList; int numProcs = 100; WCHAR secureJobNameBuffer[MAX_PATH]; HANDLE jobObject = NULL; jobObject = OpenJobObject(JOB_OBJECT_QUERY, FALSE, jobObjName); if(jobObject == NULL) { // Try Global\... DWORD err = GetSecureJobObjectName(jobObjName, MAX_PATH, secureJobNameBuffer); if (err) { ReportErrorCode(L"GetSecureJobObjectName", err); return err; } jobObject = OpenJobObject(JOB_OBJECT_QUERY, FALSE, secureJobNameBuffer); } if(jobObject == NULL) { DWORD err = GetLastError(); return err; } procList = (PJOBOBJECT_BASIC_PROCESS_ID_LIST) LocalAlloc(LPTR, sizeof (JOBOBJECT_BASIC_PROCESS_ID_LIST) + numProcs*32); if (!procList) { DWORD err = GetLastError(); CloseHandle(jobObject); return err; } while(QueryInformationJobObject(jobObject, JobObjectBasicProcessIdList, procList, sizeof(JOBOBJECT_BASIC_PROCESS_ID_LIST)+numProcs*32, NULL) == 0) { DWORD err = GetLastError(); if(err != ERROR_MORE_DATA) { CloseHandle(jobObject); LocalFree(procList); return err; } numProcs = procList->NumberOfAssignedProcesses; LocalFree(procList); procList = (PJOBOBJECT_BASIC_PROCESS_ID_LIST) LocalAlloc(LPTR, sizeof (JOBOBJECT_BASIC_PROCESS_ID_LIST) + numProcs*32); if (procList == NULL) { err = GetLastError(); CloseHandle(jobObject); return err; } } for(i=0; iNumberOfProcessIdsInList; ++i) { HANDLE hProcess = OpenProcess( PROCESS_QUERY_INFORMATION, FALSE, (DWORD)procList->ProcessIdList[i] ); if( hProcess != NULL ) { PROCESS_MEMORY_COUNTERS_EX pmc; if ( GetProcessMemoryInfo( hProcess, (PPROCESS_MEMORY_COUNTERS)&pmc, sizeof(pmc)) ) { FILETIME create, exit, kernel, user; if( GetProcessTimes( hProcess, &create, &exit, &kernel, &user) ) { ULARGE_INTEGER kernelTime, userTime; ULONGLONG cpuTimeMs; kernelTime.HighPart = kernel.dwHighDateTime; kernelTime.LowPart = kernel.dwLowDateTime; userTime.HighPart = user.dwHighDateTime; userTime.LowPart = user.dwLowDateTime; cpuTimeMs = (kernelTime.QuadPart+userTime.QuadPart)/10000; fwprintf_s(stdout, L"%Iu,%Iu,%Iu,%I64u\n", procList->ProcessIdList[i], pmc.PrivateUsage, pmc.WorkingSetSize, cpuTimeMs); } } CloseHandle( hProcess ); } } LocalFree(procList); CloseHandle(jobObject); return ERROR_SUCCESS; } //---------------------------------------------------------------------------- // Function: Task // // Description: // Manages a task via a jobobject (create/isAlive/kill). Outputs the // appropriate information to stdout on success, or stderr on failure. // // Returns: // ERROR_SUCCESS: On success // Error code otherwise: otherwise int Task(__in int argc, __in_ecount(argc) wchar_t *argv[]) { DWORD dwErrorCode = ERROR_SUCCESS; TaskCommandOption command = TaskInvalid; long memory = -1; long cpuRate = -1; wchar_t* cmdLine = NULL; wchar_t buffer[16*1024] = L""; // 32K max command line size_t charCountBufferLeft = sizeof(buffer)/sizeof(wchar_t); int crtArgIndex = 0; size_t argLen = 0; size_t wscatErr = 0; wchar_t* insertHere = NULL; enum { ARGC_JOBOBJECTNAME = 2, ARGC_USERNAME, ARGC_PIDFILE, ARGC_COMMAND, ARGC_COMMAND_ARGS }; if (!ParseCommandLine(argc, argv, &command, &memory, &cpuRate)) { dwErrorCode = ERROR_INVALID_COMMAND_LINE; fwprintf(stderr, L"Incorrect command line arguments.\n\n"); TaskUsage(); goto TaskExit; } if (command == TaskCreate) { // Create the task jobobject // dwErrorCode = CreateTask(argv[argc-2], argv[argc-1], memory, cpuRate); if (dwErrorCode != ERROR_SUCCESS) { ReportErrorCode(L"CreateTask", dwErrorCode); goto TaskExit; } } else if (command == TaskCreateAsUser) { // Create the task jobobject as a domain user // createAsUser accepts an open list of arguments. All arguments after the command are // to be passed as argumrnts to the command itself.Here we're concatenating all // arguments after the command into a single arg entry. // cmdLine = argv[ARGC_COMMAND]; if (argc > ARGC_COMMAND_ARGS) { crtArgIndex = ARGC_COMMAND; insertHere = buffer; while (crtArgIndex < argc) { argLen = wcslen(argv[crtArgIndex]); wscatErr = wcscat_s(insertHere, charCountBufferLeft, argv[crtArgIndex]); switch (wscatErr) { case 0: // 0 means success; break; case EINVAL: dwErrorCode = ERROR_INVALID_PARAMETER; goto TaskExit; case ERANGE: dwErrorCode = ERROR_INSUFFICIENT_BUFFER; goto TaskExit; default: // This case is not MSDN documented. dwErrorCode = ERROR_GEN_FAILURE; goto TaskExit; } insertHere += argLen; charCountBufferLeft -= argLen; insertHere[0] = L' '; insertHere += 1; charCountBufferLeft -= 1; insertHere[0] = 0; ++crtArgIndex; } cmdLine = buffer; } dwErrorCode = CreateTaskAsUser( argv[ARGC_JOBOBJECTNAME], argv[ARGC_USERNAME], argv[ARGC_PIDFILE], cmdLine); if (dwErrorCode != ERROR_SUCCESS) { ReportErrorCode(L"CreateTaskAsUser", dwErrorCode); goto TaskExit; } } else if (command == TaskIsAlive) { // Check if task jobobject // int isAlive; int numProcs; dwErrorCode = IsTaskAlive(argv[2], &isAlive, &numProcs); if (dwErrorCode != ERROR_SUCCESS) { ReportErrorCode(L"IsTaskAlive", dwErrorCode); goto TaskExit; } // Output the result if(isAlive == TRUE) { fwprintf(stdout, L"IsAlive,%d\n", numProcs); } else { dwErrorCode = ERROR_TASK_NOT_ALIVE; ReportErrorCode(L"IsTaskAlive returned false", dwErrorCode); goto TaskExit; } } else if (command == TaskKill) { // Check if task jobobject // dwErrorCode = KillTask(argv[2]); if (dwErrorCode != ERROR_SUCCESS) { ReportErrorCode(L"KillTask", dwErrorCode); goto TaskExit; } } else if (command == TaskProcessList) { // Check if task jobobject // dwErrorCode = PrintTaskProcessList(argv[2]); if (dwErrorCode != ERROR_SUCCESS) { ReportErrorCode(L"PrintTaskProcessList", dwErrorCode); goto TaskExit; } } else { // Should not happen // assert(FALSE); } TaskExit: ReportErrorCode(L"TaskExit:", dwErrorCode); return dwErrorCode; } void TaskUsage() { // Hadoop code checks for this string to determine if // jobobject's are being used. // ProcessTree.isSetsidSupported() fwprintf(stdout, L"\ Usage: task create [OPTOINS] [TASKNAME] [COMMAND_LINE]\n\ Creates a new task job object with taskname and options to set CPU\n\ and memory limits on the job object\n\ \n\ OPTIONS: -c [cup rate] set the cpu rate limit on the job object.\n\ -m [memory] set the memory limit on the job object.\n\ The cpu limit is an integral value of percentage * 100. The memory\n\ limit is an integral number of memory in MB. \n\ The limit will not be set if 0 or negative value is passed in as\n\ parameter(s).\n\ \n\ task createAsUser [TASKNAME] [USERNAME] [PIDFILE] [COMMAND_LINE]\n\ Creates a new task jobobject with taskname as the user provided\n\ \n\ task isAlive [TASKNAME]\n\ Checks if task job object is alive\n\ \n\ task kill [TASKNAME]\n\ Kills task job object\n\ \n\ task processList [TASKNAME]\n\ Prints to stdout a list of processes in the task\n\ along with their resource usage. One process per line\n\ and comma separated info per process\n\ ProcessId,VirtualMemoryCommitted(bytes),\n\ WorkingSetSize(bytes),CpuTime(Millisec,Kernel+User)\n"); }