/*++ 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 "tokenp.h" #include "adt.h" #include "adtp.h" VOID SepProbeAndCaptureString_U ( IN PUNICODE_STRING SourceString, OUT PUNICODE_STRING *DestString ); VOID SepFreeCapturedString( IN PUNICODE_STRING CapturedString ); #ifdef ALLOC_PRAGMA #pragma alloc_text(PAGE,NtAccessCheckAndAuditAlarm) #pragma alloc_text(PAGE,NtCloseObjectAuditAlarm) #pragma alloc_text(PAGE,NtDeleteObjectAuditAlarm) #pragma alloc_text(PAGE,NtOpenObjectAuditAlarm) #pragma alloc_text(PAGE,NtPrivilegeObjectAuditAlarm) #pragma alloc_text(PAGE,NtPrivilegedServiceAuditAlarm) #pragma alloc_text(PAGE,SeAuditHandleCreation) #pragma alloc_text(PAGE,SeAuditingFileEvents) #pragma alloc_text(PAGE,SeCheckAuditPrivilege) #pragma alloc_text(PAGE,SeCloseObjectAuditAlarm) #pragma alloc_text(PAGE,SeDeleteObjectAuditAlarm) #pragma alloc_text(PAGE,SeCreateObjectAuditAlarm) #pragma alloc_text(PAGE,SeObjectReferenceAuditAlarm) #pragma alloc_text(PAGE,SeOpenObjectAuditAlarm) #pragma alloc_text(PAGE,SePrivilegeObjectAuditAlarm) #pragma alloc_text(PAGE,SePrivilegedServiceAuditAlarm) #pragma alloc_text(PAGE,SeTraverseAuditAlarm) #pragma alloc_text(PAGE,SepExamineSacl) #pragma alloc_text(PAGE,SepFilterPrivilegeAudits) #pragma alloc_text(PAGE,SepFreeCapturedString) #pragma alloc_text(PAGE,SepProbeAndCaptureString_U) #pragma alloc_text(PAGE,SepSinglePrivilegeCheck) #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, // BUGWARNING need service name 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. --*/ { PAGED_CODE(); ProbeForRead( SourceString, sizeof( UNICODE_STRING ), sizeof( ULONG ) ); *DestString = NULL; // // Probe the pointer to the buffer to ensure that the // characters in the buffer are readable by the caller. // If so, then try to allocate a pool buffer and copy the // buffer into it. // ProbeForRead((PVOID)SourceString->Buffer, SourceString->Length, sizeof(WCHAR)); *DestString = ExAllocatePoolWithTag( PagedPool, sizeof( UNICODE_STRING ) + SourceString->MaximumLength, 'sUeS' ); if ( *DestString == NULL ) { return; } (*DestString)->Buffer = (PWSTR)((PCHAR)(*DestString) + sizeof( UNICODE_STRING )); (*DestString)->MaximumLength = SourceString->MaximumLength; RtlCopyUnicodeString( *DestString, SourceString ); 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; 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 SepTokenObjectType, // 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 ); ProbeForRead( Privileges, sizeof(PRIVILEGE_SET), sizeof(ULONG) ); PrivilegeParameterLength = (ULONG)sizeof(PRIVILEGE_SET) + ((Privileges->PrivilegeCount - ANYSIZE_ARRAY) * (ULONG)sizeof(LUID_AND_ATTRIBUTES) ); ProbeForRead( Privileges, PrivilegeParameterLength, sizeof(ULONG) ); CapturedPrivileges = ExAllocatePoolWithTag( PagedPool, PrivilegeParameterLength, 'rPeS' ); if (CapturedPrivileges != NULL) { RtlMoveMemory ( CapturedPrivileges, Privileges, PrivilegeParameterLength ); } } except (EXCEPTION_EXECUTE_HANDLER) { if (CapturedPrivileges != NULL) { ExFreePool( CapturedPrivileges ); } if (CapturedSubsystemName != NULL) { SepFreeCapturedString ( CapturedSubsystemName ); } SeReleaseSubjectContext ( &SubjectSecurityContext ); ObDereferenceObject( (PVOID)Token ); return GetExceptionCode(); } // // 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 ( &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; PAGED_CODE(); PreviousMode = KeGetPreviousMode(); ASSERT(PreviousMode != KernelMode); Status = ObReferenceObjectByHandle( ClientToken, // Handle TOKEN_QUERY, // DesiredAccess SepTokenObjectType, // 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 ); } ProbeForRead( Privileges, sizeof(PRIVILEGE_SET), sizeof(ULONG) ); PrivilegeParameterLength = (ULONG)sizeof(PRIVILEGE_SET) + ((Privileges->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) { RtlMoveMemory ( CapturedPrivileges, Privileges, PrivilegeParameterLength ); } } except (EXCEPTION_EXECUTE_HANDLER) { if (CapturedSubsystemName != NULL) { SepFreeCapturedString ( CapturedSubsystemName ); } if (CapturedServiceName != NULL) { SepFreeCapturedString ( CapturedServiceName ); } if (CapturedPrivileges != NULL) { ExFreePool ( CapturedPrivileges ); } SeReleaseSubjectContext ( &SubjectSecurityContext ); ObDereferenceObject( (PVOID)Token ); return GetExceptionCode(); } // // 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 ( &SeSubsystemName, ServiceName, SubjectSecurityContext->ClientToken, SubjectSecurityContext->PrimaryToken, Privileges, AccessGranted ); } return; } 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: 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. 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. 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. 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; ACCESS_MASK LocalGrantedAccess = (ACCESS_MASK)0; BOOLEAN LocalGenerateOnClose = FALSE; 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; ACCESS_MASK PreviouslyGrantedAccess = (ACCESS_MASK)0; GENERIC_MAPPING LocalGenericMapping; PPRIVILEGE_SET PrivilegeSet = NULL; BOOLEAN Result; BOOLEAN AccessGranted = FALSE; BOOLEAN GenerateAudit = FALSE; BOOLEAN GenerateAlarm = FALSE; LUID OperationId; BOOLEAN AuditPerformed; PAGED_CODE(); PreviousMode = KeGetPreviousMode(); ASSERT( PreviousMode != KernelMode ); // // Capture the subject Context // SeCaptureSubjectContext ( &SubjectSecurityContext ); // // Make sure we're impersonating a client... // if ( (SubjectSecurityContext.ClientToken == NULL) ) { SeReleaseSubjectContext ( &SubjectSecurityContext ); return(STATUS_NO_IMPERSONATION_TOKEN); } // // ...and at a high enough impersonation level // if (SubjectSecurityContext.ImpersonationLevel < SecurityIdentification) { SeReleaseSubjectContext ( &SubjectSecurityContext ); return(STATUS_BAD_IMPERSONATION_LEVEL); } try { ProbeForWriteUlong((PULONG)AccessStatus); ProbeForWriteUlong((PULONG)GrantedAccess); ProbeForRead( GenericMapping, sizeof(GENERIC_MAPPING), sizeof(ULONG) ); LocalGenericMapping = *GenericMapping; } except (EXCEPTION_EXECUTE_HANDLER) { SeReleaseSubjectContext( &SubjectSecurityContext ); return( GetExceptionCode() ); } // // Check for SeAuditPrivilege // Result = SeCheckAuditPrivilege ( &SubjectSecurityContext, PreviousMode ); if (!Result) { SeReleaseSubjectContext ( &SubjectSecurityContext ); try { *AccessStatus = STATUS_ACCESS_DENIED; *GrantedAccess = (ACCESS_MASK)0; } except (EXCEPTION_EXECUTE_HANDLER) { return( GetExceptionCode() ); } return(STATUS_PRIVILEGE_NOT_HELD); } if (DesiredAccess & ( GENERIC_READ | GENERIC_WRITE | GENERIC_EXECUTE | GENERIC_ALL )) { SeReleaseSubjectContext ( &SubjectSecurityContext ); return(STATUS_GENERIC_NOT_MAPPED); } // // 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) { SeReleaseSubjectContext ( &SubjectSecurityContext ); try { *AccessStatus = Status; *GrantedAccess = (ACCESS_MASK)0; } except (EXCEPTION_EXECUTE_HANDLER) { return( GetExceptionCode() ); } if ( CapturedSecurityDescriptor == NULL ) { return( STATUS_INVALID_SECURITY_DESCR ); } return( STATUS_SUCCESS ); } // // A valid security descriptor must have an owner and a group // if ( SepOwnerAddrSecurityDescriptor( (PISECURITY_DESCRIPTOR)CapturedSecurityDescriptor ) == NULL || SepGroupAddrSecurityDescriptor( (PISECURITY_DESCRIPTOR)CapturedSecurityDescriptor ) == NULL ) { SeReleaseSubjectContext ( &SubjectSecurityContext ); SeReleaseSecurityDescriptor ( CapturedSecurityDescriptor, PreviousMode, FALSE ); return( STATUS_INVALID_SECURITY_DESCR ); } // // 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) { SeReleaseSubjectContext ( &SubjectSecurityContext ); if (CapturedSubsystemName != NULL) { SepFreeCapturedString( CapturedSubsystemName ); } if (CapturedObjectTypeName != NULL) { SepFreeCapturedString( CapturedObjectTypeName ); } if (CapturedObjectName != NULL) { SepFreeCapturedString( CapturedObjectName ); } SeReleaseSecurityDescriptor ( CapturedSecurityDescriptor, PreviousMode, FALSE ); return( GetExceptionCode() ); } // // 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 )) { // // 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; Status = STATUS_SUCCESS; } else { // // Finally, do the access check // AccessGranted = SepAccessCheck ( CapturedSecurityDescriptor, SubjectSecurityContext.PrimaryToken, SubjectSecurityContext.ClientToken, DesiredAccess, &LocalGenericMapping, PreviouslyGrantedAccess, PreviousMode, &LocalGrantedAccess, NULL, &Status ); } } // // sound the alarms... // if ( SepAdtAuditThisEvent( AuditCategoryObjectAccess, &AccessGranted )) { SepExamineSacl( SepSaclAddrSecurityDescriptor( (PISECURITY_DESCRIPTOR)CapturedSecurityDescriptor ), EffectiveToken( &SubjectSecurityContext ), (AccessGranted ? LocalGrantedAccess : (DesiredAccess | PreviouslyGrantedAccess)), AccessGranted, &GenerateAudit, &GenerateAlarm ); } if (GenerateAudit || GenerateAlarm) { // // 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; } ExAllocateLocallyUniqueId( &OperationId ); AuditPerformed = SepAdtOpenObjectAuditAlarm ( CapturedSubsystemName, AccessGranted ? &HandleId : NULL, // Don't audit handle if failure CapturedObjectTypeName, 0, // IN PVOID Object OPTIONAL, CapturedObjectName, SubjectSecurityContext.ClientToken, SubjectSecurityContext.PrimaryToken, DesiredAccess, LocalGrantedAccess, &OperationId, PrivilegeSet, ObjectCreation, AccessGranted, GenerateAudit, GenerateAlarm, PsProcessAuditId( PsGetCurrentProcess() ) ); } 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 ); // // 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 ); } try { *AccessStatus = Status; *GrantedAccess = LocalGrantedAccess; *GenerateOnClose = LocalGenerateOnClose; } except (EXCEPTION_EXECUTE_HANDLER) { return( GetExceptionCode() ); } return(STATUS_SUCCESS); } 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; PTOKEN Token; PAGED_CODE(); PreviousMode = KeGetPreviousMode(); ASSERT( PreviousMode != KernelMode ); Status = ObReferenceObjectByHandle( ClientToken, // Handle TOKEN_QUERY, // DesiredAccess SepTokenObjectType, // 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 { if (ARGUMENT_PRESENT(Privileges)) { ProbeForRead( Privileges, sizeof(PRIVILEGE_SET), sizeof(ULONG) ); PrivilegeParameterLength = (ULONG)sizeof(PRIVILEGE_SET) + ((Privileges->PrivilegeCount - ANYSIZE_ARRAY) * (ULONG)sizeof(LUID_AND_ATTRIBUTES) ); ProbeForRead( Privileges, PrivilegeParameterLength, sizeof(ULONG) ); CapturedPrivileges = ExAllocatePoolWithTag( PagedPool, PrivilegeParameterLength, 'rPeS' ); if (CapturedPrivileges != NULL) { RtlMoveMemory ( CapturedPrivileges, Privileges, PrivilegeParameterLength ); } else { SeReleaseSecurityDescriptor ( CapturedSecurityDescriptor, PreviousMode, FALSE ); ObDereferenceObject( (PVOID)Token ); SeReleaseSubjectContext ( &SubjectSecurityContext ); return( STATUS_INSUFFICIENT_RESOURCES ); } } if (ARGUMENT_PRESENT( HandleId )) { ProbeForRead( (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) { 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 GetExceptionCode(); } if ( SepAdtAuditThisEvent( AuditCategoryObjectAccess, &AccessGranted) ) { SepExamineSacl( SepSaclAddrSecurityDescriptor( (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, NULL, CapturedObjectName, Token, SubjectSecurityContext.PrimaryToken, DesiredAccess, GrantedAccess, NULL, CapturedPrivileges, ObjectCreation, AccessGranted, GenerateAudit, GenerateAlarm, PsProcessAuditId( PsGetCurrentProcess() ) ); 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; 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 )); SeReleaseSubjectContext ( &SubjectSecurityContext ); try { SepProbeAndCaptureString_U ( SubsystemName, &CapturedSubsystemName ); } except (EXCEPTION_EXECUTE_HANDLER) { if ( CapturedSubsystemName != NULL ) { SepFreeCapturedString( CapturedSubsystemName ); } ExFreePool( CapturedUserSid ); return GetExceptionCode(); } // // This routine will check to see if auditing is enabled // SepAdtCloseObjectAuditAlarm ( CapturedSubsystemName, HandleId, NULL, CapturedUserSid, SepTokenAuthenticationId( EffectiveToken( &SubjectSecurityContext )) ); SepFreeCapturedString( CapturedSubsystemName ); ExFreePool( CapturedUserSid ); return(STATUS_SUCCESS); } 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 )); SeReleaseSubjectContext ( &SubjectSecurityContext ); try { SepProbeAndCaptureString_U ( SubsystemName, &CapturedSubsystemName ); } except (EXCEPTION_EXECUTE_HANDLER) { if ( CapturedSubsystemName != NULL ) { SepFreeCapturedString( CapturedSubsystemName ); } ExFreePool( CapturedUserSid ); return GetExceptionCode(); } // // This routine will check to see if auditing is enabled // SepAdtDeleteObjectAuditAlarm ( CapturedSubsystemName, HandleId, NULL, CapturedUserSid, SepTokenAuthenticationId( EffectiveToken( &SubjectSecurityContext )) ); 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(); 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( SepSaclAddrSecurityDescriptor( (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 ( &SeSubsystemName, NULL, LocalObjectTypeName, NULL, ObjectName, AccessState->SubjectSecurityContext.ClientToken, AccessState->SubjectSecurityContext.PrimaryToken, AccessState->OriginalDesiredAccess, AccessState->PreviouslyGrantedAccess, &AccessState->OperationID, AuxData->PrivilegesUsed, FALSE, FALSE, TRUE, FALSE, AccessState->SubjectSecurityContext.ProcessAuditId ); } else { // // Copy all the stuff we're going to need into the // AccessState and return. // if ( ObjectName != NULL ) { AccessState->ObjectName.Buffer = ExAllocatePool( PagedPool,ObjectName->MaximumLength ); if (AccessState->ObjectName.Buffer != NULL) { AccessState->ObjectName.MaximumLength = ObjectName->MaximumLength; RtlCopyUnicodeString( &AccessState->ObjectName, ObjectName ); } } if ( LocalObjectTypeName != NULL ) { 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(); 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( SepSaclAddrSecurityDescriptor( (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 ( &SeSubsystemName, NULL, LocalObjectTypeName, NULL, ObjectName, AccessState->SubjectSecurityContext.ClientToken, AccessState->SubjectSecurityContext.PrimaryToken, AccessState->OriginalDesiredAccess, AccessState->PreviouslyGrantedAccess, &AccessState->OperationID, AuxData->PrivilegesUsed, FALSE, FALSE, TRUE, FALSE, AccessState->SubjectSecurityContext.ProcessAuditId ); } else { // // Generate the delete audit first // SepAdtOpenObjectForDeleteAuditAlarm ( &SeSubsystemName, NULL, LocalObjectTypeName, NULL, ObjectName, AccessState->SubjectSecurityContext.ClientToken, AccessState->SubjectSecurityContext.PrimaryToken, AccessState->OriginalDesiredAccess, AccessState->PreviouslyGrantedAccess, &AccessState->OperationID, AuxData->PrivilegesUsed, FALSE, TRUE, TRUE, FALSE, AccessState->SubjectSecurityContext.ProcessAuditId ); // // Copy all the stuff we're going to need into the // AccessState and return. // if ( ObjectName != NULL ) { AccessState->ObjectName.Buffer = ExAllocatePool( PagedPool,ObjectName->MaximumLength ); if (AccessState->ObjectName.Buffer != NULL) { AccessState->ObjectName.MaximumLength = ObjectName->MaximumLength; RtlCopyUnicodeString( &AccessState->ObjectName, ObjectName ); } } if ( LocalObjectTypeName != NULL ) { 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; } #if 0 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: SeOpenObjectAuditForDeleteAlarm is used by the file systems for files that are opened with the FILE_DELETE_ON_CLOSE bit specified. Since a handle may not be created, it is important to generate the deletiong audit when the file is opened. No messages will be generated for Kernel mode accesses. 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; PTOKEN Token; ACCESS_MASK MappedGrantMask = (ACCESS_MASK)0; ACCESS_MASK MappedDenyMask = (ACCESS_MASK)0; PAUX_ACCESS_DATA AuxData; PAGED_CODE(); 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( SepSaclAddrSecurityDescriptor( (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; } // // Do the audit here. // // // BUGBUG: call SepAdtOpenObjectForDeleteAuditAlarm here // instead. // SepAdtOpenObjectAuditAlarm ( &SeSubsystemName, NULL, LocalObjectTypeName, NULL, ObjectName, AccessState->SubjectSecurityContext.ClientToken, AccessState->SubjectSecurityContext.PrimaryToken, AccessState->OriginalDesiredAccess, AccessState->PreviouslyGrantedAccess, &AccessState->OperationID, AuxData->PrivilegesUsed, FALSE, FALSE, TRUE, FALSE, AccessState->SubjectSecurityContext.ProcessAuditId ); if ( ObjectNameInfo != NULL ) { ExFreePool( ObjectNameInfo ); } if ( ObjectTypeNameInfo != NULL ) { ExFreePool( ObjectTypeNameInfo ); } } return; } #endif VOID SeTraverseAuditAlarm( IN PLUID OperationID, IN PVOID DirectoryObject, IN PSECURITY_DESCRIPTOR SecurityDescriptor, IN PSECURITY_SUBJECT_CONTEXT SubjectSecurityContext, IN BOOLEAN SubjectContextLocked, IN ACCESS_MASK TraverseAccess, IN PPRIVILEGE_SET Privileges OPTIONAL, IN BOOLEAN AccessGranted, IN KPROCESSOR_MODE AccessMode ) /*++ Routine Description: This routine is called to audit directory traverse operations specifically. It should be called by parse procedures as they traverse directories as part of their operation. Arguments: OperationID - LUID identifying the operation in progress DirectoryObject - Pointer to the directory being traversed. SecurityDescriptor - The security descriptor (if any) attached to the directory being traversed. SubjectSecurityContext - Security context of the client. SubjectContextLocked - Supplies whether the SubjectContext is locked for shared access. TraverseAccess - Mask to indicate the traverse access for this object type. Privileges - Optional parameter to indicate any privilges that the subject may have used to gain access to the object. 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. Return value: None. --*/ { PAGED_CODE(); #if 0 BOOLEAN GenerateAudit = FALSE; BOOLEAN GenerateAlarm = FALSE; if (AccessMode == KernelMode) { return; } if ((SeAuditingState[AuditEventTraverse].AuditOnSuccess && AccessGranted) || SeAuditingState[AuditEventTraverse].AuditOnFailure && !AccessGranted) { if ( SecurityDescriptor != NULL ) { if ( !SubjectContextLocked ) { SeLockSubjectContext( SubjectSecurityContext ); } SepExamineSacl( SepSaclAddrSecurityDescriptor( (PISECURITY_DESCRIPTOR)SecurityDescriptor ), EffectiveToken( SubjectSecurityContext ), TraverseAccess, AccessGranted, &GenerateAudit, &GenerateAlarm ); if (GenerateAudit || GenerateAlarm) { SepAdtTraverseAuditAlarm( OperationID, DirectoryObject, SepTokenUserSid(EffectiveToken( SubjectSecurityContext )), SepTokenAuthenticationId(EffectiveToken( SubjectSecurityContext )), TraverseAccess, Privileges, AccessGranted, GenerateAudit, GenerateAlarm ); } if ( !SubjectContextLocked ) { SeUnlockSubjectContext( SubjectSecurityContext ); } } } #endif return; } // #if 0 VOID SeCreateInstanceAuditAlarm( 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; if ( AccessMode == KernelMode ) { return; } if ( SepAdtAuditThisEvent( AuditCategoryObjectAccess, &AccessGranted )) { if ( SecurityDescriptor != NULL ) { SepExamineSacl( SepSaclAddrSecurityDescriptor( (PISECURITY_DESCRIPTOR)SecurityDescriptor ), EffectiveToken( SubjectSecurityContext ), DesiredAccess, AccessGranted, &GenerateAudit, &GenerateAlarm ); if ( GenerateAudit || GenerateAlarm ) { SepAdtCreateInstanceAuditAlarm( OperationID, Object, SepTokenUserSid(EffectiveToken( SubjectSecurityContext )), SepTokenAuthenticationId(EffectiveToken( SubjectSecurityContext )), DesiredAccess, Privileges, AccessGranted, GenerateAudit, GenerateAlarm ); } } } return; } #endif VOID SeCreateObjectAuditAlarm( IN PLUID OperationID OPTIONAL, IN PVOID DirectoryObject, IN PUNICODE_STRING ComponentName, IN PSECURITY_DESCRIPTOR SecurityDescriptor, IN PSECURITY_SUBJECT_CONTEXT SubjectSecurityContext, IN ACCESS_MASK DesiredAccess, IN PPRIVILEGE_SET Privileges OPTIONAL, IN BOOLEAN AccessGranted, OUT PBOOLEAN AuditPerformed, IN KPROCESSOR_MODE AccessMode ) /*++ Routine Description: Audits the creation of an object in a directory. Arguments: OperationID - Optionally supplies the LUID representing the operation id for this operation. DirectoryObject - Provides a pointer to the directory object being examined. ComponentName - Provides a pointer to a Unicode string containing the relative name of the object being created. SecurityDescriptor - The security descriptor for the passed direcctory. SubjectSecurityContext - The current subject context. DesiredAccess - The desired access to the directory. Privileges - Returns any privileges that were used for the access attempt. AccessGranted - Returns whether or not the access was successful. AuditPerformed - Returns whether or not auditing was performed. AccessMode - The previous mode. Return Value: return-value - Description of conditions needed to return value. - or - None. --*/ { PAGED_CODE(); #if 0 BOOLEAN GenerateAudit = FALSE; BOOLEAN GenerateAlarm = FALSE; PUNICODE_STRING DirectoryName; POBJECT_NAME_INFORMATION ObjectNameInformation = NULL; UNREFERENCED_PARAMETER( DirectoryObject ); UNREFERENCED_PARAMETER( Privileges ); if (AccessMode == KernelMode) { return; } if ( SecurityDescriptor != NULL ) { if ( SepAdtAuditThisEvent( AuditCategoryObjectAccess, &AccessGranted )) { SepExamineSacl( SepSaclAddrSecurityDescriptor( (PISECURITY_DESCRIPTOR)SecurityDescriptor ), EffectiveToken( SubjectSecurityContext ), DesiredAccess, AccessGranted, &GenerateAudit, &GenerateAlarm ); if ( GenerateAudit || GenerateAlarm ) { // // Call ob for the name of the directory. // ObjectNameInformation = SepQueryNameString( DirectoryObject ); if ( ObjectNameInformation != NULL ) { DirectoryName = &ObjectNameInformation->Name; } SepAdtCreateObjectAuditAlarm( OperationID, DirectoryName, ComponentName, SepTokenUserSid(EffectiveToken( SubjectSecurityContext )), SepTokenAuthenticationId( EffectiveToken( SubjectSecurityContext )), DesiredAccess, AccessGranted, GenerateAudit, GenerateAlarm ); *AuditPerformed = TRUE; if ( DirectoryName != NULL ) { ExFreePool( DirectoryName ); } } } } #endif 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(); if (AccessMode == KernelMode) { return; } if ( SecurityDescriptor != NULL ) { if ( SepAdtAuditThisEvent( AuditCategoryDetailedTracking, &AccessGranted )) { SepExamineSacl( SepSaclAddrSecurityDescriptor( (PISECURITY_DESCRIPTOR)SecurityDescriptor ), EffectiveToken( SubjectSecurityContext ), DesiredAccess, AccessGranted, &GenerateAudit, &GenerateAlarm ); if ( GenerateAudit || GenerateAlarm ) { SepAdtObjectReferenceAuditAlarm( OperationID, Object, SubjectSecurityContext, DesiredAccess, Privileges, AccessGranted, GenerateAudit, GenerateAlarm ); } } } return; } // #if 0 VOID SeImplicitObjectAuditAlarm( 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: This routine is used to generate audit messages for implicit references to objects. This includes both objects that may be referenced without either a name or a handle (such as the current process), and references to objects where access checking is being performed but the object is not being opened or created. For example, the file system may wish to determine if a user has a FILE_LIST_DIRECTORY access to a directory, but it is not opening the directory or creating a handle to it. Arguments: OperationID - If this reference is part of another, more complex object access, pass in the OperationID corresponding to that access. Object - The object being accessed. SecurityDescriptor - The security descriptor of the object being accessed. SubjectSecurityContext - The current subject context. DesiredAccess - Access mask describing the desired access to the object. Privileges - Any privileges that have been invoked as part of gaining access to this object. AccessGranted - Boolean describing whether access was granted or not. AccessMode - Previous processor mode. Return value: None. --*/ { BOOLEAN GenerateAudit = FALSE; BOOLEAN GenerateAlarm = FALSE; if (AccessMode == KernelMode) { return; } if ( SecurityDescriptor != NULL ) { if ((SeAuditingState[AuditEventImplicitAccess].AuditOnSuccess && AccessGranted) || SeAuditingState[AuditEventImplicitAccess].AuditOnFailure && !AccessGranted) { SepExamineSacl( SepSaclAddrSecurityDescriptor( (PISECURITY_DESCRIPTOR)SecurityDescriptor ), EffectiveToken( SubjectSecurityContext ), DesiredAccess, AccessGranted, &GenerateAudit, &GenerateAlarm ); if ( GenerateAudit || GenerateAlarm ) { SepAdtImplicitObjectAuditAlarm( OperationID, Object, SepTokenUserSid(EffectiveToken( SubjectSecurityContext )), DesiredAccess, Privileges, AccessGranted, GenerateAudit, GenerateAlarm ); } } } return; } #endif 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; PAUX_ACCESS_DATA AuxData; PAGED_CODE(); AuxData = (PAUX_ACCESS_DATA)AccessState->AuxData; if ( AccessState->GenerateAudit ) { if ( AccessState->AuditPrivileges ) { AuditPerformed = SepAdtPrivilegeObjectAuditAlarm ( &SeSubsystemName, Handle, (PTOKEN)AccessState->SubjectSecurityContext.ClientToken, (PTOKEN)AccessState->SubjectSecurityContext.PrimaryToken, &AccessState->SubjectSecurityContext.ProcessAuditId, AccessState->PreviouslyGrantedAccess, AuxData->PrivilegesUsed, TRUE ); } else { AuditPerformed = SepAdtOpenObjectAuditAlarm ( &SeSubsystemName, &Handle, &AccessState->ObjectTypeName, NULL, &AccessState->ObjectName, AccessState->SubjectSecurityContext.ClientToken, AccessState->SubjectSecurityContext.PrimaryToken, AccessState->OriginalDesiredAccess, AccessState->PreviouslyGrantedAccess, &AccessState->OperationID, AuxData->PrivilegesUsed, FALSE, TRUE, TRUE, FALSE, AccessState->SubjectSecurityContext.ProcessAuditId ); } } // // 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(); if (GenerateOnClose) { SeCaptureSubjectContext ( &SubjectSecurityContext ); UserSid = SepTokenUserSid( EffectiveToken (&SubjectSecurityContext)); SepAdtCloseObjectAuditAlarm ( &SeSubsystemName, (PVOID)Handle, Object, UserSid, SepTokenAuthenticationId( EffectiveToken (&SubjectSecurityContext)) ); 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(); SeCaptureSubjectContext ( &SubjectSecurityContext ); UserSid = SepTokenUserSid( EffectiveToken (&SubjectSecurityContext)); SepAdtDeleteObjectAuditAlarm ( &SeSubsystemName, (PVOID)Handle, Object, UserSid, SepTokenAuthenticationId( EffectiveToken (&SubjectSecurityContext)) ); 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, &((PSYSTEM_AUDIT_ACE)Ace)->SidStart ) ) { 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, &((PSYSTEM_ALARM_ACE)Ace)->SidStart ) ) { 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; } /****************************************************************************** * * * 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). * * * ******************************************************************************/ PLUID *SepFilterPrivileges = NULL; 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. * * * ******************************************************************************/ 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 *Privilege; ULONG Match = 0; ULONG i; PAGED_CODE(); if ( !ARGUMENT_PRESENT(PrivilegeSet) || (PrivilegeSet->PrivilegeCount == 0) ) { return( FALSE ); } for (i=0; iPrivilegeCount; 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 ( SepSaclAddrSecurityDescriptor( 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 ) ); }