W przykładzie poniżej kompilator nie zaprotestuje, gdy zrzutujemy obiekt klasy Base na klasę Derived. Problem w tym, że obiekt base rezerwuje mniej pamięci (nie ma pola value_b) niż obiekt derived. W konsekwencji, pisane do pola value_b, będzie skutkowało pisaniem po pamięci.
#include <iostream> using namespace std; struct Base { void fun() { printf("Base method\n"); } int value_a; }; struct Derived : public Base { void fun() { printf("Derived method\n"); } int value_b; }; int main() { Base* base = new Base{}; Derived* derived = static_cast<Derived*>(base); // no-error! derived->value_a = 1; derived->value_b = 2; // pisanie po pamięci! cout << derived->value_a << endl; cout << derived->value_b << endl; }Tutaj dopisało nam szczęści, program wykonał się prawidłowo.
$ clang++ -std=c++17 main.cpp $ ./a.out 1 2Sprawa wygląda inaczej, gdy dołączymy address sanitizer.
$ clang++ -std=c++17 -fsanitize=address main.cpp $ ./a.out ==23018==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x602000000014 at pc 0x0000004c63ba bp 0x7fff3301c9b0 sp 0x7fff3301c9a8 WRITE of size 4 at 0x602000000014 thread T0 ...
Sanitizer potrafi wykryć tego typu problem, tylko gdy zaczynamy pisać po nie swojej pamięci. Naukowcy: Byoungyoung Lee, Chengyu Song, Taesoo Kim i Wenke Lee z Georgia Institute of Technology, stworzyli jeszcze inne narzędzie do detekcji tego typu problemów. Oparte na LLVM, dodaje do każdego static_cast tablicę z informacjami o hierarchii dziedziczenia (czyli coś w rodzaju tego co posiada dynamic_cast) i w zgrabny sposób informuje gdzie obiekt został stworzony i gdzie źle rzutowany, gdy tylko takie rzutowanie nastąpi. Za swoją pracę zostali nagrodzeni przez Facebooka fajną nagrodą pieniężną.
- https://www.facebook.com/notes/protect-the-graph/doubling-the-internet-defense-prize-at-usenix-security-15/1634464803460331
- https://www.usenix.org/conference/usenixsecurity15/technical-sessions/presentation/lee
Brak komentarzy:
Prześlij komentarz