Jak niebezpieczne jest uzyskiwanie dostępu do tablicy poza granicami?


221

Jak niebezpieczny jest dostęp do tablicy poza jej granicami (w C)? Czasami może się zdarzyć, że czytam spoza tablicy (teraz rozumiem, że wtedy uzyskuję dostęp do pamięci używanej przez niektóre inne części mojego programu lub nawet poza nią) lub próbuję ustawić wartość na indeks poza tablicą. Program czasami ulega awarii, ale czasami po prostu działa, dając tylko nieoczekiwane wyniki.

Teraz chciałbym wiedzieć, jak naprawdę jest to niebezpieczne? Jeśli uszkodzi mój program, nie jest taki zły. Jeśli z drugiej strony psuje coś poza moim programem, ponieważ jakoś udało mi się uzyskać dostęp do jakiejś całkowicie niepowiązanej pamięci, to jest to bardzo złe, jak sądzę. Czytam dużo „wszystko może się zdarzyć”, „segmentacja może być najmniejszym problemem” , „twój dysk twardy może zmienić kolor na różowy, a jednorożce śpiewają pod twoim oknem”, co jest miłe, ale co naprawdę jest niebezpieczne?

Moje pytania:

  1. Czy odczytywanie wartości spoza tablicy może uszkodzić coś oprócz mojego programu? Wyobrażam sobie, że samo patrzenie na rzeczy niczego nie zmienia, czy może na przykład zmieniłby atrybut „ostatnio otwierane” pliku, do którego dotarłem?
  2. Czy ustawienie wartości poza macierzą może uszkodzić coś oprócz mojego programu? Z tego pytania o przepełnienie stosu dowiaduję się, że można uzyskać dostęp do dowolnego miejsca w pamięci, że nie ma gwarancji bezpieczeństwa.
  3. Teraz uruchamiam moje małe programy z poziomu XCode. Czy zapewnia to dodatkową ochronę wokół mojego programu, w przypadku gdy nie może on dotrzeć poza własną pamięć? Czy może zaszkodzić XCode?
  4. Wszelkie zalecenia dotyczące bezpiecznego uruchamiania z natury mojego błędnego kodu?

Używam OSX 10.7, Xcode 4.6.


Zasadniczo system operacyjny będzie chronić siebie i inne procesy przed twoją nieuczciwością. Nie jest to jednak coś, na co koniecznie trzeba polegać.
Hot Licks

7
Ponadto nigdy nie „dotrzesz” do pliku na dysku twardym podczas uzyskiwania dostępu i indeksowania tablicy poza granicami (w pamięci RAM).
DrummerB

1
myślę, że pytasz o macierz C, prawda? więc nie ma to nic wspólnego z ObjC i tak naprawdę nie odnosi się do żadnego IDE.
Bryan Chen

17
Oto mój ulubiony przykład dziwnych wyników (dotyczy stosu, ale uważam, że jest naprawdę pouczający ...).
phipsgabler

Odpowiedzi:


125

Jeśli chodzi o standard ISO C (oficjalna definicja języka), dostęp do tablicy poza jej granicami ma „ niezdefiniowane zachowanie ”. Dosłowne znaczenie tego:

zachowanie, po zastosowaniu niezbywalnej lub błędnej konstrukcji programu lub błędnych danych, dla których niniejszy standard międzynarodowy nie nakłada żadnych wymagań

Nota nienormatywna rozwija się na ten temat:

Możliwe niezdefiniowane zachowanie obejmuje od całkowitego zignorowania sytuacji z nieprzewidzianymi rezultatami, zachowania podczas tłumaczenia lub wykonywania programu w udokumentowany sposób charakterystyczny dla środowiska (z wydaniem komunikatu diagnostycznego lub bez niego), aż do zakończenia tłumaczenia lub wykonania (z wydaniem komunikatu diagnostycznego).

To jest teoria. Jaka jest rzeczywistość

