/*++ 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; }