Что такое переполнение в программировании
Перейти к содержимому

Что такое переполнение в программировании

  • автор:

Что такое переполнение

Выше мы уже говорили о переполнении. Пришло время разобраться с этим более подробно. Допустим, мы складываем два целых числа при работе с числами со знаком:

01101110 + 01101110 = 11011100

Или в десятичной системе 110 + 110 = 220. Но мы то работаем с числами со знаком. В этом случае максимальное значение для байта равно 127. А полученное нами двоичное число будет на самом деле отрицательным числом –36 (см. раздел Отрицательные числа). В случае переполнения устанавливается флаг OF в регистре флагов (см. раздел Регистр флагов).

Вернёмся к примеру сложения двух чисел:

01101110 + 10010010 = 100000000

Мы складывали отрицательное число 110 с таким же положительным и в итоге получили ноль, что вполне справедливо. А вот если бы мы работали с числами без знака, то это были бы уже другие числа и другой результат (точнее, по сути тот же самый, но формально другой)

01101110b = 110 10010010b = 146 110 + 146 = 256

Если мы запишем в программе такой код (прибавим к числу в регистре AL число, указанное в команде ADD – сложение):

MOV AL, 110 ADD AL, 146

то в результате в регистре AL у нас будет не 256, а 0, потому что регистр AL может работать только с одним байтом информации, а максимальное беззнаковое число, которое можно «запихнуть» в байт – это число 255. Число 256 это

100000000

то есть число, которое для записи в двоичной форме требует 9 разрядов. То есть единица находится в старшем 8-м разряде, а все младшие 8 разрядов (с 0 по 7) заполнены нулями. Именно поэтому в 8-разрядном регистре AL после выполнения операции сложения чисел 110 и 146 будет 0. В таком случае будет установлен флаг CF в регистре флагов, так как результат не поместился в регистре-приёмнике и произошёл перенос единицы из старшего бита.

Ситуации, подобные описанным выше, называются переполнением. То есть переполнение – это когда результат какой-либо операции не помещается в предназначенный для этого результата регистр. Разумеется, при переполнении результатом может быть и не ноль, а другое число. Например, при сложении чисел 110 и 147 в регистре AL будет число 1 (а не 257, как нам хотелось бы).

Как вы понимаете, переполнение – это один из подводных камней на пути программиста. Ведь совершенно неожиданно при увеличении зарплаты с 200 до 256 вы можете получить ноль. И это будет справедливо, потому что неожиданно это будет только для плохого программиста. Хороший программист при работе с числами всегда помнит о вероятности переполнения/переноса и принимает соответствующие меры. Например, для описанного выше случая избежать переполнения можно так:

MOV AX, 110 ADD AX, 146

В результате в регистре AX у нас будет число 256, потому что регистр AX – это 16-разрядный регистр, в который при работе с беззнаковыми числами можно «впихнуть» число вплоть до значения 65 535. См. также раздел Регистры процессора.

Однако всегда найдутся ситуации, когда переполнение или перенос всё-таки произойдёт. Как в этом случае определить, правильный ли результат вы получили? Для этого существует регистр флагов, который мы рассмотрим в следующем разделе.

Переполнение буфера

Переполнение буфера (Buffer Overflow) — явление, возникающее, когда компьютерная программа записывает данные за пределами выделенного в памяти буфера.

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

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

Переполнение буфера может вызывать аварийное завершение или зависание программы, ведущее к отказу обслуживания (denial of service, DoS). Отдельные виды переполнений, например переполнение в стековом кадре, позволяют злоумышленнику загрузить и выполнить произвольный машинный код от имени программы и с правами учетной записи, от которой она выполняется.

Известны примеры, когда переполнение буфера намеренно используется системными программами для обхода ограничений в существующих программных или программно-аппаратных средствах. Например, операционная система iS-DOS (для компьютеров ZX Spectrum) использовала возможность переполнения буфера встроенной TR-DOS для запуска своего загрузчика в машинных кодах (что штатными средствами в TR-DOS сделать невозможно).

Безопасность

Программа, которая использует уязвимость для разрушения защиты другой программы, называется эксплойтом. Наибольшую опасность представляют эксплойты, предназначеные для получения доступа к уровню суперпользователя или, другими словами, повышения привилегий. Эксплойт переполнения буфера достигает этого путём ввода специально изготовленных входных данных. Такие данные переполняют выделенный буфер и изменяют данные, которые следуют за этим буфером в памяти.

Представим гипотетическую программу системного администрирования, которая исполняется с привилегиями суперпользователя — к примеру, изменение паролей пользователей. Если программа не проверяет длину введённого нового пароля, то любые данные, длина которых превышает размер выделенного для их хранения буфера, будут просто записаны поверх того, что находилось после буфера. Злоумышленник может вставить в эту область памяти инструкции на машинном языке (шелл-код), выполняющие любые действия с привилегиями суперпользователя — добавление и удаление учётных записей пользователей, изменение паролей, изменение или удаление файлов и т. д. Если в дальнейшем программа передаст управление в эту область памяти, система исполнит находящийся там машинный код злоумышленника.