W „najlepszym” przypadku uzyskasz dostęp do części pamięci, która jest własnością twojego aktualnie uruchomionego programu (co może spowodować, że Twój program źle się zachowuje) lub która nie jest własnością twojego aktualnie uruchomionego programu (co prawdopodobnie spowoduje, że Twój program awaria z czymś w rodzaju błędu segmentacji). Lub możesz spróbować zapisać w pamięci, którą posiada twój program, ale jest to oznaczone jako tylko do odczytu; prawdopodobnie spowoduje to również awarię programu.

Zakłada się, że Twój program działa w systemie operacyjnym, który próbuje chronić przed sobą równolegle działające procesy. Jeśli twój kod działa na „bare metal”, powiedzmy, że jest częścią jądra systemu operacyjnego lub systemu osadzonego, wówczas taka ochrona nie istnieje; Twój źle działający kod miał zapewnić tę ochronę. W takim przypadku możliwości uszkodzenia są znacznie większe, w tym, w niektórych przypadkach, fizyczne uszkodzenie sprzętu (lub przedmiotów lub osób w pobliżu).

Nawet w chronionym środowisku systemu operacyjnego zabezpieczenia nie zawsze są w 100%. Istnieją błędy systemu operacyjnego, które pozwalają nieuprzywilejowanym programom na przykład uzyskać dostęp do roota (administratora). Nawet przy zwykłych uprawnieniach użytkownika źle działający program może zużywać nadmierne zasoby (procesor, pamięć, dysk), co może doprowadzić do awarii całego systemu. Wiele złośliwych programów (wirusów itp.) Wykorzystuje przepełnienia bufora, aby uzyskać nieautoryzowany dostęp do systemu.

(Jeden historyczny przykład: słyszałem, że w niektórych starych systemach z pamięcią rdzeniową wielokrotne uzyskiwanie dostępu do pojedynczej lokalizacji pamięci w ciasnej pętli może dosłownie spowodować stopienie tej części pamięci. Inne możliwości obejmują zniszczenie wyświetlacza CRT i przesunięcie odczytu / napisz głowicę napędu dyskowego o częstotliwości harmonicznej szafy napędu, powodując, że przechodzi ona przez stół i spada na podłogę.)

I zawsze jest o co martwić Skynet .

Najważniejsze jest to: jeśli mógłbyś napisać program celowo robiąc coś złego , przynajmniej teoretycznie możliwe jest, że błędny program mógłby zrobić to samo przypadkowo .

W praktyce jest bardzo mało prawdopodobne, aby Twój błędny program działający na systemie MacOS X zrobił coś poważniejszego niż awaria. Ale nie można całkowicie uniemożliwić błędnemu kodowi robienia naprawdę złych rzeczy.


1
dzięki, w pełni to rozumiem. Ale natychmiast pojawia się kolejne pytanie: co może zrobić początkujący programista, aby chronić swój komputer przed własnymi, potencjalnie okropnymi dziełami? Po dokładnym przetestowaniu programu mogę go uruchomić na całym świecie. Ale pierwsze uruchomienie próbne musi być niepoprawnym programem. Jak chronicie swoje systemy przed sobą?
ChrisD

6
@ChrisD: Mamy zwykle szczęście. 8-)} Poważnie, ochrona na poziomie systemu operacyjnego jest obecnie całkiem dobra. W najgorszym przypadku, jeśli napiszę przypadkową bombę widełkową , być może będę musiał zrestartować komputer, aby odzyskać. Ale prawdziwe uszkodzenie systemu prawdopodobnie nie jest warte martwienia się, o ile twój program nie próbuje zrobić czegoś na krawędzi bycia niebezpiecznym. Jeśli naprawdę się martwisz, uruchomienie programu na maszynie wirtualnej może nie być złym pomysłem.
Keith Thompson

1
Z drugiej strony widziałem wiele dziwnych rzeczy na komputerach, z których korzystałem (uszkodzone pliki, nieodwracalne błędy systemowe itp.), I nie mam pojęcia, ile z nich mogło być spowodowanych przez program C przerażające niezdefiniowane zachowanie. (Jak dotąd żadne rzeczywiste demony nie wyleciały mi z nosa.)
Keith Thompson

1
dzięki za nauczenie mnie widełek - zrobiłem coś
podobnego

