Czym jest stos programu?

1

Witajcie

Ostatnio czytałem sobie o atakach komputerowych (przepełnienie stosu) i w ogóle jak należy pisać bezpieczne aplikacje.
Zdałem sobie sprawę że na dobrą sprawę nie specjalnie go rozumiem - ani czym jest ani jak działa. W związku z tym mam kilka pytań:

1)Czym jest stos?
Czy jest po prostu miejscem w pamięci o sztywno określonym rozmiarze (np 1 MB) ?

  1. Jaki jest jego maksymalny rozmiar ? Padały różne odpowiedzi od 64 KB do 1GB (<?> rzekomo od ustawień konsolidatora to zależy </?>)
    3)Czy stos jest jeden w całym programie czy też może na przykład wątek ma swój własny stos?
    4)Czy jeżeli mam moduł (dll/so) to on współdzieli tylko sterte czy tez jeszcze stos.
    5)Skoro mam maksymalny rozmiar stosu to znaczy ze istnieje okreslona liczba obiektow/ zmiennych jakie mogę posiadać ?
    Zakładając że rozmiar stosu to 1MB to oznacza że wszystkie zmienne oraz obiekty klas mogą razem zająć 1MB (zakładając nieużywanie sterty (new/delete) ) ?
  2. Co to adres powrotu funkcji ?
    7)Od czego rozmiar stosu zależy - czy można go sobie zmiejszyć lub powiększyć za pomocą opcji w kompilatorze / konsolidatorze ?
    Spotkałem się ze zdaniem :

"Stack data - 1GB (the stack size is set by the linker, the default is 1MB. This can be increased using the Linker property System > Stack Reserve Size)."

  1. Jak się zabezpieczyć przed przepełnieniem stosu - szczególnie odnośnie tego : http://osworld.pl/ghost-14-letni-blad-w-bibliotece-glibc-na-linuksie/
    Czy wystarczy coś takiego ?
    PSEUDO KOD:
    IP_Address getHostByName(const std::string & hostName)
    {
         if(hostName.size() > maxSizeOfName)abortFunction();
 
         translateName();
    }
2

A jak już przeczytasz te linki powyżej to:

kacper546 napisał(a):

1)Czym jest stos?
Czy jest po prostu miejscem w pamięci o sztywno określonym rozmiarze (np 1 MB) ?

To jest miejsce w pamięci. Może mieć sztywno określony rozmiar a może rosnąć (poprzez syscalle)

kacper546 napisał(a):
  1. Jaki jest jego maksymalny rozmiar ? Padały różne odpowiedzi od 64 KB do 1GB (<?> rzekomo od ustawień konsolidatora to zależy </?>)

Jak wyżej. Rozmiar stosu zależy od tego jak został stworzony wątek. W linuksie jeżeli ktoś sam ręcznie wykonał syscalla clone i przekazał zaalakowaną pamięc, to stos ogranicza się do tej pamięci. Oczywiscie żeby to wymusić i wywalić proces jak zacznie pisać za daleko to trzeba zastosować odpowiednią sztuczkę (zaalokować więcej, oznaczyć strony przed i za właściwym stosem tylko do odczytu).

kacper546 napisał(a):

3)Czy stos jest jeden w całym programie czy też może na przykład wątek ma swój własny stos?

Jak wyżej. Każdy wątek ma swój stos.

kacper546 napisał(a):

4)Czy jeżeli mam moduł (dll/so) to on współdzieli tylko sterte czy tez jeszcze stos.

Moduł używa stosu z procesu w którym jest używany. Ze stertą , tu można już namieszać ... Zależy jak była alkowana (mmap).

kacper546 napisał(a):

5)Skoro mam maksymalny rozmiar stosu to znaczy ze istnieje okreslona liczba obiektow/ zmiennych jakie mogę posiadać ?
Zakładając że rozmiar stosu to 1MB to oznacza że wszystkie zmienne oraz obiekty klas mogą razem zająć 1MB (zakładając nieużywanie sterty (new/delete) ) ?

Nie do konca. Te alokowane na stosie - tak. Te globalne czy statyczne będą w innym obszarze pamięci, utworzonym podczas ładowania binarki.

kacper546 napisał(a):
  1. Co to adres powrotu funkcji ?

Odpowiedz jest w linkach powyżej.

kacper546 napisał(a):

7)Od czego rozmiar stosu zależy - czy można go sobie zmiejszyć lub powiększyć za pomocą opcji w kompilatorze / konsolidatorze ?
Spotkałem się ze zdaniem :

"Stack data - 1GB (the stack size is set by the linker, the default is 1MB. This can be increased using the Linker property System > Stack Reserve Size)."

Od tego kto ten stos tworzy oraz systemu :). W przypadku jednowątkowego programu podczas kompilacji możemy narzucić rozmair stosu. Przyznam szczerze, że nie pamiętam jak ustawienie tego podczas kompilacji wpływa na wątki inne niż głowny (pewnie ustawia się jakaś zmienna wyznaczająca domyślny rozmiar stosu). Ale system w swoich ustawieniach może mieć narzucony własny maksymalny limit na rozmiar stosu.

