Проверка параметров функции

Предыдущий пост про параметры функций вызвал на удивление много споров, так что я еще покручусь немного вокруг этой темы. Заранее прошу прощения у тех, кому эта тема оскомину набила. Итак, как следует проверять параметры функции? Вернее даже так, что нужно и, самое главное, что не нужно проверять?

Само собой однозначного ответа на этот вопрос не существует, тем более что этот вопрос вторгается в область coding guidelines, а последних, как известно, существует ровно столько, сколько есть программистов на Земле. Или около того. Как минимум ответ зависит от следующих факторов:

  1. Языка программирования, операционной системы и программного окружения;

  2. Степени доверия вызываемой функции к вызывающему коду.

Пункт №1 мы, во избежание священной войны, положим константой - язык C/C++, OS - Windows, окружение - «обычный» user/kernel mode код.

А вот пункт №2 стоит обсудить детально. Сравним два случая:

  1. Вызов функции внутри одного бинарного модуля;

  2. Вызов сервиса ядра из пользовательского кода.

В отличие от «доверенного» вызова, службы ядра дополнительно проверяют, что:

  1. Все буфера целиком находятся в user space (ProbeXxx функции);

  2. Чтение/запись в user space завершается успешно (все операции с ними помещаются в блок try-except);

  3. Чтение любых данных из user space выполняется только один раз (данные копируются в локальный буфер);

  4. Все параметры, после того, как они были скопированы в kernel space, содержат допустимые значения (проверки на выход на пределы диапазона и прочее).

Очевидно, что дополнительные проверки нужны потому, что пользовательский код менее привилегирован, чем код ядра, и более подвержен сбоям/атакам. Несколько менее очевидно, что дополнительные проверки на самом деле нужны потому, что сами данные приходят из менее привилегированного источника. О том, почему это важно - чуть ниже.

Далее, данные никогда не представлены сами по себе. Данные всегда упаковываются в некий «контейнер». Контейнером могут быть языковые конструкции, например объект или переменная типа «int», или другие данные, например TCP пакет упаковывается в IP пакет. Следует различать проверки целостности контейнера и проверки целостности данных. В примере выше, пункты №№1 - 3 представляют ни что иное, как проверку целостности контейнера (буфера в user space). Проверка целостности самих данных представлена единственным пунктом №4.

Нужно ли при каждом вызове выполнять все проверки? Конечно - нет. Какие проверки можно опустить? В приведенном примере с вызовом функции в пределах одного модуля, можно ожидать, что свойства контейнера с данными не изменятся. А если и изменятся, то только под воздействием внешних факторов, которые мы все равно не в состоянии контролировать (в пределах данного модуля). Соответственно, при «доверенном» вызове проверять целостность контейнера нецелесообразно.

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

Количество проверок целостности самих данных тоже можно сократить до минимума - до одной. В теории, конечно. Для этого используется анализ потоков данных (Data Flow Analysis, DFD), суть которого состоит в том, что:

  1. Идентифицируются все участники обмена данными (т.е. все компоненты кода и внешние источники);

  2. Определяются характер передаваемых данных, а также, откуда и куда они передаются;

  3. Определяется уровень привилегированности каждого участника обмена данными.

Передача данных от менее привилегированного к более привилегированному участнику считается передачей через границу доверия (Trust Boundary), при которой принимающая сторона должна проверить целостность полученных данных, включая как целостность контейнера, так и целостность данных. При составлении DFD диаграммы следует помнить, что данные (например, IP пакет) могут сами выступать в роли контейнера для другого компонента. В таком случае, для инкапсулированных данных граница доверия будет пролегать на один или несколько компонентов дальше.

Итак, резюме:

  1. Следует различать контейнер и сами данные, и понимать, что проверка их целостности может выполняться разными компонентами;

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

  3. Проверка целостности данных производится первым потребителем этих данных.

comments powered by Disqus