/*++

Copyright (c) Microsoft Corporation. All rights reserved. 

You may only use this code if you agree to the terms of the Windows Research Kernel Source Code License agreement (see License.txt).
If you do not agree to the terms, do not use the code.


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.

--*/

#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 forward referenced procedure prototypes.
//

ULONG
MiGetExceptionInfo (
    IN PEXCEPTION_POINTERS ExceptionPointers,
    IN PLOGICAL ExceptionAddressConfirmed,
    IN PULONG_PTR BadVa
    );

NTSTATUS
MiDoMappedCopy (
     IN PEPROCESS FromProcess,
     IN CONST VOID *FromAddress,
     IN PEPROCESS ToProcess,
     OUT PVOID ToAddress,
     IN SIZE_T BufferSize,
     IN KPROCESSOR_MODE PreviousMode,
     OUT PSIZE_T NumberOfBytesRead
     );

NTSTATUS
MiDoPoolCopy (
     IN PEPROCESS FromProcess,
     IN CONST VOID *FromAddress,
     IN PEPROCESS ToProcess,
     OUT PVOID ToAddress,
     IN SIZE_T BufferSize,
     IN KPROCESSOR_MODE PreviousMode,
     OUT PSIZE_T 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)
#pragma alloc_text(PAGE,MmCopyVirtualMemory)
#endif

#define COPY_STACK_SIZE 64

