windows驱动开发-内核编程技术汇总(七)

多处理器环境出错

在基于 NT 的操作系统上,驱动程序是多线程的;它们可以同时接收来自不同线程的多个 I/O 请求。 在设计驱动程序时,必须假定它将在 SMP 系统上运行,并采取适当的措施来确保数据完整性。

具体而言,每当驱动程序更改全局或文件对象数据时,它必须使用锁或联锁序列来防止争用条件。

引用全局或文件对象特定的数据时遇到争用条件,在以下代码片段中,当驱动程序访问 Data.LpcInfo 上的全局数据时,可能会出现争用条件:

    PLPC_INFO pLpcInfo = &Data.LpcInfo; //Pointer to global data
   ...
   ...
   // This saved pointer may be overwritten by another thread.
   pLpcInfo->LpcPortName.Buffer = ExAllocatePool(
                                     PagedPool,
                                     arg->PortName.Length);

由于 IOCTL 调用而输入此代码的多个线程可能会导致内存泄漏,因为指针被覆盖。 若要避免此问题,驱动程序在更改全局数据时应使用 ExInterlockedXxx 例程或某种类型的锁。 驱动程序的要求决定了可接受的锁类型。 

以下示例尝试将特定于文件的缓冲区重新分配 (Endpoint-LocalAddress>) 以保存终结点地址:

    Endpoint = FileObject->FsContext;

    if ( Endpoint->LocalAddress != NULL &&
         Endpoint->LocalAddressLength <
                   ListenEndpoint->LocalAddressLength ) {

      FREE_POOL (Endpoint->LocalAddress,
                 LOCAL_ADDRESS_POOL_TAG
                 );
      Endpoint->LocalAddress  = NULL;
   }

    if ( Endpoint->LocalAddress == NULL ) {
       Endpoint->LocalAddress =
            ALLOCATE_POOL (NonPagedPool,
                           ListenEndpoint->LocalAddressLength,
                           LOCAL_ADDRESS_POOL_TAG);
   }

在此示例中,访问文件对象时可能会出现争用条件。 由于驱动程序不保留任何锁,因此对同一文件对象的两个请求可以进入此函数。 结果可能是对已释放内存的引用、多次尝试释放同一内存或内存泄漏。 为了避免这些错误,应将两个 if 语句括在旋转锁中。

无法验证设备对象

许多驱动程序通过调用 IoCreateDevice 创建多种类型的设备对象。 某些驱动程序在其 DriverEntry 例程中创建控制设备对象,以允许应用程序与驱动程序通信,甚至在驱动程序创建 FDO 之前也是如此。 例如,文件系统驱动程序创建设备对象,以便在将自身注册为 使用 IoRegisterFileSystem 的文件系统时处理文件系统通知。

驱动程序应准备好为其创建的任何设备对象 IRP_MJ_CREATE 请求。 在以成功状态完成请求后,驱动程序应会收到对创建的文件对象的任何用户可访问的 I/O 请求。 因此,任何创建多个设备对象的驱动程序都必须检查每个 I/O 请求指定的设备对象。

例如,假设驱动程序在 DriverEntry 中创建整体控制设备对象,然后在其 AddDevice 例程中创建另一组设备对象。 假设 AddDevice 例程使用有关较低级别驱动程序的信息初始化设备扩展,但控制设备对象不包含此信息。 在这种情况下,所有调度例程都必须小心检查它们接收的每个设备对象。 否则,尝试使用设备扩展信息时,驱动程序可能会崩溃。

无法验证对象句柄

某些驱动程序必须操作调用方传递给它们的 对象,或者必须同时处理两个文件对象。 例如,调制解调器驱动程序可能会接收事件对象的句柄,或者网络驱动程序可能会接收两个不同文件对象的句柄。 驱动程序必须验证这些句柄。 由于它们由调用方传递,而不是通过 I/O 管理器传递,因此 I/O 管理器无法执行任何验证检查。

