532 lines
16 KiB
C
532 lines
16 KiB
C
|
/*++
|
|||
|
|
|||
|
Copyright (c) 1989 Microsoft Corporation
|
|||
|
|
|||
|
Module Name:
|
|||
|
|
|||
|
loadunld.c
|
|||
|
|
|||
|
Abstract:
|
|||
|
|
|||
|
This module contains the code to implement the NtLoadDriver and
|
|||
|
NtUnLoadDriver system services for the NT I/O system.
|
|||
|
|
|||
|
Author:
|
|||
|
|
|||
|
Darryl E. Havens (darrylh) 5-Apr-1992
|
|||
|
|
|||
|
Environment:
|
|||
|
|
|||
|
Kernel mode only
|
|||
|
|
|||
|
Revision History:
|
|||
|
|
|||
|
|
|||
|
--*/
|
|||
|
|
|||
|
#include "iomgr.h"
|
|||
|
|
|||
|
#ifdef ALLOC_PRAGMA
|
|||
|
#pragma alloc_text(PAGE, NtLoadDriver)
|
|||
|
#pragma alloc_text(PAGE, NtUnloadDriver)
|
|||
|
#endif
|
|||
|
|
|||
|
NTSTATUS
|
|||
|
NtLoadDriver(
|
|||
|
IN PUNICODE_STRING DriverServiceName
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This service dynamically loads a device or file system driver into
|
|||
|
the currently running system. It requires that the caller have the
|
|||
|
appropriate privilege to execute this service.
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
DriverServiceName - Specifies the name of the node in the registry
|
|||
|
associated with the driver to be loaded.
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
The status returned is the final completion status of the load operation.
|
|||
|
|
|||
|
--*/
|
|||
|
|
|||
|
{
|
|||
|
KPROCESSOR_MODE requestorMode;
|
|||
|
UNICODE_STRING driverServiceName;
|
|||
|
PWCHAR nameBuffer = (PWCHAR) NULL;
|
|||
|
LOAD_PACKET loadPacket;
|
|||
|
PETHREAD CurrentThread;
|
|||
|
|
|||
|
PAGED_CODE();
|
|||
|
|
|||
|
//
|
|||
|
// Get the previous mode; i.e., the mode of the caller.
|
|||
|
//
|
|||
|
|
|||
|
CurrentThread = PsGetCurrentThread ();
|
|||
|
requestorMode = KeGetPreviousModeByThread(&CurrentThread->Tcb);
|
|||
|
|
|||
|
if (requestorMode != KernelMode) {
|
|||
|
|
|||
|
//
|
|||
|
// The caller's access mode is not kernel so check to ensure that
|
|||
|
// the caller has the privilege to load a driver and probe and
|
|||
|
// capture the name of the driver service entry.
|
|||
|
//
|
|||
|
|
|||
|
if (!SeSinglePrivilegeCheck( SeLoadDriverPrivilege, requestorMode )) {
|
|||
|
return STATUS_PRIVILEGE_NOT_HELD;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// The caller has the appropriate privilege to load and unload
|
|||
|
// drivers, so capture the driver service name string so that it
|
|||
|
// can be used to locate the driver from the registry node.
|
|||
|
//
|
|||
|
|
|||
|
try {
|
|||
|
|
|||
|
driverServiceName = ProbeAndReadUnicodeString( DriverServiceName );
|
|||
|
|
|||
|
if (!driverServiceName.Length) {
|
|||
|
return STATUS_INVALID_PARAMETER;
|
|||
|
}
|
|||
|
|
|||
|
ProbeForRead( driverServiceName.Buffer,
|
|||
|
driverServiceName.Length,
|
|||
|
sizeof( WCHAR ) );
|
|||
|
|
|||
|
nameBuffer = ExAllocatePoolWithQuota( PagedPool,
|
|||
|
driverServiceName.Length );
|
|||
|
|
|||
|
RtlCopyMemory( nameBuffer,
|
|||
|
driverServiceName.Buffer,
|
|||
|
driverServiceName.Length );
|
|||
|
|
|||
|
driverServiceName.Buffer = nameBuffer;
|
|||
|
|
|||
|
} except(EXCEPTION_EXECUTE_HANDLER) {
|
|||
|
|
|||
|
//
|
|||
|
// An exception was incurred while attempting to capture the
|
|||
|
// input name string or while attempting to allocate the name
|
|||
|
// string buffer. Simply clean everything up and return an
|
|||
|
// appropriate error status code.
|
|||
|
//
|
|||
|
|
|||
|
if (nameBuffer) {
|
|||
|
ExFreePool( nameBuffer );
|
|||
|
}
|
|||
|
return GetExceptionCode();
|
|||
|
}
|
|||
|
} else {
|
|||
|
driverServiceName = *DriverServiceName;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Because drivers may wish to create a system thread and execute in
|
|||
|
// its context, the remainder of this service must be executed in the
|
|||
|
// context of the primary system process. This is accomplished by
|
|||
|
// queueing a request to one of the EX worker threads and having it
|
|||
|
// invoke the I/O system routine to complete this work.
|
|||
|
//
|
|||
|
// Fill in a request packet and queue it to the worker thread then, so
|
|||
|
// that it can actually do the load.
|
|||
|
//
|
|||
|
|
|||
|
KeInitializeEvent( &loadPacket.Event, NotificationEvent, FALSE );
|
|||
|
loadPacket.DriverObject = (PDRIVER_OBJECT) NULL;
|
|||
|
loadPacket.DriverServiceName = &driverServiceName;
|
|||
|
|
|||
|
if (PsGetCurrentProcessByThread(CurrentThread) == PsInitialSystemProcess) {
|
|||
|
|
|||
|
//
|
|||
|
// If we are already in the system process, just use this thread.
|
|||
|
//
|
|||
|
|
|||
|
IopLoadUnloadDriver(&loadPacket);
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
ExInitializeWorkItem( &loadPacket.WorkQueueItem,
|
|||
|
IopLoadUnloadDriver,
|
|||
|
&loadPacket );
|
|||
|
|
|||
|
ExQueueWorkItem( &loadPacket.WorkQueueItem, DelayedWorkQueue );
|
|||
|
|
|||
|
KeWaitForSingleObject( &loadPacket.Event,
|
|||
|
UserRequest,
|
|||
|
KernelMode,
|
|||
|
FALSE,
|
|||
|
(PLARGE_INTEGER) NULL );
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// The load operation is now complete. If a name buffer was allocated,
|
|||
|
// deallocate it now, and return the final status of the load operation.
|
|||
|
//
|
|||
|
|
|||
|
if (nameBuffer) {
|
|||
|
ExFreePool( nameBuffer );
|
|||
|
}
|
|||
|
|
|||
|
return loadPacket.FinalStatus;
|
|||
|
}
|
|||
|
|
|||
|
NTSTATUS
|
|||
|
IopCheckUnloadDriver(
|
|||
|
IN PDRIVER_OBJECT driverObject,
|
|||
|
OUT PBOOLEAN unloadDriver
|
|||
|
)
|
|||
|
{
|
|||
|
PDEVICE_OBJECT deviceObject;
|
|||
|
KIRQL irql;
|
|||
|
|
|||
|
//
|
|||
|
// Check to see whether the driver has already been marked for an unload
|
|||
|
// operation by anyone in the past.
|
|||
|
//
|
|||
|
|
|||
|
irql = KeAcquireQueuedSpinLock( LockQueueIoDatabaseLock );
|
|||
|
|
|||
|
if ((driverObject->DeviceObject == NULL &&
|
|||
|
(driverObject->Flags & DRVO_UNLOAD_INVOKED)) ||
|
|||
|
(!(driverObject->Flags & DRVO_BASE_FILESYSTEM_DRIVER) && driverObject->DeviceObject &&
|
|||
|
driverObject->DeviceObject->DeviceObjectExtension->ExtensionFlags
|
|||
|
& DOE_UNLOAD_PENDING)) {
|
|||
|
|
|||
|
//
|
|||
|
// The driver has already been marked for unload or is being
|
|||
|
// unloaded. Simply return a successful completion status since
|
|||
|
// the driver is on its way out and therefore has been "marked for
|
|||
|
// unload".
|
|||
|
//
|
|||
|
|
|||
|
KeReleaseQueuedSpinLock( LockQueueIoDatabaseLock, irql );
|
|||
|
|
|||
|
ObDereferenceObject( driverObject );
|
|||
|
return STATUS_SUCCESS;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// The driver exists, and it implements unload, and it has not, so far,
|
|||
|
// been marked for an unload operation. Simply mark all of the devices
|
|||
|
// that the driver owns as being marked for unload. While this is going
|
|||
|
// on, count the references for each of the devices. If all of the
|
|||
|
// devices have a zero reference count, then tell the driver that it
|
|||
|
// should unload itself.
|
|||
|
//
|
|||
|
|
|||
|
deviceObject = driverObject->DeviceObject;
|
|||
|
*unloadDriver = TRUE;
|
|||
|
|
|||
|
while (deviceObject) {
|
|||
|
deviceObject->DeviceObjectExtension->ExtensionFlags |= DOE_UNLOAD_PENDING;
|
|||
|
if (deviceObject->ReferenceCount || deviceObject->AttachedDevice) {
|
|||
|
*unloadDriver = FALSE;
|
|||
|
}
|
|||
|
deviceObject = deviceObject->NextDevice;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// If this is a base filesystem driver then delay the unload until all its device objects
|
|||
|
// are deleted.
|
|||
|
//
|
|||
|
|
|||
|
if (driverObject->Flags & DRVO_BASE_FILESYSTEM_DRIVER && driverObject->DeviceObject) {
|
|||
|
*unloadDriver = FALSE;
|
|||
|
}
|
|||
|
|
|||
|
if (*unloadDriver) {
|
|||
|
driverObject->Flags |= DRVO_UNLOAD_INVOKED;
|
|||
|
}
|
|||
|
|
|||
|
KeReleaseQueuedSpinLock( LockQueueIoDatabaseLock, irql );
|
|||
|
return STATUS_UNSUCCESSFUL;
|
|||
|
}
|
|||
|
|
|||
|
NTSTATUS
|
|||
|
NtUnloadDriver(
|
|||
|
IN PUNICODE_STRING DriverServiceName
|
|||
|
)
|
|||
|
{
|
|||
|
return (IopUnloadDriver(DriverServiceName, FALSE));
|
|||
|
}
|
|||
|
|
|||
|
NTSTATUS
|
|||
|
IopUnloadDriver(
|
|||
|
IN PUNICODE_STRING DriverServiceName,
|
|||
|
IN BOOLEAN InvokedByPnpMgr
|
|||
|
)
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This service dynamically unloads a device or file system driver from
|
|||
|
the currently running system. It requires that the caller have the
|
|||
|
appropriate privilege to execute this service.
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
DriverServiceName - Specifies the name of the node in the registry
|
|||
|
associated with the driver to be unloaded.
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
The status returned is the final completion status of the operation.
|
|||
|
|
|||
|
--*/
|
|||
|
|
|||
|
{
|
|||
|
KPROCESSOR_MODE requestorMode;
|
|||
|
UNICODE_STRING driverServiceName;
|
|||
|
PWCHAR nameBuffer = (PWCHAR) NULL;
|
|||
|
NTSTATUS status;
|
|||
|
OBJECT_ATTRIBUTES objectAttributes;
|
|||
|
HANDLE keyHandle;
|
|||
|
UNICODE_STRING driverName;
|
|||
|
HANDLE driverHandle;
|
|||
|
PDRIVER_OBJECT driverObject;
|
|||
|
BOOLEAN unloadDriver;
|
|||
|
|
|||
|
PAGED_CODE();
|
|||
|
|
|||
|
//
|
|||
|
// Get the previous mode; i.e., the mode of the caller.
|
|||
|
//
|
|||
|
|
|||
|
requestorMode = KeGetPreviousMode();
|
|||
|
|
|||
|
if ((requestorMode != KernelMode) && (InvokedByPnpMgr == FALSE)) {
|
|||
|
|
|||
|
//
|
|||
|
// The caller's access mode is not kernel so check to ensure that
|
|||
|
// the caller has the privilege to unload a driver and probe and
|
|||
|
// capture the name of the driver service entry.
|
|||
|
//
|
|||
|
|
|||
|
if (!SeSinglePrivilegeCheck( SeLoadDriverPrivilege, requestorMode )) {
|
|||
|
return STATUS_PRIVILEGE_NOT_HELD;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// The caller has the appropriate privilege to load and unload
|
|||
|
// drivers, so capture the driver service name string so that it
|
|||
|
// can be used to locate the driver from the registry node.
|
|||
|
//
|
|||
|
|
|||
|
try {
|
|||
|
|
|||
|
driverServiceName = ProbeAndReadUnicodeString( DriverServiceName );
|
|||
|
|
|||
|
if (!driverServiceName.Length) {
|
|||
|
return STATUS_INVALID_PARAMETER;
|
|||
|
}
|
|||
|
|
|||
|
ProbeForRead( driverServiceName.Buffer,
|
|||
|
driverServiceName.Length,
|
|||
|
sizeof( WCHAR ) );
|
|||
|
|
|||
|
nameBuffer = ExAllocatePoolWithQuota( PagedPool,
|
|||
|
driverServiceName.Length );
|
|||
|
|
|||
|
RtlCopyMemory( nameBuffer,
|
|||
|
driverServiceName.Buffer,
|
|||
|
driverServiceName.Length );
|
|||
|
|
|||
|
driverServiceName.Buffer = nameBuffer;
|
|||
|
|
|||
|
} except(EXCEPTION_EXECUTE_HANDLER) {
|
|||
|
|
|||
|
//
|
|||
|
// An exception was incurred while attempting to capture the
|
|||
|
// input name string or while attempting to allocate the name
|
|||
|
// string buffer. Simply clean everything up and return an
|
|||
|
// appropriate error status code.
|
|||
|
//
|
|||
|
|
|||
|
if (nameBuffer) {
|
|||
|
ExFreePool( nameBuffer );
|
|||
|
}
|
|||
|
return GetExceptionCode();
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Now that the caller's parameters have been captured and everything
|
|||
|
// appears to have checked out, actually attempt to unload the driver.
|
|||
|
// This is done with a previous mode of kernel so that drivers will
|
|||
|
// not fail to unload because the caller didn't happen to have access
|
|||
|
// to some resource that the driver needs in order to complete its
|
|||
|
// unload operation.
|
|||
|
//
|
|||
|
|
|||
|
status = ZwUnloadDriver( &driverServiceName );
|
|||
|
ExFreePool( nameBuffer );
|
|||
|
return status;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// The caller's mode is now kernel mode. Attempt to actually unload the
|
|||
|
// driver specified by the indicated registry node. Begin by opening
|
|||
|
// the registry node for this driver.
|
|||
|
//
|
|||
|
|
|||
|
status = IopOpenRegistryKey( &keyHandle,
|
|||
|
(HANDLE) NULL,
|
|||
|
DriverServiceName,
|
|||
|
KEY_READ,
|
|||
|
FALSE );
|
|||
|
if (!NT_SUCCESS( status )) {
|
|||
|
return status;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Get the optional object name for this driver from the value for this
|
|||
|
// key. If one exists, then its name overrides the default name of the
|
|||
|
// driver.
|
|||
|
//
|
|||
|
|
|||
|
status = IopGetDriverNameFromKeyNode( keyHandle,
|
|||
|
&driverName );
|
|||
|
NtClose( keyHandle );
|
|||
|
if (!NT_SUCCESS( status )) {
|
|||
|
return status;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Now attempt to open the driver object for the specified driver.
|
|||
|
//
|
|||
|
|
|||
|
InitializeObjectAttributes( &objectAttributes,
|
|||
|
&driverName,
|
|||
|
OBJ_CASE_INSENSITIVE,
|
|||
|
(HANDLE) NULL,
|
|||
|
(PSECURITY_DESCRIPTOR) NULL );
|
|||
|
|
|||
|
status = ObOpenObjectByName( &objectAttributes,
|
|||
|
IoDriverObjectType,
|
|||
|
KernelMode,
|
|||
|
NULL,
|
|||
|
FILE_READ_DATA,
|
|||
|
(PVOID) NULL,
|
|||
|
&driverHandle );
|
|||
|
|
|||
|
//
|
|||
|
// Perform some common cleanup by getting rid of buffers that have been
|
|||
|
// allocated up to this point so that error conditions do not have as
|
|||
|
// much work to do on each exit path.
|
|||
|
//
|
|||
|
|
|||
|
ExFreePool( driverName.Buffer );
|
|||
|
|
|||
|
//
|
|||
|
// If the driver object could not be located in the first place, then
|
|||
|
// return now before attempting to do anything else.
|
|||
|
//
|
|||
|
|
|||
|
if (!NT_SUCCESS( status )) {
|
|||
|
return status;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// The driver object was located, so convert the handle into a pointer
|
|||
|
// so that the driver object itself can be examined.
|
|||
|
//
|
|||
|
|
|||
|
status = ObReferenceObjectByHandle( driverHandle,
|
|||
|
0,
|
|||
|
IoDriverObjectType,
|
|||
|
KernelMode,
|
|||
|
(PVOID *) &driverObject,
|
|||
|
NULL );
|
|||
|
NtClose( driverHandle );
|
|||
|
|
|||
|
if (!NT_SUCCESS( status )) {
|
|||
|
return status;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Check to see whether or not this driver implements unload. Also,
|
|||
|
// if the driver has no section associated with it, then it was loaded
|
|||
|
// be the OS loader and therefore cannot be unloaded. If either is true,
|
|||
|
// return an appropriate error status code.
|
|||
|
//
|
|||
|
|
|||
|
if (driverObject->DriverUnload == (PDRIVER_UNLOAD) NULL ||
|
|||
|
!driverObject->DriverSection) {
|
|||
|
ObDereferenceObject( driverObject );
|
|||
|
return STATUS_INVALID_DEVICE_REQUEST;
|
|||
|
}
|
|||
|
|
|||
|
if (!InvokedByPnpMgr && !IopIsLegacyDriver(driverObject)) {
|
|||
|
|
|||
|
ObDereferenceObject( driverObject );
|
|||
|
return STATUS_INVALID_DEVICE_REQUEST;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Check to see whether the driver has already been marked for an unload
|
|||
|
// operation by anyone in the past.
|
|||
|
//
|
|||
|
|
|||
|
status = IopCheckUnloadDriver(driverObject,&unloadDriver);
|
|||
|
|
|||
|
if ( NT_SUCCESS(status) ) {
|
|||
|
return status;
|
|||
|
}
|
|||
|
|
|||
|
if (unloadDriver) {
|
|||
|
|
|||
|
if (PsGetCurrentProcess() == PsInitialSystemProcess) {
|
|||
|
|
|||
|
//
|
|||
|
// The current thread is alrady executing in the context of the
|
|||
|
// system process, so simply invoke the driver's unload routine.
|
|||
|
//
|
|||
|
|
|||
|
driverObject->DriverUnload( driverObject );
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
//
|
|||
|
// The current thread is not executing in the context of the system
|
|||
|
// process, which is required in order to invoke the driver's unload
|
|||
|
// routine. Queue a worker item to one of the worker threads to
|
|||
|
// get into the appropriate process context and then invoke the
|
|||
|
// routine.
|
|||
|
//
|
|||
|
|
|||
|
LOAD_PACKET loadPacket;
|
|||
|
|
|||
|
KeInitializeEvent( &loadPacket.Event, NotificationEvent, FALSE );
|
|||
|
loadPacket.DriverObject = driverObject;
|
|||
|
ExInitializeWorkItem( &loadPacket.WorkQueueItem,
|
|||
|
IopLoadUnloadDriver,
|
|||
|
&loadPacket );
|
|||
|
ExQueueWorkItem( &loadPacket.WorkQueueItem, DelayedWorkQueue );
|
|||
|
(VOID) KeWaitForSingleObject( &loadPacket.Event,
|
|||
|
Executive,
|
|||
|
KernelMode,
|
|||
|
FALSE,
|
|||
|
(PLARGE_INTEGER) NULL );
|
|||
|
}
|
|||
|
ObMakeTemporaryObject( driverObject );
|
|||
|
ObDereferenceObject( driverObject );
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// The driver has either been unloaded, or it has successfully been
|
|||
|
// marked for an unload operation. Simply dereference the pointer to
|
|||
|
// the object and return success.
|
|||
|
//
|
|||
|
|
|||
|
ObDereferenceObject( driverObject );
|
|||
|
return STATUS_SUCCESS;
|
|||
|
}
|