Windows I/O Ring 一年来的变化:技术深度解析
自I/O Ring首次引入Windows以来已过一年。初始版本发布于Windows 21H2,本文旨在跟进其显著变化和更新,详细记录并解释这些改进。
新增支持的操作
最明显的变化是新增了两个操作:写入(write)和刷新(flush)。这些操作允许使用I/O Ring执行写入和刷新功能,处理方式与自第一版即支持的读取操作类似,并转发至相应的I/O函数。KernelBase.dll中添加了新的包装函数来排队这些操作的请求:BuildIoRingWriteFile和BuildIoRingFlushFile,其定义可在ioringapi.h头文件(预览SDK中可用)中找到:
|
|
类似于BuildIoRingReadFile,这两个函数构建提交队列条目(SQE)并添加至提交队列。新操作需要不同的标志和选项,如flushMode或writeFlags。为此,NT_IORING_SQE结构现在包含一个联合体,根据请求的OpCode解释输入数据。
支持写入操作的一个小内核更改可见于IopIoRingReferenceFileObject,包括新参数和额外调用ObReferenceFileObjectForWrite。不同函数中的缓冲区探测也根据操作类型发生了变化。
用户完成事件
另一个有趣的变化是能够注册用户事件以通知每个新完成的操作。与I/O Ring的CompletionEvent(仅在所有操作完成时发出信号)不同,新的可选用户事件会在每个新操作完成时发出信号,允许应用程序在处理完成队列时即时处理结果。
为支持此功能,创建了新的系统调用NtSetInformationIoRing:
|
|
该函数接收IoRing对象句柄、信息类、长度和数据。目前仅一个信息类有效:1(暂称IoRingRegisterUserCompletionEventClass)。函数使用全局数组IopIoRingSetOperationLength检索每个信息类的预期长度。
输入检查后,函数引用I/O Ring并调用IopIoRingUpdateCompletionUserEvent设置完成用户事件。该函数主要包含同步代码,确保仅一个线程可编辑CompletionUserEvent字段。
在IopCompleteIoRingEntry中,CompletionUserEvent的发出信号条件不同:每当操作完成并写入完成队列时,若未处理条目数为1,则发出信号用户事件。这允许应用程序通过专用线程等待事件并处理每个新完成条目,确保完成队列头尾一致。
KernelBase.dll中的SetIoRingCompletionEvent函数用于注册用户完成事件:
|
|
示例应用代码展示了如何使用此API创建事件和线程处理完成操作。
Drain Preceding Operations
用户完成事件不是唯一的等待相关改进。查看NT_IORING_SQE_FLAGS枚举可见新标志NT_IORING_SQE_FLAG_DRAIN_PRECEDING_OPS。
在IopProcessIoRingEntry开始时检查此标志。若设置,调用IopIoRingSetupCompletionWait设置等待参数。该函数计算已提交但未完成的操作数,并设置CompletionWaitUntil。随后IopIoRingWaitForCompletionEvent等待CompletionEvent发出信号。
在IopCompleteIoRingEntry中,若SignalCompletionEvent设置且完成事件数等于CompletionWaitUntil,则发出信号CompletionEvent。
此标志确保所有前置操作完成后再处理当前条目,适用于依赖先前I/O操作的场景。
等待机制也出现在提交I/O Ring时。NtSubmitIoRing的第三个参数WaitOperations用于请求等待的操作数。若不为0,函数调用IopIoRingSetupCompletionWait进行健全性检查,随后等待完成事件。
应用通常设置WaitOperations为0或提交操作总数,但也可选择等待部分操作。
漏洞分析
比较不同构建的代码是发现已修复漏洞的有趣方式。此处关注一个功能性问题:阻止WoW64进程使用部分I/O Ring功能。
在IopIoRingDispatchRegisterBuffers和IopIoRingDispatchRegisterFiles中,新构建添加了检查WoW64进程的代码。问题源于PVOID类型的大小差异:64位系统为8字节,32位系统为4字节。WoW64进程使用32位结构,导致内核直接读取未转换的缓冲区数组,造成偏移错误。
新构建中,内核识别WoW64进程的结构差异,正确解释条目大小(8字节而非0x10),读取前4字节为地址,后4字节为长度。同样修复适用于预注册文件句柄。
其他变化
- 成功创建I/O Ring对象会生成ETW事件,包含初始化信息。
- IoringObject->CompletionEvent从NotificationEvent升级为SynchronizationEvent。
- 当前I/O Ring版本为3,新创建环应使用此版本。
- KernelBase.dll导出新函数
IsIoRingOpSupported,检查操作是否支持。
数据结构
Windows 11 22H2(build 22577)中,几乎所有内部I/O Ring结构在公共符号中可用,无需逆向工程。结构自21H2有重大变化,建议从符号获取最新版本。
部分结构示例(来自build 22598):
|
|
HIORING结构未在符号中,逆向工程版本如下:
|
|
结论
I/O Ring虽刚发布数月,但已接收有趣增改,旨在吸引I/O密集型应用。当前版本为3,预计未来将支持新操作类型或扩展功能。尚未有桌面应用使用此机制,但随着Windows 11普及,值得关注其使用(或滥用)情况。作为Windows 11的有趣新增,它仍存一些漏洞,需持续关注。