windows-nt/Source/XPSP1/NT/base/ntos/se/seaudit.c

4596 lines
135 KiB
C
Raw Normal View History

2020-09-26 03:20:57 -05:00
/*++
Copyright (c) 1989 Microsoft Corporation
Module Name:
seaudit.c
Abstract:
This Module implements the audit and alarm procedures.
Author:
Robert Reichel (robertre) 26-Nov-90
Scott Birrell (ScottBi) 17-Jan-92
Environment:
Kernel Mode
Revision History:
Richard Ward (richardw) 14-Apr-92
--*/
#include "pch.h"
#pragma hdrstop
VOID
SepProbeAndCaptureString_U (
IN PUNICODE_STRING SourceString,
OUT PUNICODE_STRING *DestString
);
VOID
SepFreeCapturedString(
IN PUNICODE_STRING CapturedString
);
VOID
SepAuditTypeList (
IN PIOBJECT_TYPE_LIST ObjectTypeList,
IN ULONG ObjectTypeListLength,
IN PNTSTATUS AccessStatus,
IN ULONG StartIndex,
OUT PBOOLEAN GenerateSuccessAudit,
OUT PBOOLEAN GenerateFailureAudit
);
VOID
SepExamineSaclEx(
IN PACL Sacl,
IN PACCESS_TOKEN Token,
IN ACCESS_MASK DesiredAccess,
IN PIOBJECT_TYPE_LIST ObjectTypeList OPTIONAL,
IN ULONG ObjectTypeListLength,
IN BOOLEAN ReturnResultList,
IN PNTSTATUS AccessStatus,
IN PACCESS_MASK GrantedAccess,
IN PSID PrincipalSelfSid,
OUT PBOOLEAN GenerateSuccessAudit,
OUT PBOOLEAN GenerateFailureAudit
);
NTSTATUS
SepAccessCheckAndAuditAlarm (
IN PUNICODE_STRING SubsystemName,
IN PVOID HandleId,
IN PHANDLE ClientToken OPTIONAL,
IN PUNICODE_STRING ObjectTypeName,
IN PUNICODE_STRING ObjectName,
IN PSECURITY_DESCRIPTOR SecurityDescriptor,
IN PSID PrincipalSelfSid,
IN ACCESS_MASK DesiredAccess,
IN AUDIT_EVENT_TYPE AuditType,
IN ULONG Flags,
IN POBJECT_TYPE_LIST ObjectTypeList OPTIONAL,
IN ULONG ObjectTypeListLength,
IN PGENERIC_MAPPING GenericMapping,
OUT PACCESS_MASK GrantedAccess,
OUT PNTSTATUS AccessStatus,
OUT PBOOLEAN GenerateOnClose,
IN BOOLEAN ReturnResultList
);
#ifdef ALLOC_PRAGMA
VOID
SepSetAuditInfoForObjectType(
IN UCHAR AceFlags,
IN ACCESS_MASK AccessMask,
IN ACCESS_MASK DesiredAccess,
IN PIOBJECT_TYPE_LIST ObjectTypeList,
IN ULONG ObjectTypeListLength,
IN BOOLEAN ReturnResultList,
IN ULONG ObjectTypeIndex,
IN PNTSTATUS AccessStatus,
IN PACCESS_MASK GrantedAccess,
IN BOOLEAN FailedMaximumAllowed,
OUT PBOOLEAN GenerateSuccessAudit,
OUT PBOOLEAN GenerateFailureAudit
);
#pragma alloc_text(PAGE,SepSinglePrivilegeCheck)
#pragma alloc_text(PAGE,SeCheckAuditPrivilege)
#pragma alloc_text(PAGE,SepProbeAndCaptureString_U)
#pragma alloc_text(PAGE,SepFreeCapturedString)
#pragma alloc_text(PAGE,NtPrivilegeObjectAuditAlarm)
#pragma alloc_text(PAGE,SePrivilegeObjectAuditAlarm)
#pragma alloc_text(PAGE,NtPrivilegedServiceAuditAlarm)
#pragma alloc_text(PAGE,SePrivilegedServiceAuditAlarm)
#pragma alloc_text(PAGE,SepAccessCheckAndAuditAlarm)
#pragma alloc_text(PAGE,NtAccessCheckAndAuditAlarm)
#pragma alloc_text(PAGE,NtAccessCheckByTypeAndAuditAlarm)
#pragma alloc_text(PAGE,NtAccessCheckByTypeResultListAndAuditAlarm)
#pragma alloc_text(PAGE,NtAccessCheckByTypeResultListAndAuditAlarmByHandle)
#pragma alloc_text(PAGE,NtOpenObjectAuditAlarm)
#pragma alloc_text(PAGE,NtCloseObjectAuditAlarm)
#pragma alloc_text(PAGE,NtDeleteObjectAuditAlarm)
#pragma alloc_text(PAGE,SeOpenObjectAuditAlarm)
#pragma alloc_text(PAGE,SeOpenObjectForDeleteAuditAlarm)
#pragma alloc_text(PAGE,SeObjectReferenceAuditAlarm)
#pragma alloc_text(PAGE,SeAuditHandleCreation)
#pragma alloc_text(PAGE,SeCloseObjectAuditAlarm)
#pragma alloc_text(PAGE,SeDeleteObjectAuditAlarm)
#pragma alloc_text(PAGE,SepExamineSacl)
#pragma alloc_text(PAGE,SepAuditTypeList)
#pragma alloc_text(PAGE,SepSetAuditInfoForObjectType)
#pragma alloc_text(PAGE,SepExamineSaclEx)
#pragma alloc_text(INIT,SepInitializePrivilegeFilter)
#pragma alloc_text(PAGE,SepFilterPrivilegeAudits)
#pragma alloc_text(PAGE,SeAuditingFileOrGlobalEvents)
#pragma alloc_text(PAGE,SeAuditingFileEvents)
#pragma alloc_text(PAGE,SeAuditingHardLinkEvents)
#endif
//
// Flag to tell us if we are auditing shutdown events.
//
// Move this to seglobal.c
//
//BOOLEAN SepAuditShutdownEvents = FALSE;
//
// Private useful routines
//
//
// This routine is to be called to do simple checks of single privileges
// against the passed token.
//
// DO NOT CALL THIS TO CHECK FOR SeTcbPrivilege SINCE THAT MUST
// BE CHECKED AGAINST THE PRIMARY TOKEN ONLY!
//
BOOLEAN
SepSinglePrivilegeCheck (
LUID DesiredPrivilege,
IN PACCESS_TOKEN Token,
IN KPROCESSOR_MODE PreviousMode
)
/*++
Routine Description:
Determines if the passed token has the passed privilege.
Arguments:
DesiredPrivilege - The privilege to be tested for.
Token - The token being examined.
PreviousMode - The previous processor mode.
Return Value:
Returns TRUE of the subject has the passed privilege, FALSE otherwise.
--*/
{
LUID_AND_ATTRIBUTES Privilege;
BOOLEAN Result;
PAGED_CODE();
//
// Don't let anyone call this to test for SeTcbPrivilege
//
ASSERT(!((DesiredPrivilege.LowPart == SeTcbPrivilege.LowPart) &&
(DesiredPrivilege.HighPart == SeTcbPrivilege.HighPart)));
Privilege.Luid = DesiredPrivilege;
Privilege.Attributes = 0;
Result = SepPrivilegeCheck(
Token,
&Privilege,
1,
PRIVILEGE_SET_ALL_NECESSARY,
PreviousMode
);
return(Result);
}
BOOLEAN
SeCheckAuditPrivilege (
IN PSECURITY_SUBJECT_CONTEXT SubjectSecurityContext,
IN KPROCESSOR_MODE PreviousMode
)
/*++
Routine Description:
This routine specifically searches the primary token (rather than
the effective token) of the calling process for SeAuditPrivilege.
In order to do this it must call the underlying worker
SepPrivilegeCheck directly, to ensure that the correct token is
searched
Arguments:
SubjectSecurityContext - The subject being examined.
PreviousMode - The previous processor mode.
Return Value:
Returns TRUE if the subject has SeAuditPrivilege, FALSE otherwise.
--*/
{
PRIVILEGE_SET RequiredPrivileges;
BOOLEAN AccessGranted;
PAGED_CODE();
RequiredPrivileges.PrivilegeCount = 1;
RequiredPrivileges.Control = PRIVILEGE_SET_ALL_NECESSARY;
RequiredPrivileges.Privilege[0].Luid = SeAuditPrivilege;
RequiredPrivileges.Privilege[0].Attributes = 0;
AccessGranted = SepPrivilegeCheck(
SubjectSecurityContext->PrimaryToken, // token
RequiredPrivileges.Privilege, // privilege set
RequiredPrivileges.PrivilegeCount, // privilege count
PRIVILEGE_SET_ALL_NECESSARY, // privilege control
PreviousMode // previous mode
);
if ( PreviousMode != KernelMode ) {
SePrivilegedServiceAuditAlarm (
NULL,
SubjectSecurityContext,
&RequiredPrivileges,
AccessGranted
);
}
return( AccessGranted );
}
VOID
SepProbeAndCaptureString_U (
IN PUNICODE_STRING SourceString,
OUT PUNICODE_STRING *DestString
)
/*++
Routine Description:
Helper routine to probe and capture a Unicode string argument.
This routine may fail due to lack of memory, in which case,
it will return a NULL pointer in the output parameter.
Arguments:
SourceString - Pointer to a Unicode string to be captured.
DestString - Returns a pointer to a captured Unicode string. This
will be one contiguous structure, and thus may be freed by
a single call to ExFreePool().
Return Value:
None.
--*/
{
UNICODE_STRING InputString;
ULONG Length;
NTSTATUS Status;
PAGED_CODE();
//
// Initialize the object name descriptor and capture the specified name
// string.
//
*DestString = NULL;
Status = STATUS_SUCCESS;
try {
//
// Probe and capture the name string descriptor and probe the
// name string, if necessary.
//
InputString = ProbeAndReadUnicodeString(SourceString);
ProbeForRead(InputString.Buffer,
InputString.Length,
sizeof(WCHAR));
//
// If the length of the string is not an even multiple of the
// size of a UNICODE character or cannot be zero terminated,
// then return an error.
//
Length = InputString.Length;
if (((Length & (sizeof(WCHAR) - 1)) != 0) ||
(Length == (MAXUSHORT - sizeof(WCHAR) + 1))) {
Status = STATUS_INVALID_PARAMETER;
} else {
//
// Allocate a buffer for the specified name string.
//
*DestString = ExAllocatePoolWithTag(
PagedPool,
InputString.Length + sizeof(UNICODE_STRING),
'sUeS');
if (*DestString == NULL) {
Status = STATUS_INSUFFICIENT_RESOURCES;
} else {
(*DestString)->Length = InputString.Length;
(*DestString)->MaximumLength = InputString.Length;
(*DestString)->Buffer = (PWSTR) ((*DestString) + 1);
if (InputString.Length != 0) {
RtlCopyMemory(
(*DestString)->Buffer,
InputString.Buffer,
InputString.Length);
}
}
}
} except(ExSystemExceptionFilter()) {
Status = GetExceptionCode();
if (*DestString != NULL) {
ExFreePool(*DestString);
*DestString = NULL;
}
}
return;
}
VOID
SepFreeCapturedString(
IN PUNICODE_STRING CapturedString
)
/*++
Routine Description:
Frees a string captured by SepProbeAndCaptureString.
Arguments:
CapturedString - Supplies a pointer to a string previously captured
by SepProbeAndCaptureString.
Return Value:
None.
--*/
{
PAGED_CODE();
ExFreePool( CapturedString );
return;
}
////////////////////////////////////////////////////////////////////////
// //
// Privileged Object Audit Alarms //
// //
////////////////////////////////////////////////////////////////////////
NTSTATUS
NtPrivilegeObjectAuditAlarm (
IN PUNICODE_STRING SubsystemName,
IN PVOID HandleId,
IN HANDLE ClientToken,
IN ACCESS_MASK DesiredAccess,
IN PPRIVILEGE_SET Privileges,
IN BOOLEAN AccessGranted
)
/*++
Routine Description:
This routine is used to generate audit and alarm messages when an
attempt is made to perform privileged operations on a protected
subsystem object after the object is already opened. This routine may
result in several messages being generated and sent to Port objects.
This may result in a significant latency before returning. Design of
routines that must call this routine must take this potential latency
into account. This may have an impact on the approach taken for data
structure mutex locking, for example.
This API requires the caller have SeAuditPrivilege privilege. The test
for this privilege is always against the primary token of the calling
process, allowing the caller to be impersonating a client during the
call with no ill effects.
Arguments:
SubsystemName - Supplies a name string identifying the subsystem
calling the routine.
HandleId - A unique value representing the client's handle to the
object.
ClientToken - A handle to a token object representing the client that
requested the operation. This handle must be obtained from a
communication session layer, such as from an LPC Port or Local
Named Pipe, to prevent possible security policy violations.
DesiredAccess - The desired access mask. This mask must have been
previously mapped to contain no generic accesses.
Privileges - The set of privileges required for the requested
operation. Those privileges that were held by the subject are
marked using the UsedForAccess flag of the attributes
associated with each privilege.
AccessGranted - Indicates whether the requested access was granted or
not. A value of TRUE indicates the access was granted. A value of
FALSE indicates the access was not granted.
Return value:
--*/
{
KPROCESSOR_MODE PreviousMode;
PUNICODE_STRING CapturedSubsystemName = NULL;
PPRIVILEGE_SET CapturedPrivileges = NULL;
ULONG PrivilegeParameterLength;
ULONG PrivilegeCount;
SECURITY_SUBJECT_CONTEXT SubjectSecurityContext;
BOOLEAN Result;
PTOKEN Token;
NTSTATUS Status;
BOOLEAN AuditPerformed;
PAGED_CODE();
PreviousMode = KeGetPreviousMode();
ASSERT(PreviousMode != KernelMode);
Status = ObReferenceObjectByHandle(
ClientToken, // Handle
TOKEN_QUERY, // DesiredAccess
SeTokenObjectType, // ObjectType
PreviousMode, // AccessMode
(PVOID *)&Token, // Object
NULL // GrantedAccess
);
if (!NT_SUCCESS( Status )) {
return( Status );
}
//
// If the passed token is an impersonation token, make sure
// it is at SecurityIdentification or above.
//
if (Token->TokenType == TokenImpersonation) {
if (Token->ImpersonationLevel < SecurityIdentification) {
ObDereferenceObject( (PVOID)Token );
return( STATUS_BAD_IMPERSONATION_LEVEL );
}
}
// //
// // Make sure the passed token is an impersonation token...
// //
//
// if (Token->TokenType != TokenImpersonation) {
//
// ObDereferenceObject( (PVOID)Token );
//
// return( STATUS_NO_IMPERSONATION_TOKEN );
//
// }
//
// //
// // ...and at a high enough impersonation level
// //
//
// if (Token->ImpersonationLevel < SecurityIdentification) {
//
// ObDereferenceObject( (PVOID)Token );
//
// return( STATUS_BAD_IMPERSONATION_LEVEL );
//
// }
//
// Check for SeAuditPrivilege
//
SeCaptureSubjectContext ( &SubjectSecurityContext );
Result = SeCheckAuditPrivilege (
&SubjectSecurityContext,
PreviousMode
);
if (!Result) {
ObDereferenceObject( (PVOID)Token );
SeReleaseSubjectContext ( &SubjectSecurityContext );
return(STATUS_PRIVILEGE_NOT_HELD);
}
try {
SepProbeAndCaptureString_U ( SubsystemName,
&CapturedSubsystemName );
ProbeForReadSmallStructure(
Privileges,
sizeof(PRIVILEGE_SET),
sizeof(ULONG)
);
PrivilegeCount = Privileges->PrivilegeCount;
if (!IsValidElementCount(PrivilegeCount, LUID_AND_ATTRIBUTES)) {
Status= STATUS_INVALID_PARAMETER;
leave ;
}
PrivilegeParameterLength = (ULONG)sizeof(PRIVILEGE_SET) +
((PrivilegeCount - ANYSIZE_ARRAY) *
(ULONG)sizeof(LUID_AND_ATTRIBUTES) );
ProbeForRead(
Privileges,
PrivilegeParameterLength,
sizeof(ULONG)
);
CapturedPrivileges = ExAllocatePoolWithTag( PagedPool,
PrivilegeParameterLength,
'rPeS'
);
if (CapturedPrivileges != NULL) {
RtlCopyMemory ( CapturedPrivileges,
Privileges,
PrivilegeParameterLength );
CapturedPrivileges->PrivilegeCount = PrivilegeCount;
}
} except (EXCEPTION_EXECUTE_HANDLER) {
Status = GetExceptionCode();
}
if (!NT_SUCCESS(Status)) {
if (CapturedPrivileges != NULL) {
ExFreePool( CapturedPrivileges );
}
if (CapturedSubsystemName != NULL) {
SepFreeCapturedString ( CapturedSubsystemName );
}
SeReleaseSubjectContext ( &SubjectSecurityContext );
ObDereferenceObject( (PVOID)Token );
return Status;
}
//
// No need to lock the token, because the only thing we're going
// to reference in it is the User's Sid, which cannot be changed.
//
//
// SepPrivilegeObjectAuditAlarm will check the global flags
// to determine if we're supposed to be auditing here.
//
AuditPerformed = SepAdtPrivilegeObjectAuditAlarm (
CapturedSubsystemName,
HandleId,
Token, // ClientToken
SubjectSecurityContext.PrimaryToken, // PrimaryToken
SubjectSecurityContext.ProcessAuditId,
DesiredAccess,
CapturedPrivileges,
AccessGranted
);
if (CapturedPrivileges != NULL) {
ExFreePool( CapturedPrivileges );
}
if (CapturedSubsystemName != NULL) {
SepFreeCapturedString ( CapturedSubsystemName );
}
SeReleaseSubjectContext ( &SubjectSecurityContext );
ObDereferenceObject( (PVOID)Token );
return(STATUS_SUCCESS);
}
VOID
SePrivilegeObjectAuditAlarm(
IN HANDLE Handle,
IN PSECURITY_SUBJECT_CONTEXT SubjectSecurityContext,
IN ACCESS_MASK DesiredAccess,
IN PPRIVILEGE_SET Privileges,
IN BOOLEAN AccessGranted,
IN KPROCESSOR_MODE AccessMode
)
/*++
Routine Description:
This routine is used by object methods that perform privileged
operations to generate audit and alarm messages related to the use
of privileges, or attempts to use privileges.
Arguments:
Object - Address of the object accessed. This value will not be
used as a pointer (referenced). It is necessary only to enter
into log messages.
Handle - Provides the handle value assigned for the open.
SecurityDescriptor - A pointer to the security descriptor of the
object being accessed.
SubjectSecurityContext - A pointer to the captured security
context of the subject attempting to open the object.
DesiredAccess - The desired access mask. This mask must have been
previously mapped to contain no generic accesses.
Privileges - Points to a set of privileges required for the access
attempt. Those privileges that were held by the subject are
marked using the UsedForAccess flag of the PRIVILEGE_ATTRIBUTES
associated with each privilege.
AccessGranted - Indicates whether the access was granted or
denied. A value of TRUE indicates the access was allowed. A
value of FALSE indicates the access was denied.
AccessMode - Indicates the access mode used for the access check.
Messages will not be generated by kernel mode accesses.
Return Value:
None.
--*/
{
BOOLEAN AuditPerformed;
PAGED_CODE();
if (AccessMode != KernelMode) {
AuditPerformed = SepAdtPrivilegeObjectAuditAlarm (
(PUNICODE_STRING)&SeSubsystemName,
Handle,
SubjectSecurityContext->ClientToken,
SubjectSecurityContext->PrimaryToken,
SubjectSecurityContext->ProcessAuditId,
DesiredAccess,
Privileges,
AccessGranted
);
}
}
////////////////////////////////////////////////////////////////////////
// //
// Privileged Service Audit Alarms //
// //
////////////////////////////////////////////////////////////////////////
NTSTATUS
NtPrivilegedServiceAuditAlarm (
IN PUNICODE_STRING SubsystemName,
IN PUNICODE_STRING ServiceName,
IN HANDLE ClientToken,
IN PPRIVILEGE_SET Privileges,
IN BOOLEAN AccessGranted
)
/*++
Routine Description:
This routine is used to generate audit and alarm messages when an
attempt is made to perform privileged system service operations. This
routine may result in several messages being generated and sent to Port
objects. This may result in a significant latency before returning.
Design of routines that must call this routine must take this potential
latency into account. This may have an impact on the approach taken
for data structure mutex locking, for example.
This API requires the caller have SeAuditPrivilege privilege. The test
for this privilege is always against the primary token of the calling
process, allowing the caller to be impersonating a client during the
call with no ill effects
Arguments:
SubsystemName - Supplies a name string identifying the subsystem
calling the routine.
ServiceName - Supplies a name of the privileged subsystem service. For
example, "RESET RUNTIME LOCAL SECURITY POLICY" might be specified
by a Local Security Authority service used to update the local
security policy database.
ClientToken - A handle to a token object representing the client that
requested the operation. This handle must be obtained from a
communication session layer, such as from an LPC Port or Local
Named Pipe, to prevent possible security policy violations.
Privileges - Points to a set of privileges required to perform the
privileged operation. Those privileges that were held by the
subject are marked using the UsedForAccess flag of the
attributes associated with each privilege.
AccessGranted - Indicates whether the requested access was granted or
not. A value of TRUE indicates the access was granted. A value of
FALSE indicates the access was not granted.
Return value:
--*/
{
PPRIVILEGE_SET CapturedPrivileges = NULL;
ULONG PrivilegeParameterLength = 0;
BOOLEAN Result;
SECURITY_SUBJECT_CONTEXT SubjectSecurityContext;
KPROCESSOR_MODE PreviousMode;
PUNICODE_STRING CapturedSubsystemName = NULL;
PUNICODE_STRING CapturedServiceName = NULL;
NTSTATUS Status;
PTOKEN Token;
ULONG PrivilegeCount;
PAGED_CODE();
PreviousMode = KeGetPreviousMode();
ASSERT(PreviousMode != KernelMode);
Status = ObReferenceObjectByHandle(
ClientToken, // Handle
TOKEN_QUERY, // DesiredAccess
SeTokenObjectType, // ObjectType
PreviousMode, // AccessMode
(PVOID *)&Token, // Object
NULL // GrantedAccess
);
if ( !NT_SUCCESS( Status )) {
return( Status );
}
//
// If the passed token is an impersonation token, make sure
// it is at SecurityIdentification or above.
//
if (Token->TokenType == TokenImpersonation) {
if (Token->ImpersonationLevel < SecurityIdentification) {
ObDereferenceObject( (PVOID)Token );
return( STATUS_BAD_IMPERSONATION_LEVEL );
}
}
// //
// // Make sure the passed token is an impersonation token...
// //
//
// if (Token->TokenType != TokenImpersonation) {
//
// ObDereferenceObject( (PVOID)Token );
//
// return( STATUS_NO_IMPERSONATION_TOKEN );
//
// }
//
// //
// // ...and at a high enough impersonation level
// //
//
// if (Token->ImpersonationLevel < SecurityIdentification) {
//
// ObDereferenceObject( (PVOID)Token );
//
// return( STATUS_BAD_IMPERSONATION_LEVEL );
//
// }
//
// Check for SeAuditPrivilege
//
SeCaptureSubjectContext ( &SubjectSecurityContext );
Result = SeCheckAuditPrivilege (
&SubjectSecurityContext,
PreviousMode
);
if (!Result) {
ObDereferenceObject( (PVOID)Token );
SeReleaseSubjectContext ( &SubjectSecurityContext );
return(STATUS_PRIVILEGE_NOT_HELD);
}
try {
if ( ARGUMENT_PRESENT( SubsystemName )) {
SepProbeAndCaptureString_U ( SubsystemName,
&CapturedSubsystemName );
}
if ( ARGUMENT_PRESENT( ServiceName )) {
SepProbeAndCaptureString_U ( ServiceName,
&CapturedServiceName );
}
ProbeForReadSmallStructure(
Privileges,
sizeof(PRIVILEGE_SET),
sizeof(ULONG)
);
PrivilegeCount = Privileges->PrivilegeCount;
if (!IsValidElementCount( PrivilegeCount, LUID_AND_ATTRIBUTES ) ) {
Status = STATUS_INVALID_PARAMETER;
leave ;
}
PrivilegeParameterLength = (ULONG)sizeof(PRIVILEGE_SET) +
((PrivilegeCount - ANYSIZE_ARRAY) *
(ULONG)sizeof(LUID_AND_ATTRIBUTES) );
ProbeForRead(
Privileges,
PrivilegeParameterLength,
sizeof(ULONG)
);
CapturedPrivileges = ExAllocatePoolWithTag( PagedPool,
PrivilegeParameterLength,
'rPeS'
);
//
// If ExAllocatePool has failed, too bad. Carry on and do as much of the
// audit as we can.
//
if (CapturedPrivileges != NULL) {
RtlCopyMemory ( CapturedPrivileges,
Privileges,
PrivilegeParameterLength );
CapturedPrivileges->PrivilegeCount = PrivilegeCount;
}
} except (EXCEPTION_EXECUTE_HANDLER) {
Status = GetExceptionCode();
}
if (!NT_SUCCESS(Status)) {
if (CapturedSubsystemName != NULL) {
SepFreeCapturedString ( CapturedSubsystemName );
}
if (CapturedServiceName != NULL) {
SepFreeCapturedString ( CapturedServiceName );
}
if (CapturedPrivileges != NULL) {
ExFreePool ( CapturedPrivileges );
}
SeReleaseSubjectContext ( &SubjectSecurityContext );
ObDereferenceObject( (PVOID)Token );
return Status;
}
//
// The AuthenticationId is in the read-only part of the token,
// so we may reference it without having the token read-locked.
//
SepAdtPrivilegedServiceAuditAlarm ( CapturedSubsystemName,
CapturedServiceName,
Token,
SubjectSecurityContext.PrimaryToken,
CapturedPrivileges,
AccessGranted );
if (CapturedSubsystemName != NULL) {
SepFreeCapturedString ( CapturedSubsystemName );
}
if (CapturedServiceName != NULL) {
SepFreeCapturedString ( CapturedServiceName );
}
if (CapturedPrivileges != NULL) {
ExFreePool ( CapturedPrivileges );
}
ObDereferenceObject( (PVOID)Token );
SeReleaseSubjectContext ( &SubjectSecurityContext );
return(STATUS_SUCCESS);
}
VOID
SePrivilegedServiceAuditAlarm (
IN PUNICODE_STRING ServiceName,
IN PSECURITY_SUBJECT_CONTEXT SubjectSecurityContext,
IN PPRIVILEGE_SET Privileges,
IN BOOLEAN AccessGranted
)
/*++
Routine Description:
This routine is to be called whenever a privileged system service
is attempted. It should be called immediately after the privilege
check regardless of whether or not the test succeeds.
Arguments:
ServiceName - Supplies the name of the privileged system service.
SubjectSecurityContext - The subject security context representing
the caller of the system service.
Privileges - Supplies a privilge set containing the privilege(s)
required for the access.
AccessGranted - Supplies the results of the privilege test.
Return Value:
None.
--*/
{
PTOKEN Token;
PAGED_CODE();
if ( SepAdtAuditThisEvent( AuditCategoryPrivilegeUse, &AccessGranted ) &&
SepFilterPrivilegeAudits( Privileges )) {
Token = (PTOKEN)EffectiveToken( SubjectSecurityContext );
if ( RtlEqualSid( SeLocalSystemSid, SepTokenUserSid( Token ))) {
return;
}
SepAdtPrivilegedServiceAuditAlarm (
(PUNICODE_STRING)&SeSubsystemName,
ServiceName,
SubjectSecurityContext->ClientToken,
SubjectSecurityContext->PrimaryToken,
Privileges,
AccessGranted
);
}
return;
}
NTSTATUS
SepAccessCheckAndAuditAlarm (
IN PUNICODE_STRING SubsystemName,
IN PVOID HandleId,
IN PHANDLE ClientToken OPTIONAL,
IN PUNICODE_STRING ObjectTypeName,
IN PUNICODE_STRING ObjectName,
IN PSECURITY_DESCRIPTOR SecurityDescriptor,
IN PSID PrincipalSelfSid,
IN ACCESS_MASK DesiredAccess,
IN AUDIT_EVENT_TYPE AuditType,
IN ULONG Flags,
IN POBJECT_TYPE_LIST ObjectTypeList OPTIONAL,
IN ULONG ObjectTypeListLength,
IN PGENERIC_MAPPING GenericMapping,
OUT PACCESS_MASK GrantedAccess,
OUT PNTSTATUS AccessStatus,
OUT PBOOLEAN GenerateOnClose,
IN BOOLEAN ReturnResultList
)
/*++
Routine Description:
This system service is used to perform both an access validation and
generate the corresponding audit and alarm messages. This service may
only be used by a protected server that chooses to impersonate its
client and thereby specifies the client security context implicitly.
Arguments:
SubsystemName - Supplies a name string identifying the subsystem
calling the routine.
HandleId - A unique value that will be used to represent the client's
handle to the object. This value is ignored (and may be re-used)
if the access is denied.
ClientToken - Supplies the client token so that the caller does not have
to impersonate before making the kernel call.
ObjectTypeName - Supplies the name of the type of the object being
created or accessed.
ObjectName - Supplies the name of the object being created or accessed.
SecurityDescriptor - A pointer to the Security Descriptor against which
acccess is to be checked.
DesiredAccess - The desired acccess mask. This mask must have been
previously mapped to contain no generic accesses.
AuditType - Specifies the type of audit to be generated. Valid values
are: AuditEventObjectAccess and AuditEventDirectoryServiceAccess.
Flags - Flags modifying the execution of the API:
AUDIT_ALLOW_NO_PRIVILEGE - If the called does not have AuditPrivilege,
the call will silently continue to check access and will
generate no audit.
ObjectTypeList - Supplies a list of GUIDs representing the object (and
sub-objects) being accessed. If no list is present, AccessCheckByType
behaves identically to AccessCheck.
ObjectTypeListLength - Specifies the number of elements in the ObjectTypeList.
GenericMapping - Supplies a pointer to the generic mapping associated
with this object type.
ObjectCreation - A boolean flag indicated whether the access will
result in a new object being created if granted. A value of TRUE
indicates an object will be created, FALSE indicates an existing
object will be opened.
GrantedAccess - Receives a masking indicating which accesses have been
granted.
AccessStatus - Receives an indication of the success or failure of the
access check. If access is granted, STATUS_SUCCESS is returned.
If access is denied, a value appropriate for return to the client
is returned. This will be STATUS_ACCESS_DENIED or, when mandatory
access controls are implemented, STATUS_OBJECT_NOT_FOUND.
GenerateOnClose - Points to a boolean that is set by the audity
generation routine and must be passed to NtCloseObjectAuditAlarm
when the object handle is closed.
ReturnResultList - If true, GrantedAccess and AccessStatus are actually
arrays of entries ObjectTypeListLength elements long.
Return Value:
STATUS_SUCCESS - Indicates the call completed successfully. In this
case, ClientStatus receives the result of the access check.
STATUS_PRIVILEGE_NOT_HELD - Indicates the caller does not have
sufficient privilege to use this privileged system service.
--*/
{
SECURITY_SUBJECT_CONTEXT SubjectSecurityContext;
NTSTATUS Status = STATUS_SUCCESS;
ACCESS_MASK LocalGrantedAccess = (ACCESS_MASK)0;
PACCESS_MASK LocalGrantedAccessPointer = NULL;
BOOLEAN LocalGrantedAccessAllocated = FALSE;
NTSTATUS LocalAccessStatus = STATUS_UNSUCCESSFUL;
PNTSTATUS LocalAccessStatusPointer = NULL;
BOOLEAN LocalGenerateOnClose = FALSE;
POLICY_AUDIT_EVENT_TYPE NtAuditType;
KPROCESSOR_MODE PreviousMode;
PUNICODE_STRING CapturedSubsystemName = (PUNICODE_STRING) NULL;
PUNICODE_STRING CapturedObjectTypeName = (PUNICODE_STRING) NULL;
PUNICODE_STRING CapturedObjectName = (PUNICODE_STRING) NULL;
PSECURITY_DESCRIPTOR CapturedSecurityDescriptor = (PSECURITY_DESCRIPTOR) NULL;
PSID CapturedPrincipalSelfSid = NULL;
PIOBJECT_TYPE_LIST LocalObjectTypeList = NULL;
ACCESS_MASK PreviouslyGrantedAccess = (ACCESS_MASK)0;
GENERIC_MAPPING LocalGenericMapping;
PPRIVILEGE_SET PrivilegeSet = NULL;
BOOLEAN Result;
BOOLEAN AccessGranted;
BOOLEAN AccessDenied;
BOOLEAN GenerateSuccessAudit = FALSE;
BOOLEAN GenerateFailureAudit = FALSE;
LUID OperationId;
BOOLEAN AuditPerformed;
BOOLEAN AvoidAudit = FALSE;
PTOKEN NewToken = NULL;
PTOKEN OldToken = NULL;
BOOLEAN TokenSwapped = FALSE;
PAGED_CODE();
PreviousMode = KeGetPreviousMode();
ASSERT( PreviousMode != KernelMode );
//
// Capture the subject Context
//
SeCaptureSubjectContext ( &SubjectSecurityContext );
//
// Convert AuditType
//
if ( AuditType == AuditEventObjectAccess ) {
NtAuditType = AuditCategoryObjectAccess;
} else if ( AuditType == AuditEventDirectoryServiceAccess ) {
NtAuditType = AuditCategoryDirectoryServiceAccess;
} else {
Status = STATUS_INVALID_PARAMETER;
goto Cleanup;
}
//
// Impersonation checks should be done only if the ClientToken is NULL.
//
if ( !ARGUMENT_PRESENT( ClientToken ) ) {
//
// Make sure we're impersonating a client...
//
if ( (SubjectSecurityContext.ClientToken == NULL) ) {
Status = STATUS_NO_IMPERSONATION_TOKEN;
goto Cleanup;
}
//
// ...and at a high enough impersonation level
//
if (SubjectSecurityContext.ImpersonationLevel < SecurityIdentification) {
Status = STATUS_BAD_IMPERSONATION_LEVEL;
goto Cleanup;
}
}
try {
if ( ReturnResultList ) {
if ( ObjectTypeListLength == 0 ) {
Status = STATUS_INVALID_PARAMETER;
leave;
}
if (!IsValidElementCount(ObjectTypeListLength, ULONG)) {
Status = STATUS_INVALID_PARAMETER;
leave;
}
ProbeForWrite(
AccessStatus,
sizeof(NTSTATUS) * ObjectTypeListLength,
sizeof(ULONG)
);
ProbeForWrite(
GrantedAccess,
sizeof(ACCESS_MASK) * ObjectTypeListLength,
sizeof(ULONG)
);
} else {
ProbeForWriteUlong((PULONG)AccessStatus);
ProbeForWriteUlong((PULONG)GrantedAccess);
}
ProbeForReadSmallStructure(
GenericMapping,
sizeof(GENERIC_MAPPING),
sizeof(ULONG)
);
LocalGenericMapping = *GenericMapping;
} except (EXCEPTION_EXECUTE_HANDLER) {
Status = GetExceptionCode();
}
if (!NT_SUCCESS(Status)) {
goto Cleanup;
}
if ( ARGUMENT_PRESENT( ClientToken ) ) {
Status = ObReferenceObjectByHandle(
*ClientToken, // Handle
(ACCESS_MASK)TOKEN_QUERY, // DesiredAccess
SeTokenObjectType, // ObjectType
PreviousMode, // AccessMode
(PVOID *)&NewToken, // Object
NULL // GrantedAccess
);
if (!NT_SUCCESS(Status)) {
NewToken = NULL;
goto Cleanup;
}
//
// Save the old token so that it can be recovered before
// SeReleaseSubjectContext.
//
OldToken = SubjectSecurityContext.ClientToken;
//
// Set the impersonation token to the one that has been obtained thru
// ClientToken handle. This must be freed later in Cleanup.
//
SubjectSecurityContext.ClientToken = NewToken;
TokenSwapped = TRUE;
}
//
// Check for SeAuditPrivilege
//
Result = SeCheckAuditPrivilege (
&SubjectSecurityContext,
PreviousMode
);
if (!Result) {
if ( Flags & AUDIT_ALLOW_NO_PRIVILEGE ) {
AvoidAudit = TRUE;
} else {
Status = STATUS_PRIVILEGE_NOT_HELD;
goto Cleanup;
}
}
if (DesiredAccess &
( GENERIC_READ | GENERIC_WRITE | GENERIC_EXECUTE | GENERIC_ALL )) {
Status = STATUS_GENERIC_NOT_MAPPED;
goto Cleanup;
}
//
// Capture the passed security descriptor.
//
// SeCaptureSecurityDescriptor probes the input security descriptor,
// so we don't have to
//
Status = SeCaptureSecurityDescriptor (
SecurityDescriptor,
PreviousMode,
PagedPool,
FALSE,
&CapturedSecurityDescriptor
);
if (!NT_SUCCESS(Status) ) {
CapturedSecurityDescriptor = NULL;
goto Cleanup;
}
if ( CapturedSecurityDescriptor == NULL ) {
Status = STATUS_INVALID_SECURITY_DESCR;
goto Cleanup;
}
//
// A valid security descriptor must have an owner and a group
//
if ( RtlpOwnerAddrSecurityDescriptor(
(PISECURITY_DESCRIPTOR)CapturedSecurityDescriptor
) == NULL ||
RtlpGroupAddrSecurityDescriptor(
(PISECURITY_DESCRIPTOR)CapturedSecurityDescriptor
) == NULL ) {
Status = STATUS_INVALID_SECURITY_DESCR;
goto Cleanup;
}
//
// Probe and capture the STRING arguments
//
try {
ProbeForWriteBoolean(GenerateOnClose);
SepProbeAndCaptureString_U ( SubsystemName, &CapturedSubsystemName );
SepProbeAndCaptureString_U ( ObjectTypeName, &CapturedObjectTypeName );
SepProbeAndCaptureString_U ( ObjectName, &CapturedObjectName );
} except (EXCEPTION_EXECUTE_HANDLER) {
Status = GetExceptionCode();
goto Cleanup;
}
//
// Capture the PrincipalSelfSid.
//
if ( PrincipalSelfSid != NULL ) {
Status = SeCaptureSid(
PrincipalSelfSid,
PreviousMode,
NULL, 0,
PagedPool,
TRUE,
&CapturedPrincipalSelfSid );
if (!NT_SUCCESS(Status)) {
CapturedPrincipalSelfSid = NULL;
goto Cleanup;
}
}
//
// Capture any Object type list
//
Status = SeCaptureObjectTypeList( ObjectTypeList,
ObjectTypeListLength,
PreviousMode,
&LocalObjectTypeList );
if (!NT_SUCCESS(Status)) {
goto Cleanup;
}
//
// See if anything (or everything) in the desired access can be
// satisfied by privileges.
//
Status = SePrivilegePolicyCheck(
&DesiredAccess,
&PreviouslyGrantedAccess,
&SubjectSecurityContext,
NULL,
&PrivilegeSet,
PreviousMode
);
SeLockSubjectContext( &SubjectSecurityContext );
if (!NT_SUCCESS( Status )) {
AccessGranted = FALSE;
AccessDenied = TRUE;
LocalAccessStatus = Status;
if ( ReturnResultList ) {
ULONG ResultListIndex;
LocalGrantedAccessPointer =
ExAllocatePoolWithTag( PagedPool, (sizeof(ACCESS_MASK)+sizeof(NTSTATUS)) * ObjectTypeListLength, 'aGeS' );
if (LocalGrantedAccessPointer == NULL) {
SeUnlockSubjectContext( &SubjectSecurityContext );
Status = STATUS_INSUFFICIENT_RESOURCES;
goto Cleanup;
}
LocalGrantedAccessAllocated = TRUE;
LocalAccessStatusPointer = (PNTSTATUS)(LocalGrantedAccessPointer + ObjectTypeListLength);
for ( ResultListIndex=0; ResultListIndex<ObjectTypeListLength; ResultListIndex++ ) {
LocalGrantedAccessPointer[ResultListIndex] = LocalGrantedAccess;
LocalAccessStatusPointer[ResultListIndex] = LocalAccessStatus;
}
} else {
LocalGrantedAccessPointer = &LocalGrantedAccess;
LocalAccessStatusPointer = &LocalAccessStatus;
}
} else {
//
// If the user in the token is the owner of the object, we
// must automatically grant ReadControl and WriteDac access
// if desired. If the DesiredAccess mask is empty after
// these bits are turned off, we don't have to do any more
// access checking (ref section 4, DSA ACL Arch)
//
if ( DesiredAccess & (WRITE_DAC | READ_CONTROL | MAXIMUM_ALLOWED) ) {
if (SepTokenIsOwner( SubjectSecurityContext.ClientToken, CapturedSecurityDescriptor, TRUE )) {
if ( DesiredAccess & MAXIMUM_ALLOWED ) {
PreviouslyGrantedAccess |= ( WRITE_DAC | READ_CONTROL );
} else {
PreviouslyGrantedAccess |= (DesiredAccess & (WRITE_DAC | READ_CONTROL));
}
DesiredAccess &= ~(WRITE_DAC | READ_CONTROL);
}
}
if (DesiredAccess == 0) {
LocalGrantedAccess = PreviouslyGrantedAccess;
AccessGranted = TRUE;
AccessDenied = FALSE;
LocalAccessStatus = STATUS_SUCCESS;
if ( ReturnResultList ) {
ULONG ResultListIndex;
LocalGrantedAccessPointer =
ExAllocatePoolWithTag( PagedPool, (sizeof(ACCESS_MASK)+sizeof(NTSTATUS)) * ObjectTypeListLength, 'aGeS' );
if (LocalGrantedAccessPointer == NULL) {
Status = STATUS_INSUFFICIENT_RESOURCES;
SeUnlockSubjectContext( &SubjectSecurityContext );
goto Cleanup;
}
LocalGrantedAccessAllocated = TRUE;
LocalAccessStatusPointer = (PNTSTATUS)(LocalGrantedAccessPointer + ObjectTypeListLength);
for ( ResultListIndex=0; ResultListIndex<ObjectTypeListLength; ResultListIndex++ ) {
LocalGrantedAccessPointer[ResultListIndex] = LocalGrantedAccess;
LocalAccessStatusPointer[ResultListIndex] = LocalAccessStatus;
}
} else {
LocalGrantedAccessPointer = &LocalGrantedAccess;
LocalAccessStatusPointer = &LocalAccessStatus;
}
} else {
//
// Finally, do the access check
//
if ( ReturnResultList ) {
LocalGrantedAccessPointer =
ExAllocatePoolWithTag( PagedPool, (sizeof(ACCESS_MASK)+sizeof(NTSTATUS)) * ObjectTypeListLength, 'aGeS' );
if (LocalGrantedAccessPointer == NULL) {
Status = STATUS_INSUFFICIENT_RESOURCES;
SeUnlockSubjectContext( &SubjectSecurityContext );
goto Cleanup;
}
LocalGrantedAccessAllocated = TRUE;
LocalAccessStatusPointer = (PNTSTATUS)(LocalGrantedAccessPointer + ObjectTypeListLength);
} else {
LocalGrantedAccessPointer = &LocalGrantedAccess;
LocalAccessStatusPointer = &LocalAccessStatus;
}
//
// This does not ask for privilege set to be returned so we can ignore
// the return value of the call.
//
(VOID) SepAccessCheck (
CapturedSecurityDescriptor,
CapturedPrincipalSelfSid,
SubjectSecurityContext.PrimaryToken,
SubjectSecurityContext.ClientToken,
DesiredAccess,
LocalObjectTypeList,
ObjectTypeListLength,
&LocalGenericMapping,
PreviouslyGrantedAccess,
PreviousMode,
LocalGrantedAccessPointer,
NULL, // Privileges already checked
LocalAccessStatusPointer,
ReturnResultList,
&AccessGranted,
&AccessDenied
);
}
}
//
// sound the alarms...
//
if ( !AvoidAudit ) {
if ( SepAdtAuditThisEventEx( NtAuditType, AccessGranted, AccessDenied )) {
SepExamineSaclEx(
RtlpSaclAddrSecurityDescriptor( (PISECURITY_DESCRIPTOR)CapturedSecurityDescriptor ),
EffectiveToken( &SubjectSecurityContext ),
DesiredAccess | PreviouslyGrantedAccess,
LocalObjectTypeList,
ObjectTypeListLength,
ReturnResultList,
LocalAccessStatusPointer,
LocalGrantedAccessPointer,
CapturedPrincipalSelfSid,
&GenerateSuccessAudit,
&GenerateFailureAudit
);
}
if ( GenerateSuccessAudit ||
GenerateFailureAudit ) {
//
// Save this to a local here, so we don't
// have to risk accessing user memory and
// potentially having to exit before the audit
//
if ( AccessGranted ) {
//
// SAM calls NtCloseObjectAuditAlarm despite the fact that it may not
// have successfully opened the object, causing a spurious close audit.
// Since no one should rely on this anyway if their access attempt
// failed, make sure it's false and SAM will work properly.
//
LocalGenerateOnClose = TRUE;
}
//
// Generate the success audit if needed.
//
if ( GenerateSuccessAudit ) {
ExAllocateLocallyUniqueId( &OperationId );
// ??
ASSERT( AccessGranted );
AuditPerformed = SepAdtOpenObjectAuditAlarm (
CapturedSubsystemName,
AccessGranted ? &HandleId : NULL, // Don't audit handle if failure
CapturedObjectTypeName,
CapturedObjectName,
SubjectSecurityContext.ClientToken,
SubjectSecurityContext.PrimaryToken,
*LocalGrantedAccessPointer,
*LocalGrantedAccessPointer,
&OperationId,
PrivilegeSet,
TRUE, // Generate success case
PsProcessAuditId( PsGetCurrentProcess() ),
NtAuditType,
LocalObjectTypeList,
ObjectTypeListLength,
ReturnResultList ? LocalGrantedAccessPointer : NULL
);
}
//
// Generate failure audit if it is needed.
//
if ( GenerateFailureAudit ) {
ExAllocateLocallyUniqueId( &OperationId );
// ??
ASSERT( AccessDenied );
AuditPerformed = SepAdtOpenObjectAuditAlarm (
CapturedSubsystemName,
AccessGranted ? &HandleId : NULL, // Don't audit handle if failure
CapturedObjectTypeName,
CapturedObjectName,
SubjectSecurityContext.ClientToken,
SubjectSecurityContext.PrimaryToken,
DesiredAccess,
DesiredAccess,
&OperationId,
PrivilegeSet,
FALSE, // Generate failure case
PsProcessAuditId( PsGetCurrentProcess() ),
NtAuditType,
LocalObjectTypeList,
ObjectTypeListLength,
ReturnResultList ? LocalGrantedAccessPointer : NULL
);
}
} else {
//
// We didn't generate an audit due to the SACL. If privileges were used, we need
// to audit that.
//
if ( PrivilegeSet != NULL ) {
if ( SepAdtAuditThisEvent( AuditCategoryPrivilegeUse, &AccessGranted) ) {
AuditPerformed = SepAdtPrivilegeObjectAuditAlarm ( CapturedSubsystemName,
&HandleId,
SubjectSecurityContext.ClientToken,
SubjectSecurityContext.PrimaryToken,
PsProcessAuditId( PsGetCurrentProcess() ),
DesiredAccess,
PrivilegeSet,
AccessGranted
);
//
// We don't want close audits to be generated. May need to revisit this.
//
LocalGenerateOnClose = FALSE;
}
}
}
}
SeUnlockSubjectContext( &SubjectSecurityContext );
try {
if ( ReturnResultList ) {
ULONG ResultListIndex;
if ( LocalAccessStatusPointer == NULL ) {
for ( ResultListIndex=0; ResultListIndex<ObjectTypeListLength; ResultListIndex++ ) {
AccessStatus[ResultListIndex] = LocalAccessStatus;
GrantedAccess[ResultListIndex] = LocalGrantedAccess;
}
} else {
for ( ResultListIndex=0; ResultListIndex<ObjectTypeListLength; ResultListIndex++ ) {
AccessStatus[ResultListIndex] = LocalAccessStatusPointer[ResultListIndex];
GrantedAccess[ResultListIndex] = LocalGrantedAccessPointer[ResultListIndex];
}
}
} else {
*AccessStatus = LocalAccessStatus;
*GrantedAccess = LocalGrantedAccess;
}
*GenerateOnClose = LocalGenerateOnClose;
Status = STATUS_SUCCESS;
} except (EXCEPTION_EXECUTE_HANDLER) {
Status = GetExceptionCode();
}
//
// Free locally used resources.
//
Cleanup:
if ( TokenSwapped ) {
//
// Decrement the reference count for the ClientToken that was passed in.
//
ObDereferenceObject( (PVOID)NewToken );
//
// Reset the value of the token from saved value.
//
SubjectSecurityContext.ClientToken = OldToken;
}
//
// Free any privileges allocated as part of the access check
//
if (PrivilegeSet != NULL) {
ExFreePool( PrivilegeSet );
}
SeReleaseSubjectContext ( &SubjectSecurityContext );
SeReleaseSecurityDescriptor ( CapturedSecurityDescriptor,
PreviousMode,
FALSE );
if (CapturedSubsystemName != NULL) {
SepFreeCapturedString( CapturedSubsystemName );
}
if (CapturedObjectTypeName != NULL) {
SepFreeCapturedString( CapturedObjectTypeName );
}
if (CapturedObjectName != NULL) {
SepFreeCapturedString( CapturedObjectName );
}
if (CapturedPrincipalSelfSid != NULL) {
SeReleaseSid( CapturedPrincipalSelfSid, PreviousMode, TRUE);
}
if ( LocalObjectTypeList != NULL ) {
SeFreeCapturedObjectTypeList( LocalObjectTypeList );
}
if ( LocalGrantedAccessAllocated ) {
if ( LocalGrantedAccessPointer != NULL ) {
ExFreePool( LocalGrantedAccessPointer );
}
}
return Status;
}
NTSTATUS
NtAccessCheckAndAuditAlarm (
IN PUNICODE_STRING SubsystemName,
IN PVOID HandleId,
IN PUNICODE_STRING ObjectTypeName,
IN PUNICODE_STRING ObjectName,
IN PSECURITY_DESCRIPTOR SecurityDescriptor,
IN ACCESS_MASK DesiredAccess,
IN PGENERIC_MAPPING GenericMapping,
IN BOOLEAN ObjectCreation,
OUT PACCESS_MASK GrantedAccess,
OUT PNTSTATUS AccessStatus,
OUT PBOOLEAN GenerateOnClose
)
/*++
Routine Description:
See SepAccessCheckAndAuditAlarm.
Arguments:
See SepAccessCheckAndAuditAlarm.
Return Value:
STATUS_SUCCESS - Indicates the call completed successfully. In this
case, ClientStatus receives the result of the access check.
STATUS_PRIVILEGE_NOT_HELD - Indicates the caller does not have
sufficient privilege to use this privileged system service.
--*/
{
PAGED_CODE();
UNREFERENCED_PARAMETER( ObjectCreation );
return SepAccessCheckAndAuditAlarm(
SubsystemName,
HandleId,
NULL,
ObjectTypeName,
ObjectName,
SecurityDescriptor,
NULL, // No Principal Self sid
DesiredAccess,
AuditEventObjectAccess, // Default to ObjectAccess
0, // No Flags
NULL, // No ObjectType List
0, // No ObjectType List
GenericMapping,
GrantedAccess,
AccessStatus,
GenerateOnClose,
FALSE ); // Return a single GrantedAccess and AccessStatus
}
NTSTATUS
NtAccessCheckByTypeAndAuditAlarm (
IN PUNICODE_STRING SubsystemName,
IN PVOID HandleId,
IN PUNICODE_STRING ObjectTypeName,
IN PUNICODE_STRING ObjectName,
IN PSECURITY_DESCRIPTOR SecurityDescriptor,
IN PSID PrincipalSelfSid,
IN ACCESS_MASK DesiredAccess,
IN AUDIT_EVENT_TYPE AuditType,
IN ULONG Flags,
IN POBJECT_TYPE_LIST ObjectTypeList OPTIONAL,
IN ULONG ObjectTypeListLength,
IN PGENERIC_MAPPING GenericMapping,
IN BOOLEAN ObjectCreation,
OUT PACCESS_MASK GrantedAccess,
OUT PNTSTATUS AccessStatus,
OUT PBOOLEAN GenerateOnClose
)
/*++
Routine Description:
See SepAccessCheckAndAuditAlarm.
Arguments:
See SepAccessCheckAndAuditAlarm.
Return Value:
STATUS_SUCCESS - Indicates the call completed successfully. In this
case, ClientStatus receives the result of the access check.
STATUS_PRIVILEGE_NOT_HELD - Indicates the caller does not have
sufficient privilege to use this privileged system service.
--*/
{
PAGED_CODE();
UNREFERENCED_PARAMETER( ObjectCreation );
return SepAccessCheckAndAuditAlarm(
SubsystemName,
HandleId,
NULL,
ObjectTypeName,
ObjectName,
SecurityDescriptor,
PrincipalSelfSid,
DesiredAccess,
AuditType,
Flags,
ObjectTypeList,
ObjectTypeListLength,
GenericMapping,
GrantedAccess,
AccessStatus,
GenerateOnClose,
FALSE ); // Return a single GrantedAccess and AccessStatus
}
NTSTATUS
NtAccessCheckByTypeResultListAndAuditAlarm (
IN PUNICODE_STRING SubsystemName,
IN PVOID HandleId,
IN PUNICODE_STRING ObjectTypeName,
IN PUNICODE_STRING ObjectName,
IN PSECURITY_DESCRIPTOR SecurityDescriptor,
IN PSID PrincipalSelfSid,
IN ACCESS_MASK DesiredAccess,
IN AUDIT_EVENT_TYPE AuditType,
IN ULONG Flags,
IN POBJECT_TYPE_LIST ObjectTypeList OPTIONAL,
IN ULONG ObjectTypeListLength,
IN PGENERIC_MAPPING GenericMapping,
IN BOOLEAN ObjectCreation,
OUT PACCESS_MASK GrantedAccess,
OUT PNTSTATUS AccessStatus,
OUT PBOOLEAN GenerateOnClose
)
/*++
Routine Description:
See SepAccessCheckAndAuditAlarm.
Arguments:
See SepAccessCheckAndAuditAlarm.
Return Value:
STATUS_SUCCESS - Indicates the call completed successfully. In this
case, ClientStatus receives the result of the access check.
STATUS_PRIVILEGE_NOT_HELD - Indicates the caller does not have
sufficient privilege to use this privileged system service.
--*/
{
PAGED_CODE();
UNREFERENCED_PARAMETER( ObjectCreation );
return SepAccessCheckAndAuditAlarm(
SubsystemName,
HandleId,
NULL,
ObjectTypeName,
ObjectName,
SecurityDescriptor,
PrincipalSelfSid,
DesiredAccess,
AuditType,
Flags,
ObjectTypeList,
ObjectTypeListLength,
GenericMapping,
GrantedAccess,
AccessStatus,
GenerateOnClose,
TRUE ); // Return an array of GrantedAccess and AccessStatus
}
NTSTATUS
NtAccessCheckByTypeResultListAndAuditAlarmByHandle (
IN PUNICODE_STRING SubsystemName,
IN PVOID HandleId,
IN HANDLE ClientToken,
IN PUNICODE_STRING ObjectTypeName,
IN PUNICODE_STRING ObjectName,
IN PSECURITY_DESCRIPTOR SecurityDescriptor,
IN PSID PrincipalSelfSid,
IN ACCESS_MASK DesiredAccess,
IN AUDIT_EVENT_TYPE AuditType,
IN ULONG Flags,
IN POBJECT_TYPE_LIST ObjectTypeList OPTIONAL,
IN ULONG ObjectTypeListLength,
IN PGENERIC_MAPPING GenericMapping,
IN BOOLEAN ObjectCreation,
OUT PACCESS_MASK GrantedAccess,
OUT PNTSTATUS AccessStatus,
OUT PBOOLEAN GenerateOnClose
)
/*++
Routine Description:
See SepAccessCheckAndAuditAlarm.
Arguments:
See SepAccessCheckAndAuditAlarm.
Return Value:
STATUS_SUCCESS - Indicates the call completed successfully. In this
case, ClientStatus receives the result of the access check.
STATUS_PRIVILEGE_NOT_HELD - Indicates the caller does not have
sufficient privilege to use this privileged system service.
--*/
{
PAGED_CODE();
UNREFERENCED_PARAMETER( ObjectCreation );
return SepAccessCheckAndAuditAlarm(
SubsystemName,
HandleId,
&ClientToken,
ObjectTypeName,
ObjectName,
SecurityDescriptor,
PrincipalSelfSid,
DesiredAccess,
AuditType,
Flags,
ObjectTypeList,
ObjectTypeListLength,
GenericMapping,
GrantedAccess,
AccessStatus,
GenerateOnClose,
TRUE ); // Return an array of GrantedAccess and AccessStatus
}
NTSTATUS
NtOpenObjectAuditAlarm (
IN PUNICODE_STRING SubsystemName,
IN PVOID HandleId OPTIONAL,
IN PUNICODE_STRING ObjectTypeName,
IN PUNICODE_STRING ObjectName,
IN PSECURITY_DESCRIPTOR SecurityDescriptor OPTIONAL,
IN HANDLE ClientToken,
IN ACCESS_MASK DesiredAccess,
IN ACCESS_MASK GrantedAccess,
IN PPRIVILEGE_SET Privileges OPTIONAL,
IN BOOLEAN ObjectCreation,
IN BOOLEAN AccessGranted,
OUT PBOOLEAN GenerateOnClose
)
/*++
Routine Description:
This routine is used to generate audit and alarm messages when an
attempt is made to access an existing protected subsystem object or
create a new one. This routine may result in several messages being
generated and sent to Port objects. This may result in a significant
latency before returning. Design of routines that must call this
routine must take this potential latency into account. This may have
an impact on the approach taken for data structure mutex locking, for
example.
This routine may not be able to generate a complete audit record
due to memory restrictions.
This API requires the caller have SeAuditPrivilege privilege. The test
for this privilege is always against the primary token of the calling
process, not the impersonation token of the thread.
Arguments:
SubsystemName - Supplies a name string identifying the
subsystem calling the routine.
HandleId - A unique value representing the client's handle to the
object. If the access attempt was not successful (AccessGranted is
FALSE), then this parameter is ignored.
ObjectTypeName - Supplies the name of the type of object being
accessed.
ObjectName - Supplies the name of the object the client
accessed or attempted to access.
SecurityDescriptor - An optional pointer to the security descriptor of
the object being accessed.
ClientToken - A handle to a token object representing the client that
requested the operation. This handle must be obtained from a
communication session layer, such as from an LPC Port or Local
Named Pipe, to prevent possible security policy violations.
DesiredAccess - The desired access mask. This mask must have been
previously mapped to contain no generic accesses.
GrantedAccess - The mask of accesses that were actually granted.
Privileges - Optionally points to a set of privileges that were
required for the access attempt. Those privileges that were held
by the subject are marked using the UsedForAccess flag of the
attributes associated with each privilege.
ObjectCreation - A boolean flag indicating whether the access will
result in a new object being created if granted. A value of TRUE
indicates an object will be created, FALSE indicates an existing
object will be opened.
AccessGranted - Indicates whether the requested access was granted or
not. A value of TRUE indicates the access was granted. A value of
FALSE indicates the access was not granted.
GenerateOnClose - Points to a boolean that is set by the audit
generation routine and must be passed to NtCloseObjectAuditAlarm()
when the object handle is closed.
Return Value:
--*/
{
KPROCESSOR_MODE PreviousMode;
ULONG PrivilegeParameterLength;
PUNICODE_STRING CapturedSubsystemName = (PUNICODE_STRING) NULL;
PUNICODE_STRING CapturedObjectTypeName = (PUNICODE_STRING) NULL;
PUNICODE_STRING CapturedObjectName = (PUNICODE_STRING) NULL;
PSECURITY_DESCRIPTOR CapturedSecurityDescriptor = (PSECURITY_DESCRIPTOR) NULL;
PPRIVILEGE_SET CapturedPrivileges = NULL;
BOOLEAN LocalGenerateOnClose = FALSE;
SECURITY_SUBJECT_CONTEXT SubjectSecurityContext;
BOOLEAN Result;
NTSTATUS Status;
BOOLEAN GenerateAudit = FALSE;
BOOLEAN GenerateAlarm = FALSE;
PLUID ClientAuthenticationId = NULL;
HANDLE CapturedHandleId = NULL;
BOOLEAN AuditPerformed;
ULONG PrivilegeCount;
PTOKEN Token;
PAGED_CODE();
UNREFERENCED_PARAMETER( ObjectCreation );
PreviousMode = KeGetPreviousMode();
ASSERT( PreviousMode != KernelMode );
Status = ObReferenceObjectByHandle( ClientToken, // Handle
TOKEN_QUERY, // DesiredAccess
SeTokenObjectType, // ObjectType
PreviousMode, // AccessMode
(PVOID *)&Token, // Object
NULL // GrantedAccess
);
if (!NT_SUCCESS(Status)) {
return( Status );
}
//
// If the passed token is an impersonation token, make sure
// it is at SecurityIdentification or above.
//
if (Token->TokenType == TokenImpersonation) {
if (Token->ImpersonationLevel < SecurityIdentification) {
ObDereferenceObject( (PVOID)Token );
return( STATUS_BAD_IMPERSONATION_LEVEL );
}
}
//
// Check for SeAuditPrivilege. This must be tested against
// the caller's primary token.
//
SeCaptureSubjectContext ( &SubjectSecurityContext );
Result = SeCheckAuditPrivilege (
&SubjectSecurityContext,
PreviousMode
);
if (!Result) {
ObDereferenceObject( (PVOID)Token );
SeReleaseSubjectContext ( &SubjectSecurityContext );
return(STATUS_PRIVILEGE_NOT_HELD);
}
//
// This will just return NULL if the input descriptor is NULL
//
Status = SeCaptureSecurityDescriptor ( SecurityDescriptor,
PreviousMode,
PagedPool,
FALSE,
&CapturedSecurityDescriptor
);
//
// At this point in time, if there's no security descriptor, there's
// nothing to do. Return success.
//
if (!NT_SUCCESS( Status ) || CapturedSecurityDescriptor == NULL) {
ObDereferenceObject( (PVOID)Token );
SeReleaseSubjectContext ( &SubjectSecurityContext );
return( Status );
}
try {
//
// Only capture the privileges if we've completed a successful
// access check. Otherwise they don't mean anything.
//
if (AccessGranted && ARGUMENT_PRESENT(Privileges)) {
ProbeForReadSmallStructure(
Privileges,
sizeof(PRIVILEGE_SET),
sizeof(ULONG)
);
PrivilegeCount = Privileges->PrivilegeCount;
if (!IsValidPrivilegeCount( PrivilegeCount )) {
Status = STATUS_INVALID_PARAMETER;
leave;
}
PrivilegeParameterLength = (ULONG)sizeof(PRIVILEGE_SET) +
((PrivilegeCount - ANYSIZE_ARRAY) *
(ULONG)sizeof(LUID_AND_ATTRIBUTES) );
ProbeForRead(
Privileges,
PrivilegeParameterLength,
sizeof(ULONG)
);
CapturedPrivileges = ExAllocatePoolWithTag( PagedPool,
PrivilegeParameterLength,
'rPeS'
);
if (CapturedPrivileges != NULL) {
RtlCopyMemory ( CapturedPrivileges,
Privileges,
PrivilegeParameterLength );
CapturedPrivileges->PrivilegeCount = PrivilegeCount;
} else {
SeReleaseSecurityDescriptor ( CapturedSecurityDescriptor,
PreviousMode,
FALSE );
ObDereferenceObject( (PVOID)Token );
SeReleaseSubjectContext ( &SubjectSecurityContext );
return( STATUS_INSUFFICIENT_RESOURCES );
}
}
if (ARGUMENT_PRESENT( HandleId )) {
ProbeForReadSmallStructure( (PHANDLE)HandleId, sizeof(PVOID), sizeof(PVOID) );
CapturedHandleId = *(PHANDLE)HandleId;
}
ProbeForWriteBoolean(GenerateOnClose);
//
// Probe and Capture the parameter strings.
// If we run out of memory attempting to capture
// the strings, the returned pointer will be
// NULL and we will continue with the audit.
//
SepProbeAndCaptureString_U ( SubsystemName,
&CapturedSubsystemName );
SepProbeAndCaptureString_U ( ObjectTypeName,
&CapturedObjectTypeName );
SepProbeAndCaptureString_U ( ObjectName,
&CapturedObjectName );
} except(EXCEPTION_EXECUTE_HANDLER) {
Status = GetExceptionCode();
}
if (!NT_SUCCESS(Status)) {
if (CapturedSubsystemName != NULL) {
SepFreeCapturedString( CapturedSubsystemName );
}
if (CapturedObjectTypeName != NULL) {
SepFreeCapturedString( CapturedObjectTypeName );
}
if (CapturedObjectName != NULL) {
SepFreeCapturedString( CapturedObjectName );
}
if (CapturedPrivileges != NULL) {
ExFreePool( CapturedPrivileges );
}
if (CapturedSecurityDescriptor != NULL) {
SeReleaseSecurityDescriptor ( CapturedSecurityDescriptor,
PreviousMode,
FALSE );
}
ObDereferenceObject( (PVOID)Token );
SeReleaseSubjectContext ( &SubjectSecurityContext );
return Status;
}
if ( SepAdtAuditThisEvent( AuditCategoryObjectAccess, &AccessGranted) ) {
SepExamineSacl(
RtlpSaclAddrSecurityDescriptor( (PISECURITY_DESCRIPTOR)CapturedSecurityDescriptor ),
Token,
DesiredAccess | GrantedAccess,
AccessGranted,
&GenerateAudit,
&GenerateAlarm
);
if (GenerateAudit || GenerateAlarm) {
//
// Take a read lock on the token, because we're going to extract
// the user's Sid from it.
//
LocalGenerateOnClose = TRUE;
AuditPerformed = SepAdtOpenObjectAuditAlarm ( CapturedSubsystemName,
ARGUMENT_PRESENT(HandleId) ? (PVOID)&CapturedHandleId : NULL,
CapturedObjectTypeName,
CapturedObjectName,
Token,
SubjectSecurityContext.PrimaryToken,
DesiredAccess,
GrantedAccess,
NULL,
CapturedPrivileges,
AccessGranted,
PsProcessAuditId( PsGetCurrentProcess() ),
AuditCategoryObjectAccess,
NULL,
0,
NULL
);
LocalGenerateOnClose = AuditPerformed;
}
}
if ( !(GenerateAudit || GenerateAlarm) ) {
//
// We didn't attempt to generate an audit above, so if privileges were used,
// see if we should generate an audit here.
//
if ( ARGUMENT_PRESENT(Privileges) ) {
if ( SepAdtAuditThisEvent( AuditCategoryPrivilegeUse, &AccessGranted) ) {
AuditPerformed = SepAdtPrivilegeObjectAuditAlarm ( CapturedSubsystemName,
CapturedHandleId,
Token,
SubjectSecurityContext.PrimaryToken,
PsProcessAuditId( PsGetCurrentProcess() ),
DesiredAccess,
CapturedPrivileges,
AccessGranted
);
//
// If we generate an audit due to use of privilege, don't set generate on close,
// because then we'll have a close audit without a corresponding open audit.
//
LocalGenerateOnClose = FALSE;
}
}
}
if (CapturedSecurityDescriptor != NULL) {
SeReleaseSecurityDescriptor ( CapturedSecurityDescriptor,
PreviousMode,
FALSE );
}
if (CapturedSubsystemName != NULL) {
SepFreeCapturedString( CapturedSubsystemName );
}
if (CapturedObjectTypeName != NULL) {
SepFreeCapturedString( CapturedObjectTypeName );
}
if (CapturedObjectName != NULL) {
SepFreeCapturedString( CapturedObjectName );
}
if (CapturedPrivileges != NULL) {
ExFreePool( CapturedPrivileges );
}
ObDereferenceObject( (PVOID)Token );
SeReleaseSubjectContext ( &SubjectSecurityContext );
try {
*GenerateOnClose = LocalGenerateOnClose;
} except (EXCEPTION_EXECUTE_HANDLER) {
return GetExceptionCode();
}
return(STATUS_SUCCESS);
}
NTSTATUS
NtCloseObjectAuditAlarm (
IN PUNICODE_STRING SubsystemName,
IN PVOID HandleId,
IN BOOLEAN GenerateOnClose
)
/*++
Routine Description:
This routine is used to generate audit and alarm messages when a handle
to a protected subsystem object is deleted. This routine may result in
several messages being generated and sent to Port objects. This may
result in a significant latency before returning. Design of routines
that must call this routine must take this potential latency into
account. This may have an impact on the approach taken for data
structure mutex locking, for example.
This API requires the caller have SeAuditPrivilege privilege. The test
for this privilege is always against the primary token of the calling
process, allowing the caller to be impersonating a client during the
call with no ill effects.
Arguments:
SubsystemName - Supplies a name string identifying the subsystem
calling the routine.
HandleId - A unique value representing the client's handle to the
object.
GenerateOnClose - Is a boolean value returned from a corresponding
NtAccessCheckAndAuditAlarm() call or NtOpenObjectAuditAlarm() call
when the object handle was created.
Return value:
--*/
{
BOOLEAN Result;
SECURITY_SUBJECT_CONTEXT SubjectSecurityContext;
KPROCESSOR_MODE PreviousMode;
PUNICODE_STRING CapturedSubsystemName = NULL;
PSID UserSid;
PSID CapturedUserSid = NULL;
NTSTATUS Status;
PAGED_CODE();
PreviousMode = KeGetPreviousMode();
ASSERT(PreviousMode != KernelMode);
if (!GenerateOnClose) {
return( STATUS_SUCCESS );
}
//
// Check for SeAuditPrivilege
//
SeCaptureSubjectContext ( &SubjectSecurityContext );
Result = SeCheckAuditPrivilege (
&SubjectSecurityContext,
PreviousMode
);
if (!Result) {
Status = STATUS_PRIVILEGE_NOT_HELD;
goto Cleanup;
}
UserSid = SepTokenUserSid( EffectiveToken (&SubjectSecurityContext));
CapturedUserSid = ExAllocatePoolWithTag(
PagedPool,
SeLengthSid( UserSid ),
'iSeS'
);
if ( CapturedUserSid == NULL ) {
Status = STATUS_INSUFFICIENT_RESOURCES;
goto Cleanup;
}
Status = RtlCopySid (
SeLengthSid( UserSid ),
CapturedUserSid,
UserSid
);
ASSERT( NT_SUCCESS( Status ));
try {
SepProbeAndCaptureString_U ( SubsystemName,
&CapturedSubsystemName );
} except (EXCEPTION_EXECUTE_HANDLER) {
Status = GetExceptionCode();
goto Cleanup;
}
//
// This routine will check to see if auditing is enabled
//
SepAdtCloseObjectAuditAlarm( CapturedSubsystemName, HandleId, CapturedUserSid );
Status = STATUS_SUCCESS;
Cleanup:
if ( CapturedSubsystemName != NULL ) {
SepFreeCapturedString( CapturedSubsystemName );
}
if ( CapturedUserSid != NULL ) {
ExFreePool( CapturedUserSid );
}
SeReleaseSubjectContext ( &SubjectSecurityContext );
return Status;
}
NTSTATUS
NtDeleteObjectAuditAlarm (
IN PUNICODE_STRING SubsystemName,
IN PVOID HandleId,
IN BOOLEAN GenerateOnClose
)
/*++
Routine Description:
This routine is used to generate audit and alarm messages when an object
in a protected subsystem object is deleted. This routine may result in
several messages being generated and sent to Port objects. This may
result in a significant latency before returning. Design of routines
that must call this routine must take this potential latency into
account. This may have an impact on the approach taken for data
structure mutex locking, for example.
This API requires the caller have SeAuditPrivilege privilege. The test
for this privilege is always against the primary token of the calling
process, allowing the caller to be impersonating a client during the
call with no ill effects.
Arguments:
SubsystemName - Supplies a name string identifying the subsystem
calling the routine.
HandleId - A unique value representing the client's handle to the
object.
GenerateOnClose - Is a boolean value returned from a corresponding
NtAccessCheckAndAuditAlarm() call or NtOpenObjectAuditAlarm() call
when the object handle was created.
Return value:
--*/
{
BOOLEAN Result;
SECURITY_SUBJECT_CONTEXT SubjectSecurityContext;
KPROCESSOR_MODE PreviousMode;
PUNICODE_STRING CapturedSubsystemName = NULL;
PSID UserSid;
PSID CapturedUserSid;
NTSTATUS Status;
PAGED_CODE();
PreviousMode = KeGetPreviousMode();
ASSERT(PreviousMode != KernelMode);
if (!GenerateOnClose) {
return( STATUS_SUCCESS );
}
//
// Check for SeAuditPrivilege
//
SeCaptureSubjectContext ( &SubjectSecurityContext );
Result = SeCheckAuditPrivilege (
&SubjectSecurityContext,
PreviousMode
);
if (!Result) {
SeReleaseSubjectContext ( &SubjectSecurityContext );
return(STATUS_PRIVILEGE_NOT_HELD);
}
UserSid = SepTokenUserSid( EffectiveToken (&SubjectSecurityContext));
CapturedUserSid = ExAllocatePoolWithTag(
PagedPool,
SeLengthSid( UserSid ),
'iSeS'
);
if ( CapturedUserSid == NULL ) {
SeReleaseSubjectContext ( &SubjectSecurityContext );
return( STATUS_INSUFFICIENT_RESOURCES );
}
Status = RtlCopySid (
SeLengthSid( UserSid ),
CapturedUserSid,
UserSid
);
ASSERT( NT_SUCCESS( Status ));
try {
SepProbeAndCaptureString_U ( SubsystemName,
&CapturedSubsystemName );
} except (EXCEPTION_EXECUTE_HANDLER) {
if ( CapturedSubsystemName != NULL ) {
SepFreeCapturedString( CapturedSubsystemName );
}
ExFreePool( CapturedUserSid );
SeReleaseSubjectContext ( &SubjectSecurityContext );
return GetExceptionCode();
}
//
// This routine will check to see if auditing is enabled
//
SepAdtDeleteObjectAuditAlarm ( CapturedSubsystemName,
HandleId,
CapturedUserSid
);
SeReleaseSubjectContext ( &SubjectSecurityContext );
if ( CapturedSubsystemName != NULL ) {
SepFreeCapturedString( CapturedSubsystemName );
}
ExFreePool( CapturedUserSid );
return(STATUS_SUCCESS);
}
VOID
SeOpenObjectAuditAlarm (
IN PUNICODE_STRING ObjectTypeName,
IN PVOID Object OPTIONAL,
IN PUNICODE_STRING AbsoluteObjectName OPTIONAL,
IN PSECURITY_DESCRIPTOR SecurityDescriptor,
IN PACCESS_STATE AccessState,
IN BOOLEAN ObjectCreated,
IN BOOLEAN AccessGranted,
IN KPROCESSOR_MODE AccessMode,
OUT PBOOLEAN GenerateOnClose
)
/*++
Routine Description:
SeOpenObjectAuditAlarm is used by the object manager that open objects
to generate any necessary audit or alarm messages. The open may be to
existing objects or for newly created objects. No messages will be
generated for Kernel mode accesses.
This routine is used to generate audit and alarm messages when an
attempt is made to open an object.
This routine may result in several messages being generated and sent to
Port objects. This may result in a significant latency before
returning. Design of routines that must call this routine must take
this potential latency into account. This may have an impact on the
approach taken for data structure mutex locking, for example.
Arguments:
ObjectTypeName - Supplies the name of the type of object being
accessed. This must be the same name provided to the
ObCreateObjectType service when the object type was created.
Object - Address of the object accessed. This value will not be used
as a pointer (referenced). It is necessary only to enter into log
messages. If the open was not successful, then this argument is
ignored. Otherwise, it must be provided.
AbsoluteObjectName - Supplies the name of the object being accessed.
If the object doesn't have a name, then this field is left null.
Otherwise, it must be provided.
SecurityDescriptor - A pointer to the security descriptor of the
object being accessed.
AccessState - A pointer to an access state structure containing the
subject context, the remaining desired access types, the granted
access types, and optionally a privilege set to indicate which
privileges were used to permit the access.
ObjectCreated - A boolean flag indicating whether the access resulted
in a new object being created. A value of TRUE indicates an object
was created, FALSE indicates an existing object was opened.
AccessGranted - Indicates if the access was granted or denied based on
the access check or privilege check.
AccessMode - Indicates the access mode used for the access check. One
of UserMode or KernelMode. Messages will not be generated by
kernel mode accesses.
GenerateOnClose - Points to a boolean that is set by the audit
generation routine and must be passed to SeCloseObjectAuditAlarm()
when the object handle is closed.
Return value:
None.
--*/
{
BOOLEAN GenerateAudit = FALSE;
BOOLEAN GenerateAlarm = FALSE;
ACCESS_MASK RequestedAccess;
POBJECT_NAME_INFORMATION ObjectNameInfo = NULL;
PUNICODE_STRING ObjectTypeNameInfo = NULL;
PUNICODE_STRING ObjectName = NULL;
PUNICODE_STRING LocalObjectTypeName = NULL;
PLUID PrimaryAuthenticationId = NULL;
PLUID ClientAuthenticationId = NULL;
BOOLEAN AuditPrivileges = FALSE;
BOOLEAN AuditPerformed;
PTOKEN Token;
ACCESS_MASK MappedGrantMask = (ACCESS_MASK)0;
ACCESS_MASK MappedDenyMask = (ACCESS_MASK)0;
PAUX_ACCESS_DATA AuxData;
PAGED_CODE();
UNREFERENCED_PARAMETER( ObjectCreated );
if ( AccessMode == KernelMode ) {
return;
}
AuxData = (PAUX_ACCESS_DATA)AccessState->AuxData;
Token = EffectiveToken( &AccessState->SubjectSecurityContext );
if (ARGUMENT_PRESENT(Token->AuditData)) {
MappedGrantMask = Token->AuditData->GrantMask;
RtlMapGenericMask(
&MappedGrantMask,
&AuxData->GenericMapping
);
MappedDenyMask = Token->AuditData->DenyMask;
RtlMapGenericMask(
&MappedDenyMask,
&AuxData->GenericMapping
);
}
if (SecurityDescriptor != NULL) {
RequestedAccess = AccessState->RemainingDesiredAccess |
AccessState->PreviouslyGrantedAccess;
if ( SepAdtAuditThisEvent( AuditCategoryObjectAccess, &AccessGranted )) {
if ( RequestedAccess & (AccessGranted ? MappedGrantMask : MappedDenyMask)) {
GenerateAudit = TRUE;
} else {
SepExamineSacl(
RtlpSaclAddrSecurityDescriptor( (PISECURITY_DESCRIPTOR)SecurityDescriptor ),
Token,
RequestedAccess,
AccessGranted,
&GenerateAudit,
&GenerateAlarm
);
}
//
// Only generate an audit on close of we're auditing from SACL
// settings.
//
if (GenerateAudit) {
*GenerateOnClose = TRUE;
//
// Construct the audit mask that will be placed into the handle.
//
if (AccessGranted) {
SeMaximumAuditMask(
RtlpSaclAddrSecurityDescriptor( (PISECURITY_DESCRIPTOR)SecurityDescriptor ),
RequestedAccess,
Token,
&AuxData->MaximumAuditMask
);
}
}
}
}
//
// If we don't generate an audit via the SACL, see if we need to generate
// one for privilege use.
//
// Note that we only audit privileges successfully used to open objects,
// so we don't care about a failed privilege use here. Therefore, only
// do this test of access has been granted.
//
if (!GenerateAudit && (AccessGranted == TRUE)) {
if ( SepAdtAuditThisEvent( AuditCategoryPrivilegeUse, &AccessGranted )) {
if ((AuxData->PrivilegesUsed != NULL) &&
(AuxData->PrivilegesUsed->PrivilegeCount > 0) ) {
//
// Make sure these are actually privileges that we want to audit
//
if (SepFilterPrivilegeAudits( AuxData->PrivilegesUsed )) {
GenerateAudit = TRUE;
//
// When we finally try to generate this audit, this flag
// will tell us that we need to audit the fact that we
// used a privilege, as opposed to audit due to the SACL.
//
AccessState->AuditPrivileges = TRUE;
}
}
}
}
//
// Set up either to generate an audit (if the access check has failed), or save
// the stuff that we're going to audit later into the AccessState structure.
//
if (GenerateAudit || GenerateAlarm) {
AccessState->GenerateAudit = TRUE;
//
// Figure out what we've been passed, and obtain as much
// missing information as possible.
//
if ( !ARGUMENT_PRESENT( AbsoluteObjectName )) {
if ( ARGUMENT_PRESENT( Object )) {
ObjectNameInfo = SepQueryNameString( Object );
if ( ObjectNameInfo != NULL ) {
ObjectName = &ObjectNameInfo->Name;
}
}
} else {
ObjectName = AbsoluteObjectName;
}
if ( !ARGUMENT_PRESENT( ObjectTypeName )) {
if ( ARGUMENT_PRESENT( Object )) {
ObjectTypeNameInfo = SepQueryTypeString( Object );
if ( ObjectTypeNameInfo != NULL ) {
LocalObjectTypeName = ObjectTypeNameInfo;
}
}
} else {
LocalObjectTypeName = ObjectTypeName;
}
//
// If the access attempt failed, do the audit here. If it succeeded,
// we'll do the audit later, when the handle is allocated.
//
//
if (!AccessGranted) {
AuditPerformed = SepAdtOpenObjectAuditAlarm ( (PUNICODE_STRING)&SeSubsystemName,
NULL,
LocalObjectTypeName,
ObjectName,
AccessState->SubjectSecurityContext.ClientToken,
AccessState->SubjectSecurityContext.PrimaryToken,
AccessState->OriginalDesiredAccess,
AccessState->PreviouslyGrantedAccess,
&AccessState->OperationID,
AuxData->PrivilegesUsed,
FALSE,
AccessState->SubjectSecurityContext.ProcessAuditId,
AuditCategoryObjectAccess,
NULL,
0,
NULL );
} else {
//
// Copy all the stuff we're going to need into the
// AccessState and return.
//
if ( ObjectName != NULL ) {
if ( AccessState->ObjectName.Buffer != NULL ) {
ExFreePool( AccessState->ObjectName.Buffer );
AccessState->ObjectName.Length = 0;
AccessState->ObjectName.MaximumLength = 0;
}
AccessState->ObjectName.Buffer = ExAllocatePool( PagedPool,ObjectName->MaximumLength );
if (AccessState->ObjectName.Buffer != NULL) {
AccessState->ObjectName.MaximumLength = ObjectName->MaximumLength;
RtlCopyUnicodeString( &AccessState->ObjectName, ObjectName );
}
}
if ( LocalObjectTypeName != NULL ) {
if ( AccessState->ObjectTypeName.Buffer != NULL ) {
ExFreePool( AccessState->ObjectTypeName.Buffer );
AccessState->ObjectTypeName.Length = 0;
AccessState->ObjectTypeName.MaximumLength = 0;
}
AccessState->ObjectTypeName.Buffer = ExAllocatePool( PagedPool, LocalObjectTypeName->MaximumLength );
if (AccessState->ObjectTypeName.Buffer != NULL) {
AccessState->ObjectTypeName.MaximumLength = LocalObjectTypeName->MaximumLength;
RtlCopyUnicodeString( &AccessState->ObjectTypeName, LocalObjectTypeName );
}
}
}
if ( ObjectNameInfo != NULL ) {
ExFreePool( ObjectNameInfo );
}
if ( ObjectTypeNameInfo != NULL ) {
ExFreePool( ObjectTypeNameInfo );
}
}
return;
}
VOID
SeOpenObjectForDeleteAuditAlarm (
IN PUNICODE_STRING ObjectTypeName,
IN PVOID Object OPTIONAL,
IN PUNICODE_STRING AbsoluteObjectName OPTIONAL,
IN PSECURITY_DESCRIPTOR SecurityDescriptor,
IN PACCESS_STATE AccessState,
IN BOOLEAN ObjectCreated,
IN BOOLEAN AccessGranted,
IN KPROCESSOR_MODE AccessMode,
OUT PBOOLEAN GenerateOnClose
)
/*++
Routine Description:
SeOpenObjectForDeleteAuditAlarm is used by the object manager that open
objects to generate any necessary audit or alarm messages. The open may
be to existing objects or for newly created objects. No messages will be
generated for Kernel mode accesses.
This routine is used to generate audit and alarm messages when an
attempt is made to open an object with the intent to delete it.
Specifically, this is used by file systems when the flag
FILE_DELETE_ON_CLOSE is specified.
This routine may result in several messages being generated and sent to
Port objects. This may result in a significant latency before
returning. Design of routines that must call this routine must take
this potential latency into account. This may have an impact on the
approach taken for data structure mutex locking, for example.
Arguments:
ObjectTypeName - Supplies the name of the type of object being
accessed. This must be the same name provided to the
ObCreateObjectType service when the object type was created.
Object - Address of the object accessed. This value will not be used
as a pointer (referenced). It is necessary only to enter into log
messages. If the open was not successful, then this argument is
ignored. Otherwise, it must be provided.
AbsoluteObjectName - Supplies the name of the object being accessed.
If the object doesn't have a name, then this field is left null.
Otherwise, it must be provided.
SecurityDescriptor - A pointer to the security descriptor of the
object being accessed.
AccessState - A pointer to an access state structure containing the
subject context, the remaining desired access types, the granted
access types, and optionally a privilege set to indicate which
privileges were used to permit the access.
ObjectCreated - A boolean flag indicating whether the access resulted
in a new object being created. A value of TRUE indicates an object
was created, FALSE indicates an existing object was opened.
AccessGranted - Indicates if the access was granted or denied based on
the access check or privilege check.
AccessMode - Indicates the access mode used for the access check. One
of UserMode or KernelMode. Messages will not be generated by
kernel mode accesses.
GenerateOnClose - Points to a boolean that is set by the audit
generation routine and must be passed to SeCloseObjectAuditAlarm()
when the object handle is closed.
Return value:
None.
--*/
{
BOOLEAN GenerateAudit = FALSE;
BOOLEAN GenerateAlarm = FALSE;
ACCESS_MASK RequestedAccess;
POBJECT_NAME_INFORMATION ObjectNameInfo = NULL;
PUNICODE_STRING ObjectTypeNameInfo = NULL;
PUNICODE_STRING ObjectName = NULL;
PUNICODE_STRING LocalObjectTypeName = NULL;
PLUID PrimaryAuthenticationId = NULL;
PLUID ClientAuthenticationId = NULL;
BOOLEAN AuditPrivileges = FALSE;
BOOLEAN AuditPerformed;
PTOKEN Token;
ACCESS_MASK MappedGrantMask = (ACCESS_MASK)0;
ACCESS_MASK MappedDenyMask = (ACCESS_MASK)0;
PAUX_ACCESS_DATA AuxData;
PAGED_CODE();
UNREFERENCED_PARAMETER( ObjectCreated );
if ( AccessMode == KernelMode ) {
return;
}
AuxData = (PAUX_ACCESS_DATA)AccessState->AuxData;
Token = EffectiveToken( &AccessState->SubjectSecurityContext );
if (ARGUMENT_PRESENT(Token->AuditData)) {
MappedGrantMask = Token->AuditData->GrantMask;
RtlMapGenericMask(
&MappedGrantMask,
&AuxData->GenericMapping
);
MappedDenyMask = Token->AuditData->DenyMask;
RtlMapGenericMask(
&MappedDenyMask,
&AuxData->GenericMapping
);
}
if (SecurityDescriptor != NULL) {
RequestedAccess = AccessState->RemainingDesiredAccess |
AccessState->PreviouslyGrantedAccess;
if ( SepAdtAuditThisEvent( AuditCategoryObjectAccess, &AccessGranted )) {
if ( RequestedAccess & (AccessGranted ? MappedGrantMask : MappedDenyMask)) {
GenerateAudit = TRUE;
} else {
SepExamineSacl(
RtlpSaclAddrSecurityDescriptor( (PISECURITY_DESCRIPTOR)SecurityDescriptor ),
Token,
RequestedAccess,
AccessGranted,
&GenerateAudit,
&GenerateAlarm
);
}
//
// Only generate an audit on close of we're auditing from SACL
// settings.
//
if (GenerateAudit) {
*GenerateOnClose = TRUE;
}
}
}
//
// If we don't generate an audit via the SACL, see if we need to generate
// one for privilege use.
//
// Note that we only audit privileges successfully used to open objects,
// so we don't care about a failed privilege use here. Therefore, only
// do this test of access has been granted.
//
if (!GenerateAudit && (AccessGranted == TRUE)) {
if ( SepAdtAuditThisEvent( AuditCategoryPrivilegeUse, &AccessGranted )) {
if ((AuxData->PrivilegesUsed != NULL) &&
(AuxData->PrivilegesUsed->PrivilegeCount > 0) ) {
//
// Make sure these are actually privileges that we want to audit
//
if (SepFilterPrivilegeAudits( AuxData->PrivilegesUsed )) {
GenerateAudit = TRUE;
//
// When we finally try to generate this audit, this flag
// will tell us that we need to audit the fact that we
// used a privilege, as opposed to audit due to the SACL.
//
AccessState->AuditPrivileges = TRUE;
}
}
}
}
//
// Set up either to generate an audit (if the access check has failed), or save
// the stuff that we're going to audit later into the AccessState structure.
//
if (GenerateAudit || GenerateAlarm) {
AccessState->GenerateAudit = TRUE;
//
// Figure out what we've been passed, and obtain as much
// missing information as possible.
//
if ( !ARGUMENT_PRESENT( AbsoluteObjectName )) {
if ( ARGUMENT_PRESENT( Object )) {
ObjectNameInfo = SepQueryNameString( Object );
if ( ObjectNameInfo != NULL ) {
ObjectName = &ObjectNameInfo->Name;
}
}
} else {
ObjectName = AbsoluteObjectName;
}
if ( !ARGUMENT_PRESENT( ObjectTypeName )) {
if ( ARGUMENT_PRESENT( Object )) {
ObjectTypeNameInfo = SepQueryTypeString( Object );
if ( ObjectTypeNameInfo != NULL ) {
LocalObjectTypeName = ObjectTypeNameInfo;
}
}
} else {
LocalObjectTypeName = ObjectTypeName;
}
//
// If the access attempt failed, do the audit here. If it succeeded,
// we'll do the audit later, when the handle is allocated.
//
//
if (!AccessGranted) {
AuditPerformed = SepAdtOpenObjectAuditAlarm ( (PUNICODE_STRING)&SeSubsystemName,
NULL,
LocalObjectTypeName,
ObjectName,
AccessState->SubjectSecurityContext.ClientToken,
AccessState->SubjectSecurityContext.PrimaryToken,
AccessState->OriginalDesiredAccess,
AccessState->PreviouslyGrantedAccess,
&AccessState->OperationID,
AuxData->PrivilegesUsed,
FALSE,
AccessState->SubjectSecurityContext.ProcessAuditId,
AuditCategoryObjectAccess,
NULL,
0,
NULL );
} else {
//
// Generate the delete audit first
//
SepAdtOpenObjectForDeleteAuditAlarm ( (PUNICODE_STRING)&SeSubsystemName,
NULL,
LocalObjectTypeName,
ObjectName,
AccessState->SubjectSecurityContext.ClientToken,
AccessState->SubjectSecurityContext.PrimaryToken,
AccessState->OriginalDesiredAccess,
AccessState->PreviouslyGrantedAccess,
&AccessState->OperationID,
AuxData->PrivilegesUsed,
TRUE,
AccessState->SubjectSecurityContext.ProcessAuditId );
//
// Copy all the stuff we're going to need into the
// AccessState and return.
//
if ( ObjectName != NULL ) {
if ( AccessState->ObjectName.Buffer != NULL ) {
ExFreePool( AccessState->ObjectName.Buffer );
AccessState->ObjectName.Length = 0;
AccessState->ObjectName.MaximumLength = 0;
}
AccessState->ObjectName.Buffer = ExAllocatePool( PagedPool,ObjectName->MaximumLength );
if (AccessState->ObjectName.Buffer != NULL) {
AccessState->ObjectName.MaximumLength = ObjectName->MaximumLength;
RtlCopyUnicodeString( &AccessState->ObjectName, ObjectName );
}
}
if ( LocalObjectTypeName != NULL ) {
if ( AccessState->ObjectTypeName.Buffer != NULL ) {
ExFreePool( AccessState->ObjectTypeName.Buffer );
AccessState->ObjectTypeName.Length = 0;
AccessState->ObjectTypeName.MaximumLength = 0;
}
AccessState->ObjectTypeName.Buffer = ExAllocatePool( PagedPool, LocalObjectTypeName->MaximumLength );
if (AccessState->ObjectTypeName.Buffer != NULL) {
AccessState->ObjectTypeName.MaximumLength = LocalObjectTypeName->MaximumLength;
RtlCopyUnicodeString( &AccessState->ObjectTypeName, LocalObjectTypeName );
}
}
}
if ( ObjectNameInfo != NULL ) {
ExFreePool( ObjectNameInfo );
}
if ( ObjectTypeNameInfo != NULL ) {
ExFreePool( ObjectTypeNameInfo );
}
}
return;
}
VOID
SeObjectReferenceAuditAlarm(
IN PLUID OperationID OPTIONAL,
IN PVOID Object,
IN PSECURITY_DESCRIPTOR SecurityDescriptor,
IN PSECURITY_SUBJECT_CONTEXT SubjectSecurityContext,
IN ACCESS_MASK DesiredAccess,
IN PPRIVILEGE_SET Privileges OPTIONAL,
IN BOOLEAN AccessGranted,
IN KPROCESSOR_MODE AccessMode
)
/*++
Routine Description:
description-of-function.
Arguments:
argument-name - Supplies | Returns description of argument.
.
.
Return Value:
return-value - Description of conditions needed to return value. - or -
None.
--*/
{
BOOLEAN GenerateAudit = FALSE;
BOOLEAN GenerateAlarm = FALSE;
PAGED_CODE();
UNREFERENCED_PARAMETER( OperationID );
UNREFERENCED_PARAMETER( Privileges );
if (AccessMode == KernelMode) {
return;
}
if ( SecurityDescriptor != NULL ) {
if ( SepAdtAuditThisEvent( AuditCategoryDetailedTracking, &AccessGranted )) {
SepExamineSacl(
RtlpSaclAddrSecurityDescriptor( (PISECURITY_DESCRIPTOR)SecurityDescriptor ),
EffectiveToken( SubjectSecurityContext ),
DesiredAccess,
AccessGranted,
&GenerateAudit,
&GenerateAlarm
);
if ( GenerateAudit || GenerateAlarm ) {
SepAdtObjectReferenceAuditAlarm(
Object,
SubjectSecurityContext,
DesiredAccess,
AccessGranted
);
}
}
}
return;
}
VOID
SeAuditHandleCreation(
IN PACCESS_STATE AccessState,
IN HANDLE Handle
)
/*++
Routine Description:
This function audits the creation of a handle.
It will examine the AuditHandleCreation field in the passed AccessState,
which will indicate whether auditing was performed when the object
was found or created.
This routine is necessary because object name decoding and handle
allocation occur in widely separate places, preventing us from
auditing everything at once.
Arguments:
AccessState - Supplies a pointer to the AccessState structure
representing this access attempt.
Handle - The newly allocated handle value.
Return Value:
None.
--*/
{
BOOLEAN AuditPerformed = FALSE;
PAUX_ACCESS_DATA AuxData;
PAGED_CODE();
AuxData = (PAUX_ACCESS_DATA)AccessState->AuxData;
if ( AccessState->GenerateAudit ) {
if ( AccessState->AuditPrivileges ) {
AuditPerformed = SepAdtPrivilegeObjectAuditAlarm (
(PUNICODE_STRING)&SeSubsystemName,
Handle,
(PTOKEN)AccessState->SubjectSecurityContext.ClientToken,
(PTOKEN)AccessState->SubjectSecurityContext.PrimaryToken,
AccessState->SubjectSecurityContext.ProcessAuditId,
AccessState->PreviouslyGrantedAccess,
AuxData->PrivilegesUsed,
TRUE
);
} else {
AuditPerformed = SepAdtOpenObjectAuditAlarm (
(PUNICODE_STRING)&SeSubsystemName,
&Handle,
&AccessState->ObjectTypeName,
&AccessState->ObjectName,
AccessState->SubjectSecurityContext.ClientToken,
AccessState->SubjectSecurityContext.PrimaryToken,
AccessState->OriginalDesiredAccess,
AccessState->PreviouslyGrantedAccess,
&AccessState->OperationID,
AuxData->PrivilegesUsed,
TRUE,
PsGetCurrentProcessId(),
AuditCategoryObjectAccess,
NULL,
0,
NULL );
}
}
//
// If we generated an 'open' audit, make sure we generate a close
//
AccessState->GenerateOnClose = AuditPerformed;
return;
}
VOID
SeCloseObjectAuditAlarm(
IN PVOID Object,
IN HANDLE Handle,
IN BOOLEAN GenerateOnClose
)
/*++
Routine Description:
This routine is used to generate audit and alarm messages when a handle
to an object is deleted.
This routine may result in several messages being generated and sent to
Port objects. This may result in a significant latency before
returning. Design of routines that must call this routine must take
this potential latency into account. This may have an impact on the
approach taken for data structure mutex locking, for example.
Arguments:
Object - Address of the object being accessed. This value will not be
used as a pointer (referenced). It is necessary only to enter into
log messages.
Handle - Supplies the handle value assigned to the open.
GenerateOnClose - Is a boolean value returned from a corresponding
SeOpenObjectAuditAlarm() call when the object handle was created.
Return Value:
None.
--*/
{
SECURITY_SUBJECT_CONTEXT SubjectSecurityContext;
PSID UserSid;
NTSTATUS Status;
PAGED_CODE();
UNREFERENCED_PARAMETER( Object );
if (GenerateOnClose) {
SeCaptureSubjectContext ( &SubjectSecurityContext );
UserSid = SepTokenUserSid( EffectiveToken (&SubjectSecurityContext));
SepAdtCloseObjectAuditAlarm( (PUNICODE_STRING) &SeSubsystemName,
Handle,
UserSid );
SeReleaseSubjectContext ( &SubjectSecurityContext );
}
return;
}
VOID
SeDeleteObjectAuditAlarm(
IN PVOID Object,
IN HANDLE Handle
)
/*++
Routine Description:
This routine is used to generate audit and alarm messages when an object
is marked for deletion.
This routine may result in several messages being generated and sent to
Port objects. This may result in a significant latency before
returning. Design of routines that must call this routine must take
this potential latency into account. This may have an impact on the
approach taken for data structure mutex locking, for example.
Arguments:
Object - Address of the object being accessed. This value will not be
used as a pointer (referenced). It is necessary only to enter into
log messages.
Handle - Supplies the handle value assigned to the open.
Return Value:
None.
--*/
{
SECURITY_SUBJECT_CONTEXT SubjectSecurityContext;
PSID UserSid;
NTSTATUS Status;
PAGED_CODE();
UNREFERENCED_PARAMETER( Object );
SeCaptureSubjectContext ( &SubjectSecurityContext );
UserSid = SepTokenUserSid( EffectiveToken (&SubjectSecurityContext));
SepAdtDeleteObjectAuditAlarm (
(PUNICODE_STRING)&SeSubsystemName,
(PVOID)Handle,
UserSid
);
SeReleaseSubjectContext ( &SubjectSecurityContext );
return;
}
VOID
SepExamineSacl(
IN PACL Sacl,
IN PACCESS_TOKEN Token,
IN ACCESS_MASK DesiredAccess,
IN BOOLEAN AccessGranted,
OUT PBOOLEAN GenerateAudit,
OUT PBOOLEAN GenerateAlarm
)
/*++
Routine Description:
This routine will examine the passed Sacl and determine what
if any action is required based its contents.
Note that this routine is not aware of any system state, ie,
whether or not auditing is currently enabled for either the
system or this particular object type.
Arguments:
Sacl - Supplies a pointer to the Sacl to be examined.
Token - Supplies the effective token of the caller
AccessGranted - Supplies whether or not the access attempt
was successful.
GenerateAudit - Returns a boolean indicating whether or not
we should generate an audit.
GenerateAlarm - Returns a boolean indiciating whether or not
we should generate an alarm.
Return Value:
STATUS_SUCCESS - The operation completed successfully.
--*/
{
ULONG i;
PVOID Ace;
ULONG AceCount;
ACCESS_MASK AccessMask;
UCHAR AceFlags;
BOOLEAN FailedMaximumAllowed;
PAGED_CODE();
*GenerateAudit = FALSE;
*GenerateAlarm = FALSE;
//
// If we failed an attempt to open an object for ONLY maximumum allowed,
// then we generate an audit if ANY ACCESS_DENIED audit matching this
// user's list of sids is found
//
FailedMaximumAllowed = FALSE;
if (!AccessGranted && (DesiredAccess & MAXIMUM_ALLOWED)) {
FailedMaximumAllowed = TRUE;
}
//
// If the Sacl is null, do nothing and return
//
if (Sacl == NULL) {
return;
}
AceCount = Sacl->AceCount;
if (AceCount == 0) {
return;
}
//
// Iterate through the ACEs on the Sacl until either we reach
// the end or discover that we have to take all possible actions,
// in which case it doesn't pay to look any further
//
for ( i = 0, Ace = FirstAce( Sacl ) ;
(i < AceCount) && !(*GenerateAudit && *GenerateAlarm);
i++, Ace = NextAce( Ace ) ) {
if ( !(((PACE_HEADER)Ace)->AceFlags & INHERIT_ONLY_ACE)) {
if ( (((PACE_HEADER)Ace)->AceType == SYSTEM_AUDIT_ACE_TYPE) ) {
if ( SepSidInToken( (PACCESS_TOKEN)Token, NULL, &((PSYSTEM_AUDIT_ACE)Ace)->SidStart, FALSE ) ) {
AccessMask = ((PSYSTEM_AUDIT_ACE)Ace)->Mask;
AceFlags = ((PACE_HEADER)Ace)->AceFlags;
if ( AccessMask & DesiredAccess ) {
if (((AceFlags & SUCCESSFUL_ACCESS_ACE_FLAG) && AccessGranted) ||
((AceFlags & FAILED_ACCESS_ACE_FLAG) && !AccessGranted)) {
*GenerateAudit = TRUE;
}
} else if ( FailedMaximumAllowed && (AceFlags & FAILED_ACCESS_ACE_FLAG) ) {
*GenerateAudit = TRUE;
}
}
continue;
}
if ( (((PACE_HEADER)Ace)->AceType == SYSTEM_ALARM_ACE_TYPE) ) {
if ( SepSidInToken( (PACCESS_TOKEN)Token, NULL, &((PSYSTEM_ALARM_ACE)Ace)->SidStart, FALSE ) ) {
AccessMask = ((PSYSTEM_ALARM_ACE)Ace)->Mask;
if ( AccessMask & DesiredAccess ) {
AceFlags = ((PACE_HEADER)Ace)->AceFlags;
if (((AceFlags & SUCCESSFUL_ACCESS_ACE_FLAG) && AccessGranted) ||
((AceFlags & FAILED_ACCESS_ACE_FLAG) && !AccessGranted)) {
*GenerateAlarm = TRUE;
}
}
}
}
}
}
return;
}
VOID
SepAuditTypeList (
IN PIOBJECT_TYPE_LIST ObjectTypeList,
IN ULONG ObjectTypeListLength,
IN PNTSTATUS AccessStatus,
IN ULONG StartIndex,
OUT PBOOLEAN GenerateSuccessAudit,
OUT PBOOLEAN GenerateFailureAudit
)
/*++
Routine Description:
This routine determines if any children of the object represented by
StartIndex have a different degree of success than the StartIndex element.
Arguments:
ObjectTypeList - The object type list to update.
ObjectTypeListLength - Number of elements in ObjectTypeList
AccessStatus - Specifies STATUS_SUCCESS or other error code to be
propogated back to the caller
StartIndex - Index to the target element to update.
GenerateSuccessAudit - Returns a boolean indicating whether or not
we should generate a success audit.
GenerateFailureAudit - Returns a boolean indicating whether or not
we should generate a failure audit.
Return Value:
None.
--*/
{
ULONG Index;
BOOLEAN WasSuccess;
PAGED_CODE();
//
// Determine if the target was successful.
//
WasSuccess = NT_SUCCESS( AccessStatus[StartIndex] );
//
// Loop handling all children of the target.
//
for ( Index=StartIndex+1; Index < ObjectTypeListLength; Index++ ) {
//
// By definition, the children of an object are all those entries
// immediately following the target. The list of children (or
// grandchildren) stops as soon as we reach an entry the has the
// same level as the target (a sibling) or lower than the target
// (an uncle).
//
if ( ObjectTypeList[Index].Level <= ObjectTypeList[StartIndex].Level ) {
break;
}
//
// If the child has different access than the target,
// mark the child.
//
if ( WasSuccess && !NT_SUCCESS( AccessStatus[Index]) ) {
*GenerateFailureAudit = TRUE;
ObjectTypeList[Index].Flags |= OBJECT_FAILURE_AUDIT;
} else if ( !WasSuccess && NT_SUCCESS( AccessStatus[Index]) ) {
*GenerateSuccessAudit = TRUE;
ObjectTypeList[Index].Flags |= OBJECT_SUCCESS_AUDIT;
}
}
}
VOID
SepSetAuditInfoForObjectType(
IN UCHAR AceFlags,
IN ACCESS_MASK AccessMask,
IN ACCESS_MASK DesiredAccess,
IN PIOBJECT_TYPE_LIST ObjectTypeList,
IN ULONG ObjectTypeListLength,
IN BOOLEAN ReturnResultList,
IN ULONG ObjectTypeIndex,
IN PNTSTATUS AccessStatus,
IN PACCESS_MASK GrantedAccess,
IN BOOLEAN FailedMaximumAllowed,
OUT PBOOLEAN GenerateSuccessAudit,
OUT PBOOLEAN GenerateFailureAudit
)
/*++
Routine Description:
Determine if success/failure audit needs to be generated for
object at ObjectTypeIndex in ObjectTypeList.
This helper function is called only by SepExamineSaclEx.
Arguments:
please refer to arg help for function SepExamineSaclEx
Return Value:
None.
--*/
{
PAGED_CODE();
if ( AccessMask & (DesiredAccess|GrantedAccess[ObjectTypeIndex]) ) {
if ( ( AceFlags & SUCCESSFUL_ACCESS_ACE_FLAG ) &&
NT_SUCCESS(AccessStatus[ObjectTypeIndex]) ) {
*GenerateSuccessAudit = TRUE;
if ( ObjectTypeListLength != 0 ) {
ObjectTypeList[ObjectTypeIndex].Flags |= OBJECT_SUCCESS_AUDIT;
if ( ReturnResultList ) {
SepAuditTypeList( ObjectTypeList,
ObjectTypeListLength,
AccessStatus,
ObjectTypeIndex,
GenerateSuccessAudit,
GenerateFailureAudit );
}
}
} else if ( ( AceFlags & FAILED_ACCESS_ACE_FLAG ) &&
!NT_SUCCESS(AccessStatus[ObjectTypeIndex]) ) {
*GenerateFailureAudit = TRUE;
if ( ObjectTypeListLength != 0 ) {
ObjectTypeList[ObjectTypeIndex].Flags |= OBJECT_FAILURE_AUDIT;
if ( ReturnResultList ) {
SepAuditTypeList( ObjectTypeList,
ObjectTypeListLength,
AccessStatus,
ObjectTypeIndex,
GenerateSuccessAudit,
GenerateFailureAudit );
}
}
}
} else if ( FailedMaximumAllowed && (AceFlags & FAILED_ACCESS_ACE_FLAG) ) {
*GenerateFailureAudit = TRUE;
if ( ObjectTypeListLength != 0 ) {
ObjectTypeList[ObjectTypeIndex].Flags |= OBJECT_FAILURE_AUDIT;
}
}
}
VOID
SepExamineSaclEx(
IN PACL Sacl,
IN PACCESS_TOKEN Token,
IN ACCESS_MASK DesiredAccess,
IN PIOBJECT_TYPE_LIST ObjectTypeList OPTIONAL,
IN ULONG ObjectTypeListLength,
IN BOOLEAN ReturnResultList,
IN PNTSTATUS AccessStatus,
IN PACCESS_MASK GrantedAccess,
IN PSID PrincipalSelfSid,
OUT PBOOLEAN GenerateSuccessAudit,
OUT PBOOLEAN GenerateFailureAudit
)
/*++
Routine Description:
This routine will examine the passed Sacl and determine what
if any action is required based its contents.
Note that this routine is not aware of any system state, ie,
whether or not auditing is currently enabled for either the
system or this particular object type.
Arguments:
Sacl - Supplies a pointer to the Sacl to be examined.
Token - Supplies the effective token of the caller
DesiredAccess - Access that the caller wanted to the object
ObjectTypeList - Supplies a list of GUIDs representing the object (and
sub-objects) being accessed.
ObjectTypeListLength - Specifies the number of elements in the ObjectTypeList.
ReturnResultList - If true, AccessStatus and GrantedAccess is actually
an array of entries ObjectTypeListLength elements long.
AccessStatus - Specifies STATUS_SUCCESS or other error code to be
propogated back to the caller
PrincipalSelfSid - If the security descriptor is associated with an object
that represents a principal (for example, a user object),
the PrincipalSelfSid parameter should be the SID of the object.
When evaluating access, this SID logically replaces the SID in any ACE
containing the well-known PRINCIPAL_SELF SID (S-1-5-10).
GrantedAccess - Specifies the access granted to the caller.
GenerateSuccessAudit - Returns a boolean indicating whether or not
we should generate a success audit.
GenerateFailureAudit - Returns a boolean indicating whether or not
we should generate a failure audit.
Return Value:
STATUS_SUCCESS - The operation completed successfully.
--*/
{
ULONG i, j;
PVOID Ace;
ULONG AceCount;
ACCESS_MASK AccessMask=0;
UCHAR AceFlags;
BOOLEAN FailedMaximumAllowed;
ULONG Index;
ULONG SuccessIndex;
#define INVALID_OBJECT_TYPE_LIST_INDEX 0xFFFFFFFF
PAGED_CODE();
*GenerateSuccessAudit = FALSE;
*GenerateFailureAudit = FALSE;
//
// If we failed an attempt to open an object for maximumum allowed,
// then we generate an audit if ANY ACCESS_DENIED audit matching this
// user's list of sids is found
//
FailedMaximumAllowed = FALSE;
if (!NT_SUCCESS(*AccessStatus) && (DesiredAccess & MAXIMUM_ALLOWED)) {
FailedMaximumAllowed = TRUE;
}
//
// If the Sacl is null, do nothing and return
//
if (Sacl == NULL) {
return;
}
AceCount = Sacl->AceCount;
if (AceCount == 0) {
return;
}
//
// Iterate through the ACEs on the Sacl until either we reach
// the end or discover that we have to take all possible actions,
// in which case it doesn't pay to look any further
//
for ( i = 0, Ace = FirstAce( Sacl ) ;
(i < AceCount) && !((*GenerateSuccessAudit || *GenerateFailureAudit) && ObjectTypeListLength <= 1 );
i++, Ace = NextAce( Ace ) ) {
AceFlags = ((PACE_HEADER)Ace)->AceFlags;
if ( AceFlags & INHERIT_ONLY_ACE ) {
continue;
}
Index = INVALID_OBJECT_TYPE_LIST_INDEX;
if ( (((PACE_HEADER)Ace)->AceType == SYSTEM_AUDIT_ACE_TYPE) ) {
if ( SepSidInToken( Token, PrincipalSelfSid, &((PSYSTEM_AUDIT_ACE)Ace)->SidStart, (BOOLEAN) ((AceFlags & FAILED_ACCESS_ACE_FLAG) != 0) ) ) {
AccessMask = ((PSYSTEM_AUDIT_ACE)Ace)->Mask;
if (ObjectTypeListLength == 0) {
if ( NT_SUCCESS(AccessStatus[0]) ) {
if ( ( AceFlags & SUCCESSFUL_ACCESS_ACE_FLAG ) &&
( AccessMask & GrantedAccess[0] ) ) {
*GenerateSuccessAudit = TRUE;
}
} else {
if ( ( AceFlags & FAILED_ACCESS_ACE_FLAG ) &&
( AccessMask & DesiredAccess ) ) {
*GenerateFailureAudit = TRUE;
}
}
} else {
for (j=0; j < ObjectTypeListLength; j++)
{
SepSetAuditInfoForObjectType(AceFlags,
AccessMask,
DesiredAccess,
ObjectTypeList,
ObjectTypeListLength,
ReturnResultList,
j,
AccessStatus,
GrantedAccess,
FailedMaximumAllowed,
GenerateSuccessAudit,
GenerateFailureAudit
);
}
Index = INVALID_OBJECT_TYPE_LIST_INDEX;
}
}
//
// Handle an object specific audit ACE
//
} else if ( (((PACE_HEADER)Ace)->AceType == SYSTEM_AUDIT_OBJECT_ACE_TYPE) ) {
GUID *ObjectTypeInAce;
//
// If no object type is in the ACE,
// treat this as a normal audit ACE.
//
AccessMask = ((PSYSTEM_AUDIT_OBJECT_ACE)Ace)->Mask;
ObjectTypeInAce = RtlObjectAceObjectType(Ace);
if ( ObjectTypeInAce == NULL ) {
if ( SepSidInToken( Token, PrincipalSelfSid, RtlObjectAceSid(Ace), (BOOLEAN)((AceFlags & FAILED_ACCESS_ACE_FLAG) != 0) ) ) {
for (j=0; j < ObjectTypeListLength; j++)
{
SepSetAuditInfoForObjectType(AceFlags,
AccessMask,
DesiredAccess,
ObjectTypeList,
ObjectTypeListLength,
ReturnResultList,
j,
AccessStatus,
GrantedAccess,
FailedMaximumAllowed,
GenerateSuccessAudit,
GenerateFailureAudit
);
}
Index = INVALID_OBJECT_TYPE_LIST_INDEX;
}
//
// If an object type is in the ACE,
// Find it in the LocalTypeList before using the ACE.
//
} else {
if ( SepSidInToken( Token, PrincipalSelfSid, RtlObjectAceSid(Ace), (BOOLEAN)((AceFlags & FAILED_ACCESS_ACE_FLAG) != 0) ) ) {
if ( !SepObjectInTypeList( ObjectTypeInAce,
ObjectTypeList,
ObjectTypeListLength,
&Index ) ) {
Index = INVALID_OBJECT_TYPE_LIST_INDEX;
}
}
}
}
//
// If the ACE has a matched SID and a matched GUID,
// handle it.
//
if ( Index != INVALID_OBJECT_TYPE_LIST_INDEX ) {
//
// ASSERT: we have an ACE to be audited.
//
// Index is an index into ObjectTypeList of the entry to mark
// as the GUID needs auditing.
//
// SuccessIndex is an index into AccessStatus to determine if
// a success or failure audit is to be generated
//
SepSetAuditInfoForObjectType(AceFlags,
AccessMask,
DesiredAccess,
ObjectTypeList,
ObjectTypeListLength,
ReturnResultList,
Index,
AccessStatus,
GrantedAccess,
FailedMaximumAllowed,
GenerateSuccessAudit,
GenerateFailureAudit
);
}
}
return;
}
/******************************************************************************
* *
* The following list of privileges is checked at high frequency *
* during normal operation, and tend to clog up the audit log when *
* privilege auditing is enabled. The use of these privileges will *
* not be audited when they are checked singly or in combination with *
* each other. *
* *
* When adding new privileges, be careful to preserve the NULL *
* privilege pointer marking the end of the array. *
* *
* Be sure to update the corresponding array in LSA when adding new *
* privileges to this list (LsaFilterPrivileges). *
* *
******************************************************************************/
#ifdef ALLOC_DATA_PRAGMA
#pragma data_seg("PAGEDATA")
#pragma const_seg("PAGECONST")
#endif
PLUID const * SepFilterPrivileges = NULL;
const PLUID SepFilterPrivilegesLong[] =
{
&SeChangeNotifyPrivilege,
&SeAuditPrivilege,
&SeCreateTokenPrivilege,
&SeAssignPrimaryTokenPrivilege,
&SeBackupPrivilege,
&SeRestorePrivilege,
&SeDebugPrivilege,
NULL
};
/******************************************************************************
* *
* The following list of privileges is the same as the above list, except *
* is missing backup and restore privileges. This allows for auditing *
* the use of those privileges at the time they are used. *
* *
* The use of this list or the one above is determined by settings in *
* the registry. *
* *
******************************************************************************/
const PLUID SepFilterPrivilegesShort[] =
{
&SeChangeNotifyPrivilege,
&SeAuditPrivilege,
&SeCreateTokenPrivilege,
&SeAssignPrimaryTokenPrivilege,
&SeDebugPrivilege,
NULL
};
BOOLEAN
SepInitializePrivilegeFilter(
BOOLEAN Verbose
)
/*++
Routine Description:
Initializes SepFilterPrivileges for either normal or verbose auditing.
Arguments:
Verbose - Whether we want to filter by the short or long privileges
list. Verbose == TRUE means use the short list.
Return Value:
TRUE for success, FALSE for failure
--*/
{
if (Verbose) {
SepFilterPrivileges = SepFilterPrivilegesShort;
} else {
SepFilterPrivileges = SepFilterPrivilegesLong;
}
return( TRUE );
}
BOOLEAN
SepFilterPrivilegeAudits(
IN PPRIVILEGE_SET PrivilegeSet
)
/*++
Routine Description:
This routine will filter out a list of privileges as listed in the
SepFilterPrivileges array.
Arguments:
Privileges - The privilege set to be audited
Return Value:
FALSE means that this use of privilege is not to be audited.
TRUE means that the audit should continue normally.
--*/
{
PLUID const *Privilege;
ULONG Match = 0;
ULONG i;
PAGED_CODE();
if ( !ARGUMENT_PRESENT(PrivilegeSet) ||
(PrivilegeSet->PrivilegeCount == 0) ) {
return( FALSE );
}
for (i=0; i<PrivilegeSet->PrivilegeCount; i++) {
Privilege = SepFilterPrivileges;
do {
if ( RtlEqualLuid( &PrivilegeSet->Privilege[i].Luid, *Privilege )) {
Match++;
break;
}
} while ( *++Privilege != NULL );
}
if ( Match == PrivilegeSet->PrivilegeCount ) {
return( FALSE );
} else {
return( TRUE );
}
}
BOOLEAN
SeAuditingFileOrGlobalEvents(
IN BOOLEAN AccessGranted,
IN PSECURITY_DESCRIPTOR SecurityDescriptor,
IN PSECURITY_SUBJECT_CONTEXT SubjectSecurityContext
)
/*++
Routine Description:
This routine is to be called by a file system to quickly determine
if we are auditing file open events. This allows the file system
to avoid the often considerable setup involved in generating an audit.
Arguments:
AccessGranted - Supplies whether the access attempt was successful
or a failure.
Return Value:
Boolean - TRUE if events of type AccessGranted are being audited, FALSE
otherwise.
--*/
{
PISECURITY_DESCRIPTOR ISecurityDescriptor = (PISECURITY_DESCRIPTOR) SecurityDescriptor;
PAGED_CODE();
if ( ((PTOKEN)EffectiveToken( SubjectSecurityContext ))->AuditData != NULL) {
return( TRUE );
}
if ( RtlpSaclAddrSecurityDescriptor( ISecurityDescriptor ) == NULL ) {
return( FALSE );
}
return( SepAdtAuditThisEvent( AuditCategoryObjectAccess, &AccessGranted ) );
}
BOOLEAN
SeAuditingFileEvents(
IN BOOLEAN AccessGranted,
IN PSECURITY_DESCRIPTOR SecurityDescriptor
)
/*++
Routine Description:
This routine is to be called by a file system to quickly determine
if we are auditing file open events. This allows the file system
to avoid the often considerable setup involved in generating an audit.
Arguments:
AccessGranted - Supplies whether the access attempt was successful
or a failure.
Return Value:
Boolean - TRUE if events of type AccessGranted are being audited, FALSE
otherwise.
--*/
{
PAGED_CODE();
UNREFERENCED_PARAMETER( SecurityDescriptor );
return( SepAdtAuditThisEvent( AuditCategoryObjectAccess, &AccessGranted ) );
}
BOOLEAN
SeAuditingHardLinkEvents(
IN BOOLEAN AccessGranted,
IN PSECURITY_DESCRIPTOR SecurityDescriptor
)
/*++
Routine Description:
This routine is to be called by a file system to quickly determine
if we are auditing hard link creation.
Arguments:
AccessGranted - Supplies whether the access attempt was successful
or a failure.
SecurityDescriptor - SD of the linked file.
Return Value:
Boolean - TRUE if events of type AccessGranted are being audited, FALSE
otherwise.
--*/
{
PISECURITY_DESCRIPTOR pSD = SecurityDescriptor;
PACL Sacl = RtlpSaclAddrSecurityDescriptor( pSD );
PAGED_CODE();
//
// Audit hard link creation if object access auditing is on and the original file
// has a non empty SACL.
//
if ( (SepAdtAuditThisEvent( AuditCategoryObjectAccess, &AccessGranted )) &&
(NULL != Sacl) &&
(0 != Sacl->AceCount)) {
return TRUE;
}
return FALSE;
}