19 смертных грехов, угрожающих безопасности программ. Майкл Ховард

Чтение книги онлайн.

Читать онлайн книгу 19 смертных грехов, угрожающих безопасности программ - Майкл Ховард страница 21

19 смертных грехов, угрожающих безопасности программ - Майкл Ховард

Скачать книгу

внимание на функции, которые получают входные данные. Автор как–то встретил примерно такой код:

      THING* AllocThings(int a, int b, int c, int d)

      {

      int bufsize;

      THING* ptr;

      bufsize = IntegerOverflowsRUs(a, b, c, d);

      ptr = (THING*)malloc(bufsize);

      return ptr;

      }

      Ошибка скрывалась в функции, вычисляющей размер буфера, к тому же найти ее мешали загадочные, ничего не говорящие читателю имена переменных (и литералов, которые представлены типом signed int). Если у вас есть время на доскональный анализ, проследите порядок вызова всех ваших функций вплоть до обращений к низкоуровневым библиотечным функциям или системным вызовам. И напоследок выясните, откуда поступают данные. Можете ли вы утверждать, что аргументы функций не подвергались манипуляциям? Кто контролирует аргументы: вы или потенциальный противник?

      По мнению автора языка Perl, величайшим достоинством программиста является лень! Так давайте пойдем простым путем – заставим потрудиться компилятор. Включите уровень диагностики /W4 (для Visual С++) или–Wall либо–Wsign–compare (для gcc) – и вы увидите, как много в вашей программе мест, где возможны проблемы с целыми числами. Обращайте внимание на все предупреждения, касающиеся целых чисел, особенно на те, в которых говорится о сравнении знаковых и беззнаковых величин, а также об усечении.

      В Visual С++ самыми важными с этой точки зрения являются предупреждения С4018, С4389иС4244.

      В gcc ищите предупреждения «warning: comparison between signed and unsigned integer expressions».

      Относитесь с подозрением к директивам #pragma отключающим предупреждения, например:

      #pragma warning(disable : 4244)

      Во вторую очередь следует искать места, где вы пытаетесь защититься от переполнения буфера (в стеке или в куче) путем проверки выхода за границы. Убедитесь, что все арифметические вычисления корректны. В следующем примере показано, как может возникнуть ошибка:

      int ConcatBuffers(char *buf1, char *buf2,

      size_t len1, size_t len2) {

      char buf[0xFF];

      if(len1 + len2) > 0xFF) return -1;

      memcpy(buf, buf1, len1);

      memcpy(buf + len1, buf2, len2);

      // сделать что-то с buf

      return 0;

      }

      Здесь проверяется, что суммарный размер двух входных буферов не превышает размера выходного буфера. Но если lenl равно 0x103, а 1еп2 равно 0xfffffffc, то сумма переполняет 32–разрядный регистр процессора и оказывается равной 255 (0xff), так что проверка успешно проходит. В результате memcpy попытается записать примерно 4 Гб в буфер размером всего 255 байтов!

      Не вздумайте подавлять эти надоедливые предупреждения путем приведения типов. Теперь вы знаете, насколько это рискованно и как внимательно нужно все проверять. Найдите все приведения и убедитесь, что они безопасны. О приведениях и преобразованиях в языках С и С++ см. раздел «Операции приведения» выше.

      Вот еще один пример:

      int read(char* buf, size_t count) {

      // Сделать что-то с памятью

      }

      ...

      while (true) {

      BYTE buf[1024];

      int

Скачать книгу