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

Автор: Майкл Ховард
Издательство:
Серия:
Жанр произведения: Программирование
Год издания: 0
isbn: 5-9706-0027-X
Скачать книгу
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 skip = count – cbBytesRead;

      if (skip > sizeof(buf))

      skip = sizeof(buf);

      if (read(buf, skip))

      cbBytesRead += skip;

      else

      break;

      ...

      В этом фрагменте значение skip сравнивается с 1024, и если оно меньше, то в буфер buf копируется skip байтов. Проблема в том, что если skip оказывается отрицательным (скажем, -2), то оно будет заведомо меньше 1024, так что функция read попытается скопировать–2 байта, а это значение, будучи представлено как целое без знака (тип size_t), равно без малого 4 Гб. Итак, read() копирует 4 Гб в буфер размером 1 Кб. Печально!

      Еще один источник неприятностей – это оператор new в языке С++. Он сопряжен с неявным умножением:

      Foo *p = new Foo(N);

      Если N контролируется противником, то возможно переполнение внутри operator new при вычислении выражения N * sizeof(Foo);

      С#

      Хотя сам язык С# и не допускает прямых обращений к памяти, но иногда программа обращается к системным вызовам, помещенным в блок, помеченный ключевым словом unsafe (при этом ее еще надо компилировать с флагом /unsafe). Любые вычисления, результат которых передается системному вызову, нужно контролировать. Для этого полезно применить ключевое слово checked или – еще лучше – соответствующий флаг компилятора. Включите его и следите, не произойдет ли исключения. Напротив, ключевое слово unchecked используйте изредка, предварительно обдумав все последствия.

      Java

      В языке Java прямые обращения к памяти также запрещены, поэтому он не так опасен, как C/C++. Но проявлять беспечность все же не следует: как и в C/C++, в Java нет никакой защиты от переполнения целых, и легко можно допустить логические ошибки. О том, какие существуют программные решения, см. в разделе «Искупление греха».

      Visual Basic