Leaked source code of windows server 2003
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

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