You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
672 lines
18 KiB
672 lines
18 KiB
/*
|
|
|
|
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;
|
|
}
|
|
|
|
|