Новое Распределяем каналы MAX по категориям и территориям. Читать новость

Репост из «Хакер | Этичный хакинг с Михаилом Тарасовым»

Когда математика против защиты: как целочисленное переполнение ломает логику в коде на Си



В прошлых разборах мы изучали ошибки адресации: выход за пределы массива на стеке, утечки через форматные строки и повторное использование памяти в куче. Сегодня протестируем вектор, где код внешне выглядит аккуратно, проверка размера буфера на месте, но программа все равно падает в Segmentation fault.

Речь идет об Integer Overflow (целочисленном переполнении).

Я собрал тестовый стенд в Parrot Security OS на ARM64 и разобрал, почему проверка if (size > MAX) может пропустить опасный объем данных.

1. В чем идея уязвимости?
Представьте обработчик сетевых пакетов: сервер принимает длину полезной нагрузки, прибавляет к ней фиксированный размер служебного заголовка (например, 8 байт) и проверяет, поместится ли итоговый блок в локальный буфер на 64 байта.

Логика программиста:
unsigned short total_size = length + 8;
if (total_size > 64) { reject(); }

Если передать length = 100, программа честно вычислит 108, увидит превышение лимита и отклонит пакет. Проверка работает штатно.

Но у типа unsigned short фиксированный размер - 16 бит.
Его диапазон строго ограничен значениями от 0 до 65535 (0xFFFF).

2. Эксперимент: переполняем 16-битный регистр
Что произойдет, если на вход передать число 65530?

1. Процессор выполняет сложение: 65530 + 8 = 65538.
2. Значение 65538 не помещается в 16 бит (0x10002).
3. Старший семнадцатый бит отбрасывается аппаратным сумматором, и в переменной total_size остается остаток от деления на 65536 - число 2!
4. Условие if (total_size > 64) проверяет двойку: 2 > 64 ложно.
5. Проверка безопасности успешно пройдена!

А дальше управление переходит к функции копирования:
memcpy(local_buffer, data, length);
где length по-прежнему равен исходным 65530 байтам.

3. Результат в терминале
Программа бодро рапортует:

- [*] Входящая длина: 65530 (размер short: 2 байт)
- [*] Вычисленный общий размер (length + 8): 2
- [+] Проверка пройдена! Выделяем место и копируем 65530 байт...
- Segmentation fault

Попытка записать 65 килобайт данных в буфер размером 64 байта мгновенно разрушает структуру памяти и приводит к аварийному завершению процесса. Математический оборот счетчика (Wrap-around) полностью обошел логический фильтр.

4. Вторая сторона медали: знаковые числа (Signedness Bug)
Еще опаснее ситуация, когда разработчик использует обычный знаковый int:
Если переменная длины принимает значение -1, проверка вида if (len > 64) сочтет его безопасным (-1 меньше 64).

Однако стандартная функция memcpy() ожидает параметр размера типа size_t (беззнаковый 64-битный тип). При передаче в функцию -1 неявно приводится к гигантскому числу 18 446 744 073 709 551 615 (0xFFFFFFFFFFFFFFFF), что приводит к попытке чтения несуществующей памяти и моментальному падению сервиса.

Как предотвратить такие уязвимости?
1. Проверять границы до выполнения операции:
Вместо сложения с последующим сравнением используйте вычитание из константы предела: if (length > MAX_BUFFER_SIZE - 8) { reject(); }. В таком случае переполнение физически невозможно.

2. Использовать встроенные макросы компилятора:
Современные версии GCC и Clang поддерживают встроенные функции с аппаратной проверкой флага переноса: __builtin_add_overflow(a, b, &res).

3. Статический анализ кода:
Линтеры и SAST-инструменты уверенно находят неявные приведения типов (signed в unsigned) и предупреждают о риске переполнения еще на этапе сборки проекта.
Просмотры: 9
Открыть в MAX Сообщить о неточности

Сообщить о неточности

Опишите, что нужно исправить. Если есть источник, добавьте ссылку.

Вы покидаете datool.ru

Ссылка ведёт на внешний сайт. datool.ru не отвечает за его содержимое — проверьте адрес, прежде чем вводить пароли или платёжные данные.

Переход через 10 с

Перейти