NFsec Logo

Race conditions

29/03/2013 w Bezpieczeństwo Brak komentarzy.  (artykuł nr 402, ilość słów: 1028)

R

ace conditions (sytuacje wyścigu) – jak sama nazwa wskazuje, głównym założeniem tego zjawiska jest niepożądana sytuacja, która występuje, gdy urządzenie lub układ próbuje wykonać dwie lub więcej operacji w tym samym czasie, ale ze względu na charakter systemu lub urządzenia czynności te powinny być wykonywane w odpowiedniej kolejności, aby były wykonane poprawnie. W odniesieniu do programów w systemie operacyjnym można przedstawić to następująco: program zakłada, że dane które pobrał / otrzymał przed chwilą wciąż są aktualne. Aplikacja zakłada, że dane znajdujące się w pliku, czy zmiennej są takie jak kilka chwil wcześniej – a przecież tak naprawdę mogły ulec w międzyczasie zmianie poprzez inne procesy czy wątki. Typowy przykład wykorzystania sytuacji wyścigu:

Program nie przewidział, iż plik może w międzyczasie (po odczytaniu z niego informacji, a przed jego usunięciem) stać się np. linkiem do pliku /etc/shadow!. Innym przykładem może być użycie polecenia chmod, chown, chgrp i rm w połączeniu ze znacznikami -R lub --recursive. Kiedy wykonywane jest np. rekurencyjne usuwanie drzewa katalogów z opcją -r może wystąpić zjawisko wyścigu. Jeżeli zwyczajny użytkownik posiada prawa zapisu do drzewa katalogów, może dodać dowiązanie symboliczne wskazujące inne miejsce (na przykład partycja /root), wskutek czego polecenie rm odpalone z prawami administratora przejdzie do tego miejsca. Celem tego wyścigu jest określony wpis w usuwanym drzewie katalogów. Załóżmy, że taki wpis nosi nazwę narf. Polecenie rm wykonuje wywołanie funkcji lstat("narf", buf) dla tego wpisu; jeżeli jest to katalog, zostanie wykonane polecenie: chdir("narf"). Jeżeli złośliwy użytkownik / włamywacz zdąży wykonać pokazane tu polecenie (ln -s / narf) pomiędzy operacjami lstat() i chdir(), wygra wyścig. Innym przykładem może być wskazanie pliku /etc/passwd podczas wykonywania polecenia chmod itp. Najprostszym sposobem uniknięcia tego typu problemów jest używanie poleceń chmod i chown dla katalogu najwyższego poziomu, na którym będzie wykonywana dana operacja – tak by tylko użytkownik root posiadał prawo do tego katalogu. Po wykonaniu operacji można przywrócić pierwotne prawa.

/tmp races (wyścigi w /tmp) – jest to popularny rodzaj sytuacji wyścigu sprowadzający się do działania w ogólnie dostępnym katalogu /tmp. Opiera się on na fakcie, że niektóre programy do poprawnego działania potrzebują możliwości zakładania plików tymczasowych. Jeżeli nazwę używanego pliku tymczasowego można przewidzieć lub zgadnąć to w środowisku wielozadaniowym można przeprowadzić tego rodzaju atak. Zakładając, że wiemy, że użytkownik root często korzysta z programu narf, a program podczas pracy zakłada plik tymczasowy o nazwie /tmp/narf.tmp$$ (gdzie $$ jest PIDem programu). W ten sposób możemy przewidzieć lub stworzyć wystarczająco dużo linków symbolicznych i poinformować system, że np. /tmp/narf.tmp123 jest inną nazwą pliku /etc/shadow, wydając polecenie: ln -s /etc/shadow /tmp/narf.tmp123. Jeżeli teraz administrator zaloguje się w systemie i uruchomi swój ulubiony program narf, nadpisze zawartość pliku /tmp/narf.tmp123 i tym samym /etc/shadow danymi, które z pewnością nie będą zgodne z formatem pliku do przetrzymywania zaszyfrowanych haseł. W ten sposób system będzie zamknięty dla każdego użytkownika, który będzie próbował się zalogować. Jednak bardzo rzadko zdarza się (pomijając błędy programistów i skrypty początkujących administratorów), żeby nazwa pliku tymczasowego była ustalona z góry na jedną konkretną wartość. Najczęściej używa się “losowych” nazw generowanych na przykład za pomocą funkcji bibliotecznej mktemp(). Można powiedzieć, że ciąg znaków jest losowy, ale w większości systemów operacyjnych to nie do końca prawda – zazwyczaj można przewidzieć, jaką nazwę zaproponuje mktemp() przy następnym wywołaniu. Jednak zanim mktemp() uzna, że jakaś nazwa jest dobra, sprawdza, czy nie istnieje już plik o tej nazwie. Po upewnieniu się, że go nie ma zwraca proponowaną nazwę (co powinno również robić się w prostych skryptach napisanych w powłoce bash). W przypadku programów używających mktemp(), aby skutecznie zaatakować, trzeba być szybszym i zmieścić się pomiędzy dokonywanym przez funkcję sprawdzeniem, czy dany plik już istnieje, a próbą założenia takiego pliku przez program, który jest atakowany. Typowy wyścig.