Правильно написанные программы должны проверять длину входных данных, чтобы убедиться, что они не больше, чем выделенный буфер данных. Однако программисты часто забывают об этом. В случае если буфер расположен в стеке и стек «растёт вниз» (например в архитектуре x86), то с помощью переполнения буфера можно изменить адрес возврата выполняемой функции, так как адрес возврата расположен после буфера, выделенного выполняемой функцией. Тем самым есть возможность выполнить произвольный участок машинного кода в адресном пространстве процесса. В случае же, если стек «растёт вверх» (в этом случае адрес возврата обычно находятся перед буфером), использовать переполнение буфера для искажения адреса возврата возможно в очень редких случаях [источник не указан 30 дней] .

Даже опытным программистам бывает трудно определить, насколько то или иное переполнение буфера может быть уязвимостью. Это требует глубоких знаний об архитектуре компьютера и о целевой программе. Было показано [кем?] , что даже настолько малые переполнения, как запись одного байта за пределами буфера, могут представлять собой уязвимости.

Переполнения буфера широко распространены в программах, написанных на относительно низкоуровневых языках программирования, таких как язык ассемблера, Си и C++, которые требуют от программиста самостоятельного управления размером выделяемой памяти. Устранение ошибок переполнения буфера до сих пор является слабо автоматизированным процессом. Системы формальной верификации программ не очень эффективны при современных языках программирования.

Многие языки программирования, например, Java и Lisp, управляют выделением памяти автоматически, и используют комбинацию статического анализа и проверки корректности действий программы во время выполнения. Это делает ошибки, связанные с переполнением буфера, маловероятными или невозможными. Perl для избежания переполнений буфера обеспечивает автоматическое изменение размера массивов. Однако системы времени выполнения и библиотеки для таких языков всё равно могут быть подвержены переполнениям буфера, вследствие возможных внутренних ошибок в реализации этих систем проверки. В Windows доступны некоторые программные решения, которые предотвращают выполнение кода за пределами переполненного буфера, если такое переполнение было осуществлено. Среди этих решений — DEP в Windows XP SP2, OSsurance и Anti-Execute.

Краткое техническое изложение

Описание

Рассмотрим более подробно случай переполнения буфера, расположенного в области стека. Это удобнее всего сделать с помощью примера программы на языке Си. Пример ориентирован на архитектуру x86.

Когда динамический буфер, представляющий собой автоматический массив, выделяется в функции, он создаётся на стеке во время вызова этой функции. В архитектуре x86 стек растёт от бо́льших адресов к меньшим (или справа налево, в приведённых ниже диаграммах), то есть новые данные помещаются перед теми, которые уже находятся в стеке. Здесь, (DATA) (DATA) (…) представляет существующий стек, и (NEWDATA) — это некоторое новое значение, которое ЦП поместил в стек:

(NEWDATA)(DATA)(DATA)(. )

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

(ADDR)(DATA)(DATA)(. )

Когда выделяется динамический буфер, стек растёт влево на размер буфера. Так, если функция начинается с объявления char a[10] , результатом будет:

(.a. )(ADDR)(DATA)(DATA)(. )

В конце подпрограммы память, занятая буфером, освобождается, и вызывается операция RET . Она извлекает адрес возврата из стека и выполняет переход по этому адресу, возвращая управление туда, откуда была вызвана подпрограмма.

Предположим, что 10-байтный буфер предназначен для того, чтобы содержать данные, предоставляемые пользователем (например — пароль). Если программа не проверяет количество символов, которые были введены пользователем, и записывает 14 байт в буфер, эти лишние данные будут помещены поверх адреса возврата. Таким образом, это изменит адрес, по которому будет передано управление, когда завершится подпрограмма, и с которого программа продолжит исполнение после этого.

Если пользователь не злонамерен и вводит более, чем 10 символов, добавочные данные будут скорее всего случайными. В таком случае вполне возможно, что адрес возврата будет указывать на область памяти, которая неподконтрольна текущей исполняемой программе. Это вызовет ошибку сегментации в UNIX-системах или аналогичную ошибку в других операционных системах.

Однако пользователь может подставить в качестве адреса возврата и некий правильный адрес. Это вызовет переход управления в любую точку программы по его выбору. В результате потенциально может быть выполнен любой произвольный код, который этот пользователь поместил в данную область памяти, с теми привилегиями, с которыми выполняется текущая программа.

Пример

Рассмотрим следующую программу на языке Си. Скомпилировав эту программу, мы сможем использовать её для генерации ошибок переполнения буфера. Первый аргумент командной строки программа принимает как текст, которым заполняется буфер.

/* overflow.c - демонстрирует процесс переполнения буфера */ #include #include int main(int argc, char *argv[])  char buffer[10]; if (argc  2)  fprintf(stderr, "ИСПОЛЬЗОВАНИЕ: %s строка\n", argv[0]); return 1; > strcpy(buffer, argv[1]); return 0; > 

Программу можно опробовать с несколькими разными строками. Строки размером в 9 или меньше символов не будут вызывать переполнение буфера. Строки в 10 и более символов будут вызывать переполнение, хотя это может и не приводить к ошибке сегментации.

Эта программа может быть переписана следующим образом, с использованием функции Strncpy для предотвращения переполнения. Однако, следует учитывать, что простое отбрасывание лишних данных, как в этом примере, также может приводить к нежелательным последствиям, в том числе, при определённых условиях, к повышению привилегий. Как правило, требуется более тщательная обработка таких ситуаций.

