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;
|
||
}
|