Windows2003-3790/base/screg/sc/server/scsec.cxx
2020-09-30 16:53:55 +02:00

1937 lines
52 KiB
C++
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*++
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 & <other bits>), 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;
}