Поделиться через


Проблемы безопасности для сетевых драйверов

Общие сведения о написании безопасных драйверов см. в статье "Создание надежных Kernel-Mode драйверов".

Помимо следующих безопасных методов написания кода и общих рекомендаций по драйверам устройств, сетевые драйверы должны выполнять следующие действия, чтобы повысить безопасность.

  • Все сетевые драйверы должны проверять значения, которые они считывают из реестра. В частности, вызывающий объект NdisReadConfiguration или NdisReadNetworkAddress не должен делать никаких предположений о значениях, считываемых из реестра, и должен проверять каждое значение реестра, которое он считывает. Если вызывающий объект NdisReadConfiguration определяет, что значение выходит из границ, вместо него следует использовать значение по умолчанию. Если вызывающая сторона NdisReadNetworkAddress определяет, что значение выходит за допустимые пределы, следует использовать постоянный MAC-адрес или адрес по умолчанию.

Проблемы, связанные с OID

Запрос руководящих принципов по безопасности OID

Большинство идентификаторов запросов можно выдавать любым приложением пользовательского режима в системе. Следуйте этим конкретным рекомендациям по работе с OID запросов.

  1. Всегда проверяйте, что размер буфера достаточно велик для выходных данных. Любой обработчик OID запроса без проверки размера выходного буфера имеет ошибку безопасности.

    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; 
    

    ОС скопирует байты BytesWritten обратно в приложение пользовательского режима. Если BytesWritten больше количества байтов, которые на самом деле записал драйвер, ОС может в конечном итоге скопировать неинициализированную память ядра в пользовательский режим, что станет уязвимостью раскрытия информации. Вместо этого используйте следующий код:

    oid->DATA.QUERY_INFORMATION.BytesWritten = sizeof(ULONG);
    
  3. Никогда не считывать значения обратно из буфера. ** В некоторых случаях выходной буфер 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] = . . .;
    }
    

    С помощью этого подхода цикл считывает данные из переменной num стека драйвера, а не из выходного буфера. Драйвер также должен пометить выходной буфер ключевым volatile словом, чтобы предотвратить автоматическое отмену этого исправления компилятором.

Настройка рекомендаций по безопасности OID

Большинство наборов 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. Дополнительные сведения см. в разделе "Проблемы безопасности" для кодов управления ввода-вывода.

  • Хорошо разработанные драйверы минипорта NDIS не должны полагаться на вызов в определенном контексте процесса и не должны взаимодействовать слишком тесно с пользовательским режимом (за исключением IOCTL и OID). Это был бы красный флаг для обнаружения минипорта, который открыл дескрипторы пользовательского режима, выполнил ожидания пользовательского режима или выделил память, превышая квоту пользовательского режима. Этот код следует исследовать.

  • Большинство драйверов мини-порта NDIS не должны участвовать в разборе данных полезной нагрузки пакетов. Однако в некоторых случаях может потребоваться. Если это так, этот код следует тщательно проверять, так как драйвер анализирует данные из ненадежного источника.

  • Как и в стандартном случае при выделении памяти в режиме ядра, драйверы NDIS должны использовать соответствующие механизмы пула NX Opt-In. В WDK 8 и более поздней NdisAllocate* версии семейство функций правильно выбрано.