|
|
/*
Copyright (c) 2001 Microsoft Corporation
File name:
mmpatch.c Author: Adrian Marinescu (adrmarin) Dec 20 2001
Environment:
Kernel mode only.
Revision History:
*/
#include "mi.h"
#pragma hdrstop
#define NTOS_KERNEL_RUNTIME
#include "hotpatch.h"
NTSTATUS MiPerformHotPatch ( IN PKLDR_DATA_TABLE_ENTRY PatchHandle, IN PVOID ImageBaseAddress, IN ULONG PatchFlags );
VOID MiRundownHotpatchList ( IN PRTL_PATCH_HEADER PatchHead ); #ifdef ALLOC_PRAGMA
#pragma alloc_text(PAGE,MmLockAndCopyMemory)
#pragma alloc_text(PAGE,MiPerformHotPatch)
#pragma alloc_text(PAGE,MmHotPatchRoutine)
#pragma alloc_text(PAGE,MiRundownHotpatchList)
#endif
LIST_ENTRY MiHotPatchList;
#define MiInValidRange(s,offset,size,total) \
(((s).offset>(total)) || \ ((s).size>(total)) || \ (((s).offset + (s).size)>(total)))
VOID MiDoCopyMemory ( IN PKDPC Dpc, IN PVOID DeferredContext, IN PVOID SystemArgument1, IN PVOID SystemArgument2 )
/*++
Routine Description:
This target function copies a captured buffer containing the new code over existing code.
Arguments:
Dpc - Supplies a pointer to a control object of type DPC.
DeferredContext - Deferred context.
SystemArgument1 - Used to signal completion of this call.
SystemArgument2 - Used for internal lockstepping during this call.
Return Value:
None.
Environment:
Kernel mode, DISPATCH_LEVEL as the target of a broadcast DPC.
--*/
{ ULONG i; KIRQL OldIrql; PSYSTEM_HOTPATCH_CODE_INFORMATION PatchInfo;
ASSERT (KeGetCurrentIrql () == DISPATCH_LEVEL);
UNREFERENCED_PARAMETER (Dpc);
PatchInfo = (PSYSTEM_HOTPATCH_CODE_INFORMATION)DeferredContext;
//
// Raise IRQL and wait for all processors to synchronize to ensure no
// processor can be executing the code we're about to modify.
//
KeRaiseIrql (IPI_LEVEL - 1, &OldIrql); if (KeSignalCallDpcSynchronize (SystemArgument2)) {
PatchInfo->Flags &= ~FLG_HOTPATCH_VERIFICATION_ERROR;
//
// Compare the existing code.
//
for (i = 0; i < PatchInfo->CodeInfo.DescriptorsCount; i += 1) {
if (PatchInfo->Flags & FLG_HOTPATCH_ACTIVE) { if (RtlCompareMemory (PatchInfo->CodeInfo.CodeDescriptors[i].MappedAddress, (PUCHAR)PatchInfo + PatchInfo->CodeInfo.CodeDescriptors[i].ValidationOffset, PatchInfo->CodeInfo.CodeDescriptors[i].ValidationSize) != PatchInfo->CodeInfo.CodeDescriptors[i].ValidationSize) {
//
// Maybe this instruction has been previously patched. See if the OrigCodeOffset matches
// in this case
//
if (RtlCompareMemory (PatchInfo->CodeInfo.CodeDescriptors[i].MappedAddress, (PUCHAR)PatchInfo + PatchInfo->CodeInfo.CodeDescriptors[i].OrigCodeOffset, PatchInfo->CodeInfo.CodeDescriptors[i].CodeSize) != PatchInfo->CodeInfo.CodeDescriptors[i].CodeSize) {
PatchInfo->Flags |= FLG_HOTPATCH_VERIFICATION_ERROR; break; } } } else { if (RtlCompareMemory (PatchInfo->CodeInfo.CodeDescriptors[i].MappedAddress, (PUCHAR)PatchInfo + PatchInfo->CodeInfo.CodeDescriptors[i].CodeOffset, PatchInfo->CodeInfo.CodeDescriptors[i].CodeSize) != PatchInfo->CodeInfo.CodeDescriptors[i].CodeSize) {
PatchInfo->Flags |= FLG_HOTPATCH_VERIFICATION_ERROR; break; } } }
if (!(PatchInfo->Flags & FLG_HOTPATCH_VERIFICATION_ERROR)) {
for (i = 0; i < PatchInfo->CodeInfo.DescriptorsCount; i += 1) {
if (PatchInfo->Flags & FLG_HOTPATCH_ACTIVE) {
RtlCopyMemory (PatchInfo->CodeInfo.CodeDescriptors[i].MappedAddress, (PUCHAR)PatchInfo + PatchInfo->CodeInfo.CodeDescriptors[i].CodeOffset, PatchInfo->CodeInfo.CodeDescriptors[i].CodeSize ); } else {
RtlCopyMemory (PatchInfo->CodeInfo.CodeDescriptors[i].MappedAddress, (PUCHAR)PatchInfo + PatchInfo->CodeInfo.CodeDescriptors[i].OrigCodeOffset, PatchInfo->CodeInfo.CodeDescriptors[i].CodeSize ); } } } }
KeSignalCallDpcSynchronize (SystemArgument2); KeSweepCurrentIcache (); KeLowerIrql (OldIrql); //
// Signal that all processing has been done.
//
KeSignalCallDpcDone (SystemArgument1);
return; }
NTSTATUS MmLockAndCopyMemory ( IN PSYSTEM_HOTPATCH_CODE_INFORMATION PatchInfo, IN KPROCESSOR_MODE ProbeMode )
/*++
Routine Description:
This function locks the code pages for IoWriteAccess and copy the new code at DPC, if all validations succeed.
Arguments:
PatchInfo - Supplies the descriptors for the target code and validation ProbeMode - Supplied the probe mode for ExLockUserBuffer
Return Value:
NTSTATUS.
--*/
{ PVOID * Locks; ULONG i; NTSTATUS Status; ASSERT (KeGetCurrentIrql () <= APC_LEVEL);
if (PatchInfo->CodeInfo.DescriptorsCount == 0) {
//
// Nothing to change
//
return STATUS_SUCCESS; }
Locks = ExAllocatePoolWithQuotaTag (PagedPool | POOL_QUOTA_FAIL_INSTEAD_OF_RAISE, PatchInfo->CodeInfo.DescriptorsCount * sizeof(PVOID), 'PtoH');
if (Locks == NULL) { return STATUS_INSUFFICIENT_RESOURCES; }
RtlZeroMemory (Locks, PatchInfo->CodeInfo.DescriptorsCount * sizeof(PVOID));
Status = STATUS_INVALID_PARAMETER;
for (i = 0; i < PatchInfo->CodeInfo.DescriptorsCount; i += 1) {
if (MiInValidRange (PatchInfo->CodeInfo.CodeDescriptors[i],CodeOffset,CodeSize, PatchInfo->InfoSize ) || MiInValidRange (PatchInfo->CodeInfo.CodeDescriptors[i],OrigCodeOffset,CodeSize, PatchInfo->InfoSize ) || MiInValidRange (PatchInfo->CodeInfo.CodeDescriptors[i],ValidationOffset,ValidationSize, PatchInfo->InfoSize ) || (PatchInfo->CodeInfo.CodeDescriptors[i].CodeSize == 0) || (PatchInfo->CodeInfo.CodeDescriptors[i].ValidationSize < PatchInfo->CodeInfo.CodeDescriptors[i].CodeSize) ) {
Status = STATUS_INVALID_PARAMETER; break; }
Status = ExLockUserBuffer ((PVOID)PatchInfo->CodeInfo.CodeDescriptors[i].TargetAddress, (ULONG)PatchInfo->CodeInfo.CodeDescriptors[i].CodeSize, ProbeMode, IoWriteAccess, (PVOID)&PatchInfo->CodeInfo.CodeDescriptors[i].MappedAddress, &Locks[i] );
if (!NT_SUCCESS(Status)) {
break; } }
if (NT_SUCCESS(Status)) {
PatchInfo->Flags ^= FLG_HOTPATCH_ACTIVE;
KeGenericCallDpc (MiDoCopyMemory, PatchInfo);
if (PatchInfo->Flags & FLG_HOTPATCH_VERIFICATION_ERROR) {
PatchInfo->Flags ^= FLG_HOTPATCH_ACTIVE; PatchInfo->Flags &= ~FLG_HOTPATCH_VERIFICATION_ERROR;
Status = STATUS_DATA_ERROR; } }
for (i = 0; i < PatchInfo->CodeInfo.DescriptorsCount; i += 1) {
if (Locks[i] != NULL) {
ExUnlockUserBuffer (Locks[i]); } }
ExFreePool (Locks);
return Status; }
NTSTATUS MiPerformHotPatch ( IN PKLDR_DATA_TABLE_ENTRY PatchHandle, IN PVOID ImageBaseAddress, IN ULONG PatchFlags )
/*++
Routine Description:
This function performs the actual patch on the kernel or driver code.
Arguments:
PatchHandle - Supplies the handle for the patch module. ImageBaseAddress - Supplies the base address for the patch module. Note that the contents of the patch image include the names of the target drivers to be patched. PatchFlags - Supplies the flags for the patch being applied. Return Value:
NTSTATUS.
Environment:
Kernel mode. Normal APCs disabled (system load mutant is held).
--*/
{ PHOTPATCH_HEADER Patch; PRTL_PATCH_HEADER RtlPatchData; PKLDR_DATA_TABLE_ENTRY DataTableEntry = NULL; NTSTATUS Status; LOGICAL FirstLoad; PVOID KernelMappedAddress; PVOID KernelLockVariable; PLIST_ENTRY Next;
Patch = RtlGetHotpatchHeader(ImageBaseAddress);
if (Patch == NULL) {
return (ULONG)STATUS_INVALID_IMAGE_FORMAT; }
//
// The caller loaded the patch driver (if it was not already loaded).
//
// Check whether the patch has *EVER* been applied. It's only in the
// list if it has. This means being in the list says it may be active
// OR inactive right now.
//
RtlPatchData = RtlFindRtlPatchHeader (&MiHotPatchList, PatchHandle);
if (RtlPatchData == NULL) {
if (!(PatchFlags & FLG_HOTPATCH_ACTIVE)) {
return STATUS_NOT_SUPPORTED; }
Status = RtlCreateHotPatch (&RtlPatchData, Patch, PatchHandle, PatchFlags);
if (!NT_SUCCESS(Status)) {
return Status; }
//
// Walk the table entry list to find the target driver that needs to
// be patched.
//
ExAcquireResourceExclusiveLite (&PsLoadedModuleResource, TRUE);
Next = PsLoadedModuleList.Flink;
for ( ; Next != &PsLoadedModuleList; Next = Next->Flink) {
DataTableEntry = CONTAINING_RECORD (Next, KLDR_DATA_TABLE_ENTRY, InLoadOrderLinks);
//
// Skip the session images because they are generally copy on
// write (barring address collisions) and will need a different
// mechanism to update.
//
if (MI_IS_SESSION_IMAGE_ADDRESS (DataTableEntry->DllBase)) { continue; }
if (RtlpIsSameImage(RtlPatchData, DataTableEntry)) {
break; } }
ExReleaseResourceLite (&PsLoadedModuleResource); //
// The target DLL is not loaded, just return the status.
//
if (RtlPatchData->TargetDllBase == NULL) {
RtlFreeHotPatchData(RtlPatchData); return STATUS_DLL_NOT_FOUND; }
//
// Create the new rtl patch structure here.
// This requires some relocation info to be processed,
// so we need to allow write access to the patch DLL.
//
Status = ExLockUserBuffer ((PVOID)PatchHandle->DllBase, PatchHandle->SizeOfImage, KernelMode, IoWriteAccess, &KernelMappedAddress, &KernelLockVariable);
if (!NT_SUCCESS(Status)) { RtlFreeHotPatchData(RtlPatchData); return Status; }
Status = RtlInitializeHotPatch( RtlPatchData, (ULONG_PTR)KernelMappedAddress - (ULONG_PTR)PatchHandle->DllBase);
//
// Release the locked pages and system PTE alternate address.
//
ExUnlockUserBuffer (KernelLockVariable);
if (!NT_SUCCESS(Status)) { RtlFreeHotPatchData(RtlPatchData); return Status; }
FirstLoad = TRUE; } else { //
// The patch has already been applied. It may currently be enabled
// OR disabled. We allow changing the state, as well as reapplying
// if the previous call failed for some code paths.
//
FirstLoad = FALSE; if (((PatchFlags ^ RtlPatchData->CodeInfo->Flags) & FLG_HOTPATCH_ACTIVE) == 0) {
return STATUS_NOT_SUPPORTED; } //
// Rebuild the hook information, if the hotpatch was not active
//
if ((RtlPatchData->CodeInfo->Flags & FLG_HOTPATCH_ACTIVE) == 0) {
Status = RtlReadHookInformation( RtlPatchData );
if (!NT_SUCCESS(Status)) {
return Status; } } }
Status = MmLockAndCopyMemory (RtlPatchData->CodeInfo, KernelMode);
if (NT_SUCCESS (Status)) {
//
// Add the patch to the driver's loader entry the first time
// this patch is loaded.
//
if (FirstLoad == TRUE) {
if (DataTableEntry->PatchInformation != NULL) {
//
// Push the new patch on the existing list.
//
RtlPatchData->NextPatch = (PRTL_PATCH_HEADER) DataTableEntry->PatchInformation; } else {
//
// First time the target driver has gotten any patch.
// Fall through.
//
}
DataTableEntry->PatchInformation = RtlPatchData;
InsertTailList (&MiHotPatchList, &RtlPatchData->PatchList); } } else { if (FirstLoad == TRUE) { RtlFreeHotPatchData (RtlPatchData); } } return Status; }
NTSTATUS MmHotPatchRoutine ( IN PSYSTEM_HOTPATCH_CODE_INFORMATION KernelPatchInfo )
/*++
Routine Description:
This is the main routine responsible for kernel hotpatching. It loads the patch module in memory, initializes the patch information and finally applies the fixups to the existing code. NOTE: This function assumes that the KernelPatchInfo structure is properly captured and validated
Arguments:
KernelPatchInfo - Supplies a pointer to a kernel buffer containing the image name of the patch. Return Value:
NTSTATUS.
Environment:
Kernel mode. PASSIVE_LEVEL on entry.
--*/
{ NTSTATUS Status; NTSTATUS PatchStatus; ULONG PatchFlags; PVOID ImageBaseAddress; PVOID ImageHandle; UNICODE_STRING PatchImageName; PKTHREAD CurrentThread;
ASSERT (KeGetCurrentIrql () == PASSIVE_LEVEL); PatchImageName.Buffer = (PWCHAR)((PUCHAR)KernelPatchInfo + KernelPatchInfo->KernelInfo.NameOffset); PatchImageName.Length = KernelPatchInfo->KernelInfo.NameLength; PatchImageName.MaximumLength = PatchImageName.Length; PatchFlags = KernelPatchInfo->Flags;
CurrentThread = KeGetCurrentThread ();
KeEnterCriticalRegionThread (CurrentThread);
//
// Acquire the loader mutant because we may discover the patch we are
// trying to load is already loaded. And we want to prevent it from
// being unloaded while we are using it.
//
KeWaitForSingleObject (&MmSystemLoadLock, WrVirtualMemory, KernelMode, FALSE, (PLARGE_INTEGER)NULL); Status = MmLoadSystemImage (&PatchImageName, NULL, NULL, 0, &ImageHandle, &ImageBaseAddress);
if (NT_SUCCESS (Status) || (Status == STATUS_IMAGE_ALREADY_LOADED)) {
PatchStatus = MiPerformHotPatch (ImageHandle, ImageBaseAddress, PatchFlags);
if ((!NT_SUCCESS (PatchStatus)) && (Status != STATUS_IMAGE_ALREADY_LOADED)) {
//
// Unload the patch DLL if applying the hotpatch failed and
// we were the initial (and only) load of the patch DLL.
//
MmUnloadSystemImage (ImageHandle); }
Status = PatchStatus; }
KeReleaseMutant (&MmSystemLoadLock, 1, FALSE, FALSE); KeLeaveCriticalRegionThread (CurrentThread);
return Status; }
VOID MiRundownHotpatchList ( IN PRTL_PATCH_HEADER PatchHead )
/*++
Routine Description:
The function walks the hotpatch list and unloads each patch module and free all data.
Arguments:
PatchHead - Supplies a pointer to the head of the patch list.
Return Value:
NTSTATUS.
Environment:
Kernel mode. System load lock held with APCs disabled.
--*/
{ PRTL_PATCH_HEADER CrtPatch;
SYSLOAD_LOCK_OWNED_BY_ME ();
while (PatchHead) { CrtPatch = PatchHead;
PatchHead = PatchHead->NextPatch;
RemoveEntryList (&CrtPatch->PatchList); //
// Unload all instances for this DLL.
//
if (CrtPatch->PatchLdrDataTableEntry) { MmUnloadSystemImage (CrtPatch->PatchLdrDataTableEntry); } RtlFreeHotPatchData (CrtPatch); }
return; }
|