2
scienceamerican.com/article/…, więc ogień jest nadal możliwy dzięki nowoczesnej elektronice.
Mooing Duck

25

Ogólnie rzecz biorąc, dzisiejsze systemy operacyjne (i tak popularne) uruchamiają wszystkie aplikacje w chronionych regionach pamięci za pomocą wirtualnego menedżera pamięci. Okazuje się, że po prostu czytać lub pisać w lokalizacji, która istnieje w PRAWDZIWEJ przestrzeni poza regionem (regionami) przypisanymi / przydzielonymi do twojego procesu, nie jest strasznie ŁATWE (na powiedzenie).

Bezpośrednie odpowiedzi:

1) Czytanie prawie nigdy nie uszkodzi bezpośrednio innego procesu, jednak może pośrednio uszkodzić proces, jeśli zdarzy się odczytać wartość KLUCZOWĄ używaną do szyfrowania, deszyfrowania lub sprawdzania poprawności programu / procesu. Czytanie poza zakresem może mieć nieco negatywny / nieoczekiwany wpływ na kod, jeśli podejmujesz decyzje na podstawie czytanych danych

2) Jedynym sposobem, aby naprawdę ZNISZCZYĆ coś, pisząc do pętli dostępnej dla adresu pamięci, jest to, że ten adres pamięci, na który piszesz, jest w rzeczywistości rejestrem sprzętowym (miejsce, które w rzeczywistości nie służy do przechowywania danych, ale do kontrolowania jakiegoś elementu sprzętu), a nie lokalizacja pamięci RAM. W rzeczywistości nadal normalnie niczego nie uszkodzisz, chyba że napiszesz kiedyś programowalną lokalizację, której nie da się ponownie zapisać (lub coś takiego).

3) Generalnie uruchamiany z poziomu debugera uruchamia kod w trybie debugowania. Uruchomienie w trybie debugowania ma tendencję do (ale nie zawsze) szybszego zatrzymywania kodu, gdy zrobisz coś, co jest uważane za niepraktyczne lub wręcz nielegalne.

4) Nigdy nie używaj makr, używaj struktur danych, które mają już wbudowane sprawdzanie granic indeksów tablic itp.

DODATKOWE Powinienem dodać, że powyższe informacje są naprawdę tylko dla systemów korzystających z systemu operacyjnego z oknami ochrony pamięci. Jeśli piszesz kod dla systemu osadzonego, a nawet systemu wykorzystującego system operacyjny (w czasie rzeczywistym lub inny), który nie ma okien ochrony pamięci (lub wirtualnych okien adresowanych), należy zachować większą ostrożność podczas odczytywania i zapisywania w pamięci. Również w tych przypadkach należy zawsze stosować BEZPIECZNE i BEZPIECZNE praktyki kodowania, aby uniknąć problemów z bezpieczeństwem.


4
Bezpieczne praktyki kodowania powinny Zawsze należy stosować .
Nik Bougalis

3
Radziłbym NIE używać try / catch dla błędnego kodu, chyba że złapiesz bardzo specyficzne wyjątki i będziesz wiedział, jak je odzyskać. Złap (...) to najgorsza rzecz, jaką możesz dodać do błędnego kodu.
Eugene

1
@NikBougalis - całkowicie się zgadzam, ale JESZCZE WIĘCEJ WAŻNE, jeśli system operacyjny nie obejmuje ochrony pamięci / wirtualnych przestrzeni adresowych, lub brakuje systemu operacyjnego :-)
trumpetlicks

@Eugene - nigdy nie zauważyłem, że jest to dla mnie problem, ale zgadzam się z tobą, czy edytowałem to :-)
trumpetlicks

1) masz na myśli uszkodzenie, ponieważ ujawniłbym coś, co powinno pozostać tajemnicą? 2) Nie jestem pewien, czy rozumiem, co masz na myśli, ale myślę, że uzyskuję dostęp tylko do pamięci RAM, próbując uzyskać dostęp do lokalizacji poza granicami tablicy?
ChrisD

9

