Inżynierowie z firmy Apple ogłosili gotowość do testowania trybu „-fbounds-safety” dla kompilatora Clang, który zapewnia gwarancje bezpiecznego korzystania z buforów w kodzie napisanym w języku C. Tryb jest częścią forka LLVM, wspieranego przez firmę Apple dla projektu Swift. W przyszłości planowane jest stopniowe przenoszenie funkcjonalności „-fbounds-safety” do głównej bazy kodu LLVM/Clang.
Zauważono, że zaproponowany mechanizm ochrony jest już aktywnie stosowany w produktach Apple, takich jak jądro XNU, oprogramowanie układowe, biblioteki do obsługi dźwięku oraz dekodery obrazów. Włączenie trybu „-fbounds-safety” obniża wydajność aplikacji średnio o 5% (rozrzut od -1% do 29%), zwiększa rozmiar kodu o 9.1% (rozrzut od -1.4% do 38%) oraz spowalnia kompilację o 11%.
Użycie trybu „-fbounds-safety” do automatycznego wykrywania przekroczeń granic obszaru pamięci związanej z wskaźnikiem wymaga dodania do kodu specjalnych adnotacji i włączenia pliku nagłówkowego „ptrcheck.h”. Istotą zaproponowanej metody ochrony jest automatyczne dołączanie kontroli przestrzegania dozwolonych granic, dodawanych na podstawie ręcznie ustawionych adnotacji lub znanych przez kompilator rozmiarów.
W przeciwieństwie do używania w kodzie rozszerzonych wskaźników (wide pointer), które oprócz adresu zawierają informacje o górnej i dolnej granicy bufora, użycie trybu „-fbounds-safety” nie narusza ABI (Application Binary Interface), nie zmienia formatu eksportowanych wskaźników i nie wymaga przerabiania całego projektu naraz. W trybie „-fbounds-safety” rozszerzone wskaźniki stosowane są tylko w obszarach, które nie kolidują z ABI, a dla wskaźników mających wpływ na ABI stosowane są zwykłe wskaźniki z dołączonymi kontrolami, formowanymi na podstawie adnotacji zawierających informacje o granicach.
Adnotacje należy dołączać do wskaźników w polach struktur i parametrach funkcji wskazujących na tablice obiektów, a także do zmiennych globalnych z wskaźnikami. Dla wskaźników w zmiennych lokalnych adnotacje nie muszą być dodawane, ponieważ są automatycznie traktowane jako rozszerzone wskaźniki, które już zawierają informacje o dozwolonych granicach. Podpowiedzi dotyczące konstrukcji w kodzie, dla których wymagana jest dodatkowa adnotacja, są wyświetlane przez kompilator podczas uruchamiania z flagą „-fbounds-safety”.
Model zabezpieczeń oparty na „-fbounds-safety” można wdrażać stopniowo, plik po pliku, nie przerywając prac nad całym projektem. Dodawanie ochrony do projektu sprowadza się do wskazania adnotacji w określonym pliku z kodem, usunięcia ostrzeżeń kompilatora oraz przeprowadzenia testów działania programu, po czym etapy te powtarzają się dla następnego pliku. Kod z dodanymi adnotacjami pozostaje zgodny z normalnym kodem w C oraz kompilatorami, które nie obsługują „-fbounds-safety” (przy kompilacji innymi kompilatorami lub przy kompilacji bez flagi „-fbounds-safety” po prostu nie zostaną dodane dodatkowe kontrole granic).
Podczas działania programu, w przypadku wykrycia odwołania poza dozwolone granice, generowane jest wyjątek i program kończy swoje działanie. Awaryjne zakończenie także może wystąpić przy wskazywaniu niepoprawnych adnotacji, dlatego przy użyciu „-fbounds-safety” należy przeprowadzić dodatkowe testy działania programu.
W poniższym przykładzie do parametru „int *p” dodano adnotację „__counted_by(n)”, dodającą dodatkową kontrolę na dozwolone granice, działającą w czasie wykonywania. Jeśli spróbujesz skompilować kod w trybie „-fbounds-safety” bez wskazania tej adnotacji, kompilator wyświetli ostrzeżenie o braku informacji o granicach tablicy podczas przetwarzania wyrażenia „p[i] = 0”. #include void init_buf(int *__counted_by(n) p, int n) { for (int i = 0; i < n; ++i) p[i] = 0; // w trybie „-fbounds-safety” kompilator sam doda kontrolę, analogiczną do kodu „if (i = n) trap();” }
Dla lokalnych zmiennych z wskaźnikami kontrole są dołączane automatycznie, na przykład: void foo(int i){ char *buf = (char *)malloc(10); // dla wskaźnika buf zostaną zachowane informacje o granicach buf[i] = 0xff; // zostanie automatycznie dodana kontrola „if (buf + i = buf + 10) trap();” }
Tam, gdzie to możliwe, kompilator przeprowadza optymalizację i wyklucza dodanie zbędnego kodu, jeśli w kodzie już istnieją wymagane kontrole. Na przykład: for (size_t i = 0; i < count; ++i) { buf[i] = i; // kontrola „if (i = count) trap()” nie zostanie dodana, ponieważ powyżej już istnieje warunek „i < count” a i nie może być mniejsze od 0. }
Podstawowe adnotacje:
- „__counted_by(N)” — określa rozmiar bufora w elementach docelowego typu.
- „__sized_by(N)” — określa rozmiar bufora w bajtach.
- » __ended_by(P)» — ustala górną granicę bufora.
- «__null_terminated» — uwzględnia znak null jako koniec bufora.
- «__single» — wiąże wskaźnik z jednym obiektem. Stosowane domyślnie dla wskaźników wpływających na ABI, chyba że wyraźnie oznaczono inaczej.
- «__bidi_indexable» — rozszerzony wskaźnik z informacjami o górnych i dolnych granicach. Stosowane domyślnie dla wskaźników, które nie wpływają na ABI.
- «__indexable» — rozszerzony wskaźnik z informacjami o górnej granicy.
- «__unsafe_indexable» — wskaźnik bez sprawdzania granic (dla przenośności z niebezpiecznym kodem, na przykład do uzyskiwania wskaźników z zewnętrznego kodu).
Źródło: opennet.ru