例如,在以下代码片段中,驱动程序已传递句柄 AscInfo->AddressHandle,但在调用 ObReferenceObjectByHandle 之前尚未对其进行验证:

   //
   // This handle is embedded in a buffered request.
   //
   status = ObReferenceObjectByHandle(
                      AscInfo->AddressHandle,
                      0,
                      NULL,
                      KernelMode,
                      &fileObject,
                      NULL);

   if (NT_SUCCESS(status)) {
       if ( (fileObject->DeviceObject == DeviceObject) &&
            (fileObject->FsContext2 == TRANSPORT_SOCK) ) {

虽然调用 ObReferenceObjectByHandle 成功,但代码无法确保返回的指针引用文件对象;它信任调用方传入正确的信息。

即使调用 ObReferenceObjectByHandle 的所有参数都正确且调用成功,如果文件对象不用于其驱动程序,驱动程序仍可能获得意外结果。 在以下代码片段中,驱动程序假定成功的调用返回指向预期文件对象的指针:

   status = ObReferenceObjectByHandle (
                             AcpInfo->Handle,
                             0L,
                             DesiredAccess,
                             *IoFileObjectType,
                             Irp->RequestorMode,
                             (PVOID *)&AcpEndpointFileObject,
                             NULL);

   if ( !NT_SUCCESS(status) ) {
      goto complete;
   }
   AcpEndpoint = AcpEndpointFileObject->FsContext;

   if ( AcpEndpoint->Type != BlockTypeEndpoint )

尽管 ObReferenceObjectByHandle 返回指向文件对象的指针,但驱动程序不保证指针引用它所需的文件对象。 在这种情况下,驱动程序应在访问 AcpEndpointFileObject->FsContext 处的特定于驱动程序的数据之前验证指针。

为了避免此类问题,驱动程序应检查获取有效数据,如下所示:

  • 检查对象类型,确保它是驱动程序所需的类型;
  • 确保请求的访问权限适用于对象类型和所需任务。 例如,如果驱动程序执行快速文件复制,请确保句柄具有读取访问权限;
  • 请确保 (UserMode 或 KernelMode) 指定正确的访问模式,并且访问模式与请求的访问权限兼容;
  • 如果驱动程序需要驱动程序本身创建的文件对象的句柄,请针对设备对象或驱动程序验证句柄。 但是,请注意不要破坏为奇怪设备发送 I/O 请求的Filter;
  • 如果驱动程序支持多种文件对象 ,例如控制通道、地址对象和 TDI 驱动程序的连接或文件系统 的卷、目录和 File 对象,请确保有办法区分它们;
无法检查驱动程序的状态

在以下示例中,驱动程序使用 ASSERT 宏在驱动程序映像的调试版本中检查正确的设备状态,但不在同一驱动程序源的发布版本中检查设备状态:

case IOCTL_WAIT_FOR_EVENT:

      ASSERT((!Extension->WaitEventIrp));
      Extension->WaitEventIrp = Irp;
      IoMarkIrpPending(Irp);
      status = STATUS_PENDING;

在调试驱动程序映像中,如果驱动程序已保留 IRP 挂起,系统将断言。 但是,在发布版本中,驱动程序不会检查此错误。 对同一 IOCTL 的两次调用会导致驱动程序失去 IRP 的跟踪。

在多处理器系统上,此代码片段可能会导致其他问题。 假设此例程在输入时拥有 (操作此 IRP) 权限。 当例程将 Irp 指针保存在 Extension->WaitEventIrp 的全局结构中时,另一个线程可以从该全局结构获取 IRP 地址,并在 IRP 上执行操作。 若要防止此问题,驱动程序应在保存 IRP 之前将 IRP 标记为挂起,并且应在互锁序列中包含对 IoMarkIrpPending 的 调用和分配。 可能还需要 IRP 的 Cancel 例程。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值