Переполнение буфера изменяет тип данных переменной, которую он перезаписывает? [закрыто]

8

Скажем , у меня есть массив C символов char buf[15]. Скажем, переменная int set_me = 0хранит свои данные непосредственно в ячейке памяти char buf[15]. Если бы я переполнился bufстрокой "aaabbbcccdddeee\xef\xbe\xad\xde", set_meизменился бы тип данных с целого на массив символов?

Дариен Спрингер
источник
3
Зависит от того, кто интерпретирует данные. наконец, все двоично. Таким образом, как вы его интерпретируете, это может быть допустимым целочисленным значением или вызвать ошибку приведения
Ганеш Р.

Ответы:

33

Нет.

«Тип данных» переменной имеет значение только в исходном коде (и даже тогда только в некоторых языках). Он говорит компилятору, как обращаться с переменной.

Эти высокоуровневые типы данных не существуют как таковые в скомпилированном (нативном) коде. Они могут влиять на то, какие инструкции генерирует компилятор, но самим инструкциям все равно, представляют ли данные символ или число.


Переменные не существуют в аппаратном обеспечении. В оборудовании у вас есть области памяти и инструкции, которые работают с ними.

Переменная может рассматриваться как представление данных в ячейке памяти - если вы косите и смотрите на одну и ту же память немного по-разному (другая переменная с другим типом ссылается на одну и ту же ячейку), то одно и то же двоичное значение может иметь другое значение ,

Например, байт 0x41 можно интерпретировать как символ в кодировке UTF-8 A. Это также можно интерпретировать как однобайтовое целое число 65. Его также можно интерпретировать как один байт в многобайтовом целом числе или числе с плавающей запятой или один байт в многобайтовой кодировке символов. Это может быть битсет 0b1000001. Все из одного байта в одной и той же ячейке памяти. В языке C вы можете увидеть этот эффект, приведя к этим различным типам.

Когда у вас «переполнение буфера», вы делаете что-то за пределами того, что может ожидать ваш компилятор или язык. Но, что касается оборудования 1 , вы записываете байты (одиночные или множественные) в область памяти. В ячейке памяти нет «типа». Фактически, оборудование даже не знает, что какой-то конкретный набор байтов создает массив или буфер в вашем коде.

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


Чтобы использовать ваш пример, предположим, что у вас int4-байтовое (32-битное) целое число со знаком:

+-------------+--------------------------------------------+-----------+
| Source code |                  char[15]                  |    int    |
+-------------+--------------------------------------------------------+
| Memory      |61|61|61|62|62|62|63|63|63|64|64|64|65|65|65|EF|BE|AD|DE|
+-------------+--------------------------------------------------------+

Вы можете видеть, что место в intпамяти теперь содержит 0xEFBEADDE, предполагая систему с прямым порядком байтов 2 . Это подписанный 32-битный int -272716322. Теперь, если вы интерпретируете ту же память, что и unsigned int ( uint), она будет 4022250974вместо этого. Для точно таких же данных в памяти значение полностью зависит от того, как вы их просматриваете.


1 Существуют некоторые механизмы, которые препятствуют записи в защищенные области памяти и могут привести к сбою программы, если вы попытаетесь это сделать.

2 x86 на самом деле является прямым порядком байтов, что означает, что вы интерпретируете байты, составляющие большее значение в обратном направлении. Так что на x86 вы бы вместо этого 0xDEADBEEF, давали подписанные -559038737или неподписанные 3735928559.

боб
источник
Итак 0xdeadbeef, на архитектуре x86, будет занимать меньше места в памяти, чем ее десятичный аналог 3735928559,?
Дариен Спрингер
2
@DarienSpringer Оба занимают 4 байта памяти - это одинаковая 4-байтовая последовательность. Они идентичны по памяти. Если хотите, вы можете считать это основанием 2 (двоичным) в памяти. Затем, когда вы отображаете их (конвертируете в строку для вывода), вы можете выбрать базу для отображения - шестнадцатеричное число - это основание 16, а десятичное число - это базовое 10. Строковые представления хранятся в другом месте памяти и могут использовать разные суммы памяти (так как каждый символ является отдельным байтом). Строка 0xDEADBEEF хранится в памяти как 0x30 0x78 0x44 0x45 0x41 0x44 0x42 0x45 0x45 0x46.
Боб
5
@DarienSpringer Другими словами, число - это одно и то же число, независимо от того, в какой базе оно находится. Hex - удобный (компактный) способ просмотра двоичного файла. Физически это двоичный файл. Людям нравится десятичная дробь, поэтому мы чаще показываем числа как десятичные. Но пока мы не дойдем до шага отображения, все числовые операции (сложение, вычитание, умножение и т. Д.) Работают с одними и теми же двоичными данными в памяти.
Боб
1
«Вы можете видеть, что область памяти int теперь равна 0xEFBEADDE» Nitpick: Я знаю, что вы этого не предполагали, но, похоже, вы говорите, что int находится в области памяти 0xEFBEADDE. Возможно, немного перефразирую. В противном случае это превосходный ответ - мне особенно нравится аналогия «взгляда» и идея «щурки» :)
Гонки
@LightnessRacesinOrbit Хороший вопрос. Ред.
Боб
2

С точки зрения C ответом будет «Кто знает? Это неопределенное поведение».

Типы - это концепция C, а не аппаратная часть. Но правила C не применяются, если ваша программа имеет неопределенное поведение, то есть буквальное значение Undefined Behavior в стандарте C. И переполнение буфера является одной из форм этого.

Первоначально я написал «правила C больше не применяются», но на самом деле неопределенное поведение имеет обратную силу. Правила C не применяются к программе, которая будет иметь неопределенное поведение в будущем.

MSalters
источник