Brak sprawdzania granic może prowadzić do brzydkich efektów ubocznych, w tym dziur w zabezpieczeniach. Jednym z tych brzydkich jest wykonanie dowolnego kodu . W klasycznym przykładzie: jeśli masz tablicę o stałym rozmiarze i używasz strcpy()do umieszczenia tam łańcucha dostarczonego przez użytkownika, użytkownik może dać ci łańcuch, który przepełnia bufor i zastępuje inne lokalizacje pamięci, w tym adres kodu, do którego procesor powinien zwrócić, gdy funkcja kończy.

Co oznacza, że ​​użytkownik może wysłać ci ciąg, który spowoduje, że Twój program w zasadzie zadzwoni exec("/bin/sh"), co zamieni go w powłokę, wykonując wszystko, co zechce w twoim systemie, w tym zbierając wszystkie dane i zamieniając maszynę w węzeł botnetu.

Widzieć Smashing the Stack dla zabawy i zysku, aby dowiedzieć się, jak to zrobić.


Wiem, że nie powinienem uzyskiwać dostępu do elementów tablicy poza granice, dzięki za wzmocnienie tego punktu. Ale pytanie polega na tym, że oprócz wyrządzenia różnego rodzaju szkód mojemu programowi, czy mogę przypadkowo sięgnąć poza pamięć mojego programu? Mam na myśli OSX.
ChrisD

@ChrisD: OS X to nowoczesny system operacyjny, więc zapewni pełną ochronę pamięci. Np. Nie powinieneś ograniczać się do tego, co twój program może robić. Nie powinno to obejmować bałaganu z innymi procesami (chyba że korzystasz z uprawnień roota).
che

Wolę powiedzieć, że masz przywileje 0, a nie uprawnienia root.
Ruslan

Bardziej interesujące jest to, że hiper-nowoczesne kompilatory mogą zdecydować, że jeśli próbuje kod do odczytania foo[0]przez foo[len-1]po poprzednio używany sprawdzenie lenz długością tablicy albo wykonać lub pominąć kawałek kodu, kompilator powinien czuć się swobodnie uruchomić ten inny kod bezwarunkowo nawet jeśli aplikacja posiada pamięć poza tablicą, a skutki odczytu byłyby łagodne, ale efekt wywołania innego kodu nie byłby.
supercat

8

Ty piszesz:

Czytam dużo „wszystko może się zdarzyć”, „segmentacja może być najmniejszym problemem”, „twój dysk twardy może stać się różowy, a jednorożce śpiewają pod twoim oknem”, co jest miłe, ale jakie jest naprawdę niebezpieczeństwo?

Ujmijmy to w ten sposób: załaduj broń. Skieruj go za okno bez żadnego szczególnego celu i ognia. Jakie jest niebezpieczeństwo?

Problem polega na tym, że nie wiesz. Jeśli twój kod zastąpi coś, co powoduje awarię programu, nic ci nie jest, bo zatrzyma go w określonym stanie. Jeśli jednak się nie zawiesi, problemy zaczną się pojawiać. Które zasoby są pod kontrolą Twojego programu i co może z nimi zrobić? Które zasoby mogą przejąć kontrolę nad twoim programem i co może z nimi zrobić? Znam co najmniej jeden poważny problem spowodowany takim przepełnieniem. Problem polegał na pozornie nieistotnej funkcji statystycznej, która pomieszała niepowiązaną tabelę konwersji dla produkcyjnej bazy danych. Rezultatem było potem bardzo drogie czyszczenie. W rzeczywistości byłoby to znacznie tańsze i łatwiejsze w obsłudze, gdyby ten problem sformatował dyski twarde ... innymi słowy: różowe jednorożce mogą być twoim najmniejszym problemem.

Idea, że ​​Twój system operacyjny będzie cię chronić, jest optymistyczna. Jeśli to możliwe, staraj się unikać pisania poza granicami.


ok, właśnie tego się obawiałem. Będę „starał się unikać pisania poza granice”, ale biorąc pod uwagę to, co robiłem przez ostatnie kilka miesięcy, na pewno będę to robić nadal. Jak udało wam się tak dobrze programować bez bezpiecznego sposobu na ćwiczenie?
ChrisD

