【问题标题】:Why dynamically allocated buffer in user program makes kernel driver crash?为什么用户程序中动态分配的缓冲区会导致内核驱动程序崩溃?
【发布时间】:2013-01-30 00:42:03
【问题描述】:

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

如果在用户程序中缓冲区是一个普通数组,驱动程序总是按它应该的方式工作。 (WORD 缓冲区[256],其中 word 是一个无符号短整数)

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

为什么?

谢谢!

编辑(附加细节):

在驱动中我这样使用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:

【问题讨论】:

  • 您能否向我们展示您的 IOCTL 和锁定页面的代码?您可能会错误地计算某些尺寸。导致蓝屏的内存地址和锁定缓冲区的起始地址是什么?这是为了查看第一个元素是导致 BSOD 还是在中间某处,并查看它是否在某个边界上
  • 在任何情况下(如果这是您的驱动程序),驱动程序应验证从用户程序传递的地址和缓冲区,以免触发页面错误。
  • 抱歉,您发布的代码没有显示您之前提到的malloced 缓冲区的使用位置。它仅表明您正在获取 MDL 的虚拟地址,因此您可以将该指针传递给 READ_PORT_BUFFER_USHORT。这看起来是正确的,AFAIK。
  • 从对我的回答的评论中获得的新数据看来,您的问题中似乎缺少一些基本信息,以便能够令人满意地了解错误原因。如果您仍然遇到此问题,请提供更多信息。
  • @Tony The Lion 我在用户程序中执行 malloc,就在调用 dll 中的函数之前,我在其中使用 DeviceIoControl 函数和我的 IOCTL 以及指向我刚刚分配的缓冲区的指针(使用METHOD_OUT_DIRECT)。正如我之前所说,如果这个缓冲区是一个普通的数组(例如:WORD buffer[256];),驱动程序永远不会崩溃并且总是正确运行。相反,如果我使用 WORD buffer = new WORD[256]; 声明缓冲区或 WORD *buffer=(WORD) malloc(sizeof(*buffer)*256)),那么我很可能会因为驱动程序崩溃而出现蓝屏错误“非页面错误” -分页区域”

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


【解决方案1】:

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

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

同一份文件也这样说:

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

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

如果您查看MmGetSystemAddressForMdlSafeMSDN documentation,您将看到以下行:

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

它说这个函数返回一个由 MDL 描述的缓冲区的非分页虚拟地址。

MDL的定义如下:

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

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

现在,继续解释page fault in non-paged area

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

【讨论】:

  • 我只对驱动程序有基本的了解,但我认为物理内存 = 分页。 Non-paged = 当前从物理内存中删除(例如,到页面文件)。
  • @Codeguard:不,更像这样:“非分页池由虚拟内存地址组成,只要分配了相应的内核对象,就保证驻留在物理内存中。分页池由可以分页进出系统的虚拟内存。” Source
  • 这就是“锁定”的来源。
  • 好的,我现在认为你是对的。在 MmGetSystemAddressForMdlSafe 的同一篇 MSDN 文章中搜索“锁定”时,它说:“在进入此例程时,指定的 MDL 必须描述被锁定的物理页面。可以使用 MmProbeAndLockPages、MmBuildMdlForNonPagedPool 构建锁定的 MDL 、IoBuildPartialMdl 或 MmAllocatePagesForMdlEx 例程”仍然不清楚问题作者是如何做到的。
  • 如果传输类型为METHOD_OUT_DIRECT,系统会检查缓冲区的地址和大小。如果这些都是有效的,系统会构建一个 MDL 来描述构成缓冲区的物理页面,然后将这些页面锁定(或“固定”)在物理内存中。 User-Mode Interactions: Guidelines for Kernel-Mode Drivers. Mapped Memory Buffers
猜你喜欢
  • 1970-01-01
  • 2011-09-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-09-08
相关资源
最近更新 更多