/* better.c - демонстрирует, как исправить ошибку */ #include #include #define BUFFER_SIZE 10 int main(int argc, char *argv[])  char buffer[BUFFER_SIZE]; if (argc  2)  fprintf(stderr, "ИСПОЛЬЗОВАНИЕ: %s строка\n", argv[0]); return 1; > strncpy(buffer, argv[1], BUFFER_SIZE); return 0; > 

Предотвращение

Для того, чтобы сделать переполнение буфера менее вероятным, используются различные приёмы.

Системы обнаружения вторжения

С помощью систем обнаружения вторжения (СОВ) можно обнаружить и предотвратить попытки удалённого использования переполнения буфера. Так как в большинстве случаев данные, предназначенные для переполнения буфера, содержат длинные массивы инструкций No Operation ( NOP или NOOP ), СОВ просто блокирует все входящие пакеты, содержащие большое количество последовательных NOP-ов. Этот способ, в общем, неэффективен, так как такие массивы могут быть записаны с использованием разнообразных инструкций языка ассемблера. В последнее время крэкеры начали использовать шелл-коды с шифрованием, самомодифицирующимся кодом, полиморфным кодом и алфавитно-цифровым кодом, а также атаки возврата в стандартную библиотеку для проникновения через СОВ.

Защита от повреждения стека

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

Существуют две системы: StackGuard и Stack-Smashing Protector (старое название — ProPolice), обе являются расширениями компилятора gcc. Начиная с gcc-4.1-stage2, SSP был интегрирован в основной дистрибутив компилятора. Gentoo Linux и OpenBSD включают SSP в состав распространяемого с ними gcc.

Размещение адреса возврата в стеке данных облегчает задачу осуществления переполнения буфера, которое ведёт к выполнению произвольного кода. Теоретически, в gcc могут быть внесены изменения, которые позволят помещать адрес в специальном стеке возврата, который полностью отделён от стека данных, аналогично тому, как это реализовано в языке Forth. Однако это не является полным решением проблемы переполнения буфера, так как другие данные стека тоже нуждаются в защите.

Защита пространства исполняемого кода для UNIX-подобных систем

Защита пространства исполняемого кода может смягчить последствия переполнений буфера, делая большинство действий злоумышленников невозможными. Это достигается рандомизацией адресного пространства (ASLR) и/или запрещением одновременного доступа к памяти на запись и исполнение. Неисполняемый стек предотвращает большинство эксплойтов кода оболочки.

Существует два исправления для ядра Linux, которые обеспечивают эту защиту — PaX и exec-shield. Ни один из них ещё не включен в основную поставку ядра. OpenBSD с версии 3.3 включает систему, называемую W^X, которая также обеспечивает контроль исполняемого пространства.

Заметим, что этот способ защиты не предотвращает повреждение стека. Однако он часто предотвращает успешное выполнение «полезной нагрузки» эксплойта. Программа не будет способна вставить код оболочки в защищённую от записи память, такую как существующие сегменты исполняемого кода. Также будет невозможно выполнение инструкций в неисполняемой памяти, такой как стек или куча.

ASLR затрудняет для взломщика определение адресов функций в коде программы, с помощью которых он мог бы осуществить успешную атаку, и делает атаки типа ret2libc очень трудной задачей, хотя они всё ещё возможны в контролируемом окружении, или если атакующий правильно угадает нужный адрес.

Некоторые процессоры, такие как Sparc фирмы Sun, Efficeon фирмы Transmeta, и новейшие 64-битные процессоры фирм AMD и Intel предотвращают выполнение кода, расположенного в областях памяти, помеченных специальным битом NX. AMD называет своё решение NX (от англ. No eXecute ), а Intel своё — XD (от англ. eXecute Disabled ).

Защита пространства исполняемого кода для Windows

Сейчас существует несколько различных решений, предназначенных для защиты исполняемого кода в системах Windows, предлагаемых как компанией Майкрософт, так и сторонними компаниями.

Майкрософт предложила своё решение, получившее название DEP (от англ. Data Execution Prevention — «предотвращение выполнения данных»), включив его в пакеты обновлений для Windows XP и Windows Server 2003. DEP использует дополнительные возможности новых процессоров Intel и AMD, которые были предназначены для преодоления ограничения в 4 ГиБ на размер адресуемой памяти, присущий 32-разрядным процессорам. Для этих целей некоторые служебные структуры были увеличены. Эти структуры теперь содержат неиспользуемый (зарезервированный) бит NX. DEP использует этот бит для предотвращения атак, связанных с изменением адреса обработчика исключений (так называемый SEH-эксплойт). DEP обеспечивает только защиту от SEH-эксплойта, он не защищает страницы памяти с исполняемым кодом.

Кроме того, Майкрософт разработала механизм защиты стека, предназначенный для Windows Server 2003. Стек помечается с помощью так называемых «осведомителей» (англ. canary ), целостность которых затем проверяется. Если «осведомитель» был изменён, значит, стек повреждён.

Существуют также сторонние решения, предотвращающие исполнение кода, расположенного в областях памяти, предназначенных для данных или реализующих механизм ASLR.