3
Kto powiedział, że wszystko jest bezpieczne;)
Udo Klein

7

Brak uruchamiania programu jako użytkownik root lub inny uprzywilejowany użytkownik nie zaszkodzi żadnemu systemowi, więc ogólnie może to być dobry pomysł.

Zapisując dane w jakiejś przypadkowej lokalizacji w pamięci, nie uszkodzisz bezpośrednio żadnego innego programu uruchomionego na twoim komputerze, ponieważ każdy proces działa w swoim własnym obszarze pamięci.

Jeśli spróbujesz uzyskać dostęp do pamięci nieprzydzielonej procesowi, system operacyjny zatrzyma działanie programu z błędem segmentacji.

Tak więc bezpośrednio (bez uruchamiania jako root i bezpośredniego dostępu do plików takich jak / dev / mem) nie ma niebezpieczeństwa, że ​​Twój program zakłóci działanie dowolnego innego programu działającego w twoim systemie operacyjnym.

Niemniej jednak - i prawdopodobnie o tym słyszałeś w kategoriach zagrożenia - przez przypadkowe zapisanie przypadkowych danych w losowych lokalizacjach pamięci przez przypadek, możesz z pewnością uszkodzić wszystko, co możesz uszkodzić.

Na przykład twój program może chcieć usunąć określony plik podany przez nazwę pliku zapisaną gdzieś w twoim programie. Jeśli przypadkowo zastąpisz lokalizację, w której jest przechowywana nazwa pliku, możesz zamiast tego usunąć zupełnie inny plik.


1
Jeśli jest uruchomiony jako root (lub innego uprzywilejowanego użytkownik), choć uważaj. Przepełnienia buforów i macierzy są powszechnym exploitem złośliwego oprogramowania.
John Bode

w rzeczywistości konto, z którego korzystam przez cały dzień, nie jest kontem administratora (używam terminologii OSX, ponieważ jest to mój system). Chcesz mi powiedzieć, że nie mogę nic uszkodzić, próbując ustawić DOWOLNĄ lokalizację pamięci? To naprawdę dobra wiadomość!
ChrisD

Jak już wspomniano wcześniej, najgorszą szkodą, jaką możesz wyrządzić przypadkowo, jest najgorsza szkoda, jaką możesz wyrządzić jako użytkownik. Jeśli chcesz mieć 100% pewności, że nie niszczysz żadnych danych, prawdopodobnie możesz dodać inne konto do swojego komputera i eksperymentować z tym.
mikyra

1
@mikyra: To prawda, tylko jeśli mechanizmy ochronne systemu są w 100% skuteczne. Istnienie złośliwego oprogramowania sugeruje, że nie zawsze możesz na tym polegać. (Nie chcę sugerować, że warto się tym martwić; możliwe, ale mało prawdopodobne, że program mógłby przypadkowo wykorzystać te same luki w zabezpieczeniach wykorzystywane przez złośliwe oprogramowanie).
Keith Thompson

1
Lista zawiera: Uruchamianie kodu z niezaufanych źródeł. Wystarczy kliknąć przycisk OK w dowolnym wyskakującym okienku zapory ogniowej, nie czytając nawet o co chodzi, lub całkowicie go wyłączyć, jeśli nie można nawiązać pożądanego połączenia sieciowego. Łagodzenie plików binarnych najnowszym hakiem z podejrzanych źródeł. Nie jest to wina sklepienia, jeśli właściciel dobrowolnie zaprosi włamywacza z obiema rękami i bardzo silnymi wzmocnionymi drzwiami szeroko otwartymi.
mikyra

4

NSArrayW Objective-C przypisano określony blok pamięci. Przekroczenie granic tablicy oznacza, że ​​miałbyś dostęp do pamięci, która nie jest przypisana do tablicy. To znaczy:

  1. Ta pamięć może mieć dowolną wartość. Nie ma możliwości sprawdzenia, czy dane są prawidłowe na podstawie typu danych.
  2. Ta pamięć może zawierać poufne informacje, takie jak klucze prywatne lub inne dane uwierzytelniające użytkownika.
  3. Adres pamięci może być nieprawidłowy lub chroniony.
  4. Pamięć może mieć zmienną wartość, ponieważ jest dostępna dla innego programu lub wątku.
  5. Inne rzeczy używają przestrzeni adresowej pamięci, takie jak porty mapowane w pamięci.
  6. Zapisywanie danych pod nieznanym adresem pamięci może spowodować awarię programu, zastąpić przestrzeń pamięci systemu operacyjnego i generalnie spowodować implode słońca.