NTSTATUS
NtReadVirtualMemory(
    __in HANDLE ProcessHandle,
    __in_opt PVOID BaseAddress,
    __out_bcount(BufferSize) PVOID Buffer,
    __in SIZE_T BufferSize,
    __out_opt PSIZE_T 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:

    NTSTATUS.

--*/

{
    SIZE_T BytesCopied;
    KPROCESSOR_MODE PreviousMode;
    PEPROCESS Process;
    NTSTATUS Status;
    PETHREAD CurrentThread;

    PAGED_CODE();

    //
    // Get the previous mode and probe output argument if necessary.
    //

    CurrentThread = PsGetCurrentThread ();
    PreviousMode = KeGetPreviousModeByThread(&CurrentThread->Tcb);
    if (PreviousMode != KernelMode) {

        if (((PCHAR)BaseAddress + BufferSize < (PCHAR)BaseAddress) ||
            ((PCHAR)Buffer + BufferSize < (PCHAR)Buffer) ||
            ((PVOID)((PCHAR)BaseAddress + BufferSize) > MM_HIGHEST_USER_ADDRESS) ||
            ((PVOID)((PCHAR)Buffer + BufferSize) > MM_HIGHEST_USER_ADDRESS)) {

            return STATUS_ACCESS_VIOLATION;
        }

        if (ARGUMENT_PRESENT(NumberOfBytesRead)) {
            try {
                ProbeForWriteUlong_ptr (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,
                                          PsGetCurrentProcessByThread(CurrentThread),
                                          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,
    __in_opt PVOID BaseAddress,
    __in_bcount(BufferSize) CONST VOID *Buffer,
    __in SIZE_T BufferSize,
    __out_opt PSIZE_T NumberOfBytesWritten
    )

/*++

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:

    NTSTATUS.

--*/

{
    SIZE_T BytesCopied;
    KPROCESSOR_MODE PreviousMode;
    PEPROCESS Process;
    NTSTATUS Status;
    PETHREAD CurrentThread;

    PAGED_CODE();

    //
    // Get the previous mode and probe output argument if necessary.
    //

    CurrentThread = PsGetCurrentThread ();
    PreviousMode = KeGetPreviousModeByThread(&CurrentThread->Tcb);
    if (PreviousMode != KernelMode) {

        if (((PCHAR)BaseAddress + BufferSize < (PCHAR)BaseAddress) ||
            ((PCHAR)Buffer + BufferSize < (PCHAR)Buffer) ||
            ((PVOID)((PCHAR)BaseAddress + BufferSize) > MM_HIGHEST_USER_ADDRESS) ||
            ((PVOID)((PCHAR)Buffer + BufferSize) > MM_HIGHEST_USER_ADDRESS)) {

            return STATUS_ACCESS_VIOLATION;
        }

        if (ARGUMENT_PRESENT(NumberOfBytesWritten)) {
            try {
                ProbeForWriteUlong_ptr(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 (PsGetCurrentProcessByThread(CurrentThread),
                                          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 CONST VOID *FromAddress,
    IN PEPROCESS ToProcess,
    OUT PVOID ToAddress,
    IN SIZE_T BufferSize,
    IN KPROCESSOR_MODE PreviousMode,
    OUT PSIZE_T NumberOfBytesCopied
    )
{
    NTSTATUS Status;
    PEPROCESS ProcessToLock;

    if (BufferSize == 0) {
        ASSERT (FALSE);         // No one should call with a zero size.
        return STATUS_SUCCESS;
    }

    ProcessToLock = FromProcess;
    if (FromProcess == PsGetCurrentProcess()) {
        ProcessToLock = ToProcess;
    }

    //
    // Make sure the process still has an address space.
    //

    if (ExAcquireRundownProtection (&ProcessToLock->RundownProtect) == FALSE) {
        return STATUS_PROCESS_IS_TERMINATING;
    }

    //
    // 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,
                          PreviousMode,
                          NumberOfBytesCopied);

    //
    // Dereference the target process.
    //

CompleteService:

    //
    // Indicate that the vm operation is complete.
    //

    ExReleaseRundownProtection (&ProcessToLock->RundownProtect);

    return Status;
}


ULONG
MiGetExceptionInfo (
    IN PEXCEPTION_POINTERS ExceptionPointers,
    IN OUT PLOGICAL ExceptionAddressConfirmed,
    IN OUT PULONG_PTR 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.

    ExceptionAddressConfirmed - Receives TRUE if the exception address was
                                reliably detected, FALSE if not.

    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.
    //

    *ExceptionAddressConfirmed = FALSE;

    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.
        //
        // The number of parameters will be zero if an exception handler
        // above us (like the one in MmProbeAndLockPages) caught the
        // original exception and subsequently just raised status.
        // This means the number of bytes copied is zero.
        //

        if (ExceptionRecord->NumberParameters > 1) {
            *ExceptionAddressConfirmed = TRUE;
            *BadVa = ExceptionRecord->ExceptionInformation[1];
        }
    }

    return EXCEPTION_EXECUTE_HANDLER;
}

NTSTATUS
MiDoMappedCopy (
    IN PEPROCESS FromProcess,
    IN CONST VOID *FromAddress,
    IN PEPROCESS ToProcess,
    OUT PVOID ToAddress,
    IN SIZE_T BufferSize,
    IN KPROCESSOR_MODE PreviousMode,
    OUT PSIZE_T 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:

    NTSTATUS.

--*/

{
    KAPC_STATE ApcState;
    SIZE_T AmountToMove;
    ULONG_PTR BadVa;
    LOGICAL Moving;
    LOGICAL Probing;
    LOGICAL LockedMdlPages;
    CONST VOID *InVa;
    SIZE_T LeftToMove;
    PSIZE_T MappedAddress;
    SIZE_T MaximumMoved;
    PMDL Mdl;
    PFN_NUMBER MdlHack[(sizeof(MDL)/sizeof(PFN_NUMBER)) + (MAX_LOCK_SIZE >> PAGE_SHIFT) + 1];
    PVOID OutVa;
    LOGICAL MappingFailed;
    LOGICAL ExceptionAddressConfirmed;

    PAGED_CODE();

    MappingFailed = FALSE;

    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;

    Probing = FALSE;

    //
    // Initializing BadVa & ExceptionAddressConfirmed is not needed for
    // correctness but without it the compiler cannot compile this code
    // W4 to check for use of uninitialized variables.
    //

    BadVa = 0;
    ExceptionAddressConfirmed = FALSE;

    while (LeftToMove > 0) {

        if (LeftToMove < AmountToMove) {

            //
            // Set to move the remaining bytes.
            //

            AmountToMove = LeftToMove;
        }

        KeStackAttachProcess (&FromProcess->Pcb, &ApcState);

        MappedAddress = NULL;
        LockedMdlPages = FALSE;
        Moving = FALSE;
        ASSERT (Probing == FALSE);

        //
        // 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 accessible in
            // the target process.
            //

            if ((InVa == FromAddress) && (PreviousMode != KernelMode)){
                Probing = TRUE;
                ProbeForRead (FromAddress, BufferSize, sizeof(CHAR));
                Probing = FALSE;
            }

            //
            // Initialize MDL for request.
            //

            MmInitializeMdl (Mdl, (PVOID)InVa, AmountToMove);

            MmProbeAndLockPages (Mdl, PreviousMode, IoReadAccess);

            LockedMdlPages = TRUE;

            MappedAddress = MmMapLockedPagesSpecifyCache (Mdl,
                                                          KernelMode,
                                                          MmCached,
                                                          NULL,
                                                          FALSE,
                                                          HighPagePriority);

            if (MappedAddress == NULL) {
                MappingFailed = TRUE;
                ExRaiseStatus(STATUS_INSUFFICIENT_RESOURCES);
            }

            //
            // Deattach from the FromProcess and attach to the ToProcess.
            //

            KeUnstackDetachProcess (&ApcState);
            KeStackAttachProcess (&ToProcess->Pcb, &ApcState);

            //
            // Now operating in the context of the ToProcess.
            //
            if ((InVa == FromAddress) && (PreviousMode != KernelMode)){
                Probing = TRUE;
                ProbeForWrite (ToAddress, BufferSize, sizeof(CHAR));
                Probing = FALSE;
            }

            Moving = TRUE;
            RtlCopyMemory (OutVa, MappedAddress, AmountToMove);

        } except (MiGetExceptionInfo (GetExceptionInformation(),
                                      &ExceptionAddressConfirmed,
                                      &BadVa)) {


            //
            // If an exception occurs during the move operation or probe,
            // return the exception code as the status value.
            //

            KeUnstackDetachProcess (&ApcState);

            if (MappedAddress != NULL) {
                MmUnmapLockedPages (MappedAddress, Mdl);
            }
            if (LockedMdlPages == TRUE) {
                MmUnlockPages (Mdl);
            }

            if (GetExceptionCode() == STATUS_WORKING_SET_QUOTA) {
                return STATUS_WORKING_SET_QUOTA;
            }

            if ((Probing == TRUE) || (MappingFailed == TRUE)) {
                return GetExceptionCode();

            }

            //
            // If the failure occurred during the move operation, determine
            // which move failed, and calculate the number of bytes
            // actually moved.
            //

            *NumberOfBytesRead = BufferSize - LeftToMove;

            if (Moving == TRUE) {
                if (ExceptionAddressConfirmed == TRUE) {
                    *NumberOfBytesRead = (SIZE_T)((ULONG_PTR)BadVa - (ULONG_PTR)FromAddress);
                }
            }

            return STATUS_PARTIAL_COPY;
        }

        KeUnstackDetachProcess (&ApcState);

        MmUnmapLockedPages (MappedAddress, Mdl);
        MmUnlockPages (Mdl);

        LeftToMove -= AmountToMove;
        InVa = (PVOID)((ULONG_PTR)InVa + AmountToMove);
        OutVa = (PVOID)((ULONG_PTR)OutVa + AmountToMove);
    }

    //
    // Set number of bytes moved.
    //

    *NumberOfBytesRead = BufferSize;
    return STATUS_SUCCESS;
}

NTSTATUS
MiDoPoolCopy (
     IN PEPROCESS FromProcess,
     IN CONST VOID *FromAddress,
     IN PEPROCESS ToProcess,
     OUT PVOID ToAddress,
     IN SIZE_T BufferSize,
     IN KPROCESSOR_MODE PreviousMode,
     OUT PSIZE_T 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.

     PreviousMode - Supplies the previous processor mode.

     NumberOfBytesRead - Receives the actual number of bytes
                         transferred into the specified buffer.

Return Value:

    NTSTATUS.

--*/

{
    KAPC_STATE ApcState;
    SIZE_T AmountToMove;
    LOGICAL ExceptionAddressConfirmed;
    ULONG_PTR BadVa;
    PEPROCESS CurrentProcess;
    LOGICAL Moving;
    LOGICAL Probing;
    CONST VOID *InVa;
    SIZE_T LeftToMove;
    SIZE_T MaximumMoved;
    PVOID OutVa;
    PVOID PoolArea;
    LONGLONG StackArray[COPY_STACK_SIZE];
    ULONG FreePool;

    PAGED_CODE();

    ASSERT (BufferSize != 0);

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

    FreePool = FALSE;
    if (BufferSize <= sizeof(StackArray)) {
        PoolArea = (PVOID)&StackArray[0];
    } else {
        do {
            PoolArea = ExAllocatePoolWithTag (NonPagedPool, MaximumMoved, 'wRmM');
            if (PoolArea != NULL) {
                FreePool = TRUE;
                break;
            }

            MaximumMoved = MaximumMoved >> 1;
            if (MaximumMoved <= sizeof(StackArray)) {
                PoolArea = (PVOID)&StackArray[0];
                break;
            }
        } while (TRUE);
    }

    //
    // Initializing BadVa & ExceptionAddressConfirmed is not needed for
    // correctness but without it the compiler cannot compile this code
    // W4 to check for use of uninitialized variables.
    //

    BadVa = 0;
    ExceptionAddressConfirmed = FALSE;

    //
    // Copy the data into pool, then copy back into the ToProcess.
    //

    LeftToMove = BufferSize;
    AmountToMove = MaximumMoved;
    Probing = FALSE;

    while (LeftToMove > 0) {

        if (LeftToMove < AmountToMove) {

            //
            // Set to move the remaining bytes.
            //

            AmountToMove = LeftToMove;
        }

        KeStackAttachProcess (&FromProcess->Pcb, &ApcState);

        Moving = FALSE;
        ASSERT (Probing == FALSE);

        //
        // 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 accessible in
            // the target process.
            //

            if ((InVa == FromAddress) && (PreviousMode != KernelMode)){
                Probing = TRUE;
                ProbeForRead (FromAddress, BufferSize, sizeof(CHAR));
                Probing = FALSE;
            }

            RtlCopyMemory (PoolArea, InVa, AmountToMove);

            KeUnstackDetachProcess (&ApcState);

            KeStackAttachProcess (&ToProcess->Pcb, &ApcState);

            //
            // Now operating in the context of the ToProcess.
            //

            if ((InVa == FromAddress) && (PreviousMode != KernelMode)){
                Probing = TRUE;
                ProbeForWrite (ToAddress, BufferSize, sizeof(CHAR));
                Probing = FALSE;
            }

            Moving = TRUE;

            RtlCopyMemory (OutVa, PoolArea, AmountToMove);

        } except (MiGetExceptionInfo (GetExceptionInformation(),
                                      &ExceptionAddressConfirmed,
                                      &BadVa)) {

            //
            // If an exception occurs during the move operation or probe,
            // return the exception code as the status value.
            //

            KeUnstackDetachProcess (&ApcState);

            if (FreePool) {
                ExFreePool (PoolArea);
            }
            if (Probing == TRUE) {
                return GetExceptionCode();

            }

            //
            // If the failure occurred during the move operation, determine
            // which move failed, and calculate the number of bytes
            // actually moved.
            //

            *NumberOfBytesRead = BufferSize - LeftToMove;

            if (Moving == TRUE) {

                //
                // The failure occurred writing the data.
                //

                if (ExceptionAddressConfirmed == TRUE) {
                    *NumberOfBytesRead = (SIZE_T)((ULONG_PTR)(BadVa - (ULONG_PTR)FromAddress));
                }

            }

            return STATUS_PARTIAL_COPY;
        }

        KeUnstackDetachProcess (&ApcState);

        LeftToMove -= AmountToMove;
        InVa = (PVOID)((ULONG_PTR)InVa + AmountToMove);
        OutVa = (PVOID)((ULONG_PTR)OutVa + AmountToMove);
    }

    if (FreePool) {
        ExFreePool (PoolArea);
    }

    //
    // Set number of bytes moved.
    //

    *NumberOfBytesRead = BufferSize;
    return STATUS_SUCCESS;
}

