diff options
Diffstat (limited to 'private/ntos/mm/readwrt.c')
-rw-r--r-- | private/ntos/mm/readwrt.c | 971 |
1 files changed, 971 insertions, 0 deletions
diff --git a/private/ntos/mm/readwrt.c b/private/ntos/mm/readwrt.c new file mode 100644 index 000000000..31b02996a --- /dev/null +++ b/private/ntos/mm/readwrt.c @@ -0,0 +1,971 @@ +/*++ + +Copyright (c) 1989 Microsoft Corporation +Copyright (c) 1992 Microsoft Corporation + +Module Name: + + readwrt.c + +Abstract: + + This module contains the routines which implement the capability + to read and write the virtual memory of a target process. + +Author: + + Lou Perazzoli (loup) 22-May-1989 + +Revision History: + +--*/ + +#include "mi.h" + +// +// The maximum amount to try to Probe and Lock is 14 pages, this +// way it always fits in a 16 page allocation. +// + +#define MAX_LOCK_SIZE ((ULONG)(14 * PAGE_SIZE)) + +// +// The maximum to move in a single block is 64k bytes. +// + +#define MAX_MOVE_SIZE (LONG)0x10000 + +// +// The minimum to move is a single block is 128 bytes. +// + +#define MINIMUM_ALLOCATION (LONG)128 + +// +// Define the pool move threshold value. +// + +#define POOL_MOVE_THRESHOLD 511 + +// +// Define foreward referenced procedure prototypes. +// + +ULONG +MiGetExceptionInfo ( + IN PEXCEPTION_POINTERS ExceptionPointers, + IN PULONG BadVa + ); + +NTSTATUS +MiDoMappedCopy ( + IN PEPROCESS FromProcess, + IN PVOID FromAddress, + IN PEPROCESS ToProcess, + OUT PVOID ToAddress, + IN ULONG BufferSize, + IN KPROCESSOR_MODE PreviousMode, + OUT PULONG NumberOfBytesRead + ); + +NTSTATUS +MiDoPoolCopy ( + IN PEPROCESS FromProcess, + IN PVOID FromAddress, + IN PEPROCESS ToProcess, + OUT PVOID ToAddress, + IN ULONG BufferSize, + OUT PULONG NumberOfBytesRead + ); + +#ifdef ALLOC_PRAGMA +#pragma alloc_text(PAGE,MiGetExceptionInfo) +#pragma alloc_text(PAGE,NtReadVirtualMemory) +#pragma alloc_text(PAGE,NtWriteVirtualMemory) +#pragma alloc_text(PAGE,MiDoMappedCopy) +#pragma alloc_text(PAGE,MiDoPoolCopy) +#endif + +#define COPY_STACK_SIZE 64 + + +NTSTATUS +NtReadVirtualMemory( + IN HANDLE ProcessHandle, + IN PVOID BaseAddress, + OUT PVOID Buffer, + IN ULONG BufferSize, + OUT PULONG NumberOfBytesRead OPTIONAL + ) + +/*++ + +Routine Description: + + This function copies the specified address range from the specified + process into the specified address range of the current process. + +Arguments: + + ProcessHandle - Supplies an open handle to a process object. + + BaseAddress - Supplies the base address in the specified process + to be read. + + Buffer - Supplies the address of a buffer which receives the + contents from the specified process address space. + + BufferSize - Supplies the requested number of bytes to read from + the specified process. + + NumberOfBytesRead - Receives the actual number of bytes + transferred into the specified buffer. + +Return Value: + + TBS + +--*/ + +{ + + ULONG BytesCopied; + KPROCESSOR_MODE PreviousMode; + PEPROCESS Process; + NTSTATUS Status; + + PAGED_CODE(); + + // + // Get the previous mode and probe output argument if necessary. + // + + PreviousMode = KeGetPreviousMode(); + if (PreviousMode != KernelMode) { + +#ifdef MIPS + + // + // Handle the PCR case for mips. + // + + if (((ULONG)BaseAddress >= KSEG0_BASE) || + (((ULONG)BaseAddress + BufferSize) > (ULONG)KSEG0_BASE) || + (((ULONG)BaseAddress + BufferSize) < (ULONG)BaseAddress)) { + return STATUS_ACCESS_VIOLATION; + } + if (((ULONG)Buffer >= KSEG0_BASE) || + (((ULONG)Buffer + BufferSize) > (ULONG)KSEG0_BASE) || + (((ULONG)Buffer + BufferSize) < (ULONG)Buffer)) { + return STATUS_ACCESS_VIOLATION; + } + +#elif defined(_PPC_) + + // + // Handle the PCR case for PPC. + // + + if (((ULONG)BaseAddress >= KIPCR) && + ((ULONG)BaseAddress < (KIPCR2 + PAGE_SIZE)) && + (((ULONG)BaseAddress + BufferSize) < (KIPCR2 + PAGE_SIZE)) && + (((ULONG)BaseAddress + BufferSize) >= (ULONG)BaseAddress)) { + ; + } else if (BaseAddress > MM_HIGHEST_USER_ADDRESS) { + return STATUS_ACCESS_VIOLATION; + } + if (Buffer > MM_HIGHEST_USER_ADDRESS) { + return STATUS_ACCESS_VIOLATION; + } + +#else + + if ((BaseAddress > MM_HIGHEST_USER_ADDRESS) || + (Buffer > MM_HIGHEST_USER_ADDRESS)) { + return STATUS_ACCESS_VIOLATION; + } +#endif + + if (ARGUMENT_PRESENT(NumberOfBytesRead)) { + try { + ProbeForWriteUlong(NumberOfBytesRead); + + } except(EXCEPTION_EXECUTE_HANDLER) { + return GetExceptionCode(); + } + } + } + + // + // If the buffer size is not zero, then attempt to read data from the + // specified process address space into the current process address + // space. + // + + BytesCopied = 0; + Status = STATUS_SUCCESS; + if (BufferSize != 0) { + + // + // Reference the target process. + // + + Status = ObReferenceObjectByHandle(ProcessHandle, + PROCESS_VM_READ, + PsProcessType, + PreviousMode, + (PVOID *)&Process, + NULL); + + // + // If the process was successfully referenced, then attempt to + // read the specified memory either by direct mapping or copying + // through nonpaged pool. + // + + if (Status == STATUS_SUCCESS) { + + Status = MmCopyVirtualMemory(Process, + BaseAddress, + PsGetCurrentProcess(), + Buffer, + BufferSize, + PreviousMode, + &BytesCopied); + + // + // Dereference the target process. + // + + ObDereferenceObject(Process); + } + } + + // + // If requested, return the number of bytes read. + // + + if (ARGUMENT_PRESENT(NumberOfBytesRead)) { + try { + *NumberOfBytesRead = BytesCopied; + + } except(EXCEPTION_EXECUTE_HANDLER) { + NOTHING; + } + } + + return Status; +} + +NTSTATUS +NtWriteVirtualMemory( + IN HANDLE ProcessHandle, + OUT PVOID BaseAddress, + IN PVOID Buffer, + IN ULONG BufferSize, + OUT PULONG NumberOfBytesWritten OPTIONAL + ) + +/*++ + +Routine Description: + + This function copies the specified address range from the current + process into the specified address range of the specified process. + +Arguments: + + ProcessHandle - Supplies an open handle to a process object. + + BaseAddress - Supplies the base address to be written to in the + specified process. + + Buffer - Supplies the address of a buffer which contains the + contents to be written into the specified process + address space. + + BufferSize - Supplies the requested number of bytes to write + into the specified process. + + NumberOfBytesWritten - Receives the actual number of + bytes transferred into the specified address + space. + +Return Value: + + TBS + +--*/ + +{ + ULONG BytesCopied; + KPROCESSOR_MODE PreviousMode; + PEPROCESS Process; + NTSTATUS Status; + + PAGED_CODE(); + + // + // Get the previous mode and probe output argument if necessary. + // + + PreviousMode = KeGetPreviousMode(); + if (PreviousMode != KernelMode) { + + if ((BaseAddress > MM_HIGHEST_USER_ADDRESS) || + (Buffer > MM_HIGHEST_USER_ADDRESS)) { + return STATUS_ACCESS_VIOLATION; + } + + if (ARGUMENT_PRESENT(NumberOfBytesWritten)) { + try { + ProbeForWriteUlong(NumberOfBytesWritten); + + } except(EXCEPTION_EXECUTE_HANDLER) { + return GetExceptionCode(); + } + } + } + + // + // If the buffer size is not zero, then attempt to write data from the + // current process address space into the target process address space. + // + + BytesCopied = 0; + Status = STATUS_SUCCESS; + if (BufferSize != 0) { + + // + // Reference the target process. + // + + Status = ObReferenceObjectByHandle(ProcessHandle, + PROCESS_VM_WRITE, + PsProcessType, + PreviousMode, + (PVOID *)&Process, + NULL); + + // + // If the process was successfully referenced, then attempt to + // write the specified memory either by direct mapping or copying + // through nonpaged pool. + // + + if (Status == STATUS_SUCCESS) { + + Status = MmCopyVirtualMemory(PsGetCurrentProcess(), + Buffer, + Process, + BaseAddress, + BufferSize, + PreviousMode, + &BytesCopied); + + // + // Dereference the target process. + // + + ObDereferenceObject(Process); + } + } + + // + // If requested, return the number of bytes read. + // + + if (ARGUMENT_PRESENT(NumberOfBytesWritten)) { + try { + *NumberOfBytesWritten = BytesCopied; + + } except(EXCEPTION_EXECUTE_HANDLER) { + NOTHING; + } + } + + return Status; +} + + + +NTSTATUS +MmCopyVirtualMemory( + IN PEPROCESS FromProcess, + IN PVOID FromAddress, + IN PEPROCESS ToProcess, + OUT PVOID ToAddress, + IN ULONG BufferSize, + IN KPROCESSOR_MODE PreviousMode, + OUT PULONG NumberOfBytesCopied + ) +{ + NTSTATUS Status; + KIRQL OldIrql; + PEPROCESS ProcessToLock; + + + ProcessToLock = FromProcess; + if (FromProcess == PsGetCurrentProcess()) { + ProcessToLock = ToProcess; + } + + // + // Make sure the process still has an address space. + // + + ExAcquireSpinLock (&MmSystemSpaceLock, &OldIrql); + if (ProcessToLock->AddressSpaceDeleted != 0) { + ExReleaseSpinLock ( &MmSystemSpaceLock, OldIrql ); + return STATUS_PROCESS_IS_TERMINATING; + } + ProcessToLock->VmOperation += 1; + ExReleaseSpinLock ( &MmSystemSpaceLock, OldIrql ); + + + // + // If the buffer size is greater than the pool move threshold, + // then attempt to write the memory via direct mapping. + // + + if (BufferSize > POOL_MOVE_THRESHOLD) { + Status = MiDoMappedCopy(FromProcess, + FromAddress, + ToProcess, + ToAddress, + BufferSize, + PreviousMode, + NumberOfBytesCopied); + + // + // If the completion status is not a working quota problem, + // then finish the service. Otherwise, attempt to write the + // memory through nonpaged pool. + // + + if (Status != STATUS_WORKING_SET_QUOTA) { + goto CompleteService; + } + + *NumberOfBytesCopied = 0; + } + + // + // There was not enough working set quota to write the memory via + // direct mapping or the size of the write was below the pool move + // threshold. Attempt to write the specified memory through nonpaged + // pool. + // + + Status = MiDoPoolCopy(FromProcess, + FromAddress, + ToProcess, + ToAddress, + BufferSize, + NumberOfBytesCopied); + + // + // Dereference the target process. + // + +CompleteService: + + // + // Indicate that the vm operation is complete. + // + + ExAcquireSpinLock (&MmSystemSpaceLock, &OldIrql); + ProcessToLock->VmOperation -= 1; + if ((ProcessToLock->VmOperation == 0) && + (ProcessToLock->VmOperationEvent != NULL)) { + KeSetEvent (ProcessToLock->VmOperationEvent, 0, FALSE); + } + ExReleaseSpinLock ( &MmSystemSpaceLock, OldIrql ); + + return Status; +} + + +ULONG +MiGetExceptionInfo ( + IN PEXCEPTION_POINTERS ExceptionPointers, + IN PULONG BadVa + ) + +/*++ + +Routine Description: + + This routine examines a exception record and extracts the virtual + address of an access violation, guard page violation, or in-page error. + +Arguments: + + ExceptionPointers - Supplies a pointer to the exception record. + + BadVa - Receives the virtual address which caused the access violation. + +Return Value: + + EXECUTE_EXCEPTION_HANDLER + +--*/ + +{ + PEXCEPTION_RECORD ExceptionRecord; + + PAGED_CODE(); + + // + // If the exception code is an access violation, guard page violation, + // or an in-page read error, then return the faulting address. Otherwise. + // return a special address value. + // + + ExceptionRecord = ExceptionPointers->ExceptionRecord; + if ((ExceptionRecord->ExceptionCode == STATUS_ACCESS_VIOLATION) || + (ExceptionRecord->ExceptionCode == STATUS_GUARD_PAGE_VIOLATION) || + (ExceptionRecord->ExceptionCode == STATUS_IN_PAGE_ERROR)) { + + // + // The virtual address which caused the exception is the 2nd + // parameter in the exception information array. + // + + *BadVa = ExceptionRecord->ExceptionInformation[1]; + + } else { + + // + // Unexpected exception - set the number of bytes copied to zero. + // + + *BadVa = 0xFFFFFFFF; + } + + return EXCEPTION_EXECUTE_HANDLER; +} + +NTSTATUS +MiDoMappedCopy ( + IN PEPROCESS FromProcess, + IN PVOID FromAddress, + IN PEPROCESS ToProcess, + OUT PVOID ToAddress, + IN ULONG BufferSize, + IN KPROCESSOR_MODE PreviousMode, + OUT PULONG NumberOfBytesRead + ) + +/*++ + +Routine Description: + + This function copies the specified address range from the specified + process into the specified address range of the current process. + +Arguments: + + FromProcess - Supplies an open handle to a process object. + + FromAddress - Supplies the base address in the specified process + to be read. + + ToProcess - Supplies an open handle to a process object. + + ToAddress - Supplies the address of a buffer which receives the + contents from the specified process address space. + + BufferSize - Supplies the requested number of bytes to read from + the specified process. + + PreviousMode - Supplies the previous processor mode. + + NumberOfBytesRead - Receives the actual number of bytes + transferred into the specified buffer. + +Return Value: + + TBS + +--*/ + +{ + + ULONG AmountToMove; + ULONG BadVa; + PEPROCESS CurrentProcess; + BOOLEAN FailedMove; + BOOLEAN FailedProbe; + PULONG InVa; + ULONG LeftToMove; + PULONG MappedAddress; + ULONG MaximumMoved; + PMDL Mdl; + ULONG MdlHack[(sizeof(MDL)/4) + (MAX_LOCK_SIZE >> PAGE_SHIFT) + 1]; + PULONG OutVa; + + PAGED_CODE(); + + // + // Get the address of the current process object and initialize copy + // parameters. + // + + CurrentProcess = PsGetCurrentProcess(); + + InVa = FromAddress; + OutVa = ToAddress; + + MaximumMoved = MAX_LOCK_SIZE; + if (BufferSize <= MAX_LOCK_SIZE) { + MaximumMoved = BufferSize; + } + + Mdl = (PMDL)&MdlHack[0]; + + // + // Map the data into the system part of the address space, then copy it. + // + + LeftToMove = BufferSize; + AmountToMove = MaximumMoved; + while (LeftToMove > 0) { + + if (LeftToMove < AmountToMove) { + + // + // Set to move the remaining bytes. + // + + AmountToMove = LeftToMove; + } + + KeDetachProcess(); + KeAttachProcess (&FromProcess->Pcb); + + // + // We may be touching a user's memory which could be invalid, + // declare an exception handler. + // + + try { + + // + // Probe to make sure that the specified buffer is accessable in + // the target process. + // + + MappedAddress = NULL; + + if (((PVOID)InVa == FromAddress) && + ((PVOID)InVa <= MM_HIGHEST_USER_ADDRESS)) { + FailedProbe = TRUE; + ProbeForRead (FromAddress, BufferSize, sizeof(CHAR)); + } + + // + // Initialize MDL for request. + // + + MmInitializeMdl(Mdl, + InVa, + AmountToMove); + + FailedMove = TRUE; + MmProbeAndLockPages (Mdl, PreviousMode, IoReadAccess); + FailedMove = FALSE; + + MappedAddress = MmMapLockedPages (Mdl, KernelMode); + + // + // Deattach from the FromProcess and attach to the ToProcess. + // + + KeDetachProcess(); + KeAttachProcess (&ToProcess->Pcb); + + // + // Now operating in the context of the ToProcess. + // + + if (((PVOID)InVa == FromAddress) + && (ToAddress <= MM_HIGHEST_USER_ADDRESS)) { + ProbeForWrite (ToAddress, BufferSize, sizeof(CHAR)); + FailedProbe = FALSE; + } + + RtlCopyMemory (OutVa, MappedAddress, AmountToMove); + } except (MiGetExceptionInfo (GetExceptionInformation(), &BadVa)) { + + + // + // If an exception occurs during the move operation or probe, + // return the exception code as the status value. + // + + KeDetachProcess(); + if (MappedAddress != NULL) { + MmUnmapLockedPages (MappedAddress, Mdl); + MmUnlockPages (Mdl); + } + + if (GetExceptionCode() == STATUS_WORKING_SET_QUOTA) { + return STATUS_WORKING_SET_QUOTA; + } + + if (FailedProbe) { + return GetExceptionCode(); + + } else { + + // + // The failure occurred during the move operation, determine + // which move failed, and calculate the number of bytes + // actually moved. + // + + if (FailedMove) { + if (BadVa != 0xFFFFFFFF) { + *NumberOfBytesRead = BadVa - (ULONG)FromAddress; + } + + } else { + *NumberOfBytesRead = BadVa - (ULONG)ToAddress; + } + } + + return STATUS_PARTIAL_COPY; + } + MmUnmapLockedPages (MappedAddress, Mdl); + MmUnlockPages (Mdl); + + LeftToMove -= AmountToMove; + InVa = (PVOID)((ULONG)InVa + AmountToMove); + OutVa = (PVOID)((ULONG)OutVa + AmountToMove); + } + + KeDetachProcess(); + + // + // Set number of bytes moved. + // + + *NumberOfBytesRead = BufferSize; + return STATUS_SUCCESS; +} + +NTSTATUS +MiDoPoolCopy ( + IN PEPROCESS FromProcess, + IN PVOID FromAddress, + IN PEPROCESS ToProcess, + OUT PVOID ToAddress, + IN ULONG BufferSize, + OUT PULONG NumberOfBytesRead + ) + +/*++ + +Routine Description: + + This function copies the specified address range from the specified + process into the specified address range of the current process. + +Arguments: + + ProcessHandle - Supplies an open handle to a process object. + + BaseAddress - Supplies the base address in the specified process + to be read. + + Buffer - Supplies the address of a buffer which receives the + contents from the specified process address space. + + BufferSize - Supplies the requested number of bytes to read from + the specified process. + + NumberOfBytesRead - Receives the actual number of bytes + transferred into the specified buffer. + +Return Value: + + TBS + +--*/ + +{ + + ULONG AmountToMove; + ULONG BadVa; + PEPROCESS CurrentProcess; + BOOLEAN FailedMove; + BOOLEAN FailedProbe; + PULONG InVa; + ULONG LeftToMove; + ULONG MaximumMoved; + PULONG OutVa; + PULONG PoolArea; + LONGLONG StackArray[COPY_STACK_SIZE]; + ULONG FreePool; + + PAGED_CODE(); + + // + // Get the address of the current process object and initialize copy + // parameters. + // + + CurrentProcess = PsGetCurrentProcess(); + + InVa = FromAddress; + OutVa = ToAddress; + + // + // Allocate non-paged memory to copy in and out of. + // + + MaximumMoved = MAX_MOVE_SIZE; + if (BufferSize <= MAX_MOVE_SIZE) { + MaximumMoved = BufferSize; + } + + if (BufferSize <= (COPY_STACK_SIZE * sizeof(LONGLONG))) { + PoolArea = (PULONG)&StackArray[0]; + FreePool = FALSE; + } else { + PoolArea = ExAllocatePoolWithTag (NonPagedPool, MaximumMoved, 'wRmM'); + + while (PoolArea == NULL) { + if (MaximumMoved <= MINIMUM_ALLOCATION) { + PoolArea = ExAllocatePoolWithTag (NonPagedPoolMustSucceed, + MaximumMoved, 'wRmM'); + + } else { + MaximumMoved = MaximumMoved >> 1; + PoolArea = ExAllocatePoolWithTag (NonPagedPool, MaximumMoved, 'wRmM'); + } + } + FreePool = TRUE; + } + + // + // Copy the data into pool, then copy back into the ToProcess. + // + + LeftToMove = BufferSize; + AmountToMove = MaximumMoved; + while (LeftToMove > 0) { + + if (LeftToMove < AmountToMove) { + + // + // Set to move the remaining bytes. + // + + AmountToMove = LeftToMove; + } + + KeDetachProcess(); + KeAttachProcess (&FromProcess->Pcb); + + // + // We may be touching a user's memory which could be invalid, + // declare an exception handler. + // + + try { + + // + // Probe to make sure that the specified buffer is accessable in + // the target process. + // + + if (((PVOID)InVa == FromAddress) && + ((PVOID)InVa <= MM_HIGHEST_USER_ADDRESS)) { + FailedProbe = TRUE; + ProbeForRead (FromAddress, BufferSize, sizeof(CHAR)); + } + + FailedMove = TRUE; + RtlCopyMemory (PoolArea, InVa, AmountToMove); + FailedMove = FALSE; + + KeDetachProcess(); + KeAttachProcess (&ToProcess->Pcb); + + // + // Now operating in the context of the ToProcess. + // + + if (((PVOID)InVa == FromAddress) + && (ToAddress <= MM_HIGHEST_USER_ADDRESS)) { + ProbeForWrite (ToAddress, BufferSize, sizeof(CHAR)); + FailedProbe = FALSE; + } + + RtlCopyMemory (OutVa, PoolArea, AmountToMove); + + } except (MiGetExceptionInfo (GetExceptionInformation(), &BadVa)) { + + // + // If an exception occurs during the move operation or probe, + // return the exception code as the status value. + // + + KeDetachProcess(); + + if (FreePool) { + ExFreePool (PoolArea); + } + if (FailedProbe) { + return GetExceptionCode(); + + } else { + + // + // The failure occurred during the move operation, determine + // which move failed, and calculate the number of bytes + // actually moved. + // + + if (FailedMove) { + + // + // The failure occurred getting the data. + // + + if (BadVa != 0xFFFFFFFF) { + *NumberOfBytesRead = BadVa - (ULONG)FromAddress; + } + + } else { + + // + // The failure occurred writing the data. + // + + *NumberOfBytesRead = BadVa - (ULONG)ToAddress; + } + } + + return STATUS_PARTIAL_COPY; + } + + LeftToMove -= AmountToMove; + InVa = (PVOID)((ULONG)InVa + AmountToMove); + OutVa = (PVOID)((ULONG)OutVa + AmountToMove); + } + + if (FreePool) { + ExFreePool (PoolArea); + } + KeDetachProcess(); + + // + // Set number of bytes moved. + // + + *NumberOfBytesRead = BufferSize; + return STATUS_SUCCESS; +} |