Z punktu widzenia programu zawsze chcesz wiedzieć, kiedy kod przekracza granice tablicy. Może to spowodować zwrócenie nieznanych wartości, co spowoduje awarię aplikacji lub podanie nieprawidłowych danych.


NSArraysmają wyjątki poza granicami. I te pytania wydają się dotyczyć tablicy C.
DrummerB

Rzeczywiście miałem na myśli tablice C. Wiem, że jest NSArray, ale na razie większość moich ćwiczeń odbywa się w C
ChrisD

4

Możesz spróbować użyć memchecknarzędzia w Valgrind podczas testowania kodu - nie wykryje naruszeń granic tablicy w ramce stosu, ale powinno wykryć wiele innych problemów z pamięcią, w tym powodujących subtelne, szersze problemy poza zakresem jednej funkcji.

Z instrukcji:

Memcheck to wykrywacz błędów pamięci. Może wykryć następujące problemy, które są typowe w programach C i C ++.

  • Uzyskując dostęp do pamięci, nie powinieneś np. Przekraczać i zmniejszać bloków sterty, przekraczać górną część stosu i uzyskiwać dostęp do pamięci po jej zwolnieniu.
  • Używanie niezdefiniowanych wartości, tj. Wartości, które nie zostały zainicjowane lub które zostały uzyskane z innych niezdefiniowanych wartości.
  • Niepoprawne zwolnienie pamięci sterty, takie jak podwójne zwalnianie bloków sterty lub niedopasowane użycie malloc / new / new [] kontra free / delete / delete []
  • Nakładające się wskaźniki src i dst w memcpy i powiązanych funkcjach.
  • Wycieki pamięci

ETA: Chociaż, jak mówi odpowiedź Kaz, nie jest to panaceum i nie zawsze daje najbardziej pomocne wyniki, szczególnie gdy używasz ekscytujących wzorców dostępu.


Podejrzewam, że analizator XCode znalazłby większość tego? a moje pytanie nie polega na tym, jak znaleźć te błędy, ale wykonanie programu, który nadal zawiera te błędy, jest niebezpieczne dla pamięci nieprzydzielonej do mojego programu. Będę musiał uruchomić program, aby zobaczyć występujące błędy
ChrisD

3

Jeśli kiedykolwiek wykonujesz programowanie na poziomie systemu lub programowanie w systemach wbudowanych, bardzo złe rzeczy mogą się zdarzyć, jeśli zapiszesz w losowych lokalizacjach pamięci. Starsze systemy i wiele mikrokontrolerów używają IO zamapowanego w pamięci, więc zapis w lokalizacji pamięci, która jest mapowana do rejestru peryferyjnego, może siać spustoszenie, szczególnie jeśli odbywa się to asynchronicznie.

Przykładem jest programowanie pamięci flash. Tryb programowania na układach pamięci jest włączony poprzez zapisanie określonej sekwencji wartości w określonych lokalizacjach w zakresie adresów układu. Gdyby inny proces zapisywał w dowolnym miejscu w układzie podczas tego procesu, spowodowałoby to błąd cyklu programowania.

W niektórych przypadkach sprzęt zawija adresy (najbardziej znaczące bity / bajty adresu są ignorowane), więc zapis na adres poza końcem fizycznej przestrzeni adresowej spowoduje, że dane będą zapisywane w samym środku rzeczy.

I wreszcie, starsze procesory, takie jak MC68000, mogą zostać zablokowane do tego stopnia, że ​​tylko reset sprzętowy może je przywrócić. Nie pracowałem nad nimi przez kilka dziesięcioleci, ale myślę, że to wtedy wystąpił błąd magistrali (nieistniejąca pamięć) podczas próby obsługi wyjątku, po prostu zatrzymałby się, dopóki nie zostanie przywrócony reset sprzętowy.