Использование безопасных библиотек

Проблема переполнений буфера характерна для языков программирования Си и C++, потому что они не скрывают детали низкоуровневого представления буферов как контейнеров для типов данных. Таким образом, чтобы избежать переполнения буфера, нужно обеспечивать высокий уровень контроля за созданием и изменениями программного кода, осуществляющего управление буферами. Использование библиотек абстрактных типов данных, которые производят централизованное автоматическое управление буферами и включают в себя проверку на переполнение — один из инженерных подходов к предотвращению переполнения буфера.

Два основных типа данных, которые позволяют осуществить переполнение буфера в этих языках — это строки и массивы. Таким образом, использование библиотек для строк и списковых структур данных, которые были разработаны для предотвращения и/или обнаружения переполнений буфера, позволяет избежать многих уязвимостей.

См. также

  • Межсетевой экран
  • Информационная безопасность
  • Переполнение кучи
  • Отладчик памяти
  • Шелл-код
  • Тестирование программного обеспечения
  • Статический анализ кода
  • strcpy
  • strcat

Ссылки

  • Переполнение буфера и немного удачи
  • Локальный брутфорс уязвимости(переполнение буфера)
  • Переполнение буфера под Windows. Константин Третьяков
  • Атаки на переполнение буфера. Андрей Колищак
  • Атаки на переполнение стека в Windows NT. Андрей Колищак
  • Перезапись указателя на окно памяти
  • История одной уязвимости — анализ переполнения буфера в IIS и червя CodeRed
  • Buffer Overflow Protection Solution for Microsoft Windows® Operating Systems (англ.)
  • Hardware solutions, execute disabled, AMD’s NX and Intel’s XD solutions (англ.)
  • SANS: inside the buffer overflow attack (англ.)
  • Insecure Programming by Example (англ.)
  • Secure Programming for Linux and Unix HOWTO: Avoid Buffer Overflow (англ.)
  • Secure programmer: Countering buffer overflows (англ.)
  • Smashing The Stack For Fun And Profit, Phrack 49 by Aleph One (англ.)
  • «Buffer Overflows: Attacks and Defenses for the Vulnerability of the Decade» (англ.) — описание подхода Stackguard к предотвращению атак разрушения стека.
  • Buffer Overflows Demystified (англ.)
  • Wired 11.07: Slammed! (англ.) — подробный обзор переполнения буфера червём Slammer
  • «The Tao of Windows Buffer Overflow» (англ.)

Библиотеки и другие средства защиты

  • The Better String Library (англ.) — улучшенная строковая библиотека.
  • Stack-Smashing Protector (англ.) — расширение GCC, обеспечивающее защиту от переполнения стека
  • Уязвимости защиты
  • Ошибки программирования

Целочисленное переполнение — Integer overflow

Целочисленное переполнение может быть продемонстрировано с помощью одометр переполнение, механическая версия явления. Все цифры устанавливаются на максимум 9, и следующее приращение белой цифры вызывает каскад переходящих добавлений, устанавливающих все цифры на 0, но нет более высокой цифры (цифра 100000), которая могла бы измениться на 1, поэтому счетчик сбрасывается в ноль. Это обертывание в отличие от насыщения.

В компьютерном программировании, целочисленное переполнение происходит, когда арифметическая операция пытается создать числовое значение, которое находится за пределами диапазона, который может быть представлен заданным числом цифр — выше максимального или ниже минимального представимого значения.

Наиболее частым результатом переполнения является сохранение наименее значимых представимых цифр результата; считается, что результат оборачивается вокруг максимума (т.е. по модулю степень системы счисления, обычно два в современных компьютерах, но иногда десять или другое основание).

Состояние переполнения может привести к непредвиденным результатам. В частности, если возможность не была предвидена, переполнение может поставить под угрозу надежность программы и безопасность.

