Чи змінює переповнення буфера тип даних змінної, яку вона перезаписує? [зачинено]


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 , ви записуєте байти (одиничні чи множинні) до місця пам'яті. Місце пам'яті не має "типу". Насправді, апаратне забезпечення навіть не знає, що якийсь певний набір байтів складає масив або буфер у вашому коді.

Де б ви не отримали наступний доступ до цього місця пам'яті у вашому коді, інструкції працюватимуть як було визначено спочатку. наприклад, якщо вони очікували номер там, вони діятимуть на будь-які байти даних, як якщо б вони були числом.


Щоб використовувати свій приклад, припустимо, що intце підписане 4-байтове (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. Тепер, якщо ви інтерпретуєте ту саму пам'ять, що і без підписаного 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
"Ви можете бачити, що зараз в пам'яті інта розміщено 0xEFBEADDE" Nitpick: Я знаю, ви цього не мали наміру, але це звучить так, як ви говорите, що Int розташований у пам'яті 0xEFBEADDE. Можливо, це трохи переробити. Інакше це чудова відповідь - мені особливо подобається аналогія "погляд" та ідея "примруження" :)
Гонки легкості в орбіті

@LightnessRacesinOrbit Добре. Відредаговано.
Боб

2

З точки зору С відповідь буде "Хто знає? Це невизначена поведінка".

Типи - це поняття C, а не апаратне забезпечення. Але правила C не застосовуються, якщо у вашій програмі є не визначена поведінка, це буквальне значення Undefined Behavior у стандарті C. І переповнення буфера є однією з форм цього.

Я спочатку писав "правила C більше не діють", але насправді Невизначена поведінка має зворотну силу. Правила C не застосовуються до програми, яка матиме не визначене поведінку в майбутньому.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.