1212 lines
36 KiB
C
1212 lines
36 KiB
C
/*++
|
||
|
||
Copyright (c) 1991 Microsoft Corporation
|
||
|
||
Module Name:
|
||
|
||
rmmain.c
|
||
|
||
Abstract:
|
||
|
||
Security Reference Monitor - Init, Control and State Change
|
||
|
||
Author:
|
||
|
||
Scott Birrell (ScottBi) March 12, 1991
|
||
|
||
Environment:
|
||
|
||
Revision History:
|
||
|
||
--*/
|
||
|
||
#include "pch.h"
|
||
|
||
#pragma hdrstop
|
||
|
||
|
||
#ifdef ALLOC_PRAGMA
|
||
#pragma alloc_text(INIT,SeRmInitPhase1)
|
||
#pragma alloc_text(PAGE,SepRmCommandServerThread)
|
||
#pragma alloc_text(PAGE,SepRmCommandServerThreadInit)
|
||
#pragma alloc_text(PAGE,SepRmCallLsa)
|
||
#pragma alloc_text(INIT,SepRmInitPhase0)
|
||
#endif
|
||
|
||
//
|
||
// Reference Monitor Command Worker Table
|
||
//
|
||
|
||
//
|
||
// Keep this in sync with RM_COMMAND_NUMBER in ntrmlsa.h
|
||
//
|
||
|
||
#ifdef ALLOC_DATA_PRAGMA
|
||
#pragma const_seg("PAGECONST")
|
||
#endif
|
||
|
||
const SEP_RM_COMMAND_WORKER SepRmCommandDispatch[] = {
|
||
NULL,
|
||
SepRmSetAuditEventWrkr,
|
||
SepRmCreateLogonSessionWrkr,
|
||
SepRmDeleteLogonSessionWrkr
|
||
};
|
||
|
||
|
||
BOOLEAN
|
||
SeRmInitPhase1(
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This function is called by Phase 1 System Initialization to initialize
|
||
the Security Reference Monitor. Note that initialization of the
|
||
Reference Monitor Global State has already been performed in Phase 0
|
||
initialization to allow access validation routines to operate without
|
||
having to check that Reference Monitor Initialization is complete.
|
||
|
||
The steps listed below are performed in this routine. The remainder
|
||
of Reference Monitor initialization requires the LSA subsystem to have run,
|
||
so that initialization is performed in a separate thread (the RM Command
|
||
Server Thread, see below), so that the present thread can create the
|
||
Session Manager which execs the LSA.
|
||
|
||
o Create the Reference Monitor Command LPC port. The LSA subsystem sends
|
||
commands (e.g. turn on auditing) which change the Reference Monitor
|
||
Global State.
|
||
o Create an Event for use in synchronizing with the LSA subsystem. The
|
||
LSA will signal the event when the portion of LSA initialization upon
|
||
with the Reference Monitor depends is complete. The Reference Monitor
|
||
uses another LPC port, called the LSA Command Port to send commands
|
||
to the LSA, so the RM must know that this port has been created before
|
||
trying to connect to it.
|
||
o Create the Reference Monitor Command Server Thread. This thread is
|
||
a permanent thread of the System Init process that fields the Reference
|
||
Monitor State Change commands described above.
|
||
|
||
|
||
Arguments:
|
||
|
||
None.
|
||
|
||
Return Value:
|
||
|
||
BOOLEAN - TRUE if Rm Initialization (Phase 1) succeeded, else FALSE
|
||
|
||
--*/
|
||
|
||
{
|
||
NTSTATUS Status;
|
||
STRING RmCommandPortName;
|
||
UNICODE_STRING UnicodeRmCommandPortName;
|
||
OBJECT_ATTRIBUTES ObjectAttributes;
|
||
STRING LsaInitEventName;
|
||
UNICODE_STRING UnicodeLsaInitEventName;
|
||
OBJECT_ATTRIBUTES LsaInitEventObjectAttributes;
|
||
SECURITY_DESCRIPTOR LsaInitEventSecurityDescriptor;
|
||
ULONG AclSize;
|
||
|
||
PAGED_CODE();
|
||
|
||
//
|
||
// Create an LPC port called the Reference Monitor Command Port.
|
||
// This will be used by the LSA to send commands to the Reference
|
||
// Monitor to update its state data.
|
||
//
|
||
|
||
RtlInitString( &RmCommandPortName, "\\SeRmCommandPort" );
|
||
Status = RtlAnsiStringToUnicodeString(
|
||
&UnicodeRmCommandPortName,
|
||
&RmCommandPortName,
|
||
TRUE );
|
||
ASSERT( NT_SUCCESS(Status) );
|
||
InitializeObjectAttributes(
|
||
&ObjectAttributes,
|
||
&UnicodeRmCommandPortName,
|
||
0,
|
||
NULL,
|
||
NULL
|
||
);
|
||
|
||
Status = ZwCreatePort(
|
||
&SepRmState.RmCommandServerPortHandle,
|
||
&ObjectAttributes,
|
||
sizeof(SEP_RM_CONNECT_INFO),
|
||
sizeof(RM_COMMAND_MESSAGE),
|
||
sizeof(RM_COMMAND_MESSAGE) * 32
|
||
);
|
||
RtlFreeUnicodeString( &UnicodeRmCommandPortName );
|
||
|
||
if( !NT_SUCCESS(Status) ) {
|
||
|
||
KdPrint(("Security: Rm Create Command Port failed 0x%lx\n", Status));
|
||
return FALSE;
|
||
}
|
||
|
||
//
|
||
// Prepare to create an event for synchronizing with the LSA.
|
||
// First, build the Security Descriptor for the Init Event Object
|
||
//
|
||
|
||
Status = RtlCreateSecurityDescriptor(
|
||
&LsaInitEventSecurityDescriptor,
|
||
SECURITY_DESCRIPTOR_REVISION
|
||
);
|
||
|
||
if (!NT_SUCCESS(Status)) {
|
||
|
||
KdPrint(("Security: Creating Lsa Init Event Desc failed 0x%lx\n",
|
||
Status));
|
||
return FALSE;
|
||
}
|
||
|
||
//
|
||
// Allocate a temporary buffer from the paged pool. It is a fatal
|
||
// system error if the allocation fails since security cannot be
|
||
// enabled.
|
||
//
|
||
|
||
AclSize = sizeof(ACL) +
|
||
sizeof(ACCESS_ALLOWED_ACE) +
|
||
SeLengthSid(SeLocalSystemSid);
|
||
LsaInitEventSecurityDescriptor.Dacl =
|
||
ExAllocatePoolWithTag(PagedPool, AclSize, 'cAeS');
|
||
|
||
if (LsaInitEventSecurityDescriptor.Dacl == NULL) {
|
||
|
||
KdPrint(("Security LSA: Insufficient resources to initialize\n"));
|
||
return FALSE;
|
||
}
|
||
|
||
//
|
||
// Now create the Discretionary ACL within the Security Descriptor
|
||
//
|
||
|
||
Status = RtlCreateAcl(
|
||
LsaInitEventSecurityDescriptor.Dacl,
|
||
AclSize,
|
||
ACL_REVISION2
|
||
);
|
||
|
||
if (!NT_SUCCESS(Status)) {
|
||
|
||
KdPrint(("Security: Creating Lsa Init Event Dacl failed 0x%lx\n",
|
||
Status));
|
||
return FALSE;
|
||
}
|
||
|
||
//
|
||
// Now add an ACE giving GENERIC_ALL access to the User ID
|
||
//
|
||
|
||
Status = RtlAddAccessAllowedAce(
|
||
LsaInitEventSecurityDescriptor.Dacl,
|
||
ACL_REVISION2,
|
||
GENERIC_ALL,
|
||
SeLocalSystemSid
|
||
);
|
||
|
||
if (!NT_SUCCESS(Status)) {
|
||
|
||
KdPrint(("Security: Adding Lsa Init Event ACE failed 0x%lx\n",
|
||
Status));
|
||
return FALSE;
|
||
}
|
||
|
||
//
|
||
// Set up the Object Attributes for the Lsa Initialization Event
|
||
//
|
||
|
||
RtlInitString( &LsaInitEventName, "\\SeLsaInitEvent" );
|
||
Status = RtlAnsiStringToUnicodeString(
|
||
&UnicodeLsaInitEventName,
|
||
&LsaInitEventName,
|
||
TRUE ); ASSERT( NT_SUCCESS(Status) );
|
||
InitializeObjectAttributes(
|
||
&LsaInitEventObjectAttributes,
|
||
&UnicodeLsaInitEventName,
|
||
0,
|
||
NULL,
|
||
&LsaInitEventSecurityDescriptor
|
||
);
|
||
|
||
//
|
||
// Create an event for use in synchronizing with the LSA. The LSA will
|
||
// signal this event when LSA initialization has reached the point
|
||
// where the LSA's Reference Monitor Server Port has been created.
|
||
//
|
||
|
||
Status = ZwCreateEvent(
|
||
&(SepRmState.LsaInitEventHandle),
|
||
EVENT_MODIFY_STATE,
|
||
&LsaInitEventObjectAttributes,
|
||
NotificationEvent,
|
||
FALSE);
|
||
|
||
RtlFreeUnicodeString( &UnicodeLsaInitEventName );
|
||
|
||
if (!NT_SUCCESS(Status)) {
|
||
|
||
KdPrint(("Security: LSA init event creation failed.0x%xl\n",
|
||
Status));
|
||
return FALSE;
|
||
}
|
||
|
||
//
|
||
// Deallocate the pool memory used for the Init Event DACL
|
||
//
|
||
|
||
ExFreePool( LsaInitEventSecurityDescriptor.Dacl );
|
||
|
||
//
|
||
// Create a permanent thread of the Sysinit Process, called the
|
||
// Reference Monitor Server Thread. This thread is dedicated to
|
||
// receiving Reference Monitor commands and dispatching them.
|
||
//
|
||
|
||
Status = PsCreateSystemThread(
|
||
&SepRmState.SepRmThreadHandle,
|
||
THREAD_GET_CONTEXT |
|
||
THREAD_SET_CONTEXT |
|
||
THREAD_SET_INFORMATION,
|
||
NULL,
|
||
NULL,
|
||
NULL,
|
||
SepRmCommandServerThread,
|
||
NULL
|
||
);
|
||
|
||
if (!NT_SUCCESS(Status)) {
|
||
|
||
KdPrint(("Security: Rm Server Thread creation failed 0x%lx\n", Status));
|
||
return FALSE;
|
||
}
|
||
|
||
//
|
||
// Initialize data from the registry. This must go here because all other
|
||
// Se initialization takes place before the registry is initialized.
|
||
//
|
||
|
||
SepAdtInitializeCrashOnFail();
|
||
SepAdtInitializePrivilegeAuditing();
|
||
SepAdtInitializeAuditingOptions();
|
||
|
||
//
|
||
// Reference Monitor initialization is successful if we get to here.
|
||
//
|
||
|
||
ZwClose( SepRmState.SepRmThreadHandle );
|
||
SepRmState.SepRmThreadHandle = NULL;
|
||
return TRUE;
|
||
}
|
||
|
||
|
||
VOID
|
||
SepRmCommandServerThread(
|
||
IN PVOID StartContext
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This function is executed indefinitely by a dedicated permanent thread
|
||
of the Sysinit Process, called the Reference Monitor Server Thread.
|
||
This thread updates Reference Monitor Global State Data by dispatching
|
||
commands sent from the LSA through the the Reference Monitor LPC Command
|
||
Port. The following steps are repeated indefinitely:
|
||
|
||
o Initialize RM Command receive and reply buffer headers
|
||
o Perform remaining Reference Monitor initialization involving LSA
|
||
o Wait for RM command sent from LSA, send reply to previous command
|
||
(if any)
|
||
o Validate command
|
||
o Dispatch to command worker routine to execute command.
|
||
|
||
Arguments:
|
||
|
||
None.
|
||
|
||
Return Value:
|
||
|
||
None.
|
||
|
||
--*/
|
||
|
||
{
|
||
NTSTATUS Status;
|
||
PRM_REPLY_MESSAGE Reply;
|
||
RM_COMMAND_MESSAGE CommandMessage;
|
||
RM_REPLY_MESSAGE ReplyMessage;
|
||
|
||
PAGED_CODE();
|
||
|
||
//
|
||
// Perform the rest of the Reference Monitor initialization, involving
|
||
// synchronization with the LSA or dependency on the LSA having run.
|
||
//
|
||
|
||
if (!SepRmCommandServerThreadInit()) {
|
||
|
||
KdPrint(("Security: Terminating Rm Command Server Thread\n"));
|
||
return;
|
||
}
|
||
|
||
Status = PoRequestShutdownEvent (NULL);
|
||
if (!NT_SUCCESS (Status)) {
|
||
ZwClose (SepRmState.RmCommandPortHandle);
|
||
ZwClose (SepRmState.RmCommandServerPortHandle);
|
||
ZwClose (SepRmState.LsaCommandPortHandle);
|
||
ZwClose (SepLsaHandle);
|
||
SepRmState.RmCommandPortHandle = NULL;
|
||
SepRmState.RmCommandServerPortHandle = NULL;
|
||
SepRmState.LsaCommandPortHandle = NULL;
|
||
SepLsaHandle = NULL;
|
||
return;
|
||
}
|
||
|
||
//
|
||
// Initialize LPC port message header type and length fields for the
|
||
// received command message.
|
||
//
|
||
|
||
CommandMessage.MessageHeader.u2.ZeroInit = 0;
|
||
CommandMessage.MessageHeader.u1.s1.TotalLength =
|
||
(CSHORT) sizeof(RM_COMMAND_MESSAGE);
|
||
CommandMessage.MessageHeader.u1.s1.DataLength =
|
||
CommandMessage.MessageHeader.u1.s1.TotalLength -
|
||
(CSHORT) sizeof(PORT_MESSAGE);
|
||
|
||
//
|
||
// Initialize the LPC port message header type and data sizes for
|
||
// for the reply message.
|
||
//
|
||
|
||
ReplyMessage.MessageHeader.u2.ZeroInit = 0;
|
||
ReplyMessage.MessageHeader.u1.s1.TotalLength =
|
||
(CSHORT) sizeof(RM_COMMAND_MESSAGE);
|
||
ReplyMessage.MessageHeader.u1.s1.DataLength =
|
||
ReplyMessage.MessageHeader.u1.s1.TotalLength -
|
||
(CSHORT) sizeof(PORT_MESSAGE);
|
||
|
||
//
|
||
// First time through, there is no reply.
|
||
//
|
||
|
||
Reply = NULL;
|
||
|
||
//
|
||
// Now loop indefinitely, processing incoming Rm commands from the LSA.
|
||
//
|
||
|
||
for(;;) {
|
||
|
||
//
|
||
// Wait for Command, send reply to previous command (if any)
|
||
//
|
||
|
||
Status = ZwReplyWaitReceivePort(
|
||
SepRmState.RmCommandPortHandle,
|
||
NULL,
|
||
(PPORT_MESSAGE) Reply,
|
||
(PPORT_MESSAGE) &CommandMessage
|
||
);
|
||
|
||
if (!NT_SUCCESS(Status)) {
|
||
|
||
//
|
||
// malicious user apps can try to connect to this port. We will
|
||
// fail later, but if their thread vanishes, we'll get a failure
|
||
// here. Ignore it:
|
||
//
|
||
|
||
if ( Status == STATUS_UNSUCCESSFUL )
|
||
{
|
||
//
|
||
// skip it:
|
||
//
|
||
|
||
Reply = NULL ;
|
||
continue;
|
||
}
|
||
|
||
KdPrint(("Security: RM message receive from Lsa failed %lx\n",
|
||
Status));
|
||
|
||
}
|
||
|
||
//
|
||
// Now dispatch to a routine to handle the command. Allow
|
||
// command errors to occur without bringing system down just now.
|
||
//
|
||
|
||
CommandMessage.MessageHeader.u2.s2.Type &= ~LPC_KERNELMODE_MESSAGE;
|
||
|
||
if ( CommandMessage.MessageHeader.u2.s2.Type == LPC_REQUEST ) {
|
||
|
||
if ( (CommandMessage.CommandNumber >= RmAuditSetCommand) &&
|
||
(CommandMessage.CommandNumber <= RmDeleteLogonSession) ) {
|
||
|
||
(*(SepRmCommandDispatch[CommandMessage.CommandNumber]))
|
||
(&CommandMessage, &ReplyMessage);
|
||
|
||
//
|
||
// Initialize the client thread info and message id for the
|
||
// reply message. First time through, the reply message structure
|
||
// is not used.
|
||
//
|
||
|
||
ReplyMessage.MessageHeader.ClientId =
|
||
CommandMessage.MessageHeader.ClientId;
|
||
ReplyMessage.MessageHeader.MessageId =
|
||
CommandMessage.MessageHeader.MessageId;
|
||
|
||
Reply = &ReplyMessage;
|
||
|
||
} else {
|
||
|
||
ASSERT( (CommandMessage.CommandNumber >= RmAuditSetCommand) &&
|
||
(CommandMessage.CommandNumber <= RmDeleteLogonSession) );
|
||
Reply = NULL;
|
||
}
|
||
|
||
} else if (CommandMessage.MessageHeader.u2.s2.Type == LPC_PORT_CLOSED ) {
|
||
KEVENT Event;
|
||
BOOLEAN Wait;
|
||
|
||
KeInitializeEvent (&Event, NotificationEvent, FALSE);
|
||
|
||
SepLockLsaQueue();
|
||
|
||
SepAdtLsaDeadEvent = &Event;
|
||
|
||
Wait = !SepWorkListEmpty ();
|
||
|
||
SepUnlockLsaQueue();
|
||
|
||
if (Wait) {
|
||
KeWaitForSingleObject (&Event,
|
||
Executive,
|
||
KernelMode,
|
||
FALSE,
|
||
NULL);
|
||
}
|
||
//
|
||
// Our only client closed its handle. Tidy up and exit.
|
||
//
|
||
ZwClose (SepRmState.LsaCommandPortHandle);
|
||
ZwClose (SepRmState.RmCommandPortHandle);
|
||
ZwClose (SepRmState.RmCommandServerPortHandle);
|
||
ZwClose (SepLsaHandle);
|
||
SepRmState.LsaCommandPortHandle = NULL;
|
||
SepRmState.RmCommandPortHandle = NULL;
|
||
SepRmState.RmCommandServerPortHandle = NULL;
|
||
SepLsaHandle = NULL;
|
||
break;
|
||
} else if (CommandMessage.MessageHeader.u2.s2.Type == LPC_CONNECTION_REQUEST) {
|
||
HANDLE tmp;
|
||
//
|
||
// Reject extra connection attempts
|
||
//
|
||
Status = ZwAcceptConnectPort(&tmp,
|
||
NULL,
|
||
(PPORT_MESSAGE) &CommandMessage,
|
||
FALSE,
|
||
NULL,
|
||
NULL);
|
||
} else {
|
||
|
||
Reply = NULL;
|
||
}
|
||
} // end_for
|
||
|
||
UNREFERENCED_PARAMETER( StartContext );
|
||
|
||
}
|
||
|
||
|
||
BOOLEAN
|
||
SepRmCommandServerThreadInit(
|
||
VOID
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This function performs initialization of the Reference Monitor Server
|
||
thread. The following steps are performed.
|
||
|
||
o Wait on the LSA signalling the event. When the event is signalled,
|
||
the LSA has already created the LSA Command Server LPC Port
|
||
o Close the LSA Init Event Handle. The event is not used again.
|
||
o Listen for the LSA to connect to the Port
|
||
o Accept the connection.
|
||
o Connect to the LSA Command Server LPC Port
|
||
|
||
Arguments:
|
||
|
||
None.
|
||
|
||
Return Value:
|
||
|
||
--*/
|
||
|
||
{
|
||
NTSTATUS Status;
|
||
UNICODE_STRING LsaCommandPortName;
|
||
PORT_MESSAGE ConnectionRequest;
|
||
SECURITY_QUALITY_OF_SERVICE DynamicQos;
|
||
OBJECT_ATTRIBUTES ObjectAttributes;
|
||
PORT_VIEW ClientView;
|
||
REMOTE_PORT_VIEW LsaClientView;
|
||
BOOLEAN BooleanStatus = TRUE;
|
||
|
||
PAGED_CODE();
|
||
|
||
//
|
||
// Save a pointer to our process so we can get back into this process
|
||
// to send commands to the LSA (using a handle to an LPC port created
|
||
// below).
|
||
//
|
||
|
||
SepRmLsaCallProcess = PsGetCurrentProcess();
|
||
|
||
ObReferenceObject(SepRmLsaCallProcess);
|
||
|
||
//
|
||
// Wait on the LSA signalling the event. This means that the LSA
|
||
// has created its command port, not that LSA initialization is
|
||
// complete.
|
||
//
|
||
|
||
Status = ZwWaitForSingleObject(
|
||
SepRmState.LsaInitEventHandle,
|
||
FALSE,
|
||
NULL);
|
||
|
||
if ( !NT_SUCCESS(Status) ) {
|
||
|
||
KdPrint(("Security Rm Init: Waiting for LSA Init Event failed 0x%lx\n", Status));
|
||
goto RmCommandServerThreadInitError;
|
||
}
|
||
|
||
//
|
||
// Close the LSA Init Event Handle. The event is not used again.
|
||
//
|
||
|
||
ZwClose(SepRmState.LsaInitEventHandle);
|
||
|
||
//
|
||
// Listen for a connection to be made by the LSA to the Reference Monitor
|
||
// Command Port. This connection will be made by the LSA process.
|
||
//
|
||
|
||
ConnectionRequest.u1.s1.TotalLength = sizeof(ConnectionRequest);
|
||
ConnectionRequest.u1.s1.DataLength = (CSHORT)0;
|
||
Status = ZwListenPort(
|
||
SepRmState.RmCommandServerPortHandle,
|
||
&ConnectionRequest
|
||
);
|
||
|
||
if (!NT_SUCCESS(Status)) {
|
||
|
||
KdPrint(("Security Rm Init: Listen to Command Port failed 0x%lx\n",
|
||
Status));
|
||
goto RmCommandServerThreadInitError;
|
||
}
|
||
|
||
//
|
||
// Obtain a handle to the LSA process for use when auditing.
|
||
//
|
||
|
||
InitializeObjectAttributes( &ObjectAttributes, NULL, 0, NULL, NULL );
|
||
|
||
Status = ZwOpenProcess(
|
||
&SepLsaHandle,
|
||
PROCESS_VM_OPERATION | PROCESS_VM_WRITE,
|
||
&ObjectAttributes,
|
||
&ConnectionRequest.ClientId
|
||
);
|
||
|
||
if (!NT_SUCCESS(Status)) {
|
||
|
||
KdPrint(("Security Rm Init: Open Listen to Command Port failed 0x%lx\n",
|
||
Status));
|
||
goto RmCommandServerThreadInitError;
|
||
}
|
||
|
||
//
|
||
// Accept the connection made by the LSA process.
|
||
//
|
||
|
||
LsaClientView.Length = sizeof(LsaClientView);
|
||
|
||
|
||
Status = ZwAcceptConnectPort(
|
||
&SepRmState.RmCommandPortHandle,
|
||
NULL,
|
||
&ConnectionRequest,
|
||
TRUE,
|
||
NULL,
|
||
&LsaClientView
|
||
);
|
||
|
||
if (!NT_SUCCESS(Status)) {
|
||
|
||
KdPrint(("Security Rm Init: Accept Connect to Command Port failed 0x%lx\n",
|
||
Status));
|
||
|
||
goto RmCommandServerThreadInitError;
|
||
}
|
||
|
||
//
|
||
// Complete the connection.
|
||
//
|
||
|
||
Status = ZwCompleteConnectPort(SepRmState.RmCommandPortHandle);
|
||
|
||
if (!NT_SUCCESS(Status)) {
|
||
|
||
KdPrint(("Security Rm Init: Complete Connect to Command Port failed 0x%lx\n",
|
||
Status));
|
||
goto RmCommandServerThreadInitError;
|
||
}
|
||
|
||
//
|
||
// Set up the security quality of service parameters to use over the
|
||
// Lsa Command LPC port. Use the most efficient (least overhead) - which
|
||
// is dynamic rather than static tracking.
|
||
//
|
||
|
||
DynamicQos.ImpersonationLevel = SecurityImpersonation;
|
||
DynamicQos.ContextTrackingMode = SECURITY_DYNAMIC_TRACKING;
|
||
DynamicQos.EffectiveOnly = TRUE;
|
||
|
||
//
|
||
// Create the section to be used as unnamed shared memory for
|
||
// communication between the RM and LSA.
|
||
//
|
||
|
||
SepRmState.LsaCommandPortSectionSize.LowPart = PAGE_SIZE;
|
||
SepRmState.LsaCommandPortSectionSize.HighPart = 0;
|
||
|
||
Status = ZwCreateSection(
|
||
&SepRmState.LsaCommandPortSectionHandle,
|
||
SECTION_ALL_ACCESS,
|
||
NULL, // ObjectAttributes
|
||
&SepRmState.LsaCommandPortSectionSize,
|
||
PAGE_READWRITE,
|
||
SEC_COMMIT,
|
||
NULL // FileHandle
|
||
);
|
||
|
||
if (!NT_SUCCESS(Status)) {
|
||
|
||
KdPrint(("Security Rm Init: Create Memory Section for LSA port failed: %X\n", Status));
|
||
goto RmCommandServerThreadInitError;
|
||
}
|
||
|
||
//
|
||
// Set up for a call to NtConnectPort and connect to the LSA port.
|
||
// This setup includes a description of the port memory section so that
|
||
// the LPC connection logic can make the section visible to both the
|
||
// client and server processes.
|
||
//
|
||
|
||
ClientView.Length = sizeof(ClientView);
|
||
ClientView.SectionHandle = SepRmState.LsaCommandPortSectionHandle;
|
||
ClientView.SectionOffset = 0;
|
||
ClientView.ViewSize = SepRmState.LsaCommandPortSectionSize.LowPart;
|
||
ClientView.ViewBase = 0;
|
||
ClientView.ViewRemoteBase = 0;
|
||
|
||
//
|
||
// Set up the security quality of service parameters to use over the
|
||
// port. Use dynamic tracking so that XACTSRV will impersonate the
|
||
// user that we are impersonating when we call NtRequestWaitReplyPort.
|
||
// If we used static tracking, XACTSRV would impersonate the context
|
||
// when the connection is made.
|
||
//
|
||
|
||
DynamicQos.ImpersonationLevel = SecurityImpersonation;
|
||
DynamicQos.ContextTrackingMode = SECURITY_DYNAMIC_TRACKING;
|
||
DynamicQos.EffectiveOnly = TRUE;
|
||
|
||
//
|
||
// Connect to the Lsa Command LPC Port. This port is used to send
|
||
// commands from the RM to the LSA.
|
||
//
|
||
|
||
RtlInitUnicodeString( &LsaCommandPortName, L"\\SeLsaCommandPort" );
|
||
|
||
Status = ZwConnectPort(
|
||
&SepRmState.LsaCommandPortHandle,
|
||
&LsaCommandPortName,
|
||
&DynamicQos,
|
||
&ClientView,
|
||
NULL, // ServerView
|
||
NULL, // MaxMessageLength
|
||
NULL, // ConnectionInformation
|
||
NULL // ConnectionInformationLength
|
||
);
|
||
|
||
if (!NT_SUCCESS(Status)) {
|
||
|
||
KdPrint(("Security Rm Init: Connect to LSA Port failed 0x%lx\n", Status));
|
||
goto RmCommandServerThreadInitError;
|
||
}
|
||
|
||
//
|
||
// Store information about the section so that we can create pointers
|
||
// meaningful to LSA.
|
||
//
|
||
|
||
SepRmState.RmViewPortMemory = ClientView.ViewBase;
|
||
SepRmState.LsaCommandPortMemoryDelta =
|
||
(LONG)((ULONG_PTR)ClientView.ViewRemoteBase - (ULONG_PTR) ClientView.ViewBase );
|
||
SepRmState.LsaViewPortMemory = ClientView.ViewRemoteBase;
|
||
|
||
|
||
RmCommandServerThreadInitFinish:
|
||
|
||
//
|
||
// Dont need this section handle any more, even if returning
|
||
// success.
|
||
//
|
||
|
||
if ( SepRmState.LsaCommandPortSectionHandle != NULL ) {
|
||
|
||
NtClose( SepRmState.LsaCommandPortSectionHandle );
|
||
SepRmState.LsaCommandPortSectionHandle = NULL;
|
||
}
|
||
|
||
//
|
||
// The Reference Monitor Thread has successfully initialized.
|
||
//
|
||
|
||
return BooleanStatus;
|
||
|
||
RmCommandServerThreadInitError:
|
||
|
||
if ( SepRmState.LsaCommandPortHandle != NULL ) {
|
||
|
||
NtClose( SepRmState.LsaCommandPortHandle );
|
||
SepRmState.LsaCommandPortHandle = NULL;
|
||
}
|
||
|
||
BooleanStatus = FALSE;
|
||
goto RmCommandServerThreadInitFinish;
|
||
}
|
||
|
||
|
||
|
||
|
||
NTSTATUS
|
||
SepRmCallLsa(
|
||
PSEP_WORK_ITEM SepWorkItem
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This function sends a command to the LSA via the LSA Reference Monitor
|
||
Server Command LPC Port. If the command has parameters, they will be
|
||
copied directly into a message structure and sent via LPC, therefore,
|
||
the supplied parameters may not contain any absolute pointers. A caller
|
||
must remove pointers by "marshalling" them into the buffer CommandParams.
|
||
|
||
This function will create a queue of requests. This is in order to allow
|
||
greater throughput for the majority if its callers. If a thread enters
|
||
this routine and finds the queue empty, it is the responsibility of that
|
||
thread to service all requests that come in while it is working until the
|
||
queue is empty again. Other threads that enter will simply hook their work
|
||
item onto the queue and exit.
|
||
|
||
|
||
To implement a new LSA command, do the following:
|
||
================================================
|
||
|
||
(1) If the command takes no parameters, just call this routine directly
|
||
and provide an LSA worker routine called Lsap<command>Wrkr. See
|
||
file lsa\server\lsarm.c for examples
|
||
|
||
(2) If the command takes parameters, provide a routine called
|
||
SepRmSend<command>Command that takes the parameters in unmarshalled
|
||
form and calls SepRmCallLsa() with the command id, marshalled
|
||
parameters, length of marshalled parameters and pointer to
|
||
optional reply message. The marshalled parameters are free format:
|
||
the only restriction is that there must be no absolute address
|
||
pointers. These parameters are all placed in the passed LsaWorkItem
|
||
structure.
|
||
|
||
(3) In file private\inc\ntrmlsa.h, append a command name to the
|
||
enumerated type LSA_COMMAND_NUMBER defined in file
|
||
private\inc\ntrmlsa.h. Change the #define for LsapMaximumCommand
|
||
to reference the new command.
|
||
|
||
(4) Add the Lsap<command>Wrkr to the command dispatch table structure
|
||
LsapCommandDispatch[] in file lsarm.c.
|
||
|
||
(5) Add function prototypes to lsap.h and sep.h.
|
||
|
||
|
||
Arguments:
|
||
|
||
LsaWorkItem - Supplies a pointer to an SE_LSA_WORK_ITEM containing the
|
||
information to be passed to LSA. This structure will be freed
|
||
asynchronously by some invocation of this routine, not necessarily
|
||
in the current context.
|
||
|
||
!THIS PARAMETER MUST BE ALLOCATED OUT OF NONPAGED POOL!
|
||
|
||
Return Value:
|
||
|
||
NTSTATUS - Result Code. This is either a result code returned from
|
||
trying to send the command/receive the reply, or a status code
|
||
from the command itself.
|
||
|
||
--*/
|
||
|
||
{
|
||
NTSTATUS Status = STATUS_SUCCESS;
|
||
LSA_COMMAND_MESSAGE CommandMessage;
|
||
LSA_REPLY_MESSAGE ReplyMessage;
|
||
PSEP_LSA_WORK_ITEM WorkQueueItem;
|
||
ULONG LocalListLength = 0;
|
||
SIZE_T RegionSize;
|
||
PVOID CopiedCommandParams = NULL;
|
||
PVOID LsaViewCopiedCommandParams = NULL;
|
||
|
||
PAGED_CODE();
|
||
|
||
UNREFERENCED_PARAMETER( SepWorkItem );
|
||
|
||
#if 0
|
||
DbgPrint("Entering SepRmCallLsa\n");
|
||
#endif
|
||
|
||
WorkQueueItem = SepWorkListHead();
|
||
|
||
KeAttachProcess( &SepRmLsaCallProcess->Pcb );
|
||
|
||
while ( WorkQueueItem ) {
|
||
|
||
#if 0
|
||
DbgPrint("Got a work item from head of queue, processing\n");
|
||
#endif
|
||
|
||
//
|
||
// Construct a message for LPC. First, fill in the message header
|
||
// fields for LPC, specifying the message type and data sizes for
|
||
// the outgoing CommandMessage and the incoming ReplyMessage.
|
||
//
|
||
|
||
CommandMessage.MessageHeader.u2.ZeroInit = 0;
|
||
CommandMessage.MessageHeader.u1.s1.TotalLength =
|
||
((CSHORT) RM_COMMAND_MESSAGE_HEADER_SIZE +
|
||
(CSHORT) WorkQueueItem->CommandParamsLength);
|
||
CommandMessage.MessageHeader.u1.s1.DataLength =
|
||
CommandMessage.MessageHeader.u1.s1.TotalLength -
|
||
(CSHORT) sizeof(PORT_MESSAGE);
|
||
|
||
ReplyMessage.MessageHeader.u2.ZeroInit = 0;
|
||
ReplyMessage.MessageHeader.u1.s1.DataLength = (CSHORT) WorkQueueItem->ReplyBufferLength;
|
||
ReplyMessage.MessageHeader.u1.s1.TotalLength =
|
||
ReplyMessage.MessageHeader.u1.s1.DataLength +
|
||
(CSHORT) sizeof(PORT_MESSAGE);
|
||
|
||
//
|
||
// Next, fill in the header info needed by the LSA.
|
||
//
|
||
|
||
CommandMessage.CommandNumber = WorkQueueItem->CommandNumber;
|
||
ReplyMessage.ReturnedStatus = STATUS_SUCCESS;
|
||
|
||
//
|
||
// Set up the Command Parameters either in the LPC Command Message
|
||
// itself, in the preallocated Lsa shared memory block, or in a
|
||
// specially allocated block. The parameters are either
|
||
// immediate (i.e. in the WorkQueueItem itself, or are in a buffer
|
||
// pointed to by the address in the WorkQueueItem.
|
||
//
|
||
|
||
switch (WorkQueueItem->CommandParamsMemoryType) {
|
||
|
||
case SepRmImmediateMemory:
|
||
|
||
//
|
||
// The Command Parameters are in the CommandParams buffer
|
||
// in the Work Queue Item. Just copy them to the corresponding
|
||
// buffer in the CommandMessage buffer.
|
||
//
|
||
|
||
CommandMessage.CommandParamsMemoryType = SepRmImmediateMemory;
|
||
|
||
RtlCopyMemory(
|
||
CommandMessage.CommandParams,
|
||
&WorkQueueItem->CommandParams,
|
||
WorkQueueItem->CommandParamsLength
|
||
);
|
||
|
||
break;
|
||
|
||
case SepRmPagedPoolMemory:
|
||
case SepRmUnspecifiedMemory:
|
||
|
||
//
|
||
// The Command Parameters are contained in paged pool memory.
|
||
// Since this memory is is not accessible by the LSA, we must
|
||
// copy of them either to the LPC Command Message Block, or
|
||
// into LSA shared memory.
|
||
//
|
||
|
||
if (WorkQueueItem->CommandParamsLength <= LSA_MAXIMUM_COMMAND_PARAM_SIZE) {
|
||
|
||
//
|
||
// Parameters will fit into the LPC Command Message block.
|
||
//
|
||
|
||
CopiedCommandParams = CommandMessage.CommandParams;
|
||
|
||
RtlCopyMemory(
|
||
CopiedCommandParams,
|
||
WorkQueueItem->CommandParams.BaseAddress,
|
||
WorkQueueItem->CommandParamsLength
|
||
);
|
||
|
||
CommandMessage.CommandParamsMemoryType = SepRmImmediateMemory;
|
||
|
||
} else {
|
||
|
||
//
|
||
// Parameters too large for LPC Command Message block.
|
||
// If possible, copy them to the preallocated Lsa Shared
|
||
// Memory block. If they are too large to fit, copy them
|
||
// to an individually allocated chunk of Shared Virtual
|
||
// Memory.
|
||
//
|
||
|
||
if (WorkQueueItem->CommandParamsLength <= SEP_RM_LSA_SHARED_MEMORY_SIZE) {
|
||
|
||
RtlCopyMemory(
|
||
SepRmState.RmViewPortMemory,
|
||
WorkQueueItem->CommandParams.BaseAddress,
|
||
WorkQueueItem->CommandParamsLength
|
||
);
|
||
|
||
LsaViewCopiedCommandParams = SepRmState.LsaViewPortMemory;
|
||
CommandMessage.CommandParamsMemoryType = SepRmLsaCommandPortSharedMemory;
|
||
|
||
} else {
|
||
|
||
Status = SepAdtCopyToLsaSharedMemory(
|
||
SepLsaHandle,
|
||
WorkQueueItem->CommandParams.BaseAddress,
|
||
WorkQueueItem->CommandParamsLength,
|
||
&LsaViewCopiedCommandParams
|
||
);
|
||
|
||
if (!NT_SUCCESS(Status)) {
|
||
|
||
//
|
||
// An error occurred, most likely in allocating
|
||
// shared virtual memory. For now, just ignore
|
||
// the error and discard the Audit Record. Later,
|
||
// we may consider generating a warning record
|
||
// indicating some records lost.
|
||
//
|
||
|
||
break;
|
||
|
||
}
|
||
|
||
CommandMessage.CommandParamsMemoryType = SepRmLsaCustomSharedMemory;
|
||
}
|
||
|
||
//
|
||
// Buffer has been successfully copied to a shared Lsa
|
||
// memory buffer. Place the address of the buffer valid in
|
||
// the LSA's process context in the Command Message.
|
||
//
|
||
|
||
*((PVOID *) CommandMessage.CommandParams) =
|
||
LsaViewCopiedCommandParams;
|
||
|
||
CommandMessage.MessageHeader.u1.s1.TotalLength =
|
||
((CSHORT) RM_COMMAND_MESSAGE_HEADER_SIZE +
|
||
(CSHORT) sizeof( LsaViewCopiedCommandParams ));
|
||
CommandMessage.MessageHeader.u1.s1.DataLength =
|
||
CommandMessage.MessageHeader.u1.s1.TotalLength -
|
||
(CSHORT) sizeof(PORT_MESSAGE);
|
||
}
|
||
|
||
//
|
||
// Free input command params buffer if Paged Pool.
|
||
//
|
||
|
||
if (WorkQueueItem->CommandParamsMemoryType == SepRmPagedPoolMemory) {
|
||
|
||
ExFreePool( WorkQueueItem->CommandParams.BaseAddress );
|
||
}
|
||
|
||
break;
|
||
|
||
default:
|
||
|
||
Status = STATUS_INVALID_PARAMETER;
|
||
break;
|
||
}
|
||
|
||
if (NT_SUCCESS(Status)) {
|
||
|
||
//
|
||
// Send Message to the LSA via the LSA Server Command LPC Port.
|
||
// This must be done in the process in which the handle was created.
|
||
//
|
||
|
||
Status = ZwRequestWaitReplyPort(
|
||
SepRmState.LsaCommandPortHandle,
|
||
(PPORT_MESSAGE) &CommandMessage,
|
||
(PPORT_MESSAGE) &ReplyMessage
|
||
);
|
||
|
||
//
|
||
// If the command was successful, copy the data back to the output
|
||
// buffer.
|
||
//
|
||
|
||
if (NT_SUCCESS(Status)) {
|
||
|
||
//
|
||
// Move output from command (if any) to buffer. Note that this
|
||
// is done even if the command returns status, because some status
|
||
// values are not errors.
|
||
//
|
||
|
||
if (ARGUMENT_PRESENT(WorkQueueItem->ReplyBuffer)) {
|
||
|
||
RtlCopyMemory(
|
||
WorkQueueItem->ReplyBuffer,
|
||
ReplyMessage.ReplyBuffer,
|
||
WorkQueueItem->ReplyBufferLength
|
||
);
|
||
}
|
||
|
||
//
|
||
// Return status from command.
|
||
//
|
||
|
||
Status = ReplyMessage.ReturnedStatus;
|
||
|
||
if (!NT_SUCCESS(Status)) {
|
||
KdPrint(("Security: Command sent from RM to LSA returned 0x%lx\n",
|
||
Status));
|
||
}
|
||
|
||
} else {
|
||
|
||
KdPrint(("Security: Sending Command RM to LSA failed 0x%lx\n", Status));
|
||
}
|
||
|
||
//
|
||
// On return from the LPC call to the LSA, we expect the called
|
||
// LSA worker routine to have copied the Command Parameters
|
||
// buffer (if any). If a custom shared memory boffer was allocated,
|
||
// free it now.
|
||
//
|
||
|
||
if (CommandMessage.CommandParamsMemoryType == SepRmLsaCustomSharedMemory) {
|
||
|
||
RegionSize = 0;
|
||
|
||
Status = ZwFreeVirtualMemory(
|
||
SepLsaHandle,
|
||
(PVOID *) &CommandMessage.CommandParams,
|
||
&RegionSize,
|
||
MEM_RELEASE
|
||
);
|
||
|
||
ASSERT(NT_SUCCESS(Status));
|
||
}
|
||
|
||
}
|
||
|
||
|
||
//
|
||
// Clean up. We must call the cleanup functions on its parameter
|
||
// and then free the used WorkQueueItem itself.
|
||
//
|
||
|
||
if ( ARGUMENT_PRESENT( WorkQueueItem->CleanupFunction)) {
|
||
|
||
(WorkQueueItem->CleanupFunction)(WorkQueueItem->CleanupParameter);
|
||
}
|
||
|
||
//
|
||
// Determine if there is more work to do on this list
|
||
//
|
||
|
||
WorkQueueItem = SepDequeueWorkItem();
|
||
#if 0
|
||
if ( WorkQueueItem ) {
|
||
DbgPrint("Got another item from list, going back\n");
|
||
} else {
|
||
DbgPrint("List is empty, leaving\n");
|
||
}
|
||
#endif
|
||
|
||
|
||
}
|
||
|
||
KeDetachProcess();
|
||
|
||
if ( LocalListLength > SepLsaQueueLength ) {
|
||
SepLsaQueueLength = LocalListLength;
|
||
}
|
||
|
||
return Status;
|
||
}
|
||
|
||
|
||
|
||
|
||
|
||
BOOLEAN
|
||
SepRmInitPhase0(
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This function performs Reference Monitor Phase 0 initialization.
|
||
This includes initializing the reference monitor database to a state
|
||
which allows access validation routines to operate (always granting
|
||
access) prior to the main init of the Reference Monitor in Phase 1
|
||
initialization, without having to check if the RM is initialized.
|
||
|
||
|
||
Arguments:
|
||
|
||
None.
|
||
|
||
Return Value:
|
||
|
||
BOOLEAN - TRUE if successful, else FALSE
|
||
|
||
--*/
|
||
|
||
{
|
||
|
||
BOOLEAN CompletionStatus;
|
||
|
||
PAGED_CODE();
|
||
|
||
CompletionStatus = SepRmDbInitialization();
|
||
|
||
return CompletionStatus;
|
||
}
|