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


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

Общие сведения о написании безопасных драйверов см. в статье Создание надежных драйверов 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 bytes обратно в приложение пользовательского режима. Если значение 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] = . . .;
    }
    

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

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

Большинство идентификаторов OID set может быть выдано приложением пользовательского режима, работающим в группах безопасности "Администраторы" или "Системные". Хотя это, как правило, доверенные приложения, драйвер мини-порта по-прежнему не должен допускать повреждения памяти или внедрения кода ядра. Следуйте этим правилам для задания идентификаторов 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 методов.

Другие проблемы с безопасностью сетевого драйвера

  • Многие драйверы минипорта NDIS предоставляют управляющее устройство с помощью NdisRegisterDeviceEx. Те, кто это делает, должны проверять свои обработчики IOCTL с теми же правилами безопасности, что и драйвер WDM. Дополнительные сведения см. в разделе Проблемы безопасности для кодов управления вводом-выводом.

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

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

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