Naruszenie ochrony pamięci
Ten artykuł od 2012-06 wymaga zweryfikowania podanych informacji. |
Naruszenie ochrony pamięci – zdarzenie wykrywane przez sprzęt, polegające na korzystaniu przez program z pamięci poza zaalokowanym dla niego obszarem.
Zwykle wynika to z błędów, czasem jednak jest to świadome działanie programisty – np. program może zalokować mały stos i nie sprawdzać jego przepełnienia, za to kiedy ono nastąpi – i nastąpi naruszenie ochrony pamięci – przechwycić ten sygnał i rozszerzyć stos. Jest to o wiele bardziej efektywne od ciągłego sprawdzania przepełnienia (co musi następować ogromną liczbę razy), oraz od alokacji dużej ilości pamięci na stos (co marnuje pamięć).
Objawy błędu
W systemie Linux w konsoli pojawia się napis „Segmentation fault” opcjonalnie z dodatkowymi informacjami, o ile sygnał SIGSEGV nie zostanie przechwycony przez aplikację.
W systemach Windows aplikacje często w tej sytuacji wyświetlają okno z błędem o treści „EAccess Violation”, „Access Violation” lub informacji o kodzie błędu 0xC0000005. Zdarzają się też przypadki zakończenia aplikacji bez wyświetlenia jakiegokolwiek komunikatu.
Przykłady
Zapis do obszaru pamięci przeznaczonego tylko do odczytu
Zapis do obszaru pamięci, który przeznaczony jest tylko do odczytu powoduje zgłoszenie błędu naruszenia ochrony pamięci. Błąd ten wystąpi również przy próbie zapisu do obszaru, w którym znajduje się kod wykonywalny lub dane przeznaczone tylko do odczytu (np. tablice stałych) lub biblioteki systemowe (np. kernel32.dll).
Poniżej znajduje się fragment kodu napisanego w ANSI C, który powoduje błąd naruszenia ochrony pamięci na platformach, posiadających ochronę pamięci. Pamiętaj, że modyfikowanie stałych łańcuchów tekstowych nie jest zdefiniowane standardem ANSI C, ale większość kompilatorów nie zauważy błędu podczas kompilacji i nie zgłosi błędu ani ostrzeżenia. Uruchomiony program zakończy się błędem.
int main(void)
{
char *s = "hello world";
*s = 'H';
}
Gdy program jest kompilowany stała tekstowa „Hello World” umieszczana jest w sekcji rodata pliku wykonywalnego. Rodata jest częścią segmentu danych przeznaczoną tylko do odczytu. Do zmiennej „s” przypisywany jest wskaźnik na pierwszy znak tekstu „hello world”. Próba zmiany pierwszego znaku, na który wskazuje zmienna „s” kończy się wystąpieniem wyjątku. Uruchomienie programu na systemach Linux i Unix kończy się wyświetleniem poniższego komunikatu (lub podobnego zależnie od konfiguracji systemu):
$ gcc segfault.c -g -o segfault
$ ./segfault
Segmentation fault
Śledzenie wsteczne działania programu przy użyciu gdb zwróci:
Program received signal SIGSEGV, Segmentation fault. 0x1c0005c2 in main () at segfault.c:6 6 *s = 'H';
Powyższy błąd w kodzie może być naprawiony poprzez użycie tablicy w miejsce wskaźnika do znaku (char*
). Spowoduje to za-alokowanie tablicy na stosie oraz przepisanie w ten obszar łańcucha znaków.
char s[] = "hello world";
s[0] = 'H'; // lub również poprawnie: *s = 'H';
Z uwagi, że w C++ łańcuchy znaków są typu const char*
kompilatory powinny wykrywać próbę niejawnej konwersji zmiennej const char*
do typu char*
i zgłosić uwagę podczas kompilacji.
Odwołanie do zerowego adresu pamięci
Ponieważ dosyć częstym błędem jest próba zapisu/odczytu spod zerowego adresu pamięci (wskaźnik zainicjowany wartością NULL – oznaczającą brak obiektu), większość systemów operacyjnych nie alokuje adresu zerowego dla jakichkolwiek danych do odczytu lub zapisu. W systemach tych próba zapisu lub odczytu danych oraz wykonania instrukcji znajdujących się pod adresem zerowym kończy się zgłoszeniem błędu naruszenia ochrony pamięci.
int *ptr = NULL;
printf("%d", *ptr);
Powyższy kod tworzy zerowy wskaźnik i próbuje przeczytać dane, do których on wskazuje. Wykonanie tego kodu spowoduje naruszenie ochrony pamięci na systemach wspierających ochronę pamięci. Podobnie zakończy się próba zapisu z poniższego kodu:
int *ptr = NULL;
*ptr = 1;
Przepełnienie bufora
Przepełnienie stosu
Kolejnym przykładem błędu mogącego doprowadzić do błędu naruszenia ochrony pamięci jest przepełnienie stosu, które można uzyskać rekurencyjnym wywołaniem funkcji nie posiadającym warunku zakończenia:[1]
int main(void)
{
main();
return 0;
}
Powyższy kod z uwagi na włączoną optymalizację w kompilatorze może prowadzić do różnych zachowań w powyższym fragmencie: Wynikowy program wykonywalny może:
- nie zgłosić błędu i wykonywać się w sposób ciągły (usunięcie nieosiągalnego
return 0
oraz zamienienie na iterację) - zgłosić błąd naruszenia ochrony pamięci (gdy ramka stosu nie jest kontrolowana)
- zgłosić błąd przepełnienia stosu, gdy system obsługuje kontrolę ramki stosu.
Zobacz też
Przypisy
Media użyte na tej stronie
A kernel panic in FreeBSD 7.0 running under VirtualBox. The panic was caused by sending SIGSEGV to init, causing it to crash as if it encountered a segmentation fault. Since init is needed for all other processes to start, the kernel panics and a core dump is generated.
Autor: secretlondon123, Licencja: CC BY-SA 2.0
I've seen lots of public computer errors but I've never seen a segmentation fault on debit/credit card reader before. At least they don't run windows!