有关编写安全驱动程序的常规讨论,请参阅 创建可靠 Kernel-Mode 驱动程序。
除了遵循安全编码做法和常规设备驱动程序指南之外,网络驱动程序还应执行以下作来增强安全性:
- 所有网络驱动程序都应验证它们从注册表读取的值。 具体而言, NdisReadConfiguration 或 NdisReadNetworkAddress 的调用方不得对从注册表读取的值进行任何假设,并且必须验证它读取的每个注册表值。 如果 NdisReadConfiguration 的调用方确定值超出边界,则应改用默认值。 如果 NdisReadNetworkAddress 的调用方确定某个值超出边界,则应改用永久的中等访问控制(MAC)地址或默认地址。
特定于 OID 的问题
微型端口驱动程序在其 MiniportOidRequest 或 MiniportCoOidRequest 函数中应验证驱动程序请求设置的任何对象标识符 (OID) 值。 如果驱动程序确定要设置的值超出边界,则它应失败设置请求。 有关对象标识符的详细信息,请参阅 获取和设置微型端口驱动程序信息和 WMI 的 NDIS 支持。
如果中间驱动程序的 MiniportOidRequest 函数未将设置操作传递给底层微型端口驱动程序,该函数应验证 OID 值。 有关详细信息,请参阅 中间驱动程序查询和设置作。
查询 OID 安全准则
大多数查询 OID 都可以由系统上的任何用户模式应用程序发出。 遵循查询 OID 的以下特定准则。
始终验证缓冲区的大小是否足够大,以便输出。 没有输出缓冲区大小检查的任何查询 OID 处理程序都有安全漏洞。
if (oid->DATA.QUERY_INFORMATION.InformationBufferLength < sizeof(ULONG)) { oid->DATA.QUERY_INFORMATION.BytesNeeded = sizeof(ULONG); return NDIS_STATUS_INVALID_LENGTH; }始终将正确的最小值写入 BytesWritten。 要像以下示例那样分配
oid->BytesWritten = oid->InformationBufferLength是一个警示信号。// ALWAYS WRONG oid->DATA.QUERY_INFORMATION.BytesWritten = DATA.QUERY_INFORMATION.InformationBufferLength;OS 会将 BytesWritten 字节复制回用户模式应用程序。 如果 BytesWritten 大于驱动程序实际写入的字节数,则 OS 最终可能会将未初始化的内核内存复制到 usermode,这将是信息泄露漏洞。 请改用类似于下面的代码:
oid->DATA.QUERY_INFORMATION.BytesWritten = sizeof(ULONG);从不从缓冲区中读取值。 在某些情况下,OID 的输出缓冲区直接映射到恶意用户模式进程。 写入到输出缓冲区后,敌对进程可能会更改输出缓冲区。 例如,以下代码可能会受到攻击,因为攻击者可以在编写代码后更改 NumElements:
output->NumElements = 4; for (i = 0 ; i < output->NumElements ; i++) { output->Element[i] = . . .; }若要避免从缓冲区中回读,请保留本地副本。 例如,若要修复上述示例,请引入新的堆栈变量:
ULONG num = 4; output->NumElements = num; for (i = 0 ; i < num; i++) { output->Element[i] = . . .; }使用此方法时,for 循环会从驱动程序的堆栈变量
num而不是从其输出缓冲区中读取回。 驱动程序还应使用volatile关键字标记输出缓冲区,以防止编译器以无提示方式撤消此修补程序。
设置 OID 安全准则
大多数 Set OID 都可以由管理员或系统安全组中运行的 usermode 应用程序颁发。 尽管这些应用程序通常是受信任的应用程序,但微型端口驱动程序仍不得允许内存损坏或内核代码注入。 按照以下特定规则设置 OID:
始终验证输入是否足够大。 没有输入缓冲区大小检查的任何 OID 集处理程序都有安全漏洞。
if (oid->DATA.SET_INFORMATION.InformationBufferLength < sizeof(ULONG)) { return NDIS_STATUS_INVALID_LENGTH; }每当验证具有嵌入偏移量的 OID 时,都必须验证嵌入缓冲区是否位于 OID 有效负载中。 这需要多次检查。 例如, OID_PM_ADD_WOL_PATTERN 可以提供需要检查的嵌入模式。 正确验证需要检查:
InformationBufferSize >= sizeof(NDIS_PM_PACKET_PATTERN)
PmPattern = (PNDIS_PM_PACKET_PATTERN) InformationBuffer; if (InformationBufferLength < sizeof(NDIS_PM_PACKET_PATTERN)) { Status = NDIS_STATUS_BUFFER_TOO_SHORT; *BytesNeeded = sizeof(NDIS_PM_PACKET_PATTERN); break; }Pattern->PatternOffset + Pattern->PatternSize 不会溢出
ULONG TotalSize = 0; if (!NT_SUCCESS(RtlUlongAdd(Pattern->PatternOffset, Pattern->PatternSize, &TotalSize) || TotalSize > InformationBufferLength) { return NDIS_STATUS_INVALID_LENGTH; }可以使用类似于以下示例的代码组合这两个检查:
ULONG TotalSize = 0; if (InformationBufferLength < sizeof(NDIS_PM_PACKET_PATTERN) || !NT_SUCCESS(RtlUlongAdd(Pattern->PatternSize, Pattern->PatternOffset, &TotalSize) || TotalSize > InformationBufferLength) { return NDIS_STATUS_INVALID_LENGTH; }InformationBuffer + Pattern>-PatternOffset + Pattern>-PatternLength 不会溢出
ULONG TotalSize = 0; if (!NT_SUCCESS(RtlUlongAdd(Pattern->PatternOffset, Pattern->PatternLength, &TotalSize) || (!NT_SUCCESS(RtlUlongAdd(TotalSize, InformationBuffer, &TotalSize) || TotalSize > InformationBufferLength) { return NDIS_STATUS_INVALID_LENGTH; }Pattern-PatternOffset> + Pattern-PatternLength <>= InformationBufferSize
ULONG TotalSize = 0; if(!NT_SUCCESS(RtlUlongAdd(Pattern->PatternOffset, Pattern->PatternLength, &TotalSize) || TotalSize > InformationBufferLength)) { return NDIS_STATUS_INVALID_LENGTH; }
方法 OID 安全准则
方法 OID 可由管理员或系统安全组中运行的用户模式应用程序发出。 它们是集和查询的组合,因此上述指南列表也适用于方法 OID。
其他网络驱动程序安全问题
许多 NDIS 微型端口驱动程序使用 NdisRegisterDeviceEx 公开控制设备。 执行此作的用户必须审核其 IOCTL 处理程序,其安全规则与 WDM 驱动程序相同。 有关详细信息,请参阅 I/O 控制代码的安全问题。
设计良好的 NDIS 微型端口驱动程序不应依赖于在特定进程上下文中调用,也不应与用户模式(IOCTLs 和 OID 是例外)进行非常密切的交互。 这将是一个红色标志,可以看到打开的用户模式句柄、执行的用户模式等待或针对用户模式配额分配内存的微型端口。 应调查该代码。
大多数 NDIS 微型端口驱动程序不应参与分析数据包有效负载。 但在某些情况下,可能有必要。 如果是这样,则应非常仔细地审核此代码,因为驱动程序正在分析来自不受信任的源的数据。
与分配内核模式内存时的标准一样,NDIS 驱动程序应使用适当的 NX 池 Opt-In 机制。 在 WDK 8 及更高版本中,
NdisAllocate*函数系列已正确启用。