. Для некоторых приложений, таких как таймеры и часы, может быть желательным перенос при переполнении. В стандарте C11 указано, что для беззнаковых целых чисел перенос по модулю является определенным поведением, и термин «переполнение» никогда не применяется: «вычисление, включающее беззнаковые операнды, никогда не может переполниться».

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

  • 1 Источник
  • 2 Флаги
  • 3 Варианты определения и неоднозначность
  • 4 Методы решения проблем целочисленного переполнения
    • 4.1 Обнаружение
    • 4.2 Предотвращение
    • 4.3 Обработка
    • 4.4 Явное распространение
    • 4.5 Поддержка языка программирования
    • 4.6 Насыщенная арифметика

    Источник

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

    • 4 бита: максимальное представимое значение 2-1 = 15
    • 8 битов: максимальное представимое значение 2-1 = 255
    • 16 бит: максимальное представляемое значение 2-1 = 65 535
    • 32 бита: максимальное представляемое значение 2-1 = 4294967 295 (наиболее распространенная ширина для персональных компьютеров с 2005 года),
    • 64 биты: максимальное представляемое значение 2-1 = 18,446,744,073,709,551,615 (наиболее распространенная ширина для персональных компьютеров процессоров, по состоянию на 2017 год),
    • 128 бит: максимальное представляемое значение 2-1 = 340,282,366,920,938,463,463,374,607,431,768,211,455

    Когда арифметическая операция дает результат, превышающий указанный выше максимум для N-битового целого числа, переполнение уменьшает результат до по модулю N-й степени 2, сохраняя только младшие значащие биты результата и эффективно оборачиваясь вокруг.

    В частности, умножение или сложение двух целых чисел может привести к неожиданно маленькому значению, а вычитание из маленького целого числа может вызвать перенос на большое положительное значение (например, сложение 8-битных целых чисел 255 + 2 дает 1, что составляет 257 по модулю 2, и аналогичным образом вычитание 0 — 1 дает 255, дополнение до двух представление -1).

    Такой переход может вызвать нарушение безопасности — если переполненное значение используется в качестве количества байтов, выделяемых для буфера, буфер будет выделен неожиданно маленьким, что может привести к переполнению буфера, которое, в зависимости от использования буфера, может, в свою очередь, вызвать выполнение произвольного кода.

    Если переменная имеет тип целое число со знаком, программа может сделать предположение, что переменная всегда содержит положительное значение. Целочисленное переполнение может привести к переносу значения и стать отрицательным, что нарушает предположение программы и может привести к неожиданному поведению (например, сложение 8-битного целого числа 127 + 1 дает -128, дополнение до двух до 128). (Решением этой конкретной проблемы является использование целочисленных типов без знака для значений, которые программа ожидает и предполагает, что они никогда не будут отрицательными.)

    Флаги

    Большинство компьютеров имеют два выделенных флага процессора для проверки условия переполнения.

    Флаг переноса устанавливается, когда результат сложения или вычитания с учетом операндов и результата как беззнаковых чисел не помещается в заданное количество битов. Это указывает на переполнение переносом или заимствованием из старшего бита. Сразу после операции добавления с переносом или вычитания с заимствованием содержимое этого флага будет использоваться для изменения регистра или области памяти, которая содержит старшую часть многословного значения.

    Флаг переполнения устанавливается, когда результат операции над числами со знаком не имеет знака, который можно было бы предсказать по знакам операндов, например, отрицательный результат при сложении двух положительные числа. Это указывает на то, что произошло переполнение, и результат со знаком, представленный в форме с дополнением до двух, не уместится в данном количестве битов.

    Варианты определения и неоднозначность

    Для беззнакового типа, когда идеальный результат операции выходит за пределы представимого диапазона типа, а возвращаемый результат получается путем упаковки, это событие обычно определяется как переполнение. Напротив, стандарт C11 определяет, что это событие не является переполнением, и заявляет, что «вычисление с участием беззнаковых операндов никогда не может переполниться».

    Когда идеальный результат целочисленной операции находится за пределами представимого диапазона типа и возвращаемый результат получается путем зажима, тогда это событие обычно определяется как насыщение. Использование зависит от того, является ли насыщение переполнением или нет. Чтобы устранить двусмысленность, можно использовать термины обертывание переполнения и насыщающее переполнение.

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

    Когда идеальный результат операции не является точным целым числом, значение переполнения может быть неоднозначным в крайних случаях. Рассмотрим случай, когда идеальный результат имеет значение 127,25, а максимальное представимое значение типа вывода — 127. Если переполнение определено как идеальное значение, выходящее за пределы представимого диапазона типа вывода, то этот случай будет классифицирован как переполнение. Для операций, которые имеют четко определенное поведение округления, может потребоваться отложить классификацию переполнения до тех пор, пока не будет применено округление. Стандарт C11 определяет, что преобразования из числа с плавающей запятой в целое число должны округляться до нуля. Если C используется для преобразования значения 127,25 с плавающей запятой в целое число, то сначала следует применить округление, чтобы получить идеальное целое число на выходе 127. Поскольку округленное целое число находится в диапазоне выходных значений, стандарт C не классифицирует это преобразование как переполнение..

    Методы решения проблем целочисленного переполнения

    Обработка целочисленного переполнения в различных языках программирования

    Язык Целое число без знака Целое число со знаком
    Ada по модулю модуль типа поднять Constraint_Error
    C /C ++ по модулю степени двойки неопределенное поведение
    C# по модулю степени 2 в непроверенном контексте; System.OverflowException возникает в проверенном контексте
    Java N / A степень двойки по модулю
    JavaScript все числа имеют двойную точность с плавающей запятой, за исключением нового BigInt
    MATLAB Встроенные целые числа насыщаются. Целые числа с фиксированной запятой, настраиваемые для переноса или насыщения
    Python 2 N/A , преобразовываются в longтип (bigint)
    Seed7 N / A поднять OVERFLOW_ERROR
    Схема Н / Д преобразовать в bigNum
    Simulink , конфигурируемый для переноса или насыщения
    Smalltalk Н / Д преобразовать в LargeInteger
    Swift Вызывает ошибку, если не используются специальные операторы переполнения.

    Обнаружение

    Реализация обнаружения переполнения во время выполнения UBSan доступна для Компиляторы C.

    В Java 8 есть перегруженные методы, например, такие как Math.addExact (int, int) , которые выбрасывают ArithmeticException в случае переполнения.

    Группа реагирования на компьютерные чрезвычайные ситуации (CERT) разработала целочисленную модель с неограниченным диапазоном значений (AIR), в значительной степени автоматизированный механизм устранения переполнения и усечения целых чисел в C / C ++ с использованием обработки ошибок времени выполнения.

    Предотвращение

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

    Обработка

    Если ожидается, что может произойти переполнение, то в программу можно вставить тесты, чтобы определить, когда это произойдет, и выполнить другую обработку, чтобы смягчить его. Например, если важный результат, вычисленный из переполнения пользовательского ввода, программа может остановиться, отклонить ввод и, возможно, предложить пользователю другой ввод, вместо того, чтобы программа продолжала работу с недопустимым переполненным вводом и, возможно, как следствие, неисправна. Этот полный процесс можно автоматизировать: можно автоматически синтезировать обработчик для целочисленного переполнения, где обработчик, например, является чистым выходом.

    ЦП обычно имеют способ обнаружения этого для поддержки сложения чисел большего размера чем размер их регистра, обычно использующий бит состояния; этот метод называется арифметикой с высокой точностью. Таким образом, можно добавить два числа каждые два байта шириной, просто добавляя байты по шагам: сначала добавьте младшие байты, затем добавьте старшие байты, но если необходимо выполнить младшие байты, это арифметическое переполнение добавление байтов, и становится необходимым обнаруживать и увеличивать сумму старших байтов.

    Явное распространение

    , если значение слишком велико для сохранения, ему может быть присвоено специальное значение, указывающее, что произошло переполнение, а затем все последующие операции возвращают это значение флага. Такие значения иногда обозначаются как NaN, что означает «не число». Это полезно для того, чтобы можно было проверить проблему один раз в конце длинных вычислений, а не после каждого шага. Это часто поддерживается оборудованием с плавающей запятой, называемым FPU.

    Поддержка языков программирования

    В языках программирования реализованы различные методы предотвращения случайного переполнения: Ada, Seed7 (и некоторые варианты функциональных языков) запускают условие исключения при переполнении, тогда как Python (начиная с версии 2.4) легко преобразует внутреннее представление числа в соответствии с его ростом, в конечном итоге представляя его как long — чьи возможности ограничены только доступной памятью.

    В языках с собственной поддержкой арифметики произвольной точности и безопасности типов (например, Python или Common Lisp ), числа автоматически увеличиваются до большего размера, когда происходят переполнения, или генерируются исключения (условия сигнализируются), когда существует ограничение диапазона. Таким образом, использование таких языков может помочь смягчить эту проблему. Однако в некоторых таких языках все еще возможны ситуации, когда может произойти целочисленное переполнение. Примером является явная оптимизация пути кода, который профилировщик считает узким местом. В случае Common Lisp это возможно с помощью явного объявления для обозначения типа переменной слова машинного размера (fixnum) и понижения уровня безопасности типа до нуля для конкретного блока кода.

    Насыщенная арифметика

    В компьютерной графике или обработке сигналов обычно работают с данными в диапазоне от 0 до 1 или от -1 на 1. Например, возьмите изображение в оттенках серого , где 0 представляет черный цвет, 1 представляет белый цвет, а значения между ними представляют оттенки серого. Одна операция, которую можно поддерживать, — это увеличение яркости изображения путем умножения каждого пикселя на константу. Насыщенная арифметика позволяет просто слепо умножать каждый пиксель на эту константу, не беспокоясь о переполнении, просто придерживаясь разумного результата, что все эти пиксели больше 1 (т. Е. «ярче белого» ) просто становятся белыми, а все значения «темнее черного» просто становятся черными.

    Примеры

    Непредвиденное арифметическое переполнение — довольно частая причина ошибок программы. Такие ошибки переполнения может быть трудно обнаружить и диагностировать, поскольку они могут проявляться только для очень больших наборов входных данных, которые с меньшей вероятностью будут использоваться в проверочных тестах.

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

    Необработанное арифметическое переполнение в программном обеспечении управления двигателем было основной причиной крушения в 1996 году первого полета ракеты Ariane 5. Считалось, что программное обеспечение не содержит ошибок, так как оно использовалось во многих предыдущих полетах, но в них использовались ракеты меньшего размера, которые генерировали меньшее ускорение, чем Ariane 5. К сожалению, часть программного обеспечения, в которой произошла ошибка переполнения, даже не требовалась. запускался для Ariane 5 в то время, когда он привел к отказу ракеты — это был процесс режима запуска для меньшего предшественника Ariane 5, который остался в программном обеспечении, когда он был адаптирован для новой ракеты. Кроме того, фактической причиной сбоя был недостаток в технической спецификации того, как программное обеспечение справлялось с переполнением, когда оно было обнаружено: оно выполнило диагностический дамп на свою шину, которая должна была быть подключена к испытательному оборудованию во время тестирования программного обеспечения во время разработки. но был связан с двигателями рулевого управления ракеты во время полета; из-за сброса данных сопло двигателя сильно отклонилось в сторону, что вывело ракету из-под контроля аэродинамики и ускорило ее быстрое разрушение в воздухе.

    30 апреля 2015 года Федеральное управление гражданской авиации США объявил, что прикажет операторам Boeing 787 периодически перезагружать его электрическую систему, чтобы избежать целочисленного переполнения, которое может привести к потере электроэнергии и развертыванию воздушной турбины, и Boeing развернул обновление ПО в четвертом квартале. Европейское агентство по авиационной безопасности последовало 4 мая 2015 года. Ошибка возникает через 2 центсекунды (248,55134814815 дней), указывая на 32-битное подписанное целое число.

    . очевидно в некоторых компьютерных играх. В аркадной игре Donkey Kong, невозможно продвинуться дальше 22 уровня из-за целочисленного переполнения его времени / бонуса. Игра берет номер уровня, на котором находится пользователь, умножает его на 10 и добавляет 40. Когда они достигают уровня 22, количество времени / бонуса равно 260, что слишком велико для его 8-битного регистра значений 256, поэтому он сбрасывается. до 0 и дает оставшиеся 4 как время / бонус — слишком мало для завершения уровня. В Donkey Kong Jr. Math при попытке вычислить число, превышающее 10 000, отображаются только первые 4 цифры. Переполнение является причиной знаменитого уровня «разделенного экрана» в Pac-Man и «Ядерного Ганди» в Civilization. Это также привело к появлению «Дальних земель» в Minecraft, которые существовали с периода разработки Infdev до Beta 1.7.3; однако позже это было исправлено в Beta 1.8, но все еще существует в версиях Minecraft Pocket Edition и Windows 10 Edition. В игре Super Nintendo Lamborghini American Challenge игрок может заставить свою сумму денег упасть ниже 0 долларов во время гонки, будучи оштрафованным сверх лимита оставшихся денег после уплаты сбора за гонка, в которой происходит сбой целого числа, и игрок получает на 65 535 000 долларов больше, чем он получил бы после отрицательного результата. Подобный сбой происходит в S.T.A.L.K.E.R.: Clear Sky, где игрок может упасть до отрицательной суммы, быстро путешествуя без достаточных средств, а затем перейти к событию, где игрока ограбят и у него заберут всю валюту. После того, как игра попытается забрать деньги игрока на сумму в 0 долларов, игроку выдается 2147482963 игровой валюты.

    В структуре данных Покемон в играх с покемонами число полученных очков опыта хранится в виде 3-байтового целого числа. Однако в первом и втором поколениях группа опыта со средней медленной скоростью, которой требуется 1 059 860 очков опыта для достижения 100 уровня, по расчетам имеет -54 очка опыта на уровне 1. Поскольку целое число не имеет знака, значение превращается в 16 777 162. Если покемон набирает менее 54 очков опыта в битве, то покемон мгновенно перескакивает на 100-й уровень. Кроме того, если эти покемоны на уровне 1 помещаются на ПК, и игрок пытается их вывести, игра вылетает., из-за чего эти покемоны навсегда застревают на ПК. В тех же играх игрок, используя Редкие Конфеты, может повысить уровень своего Покемона выше 100 уровня. Если он достигает уровня 255 и используется другая Редкая Конфета, то уровень переполняется до 0 (из-за того, что уровень кодируется в один байт, например, 64 16 соответствует уровню 100).

    Ошибка целочисленной подписи в коде настройки стека, выпущенная компилятором Pascal, помешала Microsoft / IBM MACRO Assembler Version 1.00 (MASM), DOS программа 1981 года и многие другие программы, скомпилированные с помощью того же компилятора, для работы в некоторых конфигурациях с объемом памяти более 512 КБ.

    Microsoft / IBM MACRO Assembler (MASM) версии 1.00 и, вероятно, все другие программы, созданные с помощью того же Компилятор Паскаля имел ошибку переполнения целого числа и подписи в коде настройки стека, что не позволяло им работать на новых машинах DOS или эмуляторах в некоторых распространенных конфигурациях с более чем 512 КБ памяти. Программа либо зависает, либо отображает сообщение об ошибке и выходит в DOS.

    В августе 2016 года автомат казино в Resorts World Casino распечатал призовой билет на 42 949 672,76 долларов в результате ошибки переполнения. Казино отказалось выплатить эту сумму, назвав это неисправностью, используя в свою защиту то, что в автомате четко указано, что максимальная выплата составляет 10 000 долларов, поэтому любой превышающий эту сумму приз должен быть результатом ошибки программирования. Верховный суд штата Айова вынес решение в пользу казино.

    См. Также

    • Переполнение буфера
    • Переполнение кучи
    • Переключение указателя
    • Тестирование программного обеспечения
    • буфер стека переполнение
    • Статический анализ программы
    • Сигнал Unix

    Ссылки

    Внешние ссылки

    • Фракция № 60, Базовое целочисленное переполнение
    • Фракция № 60, Целочисленная защита большого цикла
    • Эффективная и Точное обнаружение целочисленных атак
    • Классификация угроз WASC — Целочисленные переполнения
    • Общие сведения о целочисленном переполнении в C / C ++
    • Двоичное переполнение — двоичная арифметика
    • Стандарт ISO C11

    Что такое переполнение в программировании

    Что такое stackframe?

    Стек вызовов – это в основном код ассемблера для конкретной программы. Это стек переменных и стекфреймов, которые сообщают компьютеру, в каком порядке выполнять инструкции. Для каждой функции, которая еще не завершила выполнение, будет создан стекфрейм, а функция, которая выполняется в данный момент, будет находиться в верхней части стека.

    Чтобы отслеживать этот процесс, компьютер хранит в памяти несколько указателей:

    • Stack Pointer: указывает на топ стека вызовов процесса (или на последний помещенный в стек элемент).
    • Instruction Pointer: указывает на адрес следующей инструкции процессора, которая будет выполнена.
    • Base Pointer (BP): (также известный как указатель кадра) указывает на основание текущего кадра стека. Он остается постоянным до тех пор, пока программа выполняет текущий стекфрейм (хотя указатель стека может измениться).

    Для примера рассмотрим следующий код:

    ❓ Что такое переполнение буфера и как с ним бороться

    Здесь main вызывает firstFunction (которая в данный момент выполняется), поэтому она находится в верхней части стека вызовов. Возвращаемый адрес – это адрес в памяти, относящийся к функции, которая его вызвала (он удерживается указателем инструкции при создании стекфрейма). Локальные переменные, которые все еще находятся в области видимости, также находятся в стеке вызовов. Когда они выполняются и выходят за пределы области действия, они удаляются из верха стека.

    Таким образом, компьютер может отслеживать, какая инструкция должна быть выполнена и в каком порядке. Переполнение стека основано на перезаписи одного из этих сохраненных обратных адресов вредоносным адресом.

    Пример уязвимости переполнения буфера:

    int main() < bufferOverflow(); >bufferOverflow()

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

    Как компьютер отреагирует на это, зависит от реализации стеков и выделения памяти в конкретной системе. Реакция на переполнение буфера может быть совершенно непредсказуемой, начиная от сбоев программы и заканчивая выполнением вредоносного кода.

    Почему происходит переполнение буфера?

    Причина, по которой переполнение буфера стало такой серьезной проблемой, заключается в отсутствии проверки границ во многих функции управления памятью в C и C++. Хотя этот процесс сейчас довольно хорошо известен, он также очень часто эксплуатируется (например, зловред WannaCry использовал переполнение буфера).

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

    Веб-серверы, серверные приложения и среды веб-приложений подвержены переполнению буфера. Исключение составляют написанные на интерпретируемых языках среды, хотя сами интерпретаторы тоже могут быть подвержены переполнению.

    Как уменьшить влияние переполнения буфера:

    • Используйте интерпретируемый язык, который не подвержен этим проблемам.
    • Избегайте использования функций, которые не выполняют проверку буфера (например, в C вместо функции gets() используйте функцию fgets()).
    • Применяйте компиляторы, которые помогают определить небезопасные функции или найти ошибки.
    • Используйте canaries, которые могут помочь предотвратить переполнение буфера. Они вставляются перед обратным адресом в стеке и проверяются перед обращением к нему. Если программа обнаружит изменение значения canary, она прервет процесс, не позволив злоумышленнику пробиться. Значение canary является либо случайным (поэтому злоумышленнику очень трудно его угадать), либо строкой, которую по техническим причинам невозможно перезаписать.
    • Переставляйте локальные переменных таким образом, чтобы скалярные (отдельные объекты данных фиксированного размера) были выше переменных массива, содержащих несколько значений. Это означает, что если переменные массива переполняются, они не будут влиять на скалярные переменные. Этот метод в сочетании с canary-значениями очень помогает.
    • Сделайте стек неисполняемым, установив бит NX (No-eXecute), чтобы злоумышленник не вставлял шелл-код непосредственно в стек и не выполнял его там. Это неидеальное решение, так как даже неисполняемые стеки могут стать жертвами атак переполнения буфера, вроде return-to-libc. Эта атака происходит, когда обратный адрес стекового фрейма заменяется адресом библиотеки, уже находящейся в адресном пространстве процесса. К тому же не все процессоры позволяют установить бит NX.
    • ASLR (рандомизация расположения адресного пространства) может служить общей защитой, а также специфической защитой от атак return-to-libc. Это означает, что всякий раз, когда файл библиотеки или другая функция вызывается запущенным процессом, ее адрес сдвигается на случайное число. Это делает практически невозможным связать фиксированный адрес памяти процесса с функциями, из чего следует, что злоумышленнику может быть трудно узнать, откуда вызывать определенные функции. ASLR включен по умолчанию во многих версиях Linux, OS X и Android.

    Stack Underflow

    Такая уязвимость возникает, когда две части программы по-разному обрабатывают один и тот же блок памяти. Например, если вы выделите массив размером X, но заполните его массивом размером x < X, а затем попытаетесь извлечь все X байтов, скорее всего вы получите «грязные» данные для X – x байтов.

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

    Заключение

    Рассмотренная уязвимость является очень серьезной угрозой стабильной работе любого продукта. Необходимо приложить все усилия и проверить ваши проекты на ее наличие, т. к. последствия могут быть весьма плачевными (уже упоминался Ransome ) и болезненными. Используйте советы из статьи и вы уменьшите вероятность успешного проникновения злоумышленников в ваш код. Удачи в обучении!

    Дополнительные материалы:

    • Алгоритмы в C++: запросы к статическим массивам
    • ТОП-10 трюков на C++, которые облегчат вам жизнь
    • Сайт на C++ своими руками с помощью библиотеки cgicc
    • 5 шагов для создания простой формы входа на C#
    • 10 самых популярных алгоритмов сортировки на C#

    Источники

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *