太长、太短还是正好?缓冲区大小问题

不同大小的错误可能会引起驱动程序或系统崩溃、损坏或造成安全漏洞

忘记检查缓冲区大小可能是 I/O 中最常见的驱动程序错误,会给驱动程序和系统带来问题。忘记检查缓冲区大小的驱动程序增加了错误和内存池损坏(可能毁坏系统)的风险。它们还会造成攻击者可以利用的安全漏洞。

在将数据写到缓冲区之外(缓冲区之前或之后)的时候,驱动程序使用的都是无效的指针,而且可能覆盖其它程序的内存,这种错误可能毁坏系统。

忘记验证可变长度的缓冲区(例如结构中使用固定大小的头部和可变长度数据的尾部)会导致整数溢出或下溢,这也会导致无效的指针。

如果驱动程序忘记检查设备 IOCTL 或 FSCTL 请求的缓冲区大小,则任何有权限访问这种请求的调用方都可以读写超出缓冲区结束地址的数据。不管调用方无意还是故意这样做,它都代表一个安全风险。

如果驱动程序忘记检查直接 I/O 中的零长度缓冲区,则调用方可以将数据写到缓冲区或系统页的结束地址之后(取决于缓冲区在哪里分配),这是驱动程序中的安全风险和难以查找的缺陷的根源。

忘记检查缓冲区大小的驱动程序可能使用输出缓冲区中的未初始化字节返回内核模式数据,这又是一个安全风险。

发生缓冲区溢出的另一个常见领域是字符串操作。C/C++ 语言运行时库提供的标准字符串操作函数(strcat、strcpy、sprintf 等)无法防止写到缓冲区结束地址之外。

您应该做什么?

始终在所有 I/O 传输请求中检查输入和输出缓冲区大小,从而确保缓冲区可以容纳所有请求的数据。

始终验证可变长度的缓冲区以避免整数溢出和下溢。

使用 MmGetSystemAddressForMdlSafe 为直接 I/O 请求映射地址空间,如果这个例程返回 NULL,就中断请求。

在返回给调用方之前始终使用零初始化所有输出缓冲区,以避免使用未初始化的字节返回内核模式数据。

ntstrsafe.h 中使用安全字符串函数 (RtlStringXxx) 来操作内核模式驱动程序中的 Unicode 和 ANSI 字符串。

使用诸如 Device Path Exerciser 和 PREfast 等工具(这些工具检查是否正确处理缓冲区)测试驱动程序。

常见的驱动程序可靠性问题 WHDC 上的文章阐述了用来验证缓冲区长度的技巧,带有演示问题和修复方式的代码段。这里是一个来自该文章的例子,显示如何通过从缓冲区长度减去偏移量来验证可变长度的缓冲区:

typedef struct _WAIT_FOR_BUFFER { LARGE_INTEGER Timeout; ULONG NameLength; BOOLEAN TimeoutSpecified; WCHAR Name[1]; } WAIT_FOR_BUFFER, *PWAIT_FOR_BUFFER;

if (InputBufferLength < sizeof(WAIT_FOR_BUFFER)) { IoCompleteRequest( Irp, STATUS_INVALID_PARAMETER ); return( STATUS_INVALID_PARAMETER ); }

WaitBuffer = Irp->AssociatedIrp.SystemBuffer;

if ((InputBufferLength - FIELD_OFFSET(WAIT_FOR_BUFFER, Name[0])  > WaitBuffer->NameLength) { IoCompleteRequest( Irp, STATUS_INVALID_PARAMETER ); return( STATUS_INVALID_PARAMETER ); }

更多信息:

常见的驱动程序可靠性问题
驱动程序 I/O 方法及其权衡

用户模式交互:内核模式驱动程序指南
验证缓冲区长度和地址

符合驱动程序特定的规则的 PREfast

Windows DDK
已缓存 I/O 中的错误
直接 I/O 中的错误
Device Path Exerciser
使用安全字符串函数

编写安全的代码
第 5 章,一号公敌:缓冲区溢出



此信息有用吗?