/*++ Copyright (c) 1992 Microsoft Corporation Module Name: scsec.cxx Abstract: This module contains security related routines: RQueryServiceObjectSecurity RSetServiceObjectSecurity ScCreateScManagerObject ScCreateScServiceObject ScGrantAccess ScPrivilegeCheckAndAudit ScAccessValidate ScAccessCheckAndAudit ScGetPrivilege ScReleasePrivilege Author: Rita Wong (ritaw) 10-Mar-1992 Environment: Calls NT native APIs. Revision History: 10-Mar-1992 ritaw created 16-Apr-1992 JohnRo Process services which are marked for delete accordingly. 06-Aug-1992 Danl Fixed a debug print statement. It indicated it was from the ScLoadDeviceDriver function - rather than in ScGetPrivilege. 21-Jan-1995 AnirudhS Added ScGrantAccess and ScPrivilegeCheckAndAudit. --*/ #include "precomp.hxx" #include "scconfig.h" // ScOpenServiceConfigKey #include "scsec.h" // Object names and security functions #include "control.h" // SERVICE_SET_STATUS #include "account.h" // ScLookupServiceAccount #include "align.h" // ROUND_UP_COUNT #define PRIVILEGE_BUF_SIZE 512 //-------------------------------------------------------------------// // // // Static global variables // // // //-------------------------------------------------------------------// // // Security descriptor of the SCManager objects to control user accesses // to the Service Control Manager and its database. // PSECURITY_DESCRIPTOR ScManagerSd; // // Structure that describes the mapping of Generic access rights to // object specific access rights for the ScManager object. // GENERIC_MAPPING ScManagerObjectMapping = { STANDARD_RIGHTS_READ | // Generic read SC_MANAGER_ENUMERATE_SERVICE | SC_MANAGER_QUERY_LOCK_STATUS, STANDARD_RIGHTS_WRITE | // Generic write SC_MANAGER_CREATE_SERVICE | SC_MANAGER_MODIFY_BOOT_CONFIG, STANDARD_RIGHTS_EXECUTE | // Generic execute SC_MANAGER_CONNECT | SC_MANAGER_LOCK, SC_MANAGER_ALL_ACCESS // Generic all }; // // Structure that describes the mapping of generic access rights to // object specific access rights for the Service object. // GENERIC_MAPPING ScServiceObjectMapping = { STANDARD_RIGHTS_READ | // Generic read SERVICE_QUERY_CONFIG | SERVICE_QUERY_STATUS | SERVICE_ENUMERATE_DEPENDENTS | SERVICE_INTERROGATE, STANDARD_RIGHTS_WRITE | // Generic write SERVICE_CHANGE_CONFIG, STANDARD_RIGHTS_EXECUTE | // Generic execute SERVICE_START | SERVICE_STOP | SERVICE_PAUSE_CONTINUE | SERVICE_USER_DEFINED_CONTROL, SERVICE_ALL_ACCESS // Generic all }; //-------------------------------------------------------------------// // // // Functions // // // //-------------------------------------------------------------------// DWORD RQueryServiceObjectSecurity( IN SC_RPC_HANDLE hService, IN SECURITY_INFORMATION dwSecurityInformation, OUT LPBYTE lpSecurityDescriptor, IN DWORD cbBufSize, OUT LPDWORD pcbBytesNeeded ) /*++ Routine Description: This is the worker function for QueryServiceObjectSecurity API. It returns the security descriptor information of a service object. Arguments: hService - Supplies the context handle to an existing service object. dwSecurityInformation - Supplies the bitwise flags describing the security information being queried. lpSecurityInformation - Supplies the output buffer from the user which security descriptor information will be written to on return. cbBufSize - Supplies the size of lpSecurityInformation buffer. pcbBytesNeeded - Returns the number of bytes needed of the lpSecurityInformation buffer to get all the requested information. Return Value: NO_ERROR - The operation was successful. ERROR_INVALID_HANDLE - The specified handle was invalid. ERROR_ACCESS_DENIED - The specified handle was not opened for either READ_CONTROL or ACCESS_SYSTEM_SECURITY access. ERROR_INVALID_PARAMETER - The dwSecurityInformation parameter is invalid. ERROR_INSUFFICIENT_BUFFER - The specified output buffer is smaller than the required size returned in pcbBytesNeeded. None of the security descriptor is returned. Note: It is expected that the RPC Stub functions will find the following parameter problems: Bad pointers for lpSecurityDescriptor, and pcbBytesNeeded. --*/ { NTSTATUS ntstatus; ACCESS_MASK DesiredAccess = 0; PSECURITY_DESCRIPTOR ServiceSd; DWORD ServiceSdSize = 0; // // Check the handle. // if (!ScIsValidServiceHandle(hService)) { return ERROR_INVALID_HANDLE; } // // Check the validity of dwSecurityInformation // if ((dwSecurityInformation == 0) || ((dwSecurityInformation & (OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION | SACL_SECURITY_INFORMATION)) != dwSecurityInformation)) { return ERROR_INVALID_PARAMETER; } // // Set the desired access based on the requested SecurityInformation // if (dwSecurityInformation & SACL_SECURITY_INFORMATION) { DesiredAccess |= ACCESS_SYSTEM_SECURITY; } if (dwSecurityInformation & (DACL_SECURITY_INFORMATION | OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION)) { DesiredAccess |= READ_CONTROL; } // // Was the handle opened for the requested access? // if (! RtlAreAllAccessesGranted( ((LPSC_HANDLE_STRUCT)hService)->AccessGranted, DesiredAccess )) { return ERROR_ACCESS_DENIED; } // // RtlZeroMemory(lpSecurityDescriptor, cbBufSize); // // Get the database lock for reading // CServiceRecordSharedLock RLock; // // The most up-to-date service security descriptor is always kept in // the service record. // ServiceSd = ((LPSC_HANDLE_STRUCT)hService)->Type.ScServiceObject.ServiceRecord->ServiceSd; // // Retrieve the appropriate security information from ServiceSd // and place it in the user supplied buffer. // ntstatus = RtlQuerySecurityObject( ServiceSd, dwSecurityInformation, (PSECURITY_DESCRIPTOR) lpSecurityDescriptor, cbBufSize, &ServiceSdSize ); if (! NT_SUCCESS(ntstatus)) { if (ntstatus == STATUS_BAD_DESCRIPTOR_FORMAT) { // // Internal error: our security descriptor is bad! // SC_LOG0(ERROR, "RQueryServiceObjectSecurity: Our security descriptor is bad!\n"); ASSERT(FALSE); return ERROR_GEN_FAILURE; } else if (ntstatus == STATUS_BUFFER_TOO_SMALL) { // // Return the required size to the user // *pcbBytesNeeded = ServiceSdSize; return ERROR_INSUFFICIENT_BUFFER; } else { return RtlNtStatusToDosError(ntstatus); } } // // Return the required size to the user // *pcbBytesNeeded = ServiceSdSize; return NO_ERROR; } DWORD RSetServiceObjectSecurity( IN SC_RPC_HANDLE hService, IN SECURITY_INFORMATION dwSecurityInformation, IN LPBYTE lpSecurityDescriptor, IN DWORD cbBufSize ) /*++ Routine Description: This is the worker function for SetServiceObjectSecurity API. It modifies the security descriptor information of a service object. Arguments: hService - Supplies the context handle to the service. dwSecurityInformation - Supplies the bitwise flags of security information being queried. lpSecurityInformation - Supplies a buffer which contains a well-formed self-relative security descriptor. cbBufSize - Supplies the size of lpSecurityInformation buffer. Return Value: NO_ERROR - The operation was successful. ERROR_INVALID_HANDLE - The specified handle was invalid. ERROR_ACCESS_DENIED - The specified handle was not opened for either WRITE_OWNER, WRITE_DAC, or ACCESS_SYSTEM_SECURITY access. ERROR_INVALID_PARAMETER - The lpSecurityDescriptor or dwSecurityInformation parameter is invalid. Note: It is expected that the RPC Stub functions will find the following parameter problems: Bad pointers for lpSecurityDescriptor. --*/ { DWORD status; NTSTATUS ntstatus; RPC_STATUS rpcstatus; ACCESS_MASK DesiredAccess = 0; LPSERVICE_RECORD serviceRecord; HANDLE ClientTokenHandle = NULL; LPBYTE lpTempSD = lpSecurityDescriptor; UNREFERENCED_PARAMETER(cbBufSize); // for RPC marshalling code // // Check the handle. // if (!ScIsValidServiceHandle(hService)) { return ERROR_INVALID_HANDLE; } // // Silently ignore flags we don't understand that may come // from higher-level object managers. // dwSecurityInformation &= (OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION | SACL_SECURITY_INFORMATION); if (dwSecurityInformation == 0) { return NO_ERROR; } #ifdef _WIN64 if (PtrToUlong(lpSecurityDescriptor) & (sizeof(PVOID) - 1)) { // // SD isn't properly aligned. Alloc an aligned heap buffer // and copy it in to fix things up. // lpTempSD = (LPBYTE) LocalAlloc(LMEM_FIXED, cbBufSize); if (lpTempSD == NULL) { status = ERROR_NOT_ENOUGH_MEMORY; goto CleanExit; } RtlCopyMemory(lpTempSD, lpSecurityDescriptor, cbBufSize); } #endif // _WIN64 // // Check the validity of lpSecurityInformation // if (! RtlValidRelativeSecurityDescriptor( (PSECURITY_DESCRIPTOR) lpTempSD, cbBufSize, dwSecurityInformation )) { status = ERROR_INVALID_PARAMETER; goto CleanExit; } // // Set the desired access based on the specified SecurityInformation // if (dwSecurityInformation & SACL_SECURITY_INFORMATION) { DesiredAccess |= ACCESS_SYSTEM_SECURITY; } if (dwSecurityInformation & (OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION)) { DesiredAccess |= WRITE_OWNER; } if (dwSecurityInformation & DACL_SECURITY_INFORMATION) { DesiredAccess |= WRITE_DAC; } // // Make sure the specified fields are present in the provided // security descriptor. // Security descriptors must have owner and group fields. // if (dwSecurityInformation & OWNER_SECURITY_INFORMATION) { if (((PISECURITY_DESCRIPTOR_RELATIVE) lpTempSD)->Owner == 0) { status = ERROR_INVALID_PARAMETER; goto CleanExit; } } if (dwSecurityInformation & GROUP_SECURITY_INFORMATION) { if (((PISECURITY_DESCRIPTOR_RELATIVE) lpTempSD)->Group == 0) { status = ERROR_INVALID_PARAMETER; goto CleanExit; } } // // Was the handle opened for the requested access? // if (! RtlAreAllAccessesGranted( ((LPSC_HANDLE_STRUCT)hService)->AccessGranted, DesiredAccess )) { status = ERROR_ACCESS_DENIED; goto CleanExit; } // // Is this service marked for delete? // serviceRecord = ((LPSC_HANDLE_STRUCT)hService)->Type.ScServiceObject.ServiceRecord; SC_ASSERT( serviceRecord != NULL ); if (DELETE_FLAG_IS_SET(serviceRecord)) { status = ERROR_SERVICE_MARKED_FOR_DELETE; goto CleanExit; } // // If caller wants to replace the owner, get a handle to the impersonation // token. // if (dwSecurityInformation & OWNER_SECURITY_INFORMATION) { if ((rpcstatus = RpcImpersonateClient(NULL)) != RPC_S_OK) { SC_LOG1( ERROR, "RSetServiceObjectSecurity: Failed to impersonate client " FORMAT_RPC_STATUS "\n", rpcstatus ); ScLogEvent( NEVENT_CALL_TO_FUNCTION_FAILED, SC_RPC_IMPERSONATE, rpcstatus ); status = rpcstatus; goto CleanExit; } ntstatus = NtOpenThreadToken( NtCurrentThread(), TOKEN_QUERY, TRUE, // OpenAsSelf &ClientTokenHandle ); // // Stop impersonating the client // if ((rpcstatus = RpcRevertToSelf()) != RPC_S_OK) { SC_LOG1( ERROR, "RSetServiceObjectSecurity: Failed to revert to self " FORMAT_RPC_STATUS "\n", rpcstatus ); ScLogEvent( NEVENT_CALL_TO_FUNCTION_FAILED, SC_RPC_REVERT, rpcstatus ); ASSERT(FALSE); status = rpcstatus; goto CleanExit; } if (! NT_SUCCESS(ntstatus)) { SC_LOG(ERROR, "RSetServiceObjectSecurity: NtOpenThreadToken failed %08lx\n", ntstatus); status = RtlNtStatusToDosError(ntstatus); goto CleanExit; } } { CServiceRecordExclusiveLock RLock; // // Replace the service security descriptor with the appropriate // security information specified in the caller supplied security // descriptor. This routine may reallocate the memory needed to // contain the new service security descriptor. // ntstatus = RtlSetSecurityObject( dwSecurityInformation, (PSECURITY_DESCRIPTOR) lpTempSD, &serviceRecord->ServiceSd, &ScServiceObjectMapping, ClientTokenHandle ); status = RtlNtStatusToDosError(ntstatus); if (! NT_SUCCESS(ntstatus)) { SC_LOG1(ERROR, "RSetServiceObjectSecurity: RtlSetSecurityObject failed %08lx\n", ntstatus); } else { HKEY ServiceKey; // // Write new security descriptor to the registry // status = ScOpenServiceConfigKey( serviceRecord->ServiceName, KEY_WRITE, FALSE, &ServiceKey ); if (status == NO_ERROR) { status = ScWriteSd( ServiceKey, serviceRecord->ServiceSd ); if (status != NO_ERROR) { SC_LOG1(ERROR, "RSetServiceObjectSecurity: ScWriteSd failed %lu\n", status); } ScRegFlushKey(ServiceKey); ScRegCloseKey(ServiceKey); } } } CleanExit: #ifdef _WIN64 if (lpTempSD != lpSecurityDescriptor) { LocalFree(lpTempSD); } #endif // _WIN64 if (ClientTokenHandle != NULL) { NtClose(ClientTokenHandle); } return status; } DWORD ScCreateScManagerObject( VOID ) /*++ Routine Description: This function creates the security descriptor which represents the ScManager object for both the "ServiceActive" and "ServicesFailed" databases. Arguments: None. Return Value: Returns values from calls to: ScCreateUserSecurityObject --*/ { NTSTATUS ntstatus; ULONG Privilege = SE_SECURITY_PRIVILEGE; // // World has SC_MANAGER_CONNECT access and GENERIC_READ access. // Local admins are allowed GENERIC_ALL access. // #define SC_MANAGER_OBJECT_ACES 5 // Number of ACEs in this DACL SC_ACE_DATA AceData[SC_MANAGER_OBJECT_ACES] = { {ACCESS_ALLOWED_ACE_TYPE, 0, 0, SC_MANAGER_CONNECT | GENERIC_READ, &AuthenticatedUserSid}, {ACCESS_ALLOWED_ACE_TYPE, 0, 0, SC_MANAGER_CONNECT | GENERIC_READ | SC_MANAGER_MODIFY_BOOT_CONFIG, &LocalSystemSid}, {ACCESS_ALLOWED_ACE_TYPE, 0, 0, GENERIC_ALL, &AliasAdminsSid}, {SYSTEM_AUDIT_ACE_TYPE, 0, FAILED_ACCESS_ACE_FLAG, GENERIC_ALL, &WorldSid}, {SYSTEM_AUDIT_ACE_TYPE, INHERIT_ONLY_ACE | OBJECT_INHERIT_ACE, FAILED_ACCESS_ACE_FLAG, GENERIC_ALL, &WorldSid} }; // // You need to have SE_SECURITY_PRIVILEGE privilege to create the SD with a // SACL // ntstatus = ScGetPrivilege(1, &Privilege); if (ntstatus != NO_ERROR) { SC_LOG1(ERROR, "ScCreateScManagerObject: ScGetPrivilege Failed %d\n", ntstatus); return(ntstatus); } ntstatus = ScCreateUserSecurityObject( NULL, // Parent SD AceData, SC_MANAGER_OBJECT_ACES, LocalSystemSid, LocalSystemSid, TRUE, // IsDirectoryObject TRUE, // UseImpersonationToken &ScManagerObjectMapping, &ScManagerSd ); #undef SC_MANAGER_OBJECT_ACES if (! NT_SUCCESS(ntstatus)) { SC_LOG( ERROR, "ScCreateScManagerObject: ScCreateUserSecurityObject failed " FORMAT_NTSTATUS "\n", ntstatus ); } ScReleasePrivilege(); return RtlNtStatusToDosError(ntstatus); } DWORD ScGetSaclParameters( OUT PACCESS_MASK AuditAccessMask, OUT PUCHAR AuditAceFlags ) /*++ Routine Description: Read registry to determine the access-mask and audit ACE flags to use when adding a SACL on a service object. Arguments: AuditAccessMask - pointer to AuditAceFlags - pointer to Return Value: NTSTATUS - Standard Nt Result Code Notes: --*/ { DWORD dwError = NO_ERROR; DWORD dwValueType; ACCESS_MASK AccessMask = 0; DWORD AceFlags = 0; DWORD dwSize; HKEY hkeyAuditParams = 0; ASSERT( sizeof(ACCESS_MASK) == sizeof(DWORD) ); dwSize = sizeof(DWORD); // // open the base auditing key // dwError = ScRegOpenKeyExW( HKEY_LOCAL_MACHINE, L"SYSTEM\\CurrentControlSet\\Control\\LSA\\audit\\Services", REG_OPTION_NON_VOLATILE, KEY_READ, &hkeyAuditParams ); if ( dwError != NO_ERROR ) { if ((dwError == ERROR_FILE_NOT_FOUND) || (dwError == ERROR_PATH_NOT_FOUND)) { dwError = NO_ERROR; } goto Cleanup; } // // get the access-mask // dwError = ScRegQueryValueExW( hkeyAuditParams, L"DefaultAuditMask", NULL, &dwValueType, (LPBYTE) &AccessMask, &dwSize ); if ( dwError != NO_ERROR ) { if ((dwError == ERROR_FILE_NOT_FOUND) || (dwError == ERROR_PATH_NOT_FOUND)) { dwError = NO_ERROR; } goto Cleanup; } if ( ( dwValueType != REG_DWORD ) || ( dwSize != sizeof(DWORD) ) ) { dwError = ERROR_INVALID_DATA; goto Cleanup; } // // get the ACE flag // dwError = ScRegQueryValueExW( hkeyAuditParams, L"DefaultAuditAceFlags", NULL, &dwValueType, (LPBYTE) &AceFlags, &dwSize ); if ( dwError != NO_ERROR ) { if ((dwError == ERROR_FILE_NOT_FOUND) || (dwError == ERROR_PATH_NOT_FOUND)) { dwError = NO_ERROR; AceFlags = SUCCESSFUL_ACCESS_ACE_FLAG | FAILED_ACCESS_ACE_FLAG; } goto Cleanup; } if ( ( dwValueType != REG_DWORD ) || ( dwSize != sizeof(DWORD) ) ) { dwError = ERROR_INVALID_DATA; goto Cleanup; } Cleanup: if ( dwError == NO_ERROR ) { *AuditAccessMask = AccessMask; *AuditAceFlags = (UCHAR) AceFlags; } else { *AuditAccessMask = 0; *AuditAceFlags = 0; } if ( hkeyAuditParams != 0 ) { ScRegCloseKey( hkeyAuditParams ); } return dwError; } DWORD ScCreateScServiceObject( OUT PSECURITY_DESCRIPTOR *ServiceSd ) /*++ Routine Description: This function creates the security descriptor which represents the Service object. Arguments: ServiceSd - Returns service object security descriptor. Return Value: Returns values from calls to: ScCreateUserSecurityObject --*/ { NTSTATUS ntstatus = STATUS_SUCCESS; UINT NumAces = 0; ACCESS_MASK AuditAccessMask = 0; UCHAR AuditAceFlags = 0; ULONG Privilege = SE_SECURITY_PRIVILEGE; BOOLEAN Impersonating = FALSE; DWORD dwError = NO_ERROR; // // Authenticated users have read access. // Local system has service start/stop and all read access. // Power user has service start and all read access (Workstation and Server). // Admin and SystemOp (DC) are allowed all access. // #define SC_SERVICE_OBJECT_ACES 5 // Number of ACEs SC_ACE_DATA AceData[SC_SERVICE_OBJECT_ACES] = { {ACCESS_ALLOWED_ACE_TYPE, 0, 0, GENERIC_READ | GENERIC_EXECUTE, &LocalSystemSid}, {ACCESS_ALLOWED_ACE_TYPE, 0, 0, GENERIC_ALL, &AliasAdminsSid}, {ACCESS_ALLOWED_ACE_TYPE, 0, 0, GENERIC_READ | SERVICE_USER_DEFINED_CONTROL, &AuthenticatedUserSid}, {ACCESS_ALLOWED_ACE_TYPE, 0, 0, 0, 0}, {SYSTEM_AUDIT_ACE_TYPE, 0, 0, 0, &WorldSid} }; // // do not include the last ACE (the audit ACE) for now // NumAces = SC_SERVICE_OBJECT_ACES - 1; switch(USER_SHARED_DATA->NtProductType) { case NtProductWinNt: case NtProductServer: // // Power users are only on Workstation and Server // AceData[SC_SERVICE_OBJECT_ACES - 2].Mask = GENERIC_READ | GENERIC_EXECUTE; AceData[SC_SERVICE_OBJECT_ACES - 2].Sid = &AliasPowerUsersSid; break; case NtProductLanManNt: // // System Ops (Server Operators) are only on a DC // AceData[SC_SERVICE_OBJECT_ACES - 2].Mask = GENERIC_ALL; AceData[SC_SERVICE_OBJECT_ACES - 2].Sid = &AliasSystemOpsSid; break; default: // // A new product type has been added -- add code to cover it // SC_ASSERT(FALSE); break; } // // get the access-mask and ACE flags to use for the audit ACE. // dwError = ScGetSaclParameters( &AuditAccessMask, &AuditAceFlags ); if ( dwError == NO_ERROR ) { if ( AuditAccessMask && AuditAceFlags ) { AceData[NumAces].Mask = AuditAccessMask; AceData[NumAces].AceFlags = AuditAceFlags; NumAces += 1; // // need SE_SECURITY_PRIVILEGE privilege to create SD with a SACL // dwError = ScGetPrivilege(1, &Privilege); if (dwError != NO_ERROR) { goto Cleanup; } Impersonating = TRUE; } ntstatus = ScCreateUserSecurityObject( ScManagerSd, // ParentSD AceData, NumAces, LocalSystemSid, LocalSystemSid, FALSE, // IsDirectoryObject Impersonating, // UseImpersonationToken &ScServiceObjectMapping, ServiceSd ); dwError = RtlNtStatusToDosError(ntstatus); if ( Impersonating ) { ScReleasePrivilege(); } } #undef SC_SERVICE_OBJECT_ACES Cleanup: return dwError; } DWORD ScGrantAccess( IN OUT LPSC_HANDLE_STRUCT ContextHandle, IN ACCESS_MASK DesiredAccess ) /*++ Routine Description: This function is called when a new service is created. It validates the access desired by the caller for the new service handle and computes the granted access to be stored in the context handle structure. Since this is object creation, all requested accesses, except for ACCESS_SYSTEM_SECURITY, are granted automatically. Arguments: DesiredAccess - Supplies the client requested desired access. ContextHandle - On return, the granted access is written back to this location if this call succeeds. Return Value: Returns values from calls to the following, mapped to Win32 error codes: ScPrivilegeCheckAndAudit --*/ { NTSTATUS Status = STATUS_SUCCESS; ACCESS_MASK AccessToGrant = DesiredAccess; // // If MAXIMUM_ALLOWED is requested, add GENERIC_ALL // if (AccessToGrant & MAXIMUM_ALLOWED) { AccessToGrant &= ~MAXIMUM_ALLOWED; AccessToGrant |= GENERIC_ALL; } // // If ACCESS_SYSTEM_SECURITY is requested, check that we have // SE_SECURITY_PRIVILEGE. // if (AccessToGrant & ACCESS_SYSTEM_SECURITY) { Status = ScPrivilegeCheckAndAudit( SE_SECURITY_PRIVILEGE, // check for this privilege ContextHandle, // client's handle to the object // (used for auditing only) DesiredAccess // (used for auditing only) ); } if (NT_SUCCESS(Status)) { // // Map the generic bits to specific and standard bits. // RtlMapGenericMask( &AccessToGrant, &ScServiceObjectMapping ); // // Return the computed access mask. // ContextHandle->AccessGranted = AccessToGrant; } return(RtlNtStatusToDosError(Status)); } NTSTATUS ScPrivilegeCheckAndAudit( IN ULONG PrivilegeId, IN PVOID ObjectHandle, IN ACCESS_MASK DesiredAccess ) /*++ Routine Description: This function is only called from ScGrantAccess. It checks if the given well known privilege is enabled for an impersonated client. It also generates an audit for the attempt to use the privilege. Arguments: PrivilegeId - Specifies the well known Privilege Id ObjectHandle - Client's handle to the object (used for auditing) DesiredAccess - Access that the client requested to the object (used for auditing) Return Value: NTSTATUS - Standard Nt Result Code STATUS_SUCCESS - The call completed successfully and the client is either trusted or has the necessary privilege enabled. STATUS_PRIVILEGE_NOT_HELD - The client does not have the necessary privilege. --*/ { NTSTATUS Status, SecondaryStatus; HANDLE ClientToken = NULL; // // Impersonate the client. // Status = I_RpcMapWin32Status(RpcImpersonateClient( NULL )); if (NT_SUCCESS(Status)) { // // Open the current thread's impersonation token (if any). // Status = NtOpenThreadToken( NtCurrentThread(), TOKEN_QUERY, TRUE, &ClientToken ); if (NT_SUCCESS(Status)) { PRIVILEGE_SET Privilege; BOOLEAN PrivilegeHeld = FALSE; UNICODE_STRING Subsystem; // // OK, we have a token open. Now check for the specified privilege. // On return, PrivilegeHeld indicates whether the client has the // privilege, and whether we will allow the operation to succeed. // Privilege.PrivilegeCount = 1; Privilege.Control = PRIVILEGE_SET_ALL_NECESSARY; Privilege.Privilege[0].Luid = RtlConvertLongToLuid(PrivilegeId); Privilege.Privilege[0].Attributes = 0; Status = NtPrivilegeCheck( ClientToken, &Privilege, &PrivilegeHeld ); SC_ASSERT(NT_SUCCESS(Status)); // // Audit the attempt to use the privilege. // RtlInitUnicodeString(&Subsystem, SC_MANAGER_AUDIT_NAME); Status = NtPrivilegeObjectAuditAlarm( &Subsystem, // Subsystem name PrivilegeHeld ? ObjectHandle : NULL, // Object handle, to display in // the audit log ClientToken, // Client's token DesiredAccess, // Access desired by client &Privilege, // Privileges attempted to use PrivilegeHeld // Whether access was granted ); SC_ASSERT(NT_SUCCESS(Status)); if ( !PrivilegeHeld ) { Status = STATUS_PRIVILEGE_NOT_HELD; } // // Close the client token. // SecondaryStatus = NtClose( ClientToken ); ASSERT(NT_SUCCESS(SecondaryStatus)); } // // Stop impersonating the client. // SecondaryStatus = I_RpcMapWin32Status(RpcRevertToSelf()); } return Status; } DWORD ScAccessValidate( IN OUT LPSC_HANDLE_STRUCT ContextHandle, IN ACCESS_MASK DesiredAccess ) /*++ Routine Description: This function is called due to an open request. It validates the desired access based on the object type specified in the context handle structure. If the requested access is granted, it is written into the context handle structure. Arguments: ContextHandle - Supplies a pointer to the context handle structure which contains information about the object. On return, the granted access is written back to this structure if this call succeeds. DesiredAccess - Supplies the client requested desired access. Return Value: ERROR_GEN_FAILURE - Object type is unrecognizable. An internal error has occured. Returns values from calls to: ScAccessCheckAndAudit Notes: The supplied ContextHandle must be verified to be valid (i.e., non-NULL) BEFORE calling this routine. --*/ { ACCESS_MASK RequestedAccess = DesiredAccess; if (ContextHandle->Signature == SC_SIGNATURE) { // // Map the generic bits to specific and standard bits. // RtlMapGenericMask(&RequestedAccess, &ScManagerObjectMapping); // // Check to see if requested access is granted to client // return ScAccessCheckAndAudit( (LPWSTR) SC_MANAGER_AUDIT_NAME, (LPWSTR) SC_MANAGER_OBJECT_TYPE_NAME, ContextHandle->Type.ScManagerObject.DatabaseName, ContextHandle, ScManagerSd, RequestedAccess, &ScManagerObjectMapping ); } else if (ContextHandle->Signature == SERVICE_SIGNATURE) { // // Special-case the status access check instead of adding the right // for the service to set its own status to the service's default SD // because of the following reasons: // // 1. This check is tighter -- since an SC_HANDLE can be used // remotely, this prevents SetServiceStatus from now being // called remotely because the LUIDs won't match. // // 2. Modifying the SD would require lots of extra work for SDs // that are stored in the registry -- the SCM would have to // detect that there's no SERVICE_SET_STATUS access ACL on // the SD and add it (also in calls to SetServiceObjectSecurity). // // Note that if the user specifies extraneous access bits (i.e., // SERVICE_SET_STATUS & ), it will be rejected by the // ScAccessCheckAndAudit call below. // if (DesiredAccess == SERVICE_SET_STATUS) { DWORD dwError = ScStatusAccessCheck(ContextHandle->Type.ScServiceObject.ServiceRecord); if (dwError == NO_ERROR) { ContextHandle->AccessGranted = SERVICE_SET_STATUS; } return dwError; } // // Map the generic bits to specific and standard bits. // RtlMapGenericMask(&RequestedAccess, &ScServiceObjectMapping); // // Check to see if requested access is granted to client // return ScAccessCheckAndAudit( (LPWSTR) SC_MANAGER_AUDIT_NAME, (LPWSTR) SC_SERVICE_OBJECT_TYPE_NAME, ContextHandle->Type.ScServiceObject.ServiceRecord->ServiceName, ContextHandle, ContextHandle->Type.ScServiceObject.ServiceRecord->ServiceSd, RequestedAccess, &ScServiceObjectMapping ); } else { // // Unknown object type. This should not happen! // SC_LOG(ERROR, "ScAccessValidate: Unknown object type, signature=0x%08lx\n", ContextHandle->Signature); ASSERT(FALSE); return ERROR_GEN_FAILURE; } } DWORD ScAccessCheckAndAudit( IN LPWSTR SubsystemName, IN LPWSTR ObjectTypeName, IN LPWSTR ObjectName, IN OUT LPSC_HANDLE_STRUCT ContextHandle, IN PSECURITY_DESCRIPTOR SecurityDescriptor, IN ACCESS_MASK DesiredAccess, IN PGENERIC_MAPPING GenericMapping ) /*++ Routine Description: This function impersonates the caller so that it can perform access validation using NtAccessCheckAndAuditAlarm; and reverts back to itself before returning. Arguments: SubsystemName - Supplies a name string identifying the subsystem calling this routine. ObjectTypeName - Supplies the name of the type of the object being accessed. ObjectName - Supplies the name of the object being accessed. ContextHandle - Supplies the context handle to the object. On return, if this call succeeds, the granted access is written to the AccessGranted field of this structure, and the SC_HANDLE_GENERATE_ON_CLOSE bit of the Flags field indicates whether NtCloseAuditAlarm must be called when this handle is closed. SecurityDescriptor - A pointer to the Security Descriptor against which acccess is to be checked. DesiredAccess - Supplies desired acccess mask. This mask must have been previously mapped to contain no generic accesses. GenericMapping - Supplies a pointer to the generic mapping associated with this object type. Return Value: NT status mapped to Win32 errors. --*/ { NTSTATUS NtStatus; RPC_STATUS RpcStatus; UNICODE_STRING Subsystem; UNICODE_STRING ObjectType; UNICODE_STRING Object; BOOLEAN GenerateOnClose; NTSTATUS AccessStatus; RtlInitUnicodeString(&Subsystem, SubsystemName); RtlInitUnicodeString(&ObjectType, ObjectTypeName); RtlInitUnicodeString(&Object, ObjectName); if ((RpcStatus = RpcImpersonateClient(NULL)) != RPC_S_OK) { SC_LOG1(ERROR, "ScAccessCheckAndAudit: Failed to impersonate client " FORMAT_RPC_STATUS "\n", RpcStatus); ScLogEvent( NEVENT_CALL_TO_FUNCTION_FAILED, SC_RPC_IMPERSONATE, RpcStatus ); return RpcStatus; } NtStatus = NtAccessCheckAndAuditAlarm( &Subsystem, (PVOID) ContextHandle, &ObjectType, &Object, SecurityDescriptor, DesiredAccess, GenericMapping, FALSE, &ContextHandle->AccessGranted, // return access granted &AccessStatus, &GenerateOnClose ); if ((RpcStatus = RpcRevertToSelf()) != RPC_S_OK) { SC_LOG(ERROR, "ScAccessCheckAndAudit: Fail to revert to self %08lx\n", RpcStatus); ScLogEvent( NEVENT_CALL_TO_FUNCTION_FAILED, SC_RPC_REVERT, RpcStatus ); ASSERT(FALSE); return RpcStatus; } if (!NT_SUCCESS(NtStatus)) { if (NtStatus != STATUS_ACCESS_DENIED) { SC_LOG1(ERROR, "ScAccessCheckAndAudit: Error calling NtAccessCheckAndAuditAlarm " FORMAT_NTSTATUS "\n", NtStatus); } return RtlNtStatusToDosError(NtStatus); } if (GenerateOnClose) { ContextHandle->Flags |= SC_HANDLE_GENERATE_ON_CLOSE; } if (AccessStatus != STATUS_SUCCESS) { SC_LOG(SECURITY, "ScAccessCheckAndAudit: Access status is %08lx\n", AccessStatus); return RtlNtStatusToDosError(AccessStatus); } SC_LOG(SECURITY, "ScAccessCheckAndAudit: Object name %ws\n", ObjectName); SC_LOG(SECURITY, " Granted access %08lx\n", ContextHandle->AccessGranted); return NO_ERROR; } DWORD ScStatusAccessCheck( IN LPSERVICE_RECORD lpServiceRecord OPTIONAL ) { RPC_STATUS RpcStatus; DWORD dwStatus; HANDLE hThreadToken = NULL; SC_ASSERT(lpServiceRecord == NULL || ScServiceRecordLock.Have()); // // If OpenService is called for SERVICE_SET_STATUS access on a // service that's not running, the ImageRecord will be NULL // if (lpServiceRecord != NULL && lpServiceRecord->ImageRecord == NULL) { return ERROR_SERVICE_NOT_ACTIVE; } RpcStatus = RpcImpersonateClient(NULL); if (RpcStatus != RPC_S_OK) { SC_LOG1(ERROR, "ScStatusAccessCheck: Failed to impersonate client " FORMAT_RPC_STATUS "\n", RpcStatus); ScLogEvent( NEVENT_CALL_TO_FUNCTION_FAILED, SC_RPC_IMPERSONATE, RpcStatus ); return RpcStatus; } if (!OpenThreadToken(GetCurrentThread(), TOKEN_QUERY, TRUE, // Open as self &hThreadToken)) { dwStatus = GetLastError(); SC_LOG1(ERROR, "ScStatusAccessCheck: OpenThreadToken FAILED %d\n", dwStatus); } else { TOKEN_STATISTICS TokenStats; if (!GetTokenInformation(hThreadToken, TokenStatistics, // Information wanted &TokenStats, sizeof(TokenStats), // Buffer size &dwStatus)) // Size required { dwStatus = GetLastError(); SC_LOG1(ERROR, "ScCreateImageRecord: GetTokenInformation FAILED %d\n", dwStatus); } else { LUID SystemLuid = SYSTEM_LUID; if (RtlEqualLuid(&TokenStats.AuthenticationId, lpServiceRecord ? &lpServiceRecord->ImageRecord->AccountLuid : &SystemLuid)) { dwStatus = NO_ERROR; } else { dwStatus = ERROR_ACCESS_DENIED; } } CloseHandle(hThreadToken); } RpcStatus = RpcRevertToSelf(); if (RpcStatus != RPC_S_OK) { SC_LOG(ERROR, "ScStatusAccessCheck: Fail to revert to self %08lx\n", RpcStatus); ScLogEvent( NEVENT_CALL_TO_FUNCTION_FAILED, SC_RPC_REVERT, RpcStatus ); ASSERT(FALSE); return RpcStatus; } return dwStatus; } DWORD ScGetPrivilege( IN DWORD numPrivileges, IN PULONG pulPrivileges ) /*++ Routine Description: This function alters the privilege level for the current thread. It does this by duplicating the token for the current thread, and then applying the new privileges to that new token, then the current thread impersonates with that new token. Privileges can be relinquished by calling ScReleasePrivilege(). Arguments: numPrivileges - This is a count of the number of privileges in the array of privileges. pulPrivileges - This is a pointer to the array of privileges that are desired. This is an array of ULONGs. Return Value: NO_ERROR - If the operation was completely successful. Otherwise, it returns mapped return codes from the various NT functions that are called. --*/ { DWORD status; NTSTATUS ntStatus; HANDLE newToken; OBJECT_ATTRIBUTES Obja; SECURITY_QUALITY_OF_SERVICE SecurityQofS; ULONG returnLen; PTOKEN_PRIVILEGES pTokenPrivilege = NULL; DWORD i; // // Initialize the Privileges Structure // pTokenPrivilege = (PTOKEN_PRIVILEGES) LocalAlloc( LMEM_FIXED, sizeof(TOKEN_PRIVILEGES) + (sizeof(LUID_AND_ATTRIBUTES) * numPrivileges) ); if (pTokenPrivilege == NULL) { status = GetLastError(); SC_LOG(ERROR,"ScGetPrivilege:LocalAlloc Failed %d\n", status); return(status); } pTokenPrivilege->PrivilegeCount = numPrivileges; for (i = 0; i < numPrivileges; i++) { pTokenPrivilege->Privileges[i].Luid = RtlConvertLongToLuid( pulPrivileges[i]); pTokenPrivilege->Privileges[i].Attributes = SE_PRIVILEGE_ENABLED; } // // Initialize Object Attribute Structure. // InitializeObjectAttributes(&Obja,NULL,0L,NULL,NULL); // // Initialize Security Quality Of Service Structure // SecurityQofS.Length = sizeof(SECURITY_QUALITY_OF_SERVICE); SecurityQofS.ImpersonationLevel = SecurityImpersonation; SecurityQofS.ContextTrackingMode = FALSE; // Snapshot client context SecurityQofS.EffectiveOnly = FALSE; Obja.SecurityQualityOfService = &SecurityQofS; // // Duplicate our Process Token // ntStatus = NtDuplicateToken( g_hProcessToken, TOKEN_IMPERSONATE | TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &Obja, FALSE, // Duplicate the entire token TokenImpersonation, // TokenType &newToken); // Duplicate token if (!NT_SUCCESS(ntStatus)) { SC_LOG(ERROR, "ScGetPrivilege: NtDuplicateToken Failed " "FORMAT_NTSTATUS" "\n", ntStatus); LocalFree(pTokenPrivilege); return(RtlNtStatusToDosError(ntStatus)); } // // Add new privileges // ntStatus = NtAdjustPrivilegesToken( newToken, // TokenHandle FALSE, // DisableAllPrivileges pTokenPrivilege, // NewState 0, // Size of previous state buffer NULL, // No info on previous state &returnLen); // numBytes required for buffer. if (!NT_SUCCESS(ntStatus)) { SC_LOG(ERROR, "ScGetPrivilege: NtAdjustPrivilegesToken Failed " "FORMAT_NTSTATUS" "\n", ntStatus); LocalFree(pTokenPrivilege); NtClose(newToken); return(RtlNtStatusToDosError(ntStatus)); } // // Begin impersonating with the new token // ntStatus = NtSetInformationThread( NtCurrentThread(), ThreadImpersonationToken, (PVOID)&newToken, (ULONG)sizeof(HANDLE)); if (!NT_SUCCESS(ntStatus)) { SC_LOG(ERROR, "ScGetPrivilege: NtAdjustPrivilegesToken Failed " "FORMAT_NTSTATUS" "\n", ntStatus); LocalFree(pTokenPrivilege); NtClose(newToken); return(RtlNtStatusToDosError(ntStatus)); } LocalFree(pTokenPrivilege); NtClose(newToken); return(NO_ERROR); } DWORD ScReleasePrivilege( VOID ) /*++ Routine Description: This function relinquishes privileges obtained by calling ScGetPrivilege(). Arguments: none Return Value: NO_ERROR - If the operation was completely successful. Otherwise, it returns mapped return codes from the various NT functions that are called. --*/ { NTSTATUS ntStatus; HANDLE NewToken; // // Revert To Self. // NewToken = NULL; ntStatus = NtSetInformationThread( NtCurrentThread(), ThreadImpersonationToken, (PVOID)&NewToken, (ULONG)sizeof(HANDLE)); if ( !NT_SUCCESS(ntStatus) ) { return(RtlNtStatusToDosError(ntStatus)); } return(NO_ERROR); } DWORD ScGetClientSid( OUT PTOKEN_USER *UserInfo ) /*++ Routine Description: This function looks up the SID of the API caller by impersonating the caller. Arguments: UserInfo - Receives a pointer to a buffer allocated by this routine which contains the TOKEN_USER information of the caller. Return Value: Returns the NT error mapped to Win32 --*/ { DWORD status; NTSTATUS ntstatus; RPC_STATUS rpcstatus; HANDLE CurrentThreadToken = NULL; DWORD UserInfoSize; *UserInfo = NULL; if ((rpcstatus = RpcImpersonateClient(NULL)) != RPC_S_OK) { SC_LOG1( ERROR, "ScGetUserSid: Failed to impersonate client " FORMAT_RPC_STATUS "\n", rpcstatus ); ScLogEvent( NEVENT_CALL_TO_FUNCTION_FAILED, SC_RPC_IMPERSONATE, rpcstatus ); return ((DWORD) rpcstatus); } ntstatus = NtOpenThreadToken( NtCurrentThread(), TOKEN_QUERY, TRUE, // Use service controller's security // context to open thread token &CurrentThreadToken ); status = RtlNtStatusToDosError(ntstatus); if (! NT_SUCCESS(ntstatus)) { SC_LOG1(ERROR, "ScGetUserSid: NtOpenThreadToken failed " FORMAT_NTSTATUS "\n", ntstatus); goto Cleanup; } // // Call NtQueryInformationToken the first time with 0 input size to // get size of returned information. // ntstatus = NtQueryInformationToken( CurrentThreadToken, TokenUser, // User information class (PVOID) *UserInfo, // Output 0, &UserInfoSize ); if (ntstatus != STATUS_BUFFER_TOO_SMALL) { SC_LOG1(ERROR, "ScGetUserSid: NtQueryInformationToken failed " FORMAT_NTSTATUS ". Expected BUFFER_TOO_SMALL.\n", ntstatus); status = RtlNtStatusToDosError(ntstatus); goto Cleanup; } // // Allocate buffer of returned size // *UserInfo = (PTOKEN_USER)LocalAlloc( LMEM_ZEROINIT, (UINT) UserInfoSize ); if (*UserInfo == NULL) { SC_LOG1(ERROR, "ScGetUserSid: LocalAlloc failed " FORMAT_DWORD "\n", GetLastError()); status = ERROR_NOT_ENOUGH_MEMORY; goto Cleanup; } // // Call NtQueryInformationToken again with the correct buffer size. // ntstatus = NtQueryInformationToken( CurrentThreadToken, TokenUser, // User information class (PVOID) *UserInfo, // Output UserInfoSize, &UserInfoSize ); status = RtlNtStatusToDosError(ntstatus); if (! NT_SUCCESS(ntstatus)) { SC_LOG1(ERROR, "ScGetUserSid: NtQueryInformationToken failed " FORMAT_NTSTATUS "\n", ntstatus); LocalFree(*UserInfo); *UserInfo = NULL; } Cleanup: if (CurrentThreadToken != NULL) { NtClose(CurrentThreadToken); } if ((rpcstatus = RpcRevertToSelf()) != RPC_S_OK) { SC_LOG1( ERROR, "ScGetUserSid: Failed to revert to self " FORMAT_RPC_STATUS "\n", rpcstatus ); ScLogEvent( NEVENT_CALL_TO_FUNCTION_FAILED, SC_RPC_REVERT, rpcstatus ); SC_ASSERT(FALSE); return ((DWORD) rpcstatus); } return status; }