关于驱动读写异步超时的处理,网络上的资料相对稀少,正好最近在工作上遇到了这个问题,所以就研究了一下,发现还是有些门道的。如果完全按照应用层读写超时的处理逻辑来处理驱动层的话就会出现蓝屏等问题

只要涉及到读写超时,那么我们第一印象肯定会想到事件和事件等待相关操作与函数的调用,那么我们来看一下驱动的几个文件操作函数声明:

首先是打开操作

NTSTATUS ZwCreateFile(
  _Out_    PHANDLE            FileHandle,
  _In_     ACCESS_MASK        DesiredAccess,
  _In_     POBJECT_ATTRIBUTES ObjectAttributes,
  _Out_    PIO_STATUS_BLOCK   IoStatusBlock,
  _In_opt_ PLARGE_INTEGER     AllocationSize,
  _In_     ULONG              FileAttributes,
  _In_     ULONG              ShareAccess,
  _In_     ULONG              CreateDisposition,
  _In_     ULONG              CreateOptions,
  _In_opt_ PVOID              EaBuffer,
  _In_     ULONG              EaLength
);

根据应用层的经验,如果要实现超时读写,我们需要以异步的方式打开文件,而驱动默认即是异步打开操作,如果你需要同步打开,需要设置下面的值(两个需要同时设置)
DesiredAccess:SYNCHRONIZE
CreateOptions:FILE_SYNCHRONOUS_IO_NONALERT

然后是读写函数,两个类似

NTSTATUS ZwReadFile(
  _In_     HANDLE           FileHandle,
  _In_opt_ HANDLE           Event,
  _In_opt_ PIO_APC_ROUTINE  ApcRoutine,
  _In_opt_ PVOID            ApcContext,
  _Out_    PIO_STATUS_BLOCK IoStatusBlock,
  _Out_    PVOID            Buffer,
  _In_     ULONG            Length,
  _In_opt_ PLARGE_INTEGER   ByteOffset,
  _In_opt_ PULONG           Key
);

NTSTATUS ZwWriteFile(
  _In_     HANDLE           FileHandle,
  _In_opt_ HANDLE           Event,
  _In_opt_ PIO_APC_ROUTINE  ApcRoutine,
  _In_opt_ PVOID            ApcContext,
  _Out_    PIO_STATUS_BLOCK IoStatusBlock,
  _In_     PVOID            Buffer,
  _In_     ULONG            Length,
  _In_opt_ PLARGE_INTEGER   ByteOffset,
  _In_opt_ PULONG           Key
);

这里根据应用层的经验,我们会找关于事件(Event)的参数,根据文档声明,正好第二个参数涉及到了事件,查阅文档后确定该参数可以用于异步超时等待。这个时候我们需要创建一个事件,关于驱动中创建事件的函数我们首先会想到 KeInitializeEvent ,但是此处需要的是 HANDLE 类型,所以我们需要使用下面的方法来创建:

ZwCreateEvent(&g_hEvent, GENERIC_ALL, NULL, SynchronizationEvent, FALSE);

然后我们自然想到以Zw开头来搜索相关的等待函数

status = ZwWaitForSingleObject(g_hEvent, FALSE, &time);

这个时候如果status返回的是 STATUS_TIMEOUT ,那么就代表文件读写超时,这时候我们就关闭事件句柄和文件句柄然后返回。如果你真的这么做了,那么等数据真正的返回的时候,就会出现蓝屏问题。

经过分析发现是因为我们在使用 ZwClose 函数关闭句柄后,windows并没有停止对文件的操作,由于是是异步的,所以后台仍然在等待数据的返回,如果这时候我们销毁了资源,但是数据在超时后返回,那么就会出现 0xc0000005 这种内存访问错误蓝屏。那么我们理所当然想到是否有一个函数用来取消文件IO操作,答案是有的,但是是未文档化函数,函数声明如下:

typedef
NTSTATUS
(NTAPI *MyZwCancelIoFile)(
    IN HANDLE               FileHandle,
    OUT PIO_STATUS_BLOCK    IoStatusBlock);

这个函数即可用来取消文件IO,当我们读写超时后,我们先要取消文件后台IO操作,然后再关闭相关句柄。我们可以这样获得该函数地址:

UNICODE_STRING funcname = RTL_CONSTANT_STRING(L"ZwCancelIoFile");
ZwCancelIoFile = (MyZwCancelIoFile)MmGetSystemRoutineAddress(&funcname);
if (ZwCancelIoFile == NULL)
{
    return FALSE;
}

~完毕~

0
赞赏

微信赞赏支付宝赞赏