Oprócz operacji na plikach możemy odwołać się do wątków procesu. Systemy Uniksopodobne – w tym Linux wpierają procesy wielu użytkowników, a każdy proces ma własny obszar pamięci – zwykle nietykalny przez inne procesy. Podstawowym zadaniem jądra każdego systemu jest stworzenie wrażenia, że procesy mogą działać jednocześnie. W systemach wieloprocesorowych ich jednoczesna praca jest na prawdę możliwa. Hipotetycznie proces ma jeden lub więcej wątków, które współdzielą pomiędzy sobą pamięć (zasoby / dane itd.). Wątki również posiadają możliwość jednoczesnego działania, a że posiadają możliwość współdzielenia zasobów mają o wiele lepsze warunki i więcej możliwości do przeprowadzania sytuacji wyścigu niż ma to miejsce między poszczególnymi procesami (dlatego m.in. wielowątkowe programy są o wiele trudniejsze do debugowania). Jądro Linuksa ma elegancko zaprojektowane od podstaw podejście do wątków: istnieją takie wątki, które wykonują inne wątki i współdzielą pomiędzy sobą zasoby, natomiast wątki, które wykonują oddzielne procesy już tej możliwości nie posiadają. Prosty przykład wykonania kodu:

b = b + 1;

Wygląda bardzo prosto prawda? Lecz załóżmy, że istnieją dwa wątki wykonujące tą samą linie kodu, gdzie zmienna b jest współdzielona przez dwa wątki, a jej wartość zaczyna się od liczby 5. Jeden z scenariuszów kolejności wykonania tego kodu może być następujący:

[wątek1] - załaduj b w swój rejestr 
   [wątek2] - załaduj b w swój rejestr
[wątek1] - dodaj 1 do załadowanej wartości b = 6
   [wątek2] - dodaj 1 do załadowanej wartości b = 6
[wątek1] - zaktualizuj rejestr dla wartości b (6)
   [wątek2] - zaktualizuj rejestr dla wartości b (6)

Wystartowaliśmy z wartością 5 dla zmiennej b. Każdy z wątków dodał po jednej wartości, ale ostatecznym wynikiem okazała się liczba 6, a nie jak oczekiwaliśmy 7. Problemem w tym przypadku jest to, że dwa wątki zamiast jeden po drugim wykonać kod i dodać odpowiednie wartości do współdzielonej zmiennej wykonały sytuacje wyścigu i wtrąciły swoje wartości jednocześnie powodując zwrócenie błędnego wyniku. Ogólnie wątki nie wykonują się w sposób atomowy – wykonując wszystkie pojedyncze operacje na raz. Jeśli wątek aplikacji nie jest odpowiednio przygotowany to inny wątek może zakłócić lub przerwać proces pomiędzy dwoma instrukcjami tego wątku i zmanipulować współdzielone zasoby. Dlatego w poprawnie i bezpiecznie skonstruowanej aplikacji każda para operacji powinna być wykonywana poprawnie – nawet jeśli kod wykonywany jest przez dowolną ilość jej wątków. Najważniejszym jest, aby określić, kiedy program ma mieć dostęp do wszelkich zasobów, jeśli inny wątek może mieć wpływ na ich zawartość i dokonać sytuacji wyścigu.

Więcej informacji: Secure programmer: Prevent race conditions, Race condition

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

Tagi T a g i :

Komentowanie tego wpisu jest zablokowane.