|
|
/*++
Copyright (c) 1991 Microsoft Corporation
Module Name:
adtlog.c
Abstract:
Auditing - Audit Record Queuing and Logging Routines
This file contains functions that construct Audit Records in self- relative form from supplied information, enqueue/dequeue them and write them to the log.
Author:
Scott Birrell (ScottBi) November 8, 1991
Environment:
Kernel Mode only
Revision History:
--*/
#include "pch.h"
#pragma hdrstop
#ifdef ALLOC_PRAGMA
#pragma alloc_text(PAGE,SepAdtLogAuditRecord)
#pragma alloc_text(PAGE,SepAuditFailed)
#pragma alloc_text(PAGE,SepAdtMarshallAuditRecord)
#pragma alloc_text(PAGE,SepAdtSetAuditLogInformation)
#pragma alloc_text(PAGE,SepAdtCopyToLsaSharedMemory)
#pragma alloc_text(PAGE,SepQueueWorkItem)
#pragma alloc_text(PAGE,SepDequeueWorkItem)
#endif
VOID SepAdtLogAuditRecord( IN PSE_ADT_PARAMETER_ARRAY AuditParameters )
/*++
Routine Description:
This function manages the logging of Audit Records. It provides the single interface to the Audit Logging component from the Audit/Alarm generation routines. The function constructs an Audit Record in self-relative format from the information provided and appends it to the Audit Record Queue, a doubly-linked list of Audit Records awaiting output to the Audit Log. A dedicated thread reads this queue, writing Audit Records to the Audit Log and removing them from the Audit Queue.
Arguments:
AuditEventType - Specifies the type of the Audit Event described by the audit information provided.
AuditInformation - Pointer to buffer containing captured auditing information related to an Audit Event of type AuditEventType.
Return Value:
STATUS_SUCCESS STATUS_UNSUCCESSFUL - Audit record was not queued STATUS_INSUFFICIENT_RESOURCES - unable to allocate heap
--*/
{ NTSTATUS Status; PSEP_LSA_WORK_ITEM AuditWorkItem;
PAGED_CODE();
AuditWorkItem = ExAllocatePoolWithTag( PagedPool, sizeof( SEP_LSA_WORK_ITEM ), 'iAeS' );
if ( AuditWorkItem == NULL ) {
SepAuditFailed(); return; }
AuditWorkItem->Tag = SepAuditRecord; AuditWorkItem->CommandNumber = LsapWriteAuditMessageCommand; AuditWorkItem->ReplyBuffer = NULL; AuditWorkItem->ReplyBufferLength = 0; AuditWorkItem->CleanupFunction = NULL;
//
// Build an Audit record in self-relative format from the supplied
// Audit Information.
//
Status = SepAdtMarshallAuditRecord( AuditParameters, (PSE_ADT_PARAMETER_ARRAY *) &AuditWorkItem->CommandParams.BaseAddress, &AuditWorkItem->CommandParamsMemoryType );
if (NT_SUCCESS(Status)) {
//
// Extract the length of the Audit Record. Store it as the length
// of the Command Parameters buffer.
//
AuditWorkItem->CommandParamsLength = ((PSE_ADT_PARAMETER_ARRAY) AuditWorkItem->CommandParams.BaseAddress)->Length;
//
// If we're going to crash on a discarded audit, ignore the queue bounds
// check and force the item onto the queue.
//
if (!SepQueueWorkItem( AuditWorkItem, (BOOLEAN)(SepCrashOnAuditFail ? TRUE : FALSE) )) {
ExFreePool( AuditWorkItem->CommandParams.BaseAddress ); ExFreePool( AuditWorkItem );
//
// We failed to put the record on the queue. Take whatever action is
// appropriate.
//
SepAuditFailed(); }
} else {
ExFreePool( AuditWorkItem ); SepAuditFailed(); } }
VOID SepAuditFailed( VOID )
/*++
Routine Description:
Bugchecks the system due to a missed audit (optional requirement for C2 compliance).
Arguments:
None.
Return Value:
None.
--*/
{ NTSTATUS Status; OBJECT_ATTRIBUTES Obja; HANDLE KeyHandle; UNICODE_STRING KeyName; UNICODE_STRING ValueName; UCHAR NewValue;
ASSERT(sizeof(UCHAR) == sizeof(BOOLEAN));
if (!SepCrashOnAuditFail) { return; }
//
// Turn off flag in the registry that controls crashing on audit failure
//
RtlInitUnicodeString( &KeyName, L"\\Registry\\Machine\\System\\CurrentControlSet\\Control\\Lsa");
InitializeObjectAttributes( &Obja, &KeyName, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL ); do {
Status = ZwOpenKey( &KeyHandle, KEY_SET_VALUE, &Obja );
} while ((Status == STATUS_INSUFFICIENT_RESOURCES) || (Status == STATUS_NO_MEMORY));
//
// If the LSA key isn't there, he's got big problems. But don't crash.
//
if (Status == STATUS_OBJECT_NAME_NOT_FOUND) { SepCrashOnAuditFail = FALSE; return; }
if (!NT_SUCCESS( Status )) { goto bugcheck; }
RtlInitUnicodeString( &ValueName, CRASH_ON_AUDIT_FAIL_VALUE );
NewValue = LSAP_ALLOW_ADIMIN_LOGONS_ONLY;
do {
Status = ZwSetValueKey( KeyHandle, &ValueName, 0, REG_NONE, &NewValue, sizeof(UCHAR) );
} while ((Status == STATUS_INSUFFICIENT_RESOURCES) || (Status == STATUS_NO_MEMORY)); ASSERT(NT_SUCCESS(Status));
if (!NT_SUCCESS( Status )) { goto bugcheck; }
do {
Status = ZwFlushKey( KeyHandle );
} while ((Status == STATUS_INSUFFICIENT_RESOURCES) || (Status == STATUS_NO_MEMORY)); ASSERT(NT_SUCCESS(Status));
//
// go boom.
//
bugcheck:
KeBugCheckEx(AUDIT_FAILURE, 0, 0, 0, 0); }
NTSTATUS SepAdtMarshallAuditRecord( IN PSE_ADT_PARAMETER_ARRAY AuditParameters, OUT PSE_ADT_PARAMETER_ARRAY *MarshalledAuditParameters, OUT PSEP_RM_LSA_MEMORY_TYPE RecordMemoryType )
/*++
Routine Description:
This routine will take an AuditParamters structure and create a new AuditParameters structure that is suitable for sending to LSA. It will be in self-relative form and allocated as a single chunk of memory.
Arguments:
AuditParameters - A filled in set of AuditParameters to be marshalled.
MarshalledAuditParameters - Returns a pointer to a block of heap memory containing the passed AuditParameters in self-relative form suitable for passing to LSA.
Return Value:
None.
--*/
{ ULONG i; ULONG TotalSize = sizeof( SE_ADT_PARAMETER_ARRAY ); PUNICODE_STRING TargetString; PCHAR Base; ULONG BaseIncr; ULONG Size; PSE_ADT_PARAMETER_ARRAY_ENTRY pInParam, pOutParam;
PAGED_CODE();
//
// Calculate the total size required for the passed AuditParameters
// block. This calculation will probably be an overestimate of the
// amount of space needed, because data smaller that 2 dwords will
// be stored directly in the parameters structure, but their length
// will be counted here anyway. The overestimate can't be more than
// 24 dwords, and will never even approach that amount, so it isn't
// worth the time it would take to avoid it.
//
for (i=0; i<AuditParameters->ParameterCount; i++) { Size = AuditParameters->Parameters[i].Length; TotalSize += PtrAlignSize( Size ); }
//
// Allocate a big enough block of memory to hold everything.
// If it fails, quietly abort, since there isn't much else we
// can do.
//
*MarshalledAuditParameters = ExAllocatePoolWithTag( PagedPool, TotalSize, 'pAeS' );
if (*MarshalledAuditParameters == NULL) {
*RecordMemoryType = SepRmNoMemory; return(STATUS_INSUFFICIENT_RESOURCES); }
*RecordMemoryType = SepRmPagedPoolMemory;
RtlCopyMemory ( *MarshalledAuditParameters, AuditParameters, sizeof( SE_ADT_PARAMETER_ARRAY ) );
(*MarshalledAuditParameters)->Length = TotalSize; (*MarshalledAuditParameters)->Flags = SE_ADT_PARAMETERS_SELF_RELATIVE;
pInParam = &AuditParameters->Parameters[0]; pOutParam = &((*MarshalledAuditParameters)->Parameters[0]); //
// Start walking down the list of parameters and marshall them
// into the target buffer.
//
Base = (PCHAR) ((PCHAR)(*MarshalledAuditParameters) + sizeof( SE_ADT_PARAMETER_ARRAY ));
for (i=0; i<AuditParameters->ParameterCount; i++, pInParam++, pOutParam++) {
switch (AuditParameters->Parameters[i].Type) { case SeAdtParmTypeNone: case SeAdtParmTypeUlong: case SeAdtParmTypeLogonId: case SeAdtParmTypeNoLogonId: case SeAdtParmTypeTime: case SeAdtParmTypeAccessMask: case SeAdtParmTypePtr: { //
// Nothing to do for this
//
break;
} case SeAdtParmTypeString: case SeAdtParmTypeFileSpec: { PUNICODE_STRING SourceString; //
// We must copy the body of the unicode string
// and then copy the body of the string. Pointers
// must be turned into offsets.
TargetString = (PUNICODE_STRING)Base;
SourceString = pInParam->Address;
*TargetString = *SourceString;
//
// Reset the data pointer in the output parameters to
// 'point' to the new string structure.
//
pOutParam->Address = Base - (ULONG_PTR)(*MarshalledAuditParameters);
Base += sizeof( UNICODE_STRING );
RtlCopyMemory( Base, SourceString->Buffer, SourceString->Length );
//
// Make the string buffer in the target string point to where we
// just copied the data.
//
TargetString->Buffer = (PWSTR)(Base - (ULONG_PTR)(*MarshalledAuditParameters));
BaseIncr = PtrAlignSize(SourceString->Length);
Base += BaseIncr;
ASSERT( (ULONG_PTR)Base <= (ULONG_PTR)(*MarshalledAuditParameters) + TotalSize ); break; }
//
// Handle types where we simply copy the buffer.
//
case SeAdtParmTypePrivs: // #if DBG
// {
// PPRIVILEGE_SET Privileges =
// (PPRIVILEGE_SET) pInParam->Address;
// ULONG i;
// for (i = 0; i < Privileges->PrivilegeCount; i++)
// {
// ASSERT( Privileges->Privilege[i].Luid.HighPart == 0);
// }
// }
// #endif
case SeAdtParmTypeSid: case SeAdtParmTypeObjectTypes: { //
// Copy the data into the output buffer
//
RtlCopyMemory( Base, pInParam->Address, pInParam->Length );
//
// Reset the 'address' of the data to be its offset in the
// buffer.
//
pOutParam->Address = Base - (ULONG_PTR)(*MarshalledAuditParameters);
Base += PtrAlignSize( pInParam->Length );
ASSERT( (ULONG_PTR)Base <= (ULONG_PTR)(*MarshalledAuditParameters) + TotalSize ); break; }
default: { //
// We got passed junk, complain.
//
ASSERT( FALSE ); break; } } }
return( STATUS_SUCCESS ); }
VOID SepAdtSetAuditLogInformation( IN PPOLICY_AUDIT_LOG_INFO AuditLogInformation )
/*++
Routine Description:
This function stores Audit Log Information in the Reference Monitor's in-memory database. This information contains parameters such as Audit Log size etc. It is the caller's responsibility to ensure that the supplied information is valid.
NOTE: After initialization, this function is only called by the LSA via a Reference Monitor command. This is a necessary restriction because the Audit Log Information stored in the LSA Database must remain in sync
Arguments:
AuditLogInformation - Pointer to Audit Log Information structure.
Return Value:
None.
--*/
{ PAGED_CODE();
//
// Acquire Reference Monitor Database write lock.
//
SepRmAcquireDbWriteLock();
//
// Write the information
//
SepAdtLogInformation = *AuditLogInformation;
//
// Release Reference Monitor Database write lock
//
SepRmReleaseDbWriteLock(); }
NTSTATUS SepAdtCopyToLsaSharedMemory( IN HANDLE LsaProcessHandle, IN PVOID Buffer, IN ULONG BufferLength, OUT PVOID *LsaBufferAddress )
/*++
Routine Description:
This function allocates memory shared with the LSA and optionally copies a given buffer to it.
Arguments:
LsaProcessHandle - Specifies a handle to the Lsa Process.
Buffer - Pointer to the buffer to be copied.
BufferLength - Length of buffer.
LsaBufferAddress - Receives the address of the buffer valid in the Lsa process context.
Return Value:
NTSTATUS - Standard Nt Result Code
Result codes returned by called routines. --*/
{ NTSTATUS Status, SecondaryStatus; PVOID OutputLsaBufferAddress = NULL; SIZE_T RegionSize = BufferLength;
PAGED_CODE();
Status = ZwAllocateVirtualMemory( LsaProcessHandle, &OutputLsaBufferAddress, 0, &RegionSize, MEM_COMMIT, PAGE_READWRITE );
if (!NT_SUCCESS(Status)) {
goto CopyToLsaSharedMemoryError; }
Status = ZwWriteVirtualMemory( LsaProcessHandle, OutputLsaBufferAddress, Buffer, BufferLength, NULL );
if (!NT_SUCCESS(Status)) {
goto CopyToLsaSharedMemoryError; }
*LsaBufferAddress = OutputLsaBufferAddress; return(Status);
CopyToLsaSharedMemoryError:
//
// If we allocated memory, free it.
//
if (OutputLsaBufferAddress != NULL) {
RegionSize = 0;
SecondaryStatus = ZwFreeVirtualMemory( LsaProcessHandle, &OutputLsaBufferAddress, &RegionSize, MEM_RELEASE );
ASSERT(NT_SUCCESS(SecondaryStatus));
OutputLsaBufferAddress = NULL; }
return(Status); }
BOOLEAN SepQueueWorkItem( IN PSEP_LSA_WORK_ITEM LsaWorkItem, IN BOOLEAN ForceQueue )
/*++
Routine Description:
Puts the passed work item on the queue to be passed to LSA, and returns the state of the queue upon arrival.
Arguments:
LsaWorkItem - Pointer to the work item to be queued.
ForceQueue - Indicate that this item is not to be discarded because of a full queue.
Return Value:
TRUE - The item was successfully queued.
FALSE - The item was not queued and must be discarded.
--*/
{ BOOLEAN rc = TRUE; BOOLEAN StartExThread = FALSE ;
PAGED_CODE();
#if 0
DbgPrint("Queueing an audit\n");
#endif
SepLockLsaQueue();
//
// See if LSA has died. If it has then just return with an error.
//
if (SepAdtLsaDeadEvent != NULL) { rc = FALSE; goto Exit; }
if (SepAdtDiscardingAudits && !ForceQueue) {
if (SepAdtCurrentListLength < SepAdtMinListLength) {
//
// We need to generate an audit saying how many audits we've
// discarded.
//
// Since we have the mutex protecting the Audit queue, we don't
// have to worry about anyone coming along and logging an
// audit. But *we* can, since a mutex may be acquired recursively.
//
// Since we are so protected, turn off the SepAdtDiscardingAudits
// flag here so that we don't come through this path again.
//
SepAdtDiscardingAudits = FALSE;
SepAdtGenerateDiscardAudit(); #if 0
DbgPrint("Auditing resumed\n"); #endif
//
// We must assume that that worked, so clear the discard count.
//
SepAdtCountEventsDiscarded = 0;
//
// Our 'audits discarded' audit is now on the queue,
// continue logging the one we started with.
//
} else {
//
// We are not yet below our low water mark. Toss
// this audit and increment the discard count.
//
SepAdtCountEventsDiscarded++; rc = FALSE; goto Exit; } }
if (SepAdtCurrentListLength < SepAdtMaxListLength || ForceQueue) {
InsertTailList(&SepLsaQueue, &LsaWorkItem->List);
if (++SepAdtCurrentListLength == 1) {
#if 0
DbgPrint("Queueing a work item\n"); #endif
StartExThread = TRUE ; }
} else {
//
// There is no room for this audit on the queue,
// so change our state to 'discarding' and tell
// the caller to toss this audit.
//
SepAdtDiscardingAudits = TRUE;
#if 0
DbgPrint("Starting to discard audits\n"); #endif
rc = FALSE; }
Exit:
SepUnlockLsaQueue();
if ( StartExThread ) { ExInitializeWorkItem( &SepExWorkItem.WorkItem, (PWORKER_THREAD_ROUTINE) SepRmCallLsa, &SepExWorkItem );
ExQueueWorkItem( &SepExWorkItem.WorkItem, DelayedWorkQueue ); }
return( rc ); }
PSEP_LSA_WORK_ITEM SepDequeueWorkItem( VOID )
/*++
Routine Description:
Removes the top element of the SepLsaQueue and returns the next element if there is one, NULL otherwise.
Arguments:
None.
Return Value:
A pointer to the next SEP_LSA_WORK_ITEM, or NULL.
--*/
{ PSEP_LSA_WORK_ITEM OldWorkQueueItem;
PAGED_CODE();
SepLockLsaQueue();
OldWorkQueueItem = (PSEP_LSA_WORK_ITEM)RemoveHeadList(&SepLsaQueue); OldWorkQueueItem->List.Flink = NULL;
SepAdtCurrentListLength--;
#if 0
DbgPrint("Removing item\n"); #endif
if (IsListEmpty( &SepLsaQueue )) { //
// If LSA has died and the RM thread is waiting till we finish up. Notify it that we are all done
//
if (SepAdtLsaDeadEvent != NULL) { KeSetEvent (SepAdtLsaDeadEvent, 0, FALSE); } SepUnlockLsaQueue();
ExFreePool( OldWorkQueueItem ); return( NULL ); }
//
// We know there's something on the queue now, so we
// can unlock it.
//
SepUnlockLsaQueue();
ExFreePool( OldWorkQueueItem );
return((PSEP_LSA_WORK_ITEM)(&SepLsaQueue)->Flink); }
|