为什么在用户程序中动态分配缓冲区会导致内核驱

时间:2013-02-14 08:55:34

标签: c++ windows memory-management driver kernel

我有一个程序,它分配一个缓冲区,其指针通过自定义IOCTL传递给内核驱动程序。在驱动程序中,我得到一个Mdl并使用“MmGetSystemAddressForMdlSafe”锁定用户程序缓冲区的页面,然后使用Mdl填充用户程序缓冲区。

如果在用户程序中缓冲区是普通数组,则驱动程序始终可以正常工作。 (WORD缓冲区[256],其中word是无符号短路)

如果用户程序缓冲区不时分配了新关键字(WORD *buffer = new WORD[256])或malloc关键字(WORD *buffer=(WORD*) malloc(sizeof(*buffer)*256))),我会得到一个BSOD,错误是“非分页区域中的页面错误”。

为什么?

谢谢!

编辑(其他详细信息):

在驱动程序中,我以这种方式使用MmGetSystemAddressForMdlSafe

PVOID p_buffer = MmGetSystemAddressForMdlSafe(Irp->MdlAddress, HighPagePriority);

Irp是我在处理IRP_MJ_DEVICE_CONTROL MajorFunction时作为第二个参数收到的PIRP。

在我检查p_buffer不为空之后,我使用该指针写入用户缓冲区:

READ_PORT_BUFFER_USHORT((PUSHORT)(USHORT)current_port.address, (PUSHORT)p_buffer, 256)

IOCTL定义:

#define IOCTL_TEST_READPORT      CTL_CODE(FILE_DEVICE_TEST,  \
    TEST_IOCTL_INDEX + 0,   \
    METHOD_OUT_DIRECT,         \
    FILE_ANY_ACCESS)

处理IRP_MJ_DEVICE_CONTROL的驱动程序函数:

    NTSTATUS TESTDispatch(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp)
{
PIO_STACK_LOCATION IrpStack;
ULONG              input_buffer_size;
ULONG              output_buffer_size;
ULONG              control_code;
PVOID              p_buffer;
NTSTATUS           nt_status;
struct             port current_port;

UNREFERENCED_PARAMETER(DeviceObject);
PAGED_CODE();

Irp->IoStatus.Status      = STATUS_SUCCESS;
Irp->IoStatus.Information = 0;

IrpStack = IoGetCurrentIrpStackLocation(Irp);

switch (IrpStack->MajorFunction)
{

case IRP_MJ_DEVICE_CONTROL:

    control_code = IrpStack->Parameters.DeviceIoControl.IoControlCode;

    switch (control_code)
    {
        case IOCTL_TEST_READPORT:
            p_buffer = MmGetSystemAddressForMdlSafe(Irp->MdlAddress, HighPagePriority);
            input_buffer_size  = IrpStack->Parameters.DeviceIoControl.InputBufferLength;

            if (!p_buffer) 
            {
                nt_status = STATUS_INSUFFICIENT_RESOURCES;
                break;
            }
            if (input_buffer_size)
            {
                memcpy (&current_port, Irp->AssociatedIrp.SystemBuffer, input_buffer_size);
                switch (current_port.size)
                {
                case 1:
                    current_port.value = (ULONG)READ_PORT_UCHAR((PUCHAR)(USHORT)current_port.address);
                    memcpy (p_buffer, &current_port.value, sizeof(current_port.value));
                    Irp->IoStatus.Information = sizeof(current_port.value);
                    break;
                case 0xF0:
                    READ_PORT_BUFFER_USHORT((PUSHORT)(USHORT)current_port.address, (PUSHORT)p_buffer, 256);
                    Irp->IoStatus.Information = sizeof(current_port.value);
                    break;
                case 2:
                    current_port.value = (ULONG)READ_PORT_USHORT((PUSHORT)(USHORT)current_port.address);
                    memcpy (p_buffer, &current_port.value, sizeof(current_port.value));
                    Irp->IoStatus.Information = sizeof(current_port.value);
                    break;
                }
            }
            else
                Irp->IoStatus.Status = STATUS_INVALID_PARAMETER;
                break;
        case IRP_MJ_CREATE:
            KdPrint(("IRP_MJ_CREATE"));
            break;

        case IRP_MJ_CLOSE:
            KdPrint(("IRP_MJ_CLOSE"));
            break;

        default:
            Irp->IoStatus.Status = STATUS_INVALID_PARAMETER;
            break;
    }
    break;
}
nt_status = Irp->IoStatus.Status;
IoCompleteRequest (Irp, IO_NO_INCREMENT);
return nt_status;
}

case 0xF0:

中的相关案例为case IOCTL_TEST_READPORT:

1 个答案:

答案 0 :(得分:1)

据我了解,你误解了MmGetSystemAddressForMdlSafe的目的。 Per this document here,您使用此函数来获取MDL(内存描述符列表)描述的虚拟地址。

  

。如果驱动程序必须使用虚拟地址访问MDL描述的页面,则必须使用MmGetSystemAddressForMdlSafe将这些页面映射到系统地址空间

同一份文件也说明了这一点:

  

要使用虚拟地址访问MDL描述的缓冲区,驱动程序调用MmGetSystemAddressForMdlSafe将缓冲区映射到系统空间。

     

<强> MmGetSystemAddressForMdlSafe :   将MDL描述的物理页面映射到系统空间,并返回MDL的虚拟地址。返回的虚拟地址可以在任何IRQL和任何进程上下文中使用

如果你查看MmGetSystemAddressForMdlSafe的{​​{3}},那么你会看到以下这一行:

  

MmGetSystemAddressForMdlSafe宏返回指定MDL描述的缓冲区的非分页系统空间虚拟地址。

它表示此函数为MDL描述的缓冲区返回非分页虚拟地址。

MDL的定义如下:

  

内存描述符列表(MDL)描述物理内存中的页面列表。

这是物理内存中页面的描述,而不是虚拟内存。由new分配的缓冲区已经有虚拟地址,尝试在其上使用MmGetSystemAddressForMdlSafe,这是错误的。您应该使用该函数从MDL获取虚拟地址,而不是从虚拟地址范围获取MDL。

现在,继续讨论page fault in non-paged area

现在,如果您考虑一下,newmalloc分配的缓冲区可能已经位于分页内存区域(实际上,看到它位于用户区域,很可能),意味着试图获取一个虚拟地址到这个缓冲区(这已经是错误的,因为它不是MDL),将在非分页区中导致页面错误,因为缓冲区的内存是在分页区域中,而您将其映射到内核中的非分页区域,而非分页区域不会导致页面错误。 (很可能与错误的IRQL级别有关)