1937 lines
52 KiB
C++
1937 lines
52 KiB
C++
/*++
|
||
|
||
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;
|
||
}
|