Trudne przypadki: buffer overflow

Buffer overflow, czyli przepełnienie bufora jest jednym z najczęściej wykrywanych błędów w różnej maści oprogramowaniu. Błąd ten polega na zapisaniu do wyznaczonego obszaru pamięci większej ilości danych, niż zostało to przewidziane przez twórcę, powodując nadpisanie sąsiadujących z buforem komórek pamięci, co w ostateczności prowadzi do rożnych nieprzewidywanych zachowań aplikacji.

W najlepszym wypadku spowoduje to "wywalenie" się całego programu, a w najgorszym umożliwi przejecie kontroli nad maszyną przez włamywacza, który odpowiednio wykorzysta zaistniałą sytuację.

Nie będziemy sie tu zbytnio rozwodzić nad typowymi błędami przepełnienia, które w dosyć prosty sposób można znaleźć i wyeliminować. W sieci można znaleźć wiele ciekawych materiałów dotyczących tego tematu. Polecić mogę artykuł "Po prostu Buffer Overflow" autorstwa h07, który może rzucić nieco więcej światła tym osobom, którzy nie bardzo orientują sie w temacie BO i sposobów ich wykorzystania.

Tutaj chciałbym sie podzielić przypadkiem dosyć specyficznym, który może sprawić troszkę więcej problemów w czasie działania i próbach znalezienia jego wystąpienia w kodzie, czyli tytułowe trudne przypadki, trudne do wykrycia.

Z jednym z tych specyficznych przypadków miałem styczność w czasie tworzenia mojego ostatniego patcha do wxWidgets, dodającego wsparcie dla themes w owner-drawn menu.

Tam właśnie miałem przyjemność trochę pomęczyć się z tym błędem, którego tutaj chce przedstawić.

Naszą historyjkę zacznijmy od początku.

Mamy krotki fragment działającego do tej pory kodu, który pobierał jakaś sobie jakaś wartość:

bool value;
GetValue(&value);

gdzie GetValue jest jakąś funkcją WinAPI-owską o następującym prototypie:

BOOL GetValue(BOOL* value);

Po małych zmianach, powyższy kod został przeniesiony do wnętrza pewnej struktury:

struct A {
	// ...
	bool value;
};

i wszystko nadal działało poprawnie.

Niestety do czasu, aż zmieniono nieco zawartość struktury A, dodając kolejne pole typu boolowskiego do struktury - value2, zaraz za naszym value. I wtedy zaczęły się dziać dziwne rzeczy.

Obie zmienne bool zaczęły zawierać różne wartości, niekoniecznie takie jakie były spodziewane. Szczególnie odczyt wartości przez funkcje GetValue modyfikował nie tylko zmienna value, ale także value2.

Wpływ na takie zachowanie ma zjawisko structure alignment, czyli wyrównywanie pól struktury do granicy słowa (dwusłowa). Więcej informacji o tym zjawisku można znaleźć choćby na wikipedii lub msdnie.

Przy standardowych ustawieniach kompilatora, adresy obu zmiennych boolowskich będą wskazywały na sąsiednie bajty pamięci. Oczywiście to stwierdzenie nie było żadną nowością i nadal nic nie mówiło dlaczego to nie działa.

Dopiero po upewnieniu się że jest tak na pewno, że wyrównanie działa tak jak powinno i kompilator nic nie "miesza", wpadła do głowy myśl, aby sprawdzić co tak naprawdę robi funkcja GetValue.

Po sprawdzeniu jej nagłówka wszystko stało się jasne. Funkcja przyjmuje jako argument wskaźnik do zmiennej typu BOOL, który w rzeczywistości w WinAPI jest liczbą całkowitą:

typedef int                 BOOL;
#define FALSE               0
#define TRUE                1

A zatem podając adres do naszej zmiennej value, modyfikujemy 32-bity (x86) począwszy od komórki, na którą wskazuje podany adres (0x0018FF38).

bo

Zatem przypisanie dowolnej wartości do zmiennej value przez funkcję GetValue w rzeczywistości wyzeruje wszytskie bity także value2, co interpretowane będzie jako wartość false, czyli mamy klasyczny przypadek buffer overflow, tyle że trochę trudny do wykrycia na pierwszy rzut oka.

Trochę czasu zajęło mi dojście do tego rozwiązania, po drodze sprawdzając wiele razy kod i przeprowadzając różne testy.

Problem o tyle trudny do znalezienia, że kod ten działał prawidłowo do tej pory i był używany przez bibliotekę, więc nawet nie przeszła mi przez głowę myśl, aby przy przenoszeniu tego fragmentu sprawdzić, czy zastanowić się nad prawidłowością wywołania danej funkcji.

Nasuwa sie również pytanie, skoro kod ten działa do tej pory, mimo iż zawierał błąd przepełnienia bufora, co tak naprawdę po drodze zostawało nadpisywane i czym to skutkowało przy używaniu biblioteki? Mogło to mieć wpływ na zupełnie inna cześć biblioteki, a nikt nie był tego świadomy.

2 przemyślenia nt. „Trudne przypadki: buffer overflow”

  1. Błąd ciekawy z gatunku wrednych.

    A czy kompilator nie rzucił warniga przy rzutowaniu bool* na BOOL* ?

    1. Poziom warningow byl chyba zbyt niski w projekcie ;) No i kod byl uzywany do tej pory, wiec zaufalem i zbytnio nie zwrocilem uwagi na jego bebechy przy przenoszeniu.

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *