Czy przepełnienie bufora zmienia typ danych zastępowanej zmiennej? [Zamknięte]

8

Że mam tablicę C znaków char buf[15]. Powiedz zmienna int set_me = 0ma swoje dane przechowywane w pamięci bezpośrednio po char buf[15]. Gdybym przepełnił bufsię ciągiem znaków "aaabbbcccdddeee\xef\xbe\xad\xde", czy set_metyp danych zmieniłby się z liczby całkowitej na tablicę znaków?

Darien Springer
źródło
3
Zależy od tego, kto interpretuje dane. wreszcie wszystko jest binarne. Zatem sposób, w jaki go interpretujesz, może być prawidłową liczbą całkowitą lub powodować błąd rzutowania
Ganesh R.

Odpowiedzi:

33

Nie.

„Typ danych” zmiennej ma znaczenie tylko w kodzie źródłowym (a nawet tylko w niektórych językach). Mówi kompilatorowi, jak traktować zmienną.

Te typy danych wysokiego poziomu nie istnieją jako takie w skompilowanym (natywnym) kodzie. Mogą wpływać na to, jakie instrukcje generuje kompilator, ale same instrukcje nie dbają o to, czy dane reprezentują znak, czy liczbę.


Zmienne nie istnieją w sprzęcie. W sprzęcie masz lokalizacje pamięci i instrukcje, które na nich działają.

Zmienna może być postrzegana jako widok danych w miejscu pamięci - jeśli spojrzysz na tę samą pamięć nieco inaczej (inna zmienna różnego typu odnosząca się do tej samej lokalizacji), ta sama wartość binarna może mieć inne znaczenie .

Na przykład bajt 0x41 może być interpretowany jako znak zakodowany w UTF-8 A. Można to również interpretować jako liczbę całkowitą jednobajtową 65. Może być również interpretowany jako jeden bajt w wielobajtowej liczbie całkowitej lub liczbie zmiennoprzecinkowej, lub jeden bajt w wielobajtowym kodowaniu znaków. To może być bitset 0b1000001. Wszystkie z tego samego bajtu w tej samej lokalizacji w pamięci. W języku C można zobaczyć ten efekt, rzutując na te różne typy.

Kiedy masz „przepełnienie bufora”, robisz coś poza granicami tego, czego może oczekiwać Twój kompilator lub język. Ale jeśli chodzi o sprzęt 1 , zapisujesz bajty (pojedyncze lub wielokrotne) w miejscu w pamięci. Lokalizacja pamięci nie ma „typu”. W rzeczywistości sprzęt nie wie nawet, że określony zestaw bajtów tworzy tablicę lub bufor w kodzie.

Ilekroć następnym razem uzyskasz dostęp do tej lokalizacji pamięci w kodzie, instrukcje będą działać zgodnie z pierwotną definicją. np. jeśli spodziewali się tam liczby, będą działać na dowolnych bajtach danych, tak jakby byli liczbą.


Na przykład, zakładając, że intjest to 4-bajtowa (32-bitowa) liczba całkowita ze znakiem:

+-------------+--------------------------------------------+-----------+
| 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|
+-------------+--------------------------------------------------------+

Widać intteraz 0xEFBEADDE, że lokalizacja pamięci zawiera teraz , zakładając, że system big-endian 2 . To jest podpisany 32-bitowy int -272716322. Teraz, jeśli interpretujesz tę samą pamięć jako int bez znaku int ( uint), byłoby to 4022250974zamiast tego. Dla dokładnie tych samych danych w pamięci znaczenie zależy całkowicie od tego, jak je przeglądasz.


1 Istnieją pewne mechanizmy, które uniemożliwiają zapisywanie w chronionych obszarach pamięci i powodują awarię programu, jeśli spróbujesz to zrobić.

2 x86 jest właściwie little-endianem, co oznacza, że ​​interpretujesz bajty składające się na większą wartość wstecz. Tak więc na x86 miałbyś zamiast tego 0xDEADBEEFdać podpisany -559038737lub niepodpisany 3735928559.

Kok
źródło
Więc 0xdeadbeefw architekturze x86 zajmowałoby mniej miejsca w pamięci niż jej dziesiętny odpowiednik 3735928559?
Darien Springer
2
@DarienSpringer Oba zajmują 4 bajty pamięci - są one tą samą 4-bajtową sekwencją. Są identyczne w pamięci. Możesz uznać to wszystko za bazę 2 (binarną) w pamięci, jeśli chcesz. Następnie, kiedy je wyświetlasz (konwertujesz na ciąg wyjściowy), możesz wybrać podstawę do wyświetlenia - szesnastka to podstawa 16, a dziesiętna to podstawa 10. Reprezentacje ciągów są przechowywane w innym miejscu w pamięci i mogą używać różnych ilości pamięci (ponieważ każdy znak jest osobnym bajtem). Ciąg 0xDEADBEEF jest przechowywany w pamięci jako 0x30 0x78 0x44 0x45 0x41 0x44 0x42 0x45 0x45 0x46.
Bob
5
@DarienSpringer Innymi słowy, liczba jest taka sama, bez względu na to, w jakiej bazie się znajduje. Hex to wygodny (kompaktowy) sposób przeglądania plików binarnych. Fizycznie jest binarny. Ludzie lubią dziesiętne, więc częściej wyświetlamy liczby jako dziesiętne. Ale dopóki nie przejdziemy do kroku wyświetlania, wszystkie operacje numeryczne (dodawanie, odejmowanie, mnożenie itp.) Działają na tych samych danych binarnych w pamięci.
Bob
1
„Widać, że lokalizacja pamięci int jest teraz 0xEFBEADDE” Nitpick: Wiem, że nie zamierzałeś tego, ale wygląda na to, że mówisz, że int znajduje się w lokalizacji pamięci 0xEFBEADDE. Być może nieco to przeredagować. W przeciwnym razie jest to znakomita odpowiedź - szczególnie podoba mi się analogia „widoku” i pomysł „mrużącego oczy” :)
Lekkość ściga się na orbicie
@LightnessRacesinOrbit Dobry punkt. Edytowane.
Bob
2

Z perspektywy C odpowiedź brzmiałaby „Kto wie? Jest to niezdefiniowane zachowanie”.

Typy to koncepcja C, a nie sprzęt. Ale reguły C nie mają zastosowania, jeśli twój program ma Niezdefiniowane zachowanie, to jest dosłowne znaczenie Niezdefiniowane zachowanie w standardzie C. Przepełnienia buforów są jedną z nich.

Początkowo napisałem „zasady C już nie obowiązują”, ale w rzeczywistości Niezdefiniowane zachowanie działa wstecz. Reguły C nie mają zastosowania do programu, który będzie miał niezdefiniowane zachowanie w przyszłości.

MSalters
źródło