Moim największym zaleceniem jest rażąca wtyczka do produktu, ale nie interesuję się nim osobiście i nie jestem z nim w żaden sposób związany - ale w oparciu o kilka dekad programowania C i systemów wbudowanych, w których niezawodność była krytyczna, komputer Gimpel Lint nie tylko wykryje tego rodzaju błędy, ale także sprawi, że stanie się lepszym programistą C / C ++, stale na ciebie napominając o złych nawykach.

Polecam również przeczytanie standardu kodowania MISRA C, jeśli możesz kogoś sfotografować. Nie widziałem żadnych ostatnich, ale w dawnych czasach dobrze wyjaśnili, dlaczego powinieneś / nie powinieneś robić rzeczy, które obejmują.

Nie wiem o tobie, ale około 2 lub 3 raz dostaję rdzeń lub zawieszenie z dowolnej aplikacji, moja opinia o każdej firmie, która go wyprodukowała, spada o połowę. Czwarty lub piąty raz i jakakolwiek jest paczka, staje się półką, a ja wbijam drewniany kołek przez środek paczki / dysku, który przyszedł, aby upewnić się, że nigdy nie wróci, by mnie prześladować.


W zależności od systemu odczyty poza zakresem mogą również wyzwalać nieprzewidziane zachowanie lub mogą być łagodne, chociaż łagodne zachowanie sprzętowe przy obciążeniach poza zasięgiem nie oznacza łagodnego zachowania kompilatora.
supercat

2

Pracuję z kompilatorem układu DSP, który celowo generuje kod, który uzyskuje dostęp do końca tablicy poza kodem C, co nie!

Wynika to z faktu, że pętle są tak skonstruowane, że koniec iteracji poprzedza niektóre dane do następnej iteracji. Tak więc dane wstępnie wybrane na końcu ostatniej iteracji nigdy nie są faktycznie używane.

Pisanie takiego kodu C wywołuje niezdefiniowane zachowanie, ale jest to tylko formalność z dokumentu standardowego, który dotyczy samego siebie z maksymalną przenośnością.

Częściej nie, program, który uzyskuje dostęp poza granicami, nie jest sprytnie zoptymalizowany. To jest po prostu błąd. Kod pobiera pewną wartość śmieci i, w przeciwieństwie do zoptymalizowanych pętli wyżej wspomnianego kompilatora, kod następnie wykorzystuje tę wartość w kolejnych obliczeniach, powodując w ten sposób ich uszkodzenie.

Warto wychwytywać takie błędy, dlatego warto sprawić, by zachowanie było niezdefiniowane tylko z tego tylko powodu: aby czas działania mógł wygenerować komunikat diagnostyczny, taki jak „przepełnienie tablicy w linii 42 main.c”.

W systemach z pamięcią wirtualną może się zdarzyć, że tablica zostanie przydzielona w taki sposób, że następujący adres znajduje się w niezmapowanym obszarze pamięci wirtualnej. Dostęp wtedy bombarduje program.

Nawiasem mówiąc, zauważmy, że w C możemy utworzyć wskaźnik, który znajduje się za końcem tablicy. I ten wskaźnik musi być większy niż jakikolwiek wskaźnik do wnętrza tablicy. Oznacza to, że implementacja C nie może umieścić tablicy bezpośrednio na końcu pamięci, gdzie adres plus będzie zawijał się i wyglądałby na mniejszy niż inne adresy w tablicy.

Niemniej jednak dostęp do niezainicjowanych lub poza granicami wartości jest czasem ważną techniką optymalizacji, nawet jeśli nie jest maksymalnie przenośna. Jest to na przykład powód, dla którego narzędzie Valgrind nie zgłasza dostępu do niezainicjowanych danych, gdy te dostępy się zdarzają, ale tylko wtedy, gdy wartość jest później wykorzystywana w jakiś sposób, który mógłby wpłynąć na wynik programu. Otrzymujesz komunikat diagnostyczny, taki jak „gałąź warunkowa w xxx: nnn zależy od niezainicjowanej wartości” i czasami może być trudno wyśledzić, skąd pochodzi. Gdyby wszystkie takie dostępy zostały natychmiast uwięzione, pojawiłoby się wiele fałszywych alarmów wynikających z kodu zoptymalizowanego przez kompilator, a także kodu zoptymalizowanego ręcznie.

Mówiąc o tym, pracowałem z jakimś kodekiem od dostawcy, który dawał te błędy, gdy był przenoszony do Linuksa i działał pod Valgrind. Ale sprzedawca przekonał mnie, że tylko kilka bitówużyta wartość faktycznie pochodzi z niezainicjowanej pamięci, a logika ostrożnie uniknęła tych bitów. Użyto tylko dobrych bitów wartości, a Valgrind nie ma możliwości wyśledzenia pojedynczego bitu. Niezainicjowany materiał powstał po przeczytaniu słowa za końcem strumienia bitów zakodowanych danych, ale kod wie, ile bitów znajduje się w strumieniu i nie zużyje więcej bitów, niż jest w rzeczywistości. Ponieważ dostęp poza końcem tablicy strumienia bitów nie powoduje żadnej szkody w architekturze DSP (po tablicy nie ma pamięci wirtualnej, nie ma portów mapowanych w pamięci, a adres się nie zawija), jest to ważna technika optymalizacji.

„Niezdefiniowane zachowanie” nie znaczy tak naprawdę wiele, ponieważ według ISO C, po prostu włączenie nagłówka, który nie jest zdefiniowany w standardzie C, lub wywołanie funkcji, która nie jest zdefiniowana w samym programie lub standardzie C, są przykładami niezdefiniowanymi zachowanie. Niezdefiniowane zachowanie nie oznacza „niezdefiniowane przez nikogo na planecie”, tylko „niezdefiniowane przez normę ISO C”. Ale oczywiście czasami nieokreślone zachowanie nie jest absolutnie przez nikogo zdefiniowane.


Dodatkowo, pod warunkiem, że istnieje co najmniej jeden program, który konkretna implementacja poprawnie przetwarza, nawet jeśli nominalnie opodatkuje wszystkie limity implementacji podane w standardzie, implementacja ta może zachowywać się arbitralnie, gdy zostanie zasilony jakikolwiek inny program, który jest wolny od naruszeń ograniczeń i nadal jest „ zgodny". W konsekwencji 99,999% programów C (cokolwiek innego niż „jeden program” platformy) opiera się na zachowaniach, w których Standard nie nakłada żadnych wymagań.
supercat

1

Poza własnym programem, nie sądzę, że cokolwiek zepsujesz, w najgorszym przypadku spróbujesz odczytać lub napisać z adresu pamięci, który odpowiada stronie, której jądro nie przypisało twoim procesom, generując odpowiedni wyjątek i bycie zabitym (mam na myśli twój proces).


3
..Co? A co powiesz na zastąpienie pamięci w swoim własnym procesie używanym do przechowywania zmiennej używanej później ... która teraz tajemniczo zmieniła swoją wartość! Zapewniam, że te błędy to świetna zabawa. Segfault byłby najlepszym rezultatem. -1
Ed S.

2
Mam na myśli, że nie „przerwie” innych procesów poza własnym programem;)
jbgs

Naprawdę nie obchodzi mnie, czy zepsuję własny program. Właśnie się uczę, program i tak jest oczywiście błędny, jeśli uzyskuję dostęp do czegokolwiek spoza mojej tablicy. Coraz bardziej martwię się o ryzyko
zepsucia

Rzecz w tym: czy mogę być pewien, że jeśli spróbuję uzyskać dostęp do nieprzypisanej mi pamięci, że mój proces zostanie zabity? (będąc na OSX)
ChrisD

3
Wiele lat temu byłem niezdarnym programistą C. Uzyskiwałem dostęp do tablic poza ich granicami setki razy. Poza tym, że mój proces został zabity przez system operacyjny, nic się nigdy nie wydarzyło.
jbgs
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.