По мнению автора языка 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 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 Кб. Печально!