Informatyka i programowanieKryptografia i bezpieczeństwo komputerowe

Jedna powtórzona linia `goto fail` wyłączyła ważny etap sprawdzania TLS w Apple

W 2014 roku Apple naprawiło CVE-2014-1266 w Secure Transport. Oficjalny opis mówił, że biblioteka nie sprawdzała poprawnie autentyczności połączenia TLS, co mogło pozwolić napastnikowi w uprzywilejowanej pozycji sieciowej przechwytywać lub modyfikować dane. Analiza kodu ujawniła wyjątkowo prostą przyczynę: powtórzoną linię `goto fail;` w funkcji weryfikującej podpis serwera. Przez brak klamer drugi skok wykonywał się bezwarunkowo, omijając właściwą weryfikację podpisu, a funkcja mogła wrócić z kodem sukcesu. To spektakularny przykład, w którym kryptografia nie została pokonana — program po prostu nie wykonał kroku, który miał ją sprawdzić.

Kod wyglądał niemal poprawnie

W podatnej funkcji kolejne wywołania aktualizujące hash miały prosty wzorzec: wykonaj operację, a jeśli zwróci błąd, przejdź do etykiety `fail`. W C brak klamer oznacza, że instrukcja `if` obejmuje tylko następną instrukcję. Po jednym z takich warunków znalazły się dwa następujące po sobie `goto fail;`. Pierwszy był warunkowy. Drugi — mimo podobnego wcięcia — nie należał do `if` i wykonywał się zawsze. Kod znajdujący się później, w tym właściwe sprawdzenie podpisu, stawał się w tej ścieżce nieosiągalny.

Najbardziej zdradliwy był kod zwracany jako „sukces”

Samo wcześniejsze wyjście z funkcji mogłoby skończyć się bezpieczną odmową połączenia. Tutaj problem był groźniejszy: zmienna błędu po udanej wcześniejszej operacji miała wartość oznaczającą brak błędu. Bezwarunkowy skok przechodził więc do wspólnego kodu sprzątającego, a funkcja zwracała sukces, mimo że nie wykonała późniejszej weryfikacji podpisu. To pokazuje, że ścieżki obsługi błędów mogą być częścią logiki bezpieczeństwa, a nie tylko „technicznego sprzątania”.

Apple opisało skutek jako brak walidacji autentyczności

W aktualizacjach iOS 6.1.6 i 7.0.6 Apple podało, że Secure Transport nie potrafił właściwie zweryfikować autentyczności połączenia i że problem naprawiono przez przywrócenie brakujących etapów walidacji. NVD doprecyzowuje, że funkcja odpowiedzialna za `SSLVerifySignedServerKeyExchange` nie sprawdzała podpisu w wiadomości TLS Server Key Exchange, co umożliwiało podszywanie się pod serwery w scenariuszu man-in-the-middle.

Matematyka TLS mogła być bez zarzutu

Certyfikaty, klucze, funkcje skrótu i algorytmy podpisu nie musiały mieć żadnej słabości. Wystarczyło, że kontrola przepływu ominęła wywołanie funkcji weryfikującej podpis. To jeden z najważniejszych motywów bezpieczeństwa: kryptografia zapewnia gwarancje tylko wtedy, gdy program rzeczywiście wykonuje właściwe sprawdzenia i poprawnie interpretuje wynik. Jeden błąd sterowania może wyłączyć warstwę matematyczną bez „łamania” żadnego szyfru.

Dlaczego ta historia jest tak pamiętana

Błąd był wizualnie banalny i jednocześnie miał poważne konsekwencje. Doskonale pokazuje ograniczenia przeglądu kodu opartego na szybkim czytaniu: wcięcia sugerowały inną strukturę niż rzeczywista składnia C. Takie przypadki napędzają stosowanie obowiązkowych klamer, analiz statycznych, testów negatywnych i języków ograniczających część ryzykownych konstrukcji. Najważniejsza lekcja nie brzmi jednak „goto jest złe”, lecz „kod odpowiedzialny za decyzję bezpieczeństwa musi być testowany także pod kątem tego, czy faktycznie osiąga wszystkie wymagane kroki”.

Kompilator nie musiał zgłosić błędu, bo program był legalnym C

Najbardziej zdradliwą cechą tej historii jest to, że dodatkowe `goto fail;` nie tworzyło błędu składni. W języku C wcięcie jest wyłącznie formatowaniem dla człowieka; tylko pierwsza instrukcja po `if` bez klamer należy do warunku. Druga identyczna linia wykonywała się bezwarunkowo i omijała późniejszy etap weryfikacji. Analizy kodu pokazują, że zmienna `err` mogła w tym miejscu nadal mieć wartość oznaczającą sukces, więc funkcja wracała tak, jakby podpis komunikatu TLS został poprawnie sprawdzony. To wyjątkowo mocny przykład ograniczeń zwykłego code review: oko czyta wzór i wcięcie, podczas gdy kompilator czyta gramatykę. Klamry, ostrzeżenia, analiza statyczna i testy negatywne są sposobami na zmniejszenie przestrzeni dla takiej rozbieżności.

Test „poprawny serwer przechodzi” nie byłby wystarczający — potrzebny jest test „fałszywy podpis musi zostać odrzucony”

Błąd walidacji ma asymetryczny charakter: zwykłe połączenia z prawidłowymi serwerami mogą działać bez zarzutu, ponieważ omijany krok i tak zakończyłby się sukcesem. Dopiero wejście specjalnie skonstruowane tak, aby powinno zostać odrzucone, ujawnia brak kontroli. To szersza lekcja dla bezpieczeństwa: testy pozytywne pokazują, że system potrafi zaakceptować dobre dane, ale nie dowodzą, że odrzuca wszystkie złe. Krytyczne walidatory wymagają testów negatywnych dla każdego warunku, który ma zamknąć drogę niepoprawnemu wejściu. Historia `goto fail` jest przez to czymś więcej niż anegdotą o jednej podwójnej linii — pokazuje, jak łatwo pokrycie „normalnych ścieżek” może pozostawić niezauważoną dziurę dokładnie w kodzie odpowiedzialnym za odmowę zaufania.

#Apple#błędy implementacji#CVE-2014-1266#goto fail#TLS
Źródła i weryfikacja
Otrzymuj codzienne losowe ciekawostki