kacper546 napisał(a):
  1. Jak się zabezpieczyć przed przepełnieniem stosu - szczególnie odnośnie tego : http://osworld.pl/ghost-14-letni-blad-w-bibliotece-glibc-na-linuksie/

Np poprzez kanarka. http://duartes.org/gustavo/blog/post/epilogues-canaries-buffer-overflows/
Kompilatory mają odpowiednie opcje, które włączają mechanizmy stack protectora.

0

2

Stos składa się z ramek stosu. Ramka stosu jest tworzona przy wejściu w funkcję/ metodę/ zwał jak zwał. Przy wyjściu z funkcji jest niszczona. Ramka stosu zawiera adres powrotny z funkcji oraz zmienne lokalne dla funkcji. Ponadto istnieją specjalne funkcje do alokowania na stosie jak alloca ale raczej nie powinno się ich używać (bo np łatwo przepełnić stos i trzeba się pilnować, by po powrocie z funkcji nic nie używało danych z porzuconej ramki stosu). Ramki są tworzone jedna po drugiej, tworzą ciągły blok. Stos jest efektywnie kolejką FIFO - ramki są dokładane na górze i ściągane z góry. Analogia do namacalnego stosu powinna być łatwa do wyobrażenia.

Przepełnienie stosu najłatwiej osiągnąć przez rekurencyjne wywołanie funkcji. Trzeba jednak trochę uważać na to, by kompilator nie zastosował optymalizacji wywołań ogonowych. Optymalizacja wywołań ogonowych polega na zamianie wywołania funkcji (tej ostatniej, ogonowej) na skok oraz na podmianie obecnej ramki stosu na nową zamiast tworzenia tej nowej za obecną (tak jak by się to działo bez optymalizacji). Ta podmiana ramki zamiast tworzenia nowej sprawia, że stos nie rośnie.

Stos jest (jak już wspomniałem) stosem wywołań funkcji. Jest lokalny dla danego wątku (nie ma znaczenia czy w danym momencie wątek jest w głównej binarce czy w bibliotece - dalej stos jest jeden i ten sam dla wątku). Łatwo to zobaczyć w debuggerze - debugger pokazuje listę wątków, a po wybraniu dowolnego wątku dostajemy stos wywołań funkcji. Wchodząc w dane wywołanie funkcji wchodzimy w odpowiadającą mu ramkę stosu i możemy zobaczyć argumenty funkcji i zmienne lokalne - wszystkie te rzeczy są umieszczone w ramkach stosu. Jeżeli w danym wątku wywoływałeś funkcje z bibliotek dołączanych dynamicznie (dll/ so/ whatever) to będziesz to widział w widoku stosu wywołań w debuggerze.

3

Przepełnienie stosu najłatwiej osiągnąć przez rekurencyjne wywołanie funkcji

To raczej mało uzyteczne przepełnienie ;) Ja bym jednak sugerował wykorzystanie kopiowania danych do pamięci bez limitu na rozmiar, podczas gdy przygotowany bufor jest ograniczony. Warto rozumieć że zapisując dane na stosie poza przygotowanym buforem idziemy "w górę" stosu a tam znajduje się między innym adres powrotu z funkcji.
Wynika to z faktu, że w trakcie wywołania funkcji w programie, na poziomie asemblera następuje wrzucenie na stos adresu pod ktorym właśnie byliśmy a potem skok do funkcji. Kiedy dojdziemy do końca funkcji pobieramy adres powrotu i skaczemy tam skąd wołalismy funkcje.
Łatwo zauważyc że nadpisanie stosu moze spowodować także zmianę wartości tego adresu powrotu. W szczególności można go zmienic na przykład na adres funkcji "system" a dodatkowo umieścić na stosie parametr w stylu stringa sh i nagle program zamiast wrócić w miejsce wywołania funkcji wywoła system("sh") dając nam shella.

0

To raczej mało uzyteczne przepełnienie ;) Ja bym jednak sugerował wykorzystanie kopiowania danych do pamięci bez limitu na rozmiar, podczas gdy przygotowany bufor jest ograniczony. Warto rozumieć że zapisując dane na stosie poza przygotowanym buforem idziemy "w górę" stosu a tam znajduje się między innym adres powrotu z funkcji.

No ale wtedy to jest przepełnienie bufora, a nie stosu :)

Żeby przepełnić stos trzeba albo mieć dużo ramek stosu (głęboka rekurencja), albo duże ramki stosu (duże zmienne lokalne). Szerzej opisane np na: https://en.wikipedia.org/wiki/Stack_overflow

Zarejestruj się i dołącz do największej społeczności programistów w Polsce.

Otrzymaj wsparcie, dziel się wiedzą i rozwijaj swoje umiejętności z najlepszymi.