网络驱动程序的安全问题

有关编写安全驱动程序的一般讨论,请参阅 创建可靠 Kernel-Mode 驱动程序

除了遵循安全编码做法和常规设备驱动程序指南之外,网络驱动程序还应执行以下操作来增强安全性:

  • 所有网络驱动程序都应验证它们从注册表中读取的值。 具体而言, NdisReadConfigurationNdisReadNetworkAddress 的调用方不得对从注册表读取的值做出任何假设,并且必须验证它读取的每个注册表值。 如果 NdisReadConfiguration 的调用方确定某个值超出边界,则应改用默认值。 如果 NdisReadNetworkAddress 的调用方确定某个值超出边界,则它应改用永久媒体访问控制 (MAC) 地址或默认地址。

特定于 OID 的问题

查询 OID 安全准则

大多数查询 OID 都可以由系统上的任何用户模式应用程序发出。 请遵循查询 OID 的这些特定准则。

  1. 始终验证缓冲区的大小是否足够大,以便输出。 任何没有输出缓冲区大小检查的查询 OID 处理程序都有一个安全 bug。

    if (oid->DATA.QUERY_INFORMATION.InformationBufferLength < sizeof(ULONG)) {
        oid->DATA.QUERY_INFORMATION.BytesNeeded = sizeof(ULONG);
        return NDIS_STATUS_INVALID_LENGTH;
    }
    
  2. 始终将正确且最小的值写入 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);
    
  3. 永远不要从缓冲区读回值。 在某些情况下,OID 的输出缓冲区直接映射到恶意用户模式进程。 恶意进程可以在写入输出缓冲区后更改输出缓冲区。 例如,以下代码可能会受到攻击,因为攻击者可以在编写 NumElement 后更改它:

    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关键字 (keyword) 标记输出缓冲区,以防止编译器以无提示方式撤消此修复。

设置 OID 安全准则

大多数 Set OID 可由管理员或系统安全组中运行的用户模式应用程序颁发。 尽管这些应用程序通常是受信任的应用程序,但微型端口驱动程序仍不得允许内存损坏或内核代码注入。 请遵循以下设置 OID 的特定规则:

  1. 始终验证输入是否足够大。 任何没有输入缓冲区大小的 OID 集处理程序检查都有安全漏洞。

    if (oid->DATA.SET_INFORMATION.InformationBufferLength < sizeof(ULONG)) {
        return NDIS_STATUS_INVALID_LENGTH;
    }
    
  2. 每当验证具有嵌入偏移量的 OID 时,都必须验证嵌入的缓冲区是否在 OID 有效负载中。 这需要多次检查。 例如, OID_PM_ADD_WOL_PATTERN 可能会提供需要检查的嵌入式模式。 正确的验证需要检查:

    1. 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;
      }
      
    2. 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;
      }
      
    3. 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;
      }
      
    4. 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 微型端口驱动程序不应依赖于在特定进程上下文中调用,也不应与用户模式 (非常密切地交互,IOCTL & OID 是例外) 。 查看已打开 usermode 处理、执行用户模式等待或根据 usermode 配额分配内存的微型端口将是一个红色标志。 应调查该代码。

  • 大多数 NDIS 微型端口驱动程序不应参与分析数据包有效负载。 不过,在某些情况下,这是必需的。 如果是这样,则应非常仔细地审核此代码,因为驱动程序正在分析来自不受信任的源的数据。

  • 根据分配内核模式内存的标准,NDIS 驱动程序应使用适当的 NX 池 Opt-In 机制。 在 WDK 8 及更新版本中 NdisAllocate* ,函数系列已正确选择加入。