W sprawie błędu Heartbleed Bruce Schneier napisał w swoim Crypto-Gram z 15 kwietnia: „Katastroficzne” to właściwe słowo. W skali od 1 do 10 jest to 11. ” Czytałem kilka lat temu, że jądro określonego systemu operacyjnego zostało rygorystycznie zweryfikowane za pomocą nowoczesnego systemu weryfikacji programów. Czy w ten sposób można zapobiec występowaniu błędów w gatunku Heartbleed, stosując dziś techniki weryfikacji programu, czy jest to nierealne, a nawet zasadniczo niemożliwe?
cryptography
correctness-proof
security
software-verification
program-correctness
Mok-Kong Shen
źródło
źródło
Odpowiedzi:
Aby odpowiedzieć na twoje pytanie w najbardziej zwięzły sposób - tak, ten błąd mógł zostać potencjalnie wykryty przez narzędzia formalnej weryfikacji. Rzeczywiście, właściwość „nigdy nie wysyłaj bloku, który jest większy niż rozmiar wysłanego impulsu” jest dość łatwa do sformalizowania w większości języków specyfikacji (np. LTL).
Problem (który jest powszechną krytyką metod formalnych) polega na tym, że specyfikacje, których używasz, są pisane przez ludzi. Rzeczywiście, metody formalne zmieniają jedynie wyzwanie polegające na wyszukiwaniu błędów od znajdowania błędów do definiowania, jakie są błędy. To trudne zadanie.
Ponadto formalna weryfikacja oprogramowania jest niezwykle trudna ze względu na problem eksplozji stanu. W tym przypadku jest to szczególnie istotne, ponieważ wiele razy, aby uniknąć eksplozji państwa, odrywamy granice. Na przykład, gdy chcemy powiedzieć „po każdym żądaniu następuje przyznanie dotacji, w ciągu 100 000 kroków”, potrzebujemy bardzo długiej formuły, więc wyodrębniamy ją do wzoru „po każdym żądaniu następuje przyznanie”.
Tak więc w przypadku serca, nawet podczas próby sformalizowania wymagań, przedmiotowe ograniczenie mogło zostać usunięte, co skutkowałoby takim samym zachowaniem.
Podsumowując, potencjalnie tego błędu można było uniknąć za pomocą metod formalnych, ale musiałby istnieć człowiek, który wcześniej określi tę właściwość.
źródło
Programy sprawdzające programy komercyjne, takie jak Klocwork czy Coverity, mogły znaleźć Heartbleed, ponieważ jest to stosunkowo proste „zapomnienie o błędzie sprawdzania granic”, co jest jednym z głównych problemów, które mają sprawdzić. Ale jest o wiele prostszy sposób: użyj nieprzejrzystych abstrakcyjnych typów danych, które są dobrze przetestowane pod kątem braku przepełnienia bufora.
Istnieje wiele abstrakcyjnych typów danych „bezpieczny ciąg” dostępnych do programowania w języku C. Najbardziej znany mi jest Vstr . Autor, James Antill, ma wielką dyskusję na temat, dlaczego potrzebny jest ciąg abstrakcyjny typ danych z własnych konstruktorów / metod fabrycznych , a także listę innych ciągów abstrakcyjnych typów danych dla C .
źródło
Jeśli liczysz jako „ technikę weryfikacji programu ” połączenie sprawdzania granic czasu wykonywania i fuzzingu, tak, ten konkretny błąd mógł zostać złapany .
Prawidłowe rozmycie sprawi, że niesławny będzie teraz
memcpy(bp, pl, payload);
czytał ponad limit bloku pamięcipl
. Sprawdzanie ograniczeń w czasie wykonywania może w zasadzie przechwycić taki dostęp, aw praktyce w tym konkretnym przypadku nawet debugowana wersja,malloc
która dba o sprawdzenie parametrówmemcpy
, wykonałaby zadanie (nie trzeba tutaj zadzierać z MMU) . Problem polega na tym, że przeprowadzanie testów fuzzingu dla każdego rodzaju pakietu sieciowego wymaga wysiłku.źródło
memcpy
się na prawdziwą granicę (dużego) regionu pierwotnie wymaganego od systemumalloc
.memcpy(bp, pl, payload)
musiałby sprawdzić granice używane przezmalloc
zastąpienie OpenSSL , a nie systemmalloc
. Wyklucza to automatyczne sprawdzanie granic na poziomie binarnym (przynajmniej bez głębokiej wiedzy na tematmalloc
zamiany). Konieczna jest ponowna kompilacja za pomocą kreatora na poziomie źródła, przy użyciu np. Makr C zastępujących tokenmalloc
lub dowolnych zastępczych używanych OpenSSL; i wydaje się, że potrzebujemy tego samego zmemcpy
wyjątkiem bardzo sprytnych sztuczek MMU.Używanie ściślejszego języka nie tylko przesuwa słupki celów od poprawnej implementacji do poprawnej specyfikacji. Trudno jest stworzyć coś bardzo złego, ale logicznie spójnego; dlatego kompilatory wyłapują tyle błędów.
Arytmetyka wskaźnika w takiej postaci, w jakiej jest normalnie sformułowana, jest niesłyszalna, ponieważ system typów w rzeczywistości nie oznacza, co powinien oznaczać. Możesz całkowicie uniknąć tego problemu, pracując w języku śmieci (normalne podejście, które powoduje, że płacisz również za abstrakcję). Możesz też sprecyzować, jakiego rodzaju wskaźników używasz, aby kompilator mógł odrzucić wszystko, co jest niespójne lub po prostu nie może być udowodnione, jak napisano. Takie jest podejście niektórych języków, takich jak Rust.
Skonstruowane typy są równoważne dowodom, więc jeśli napiszesz system typów, który o tym zapomina, wtedy wszystkie rzeczy się psują. Załóżmy przez chwilę, że kiedy deklarujemy typ, mamy na myśli, że potwierdzamy prawdę o tym, co znajduje się w zmiennej.
W tym świecie wskaźniki nie mogą być zerowe. Dereferencje NullPointer nie istnieją, a wskaźniki nie muszą być nigdzie sprawdzane pod kątem nieważności. Zamiast tego „nullable int *” to inny typ, którego wartość można wyodrębnić do wartości null lub do wskaźnika. Oznacza to, że w miejscu, w którym zaczyna się założenie niepuste , albo przechodzisz rejestrowanie wyjątku, albo schodzisz do gałęzi zerowej.
W tym świecie nie występują również błędy poza zakresem. Jeśli kompilator nie może udowodnić, że jest w granicach, spróbuj przepisać, aby kompilator mógł to udowodnić. Jeśli nie może, będziesz musiał ręcznie założyć Wniebowzięcie w tym miejscu; kompilator może później znaleźć sprzeczność.
Ponadto, jeśli nie możesz mieć wskaźnika, który nie został zainicjowany, nie będziesz mieć wskaźników do niezainicjowanej pamięci. Jeśli masz wskaźnik zwolnionej pamięci, kompilator powinien go odrzucić. W Rust istnieją różne typy wskaźników, aby uzasadnić tego rodzaju dowody. Istnieją wyłącznie posiadane wskaźniki (tj .: brak aliasów), wskaźniki do głęboko niezmiennych struktur. Domyślny typ pamięci jest niezmienny itp.
Istnieje również kwestia egzekwowania rzeczywistej, dobrze zdefiniowanej gramatyki protokołów (która obejmuje elementy interfejsu), aby ograniczyć pole powierzchni wejściowej dokładnie do tego, czego się spodziewano. Rzecz „poprawności” polega na: 1) Pozbyciu się wszystkich niezdefiniowanych stanów 2) Zapewnieniu logicznej spójności . Trudność dotarcia tam ma wiele wspólnego ze stosowaniem bardzo złego oprzyrządowania (z punktu widzenia poprawności).
Właśnie dlatego dwie najgorsze praktyki to zmienne globalne i gotos. Te rzeczy uniemożliwiają ustawienie warunków przed / po / niezmiennych wokół wszystkiego. Właśnie dlatego typy są tak skuteczne. Gdy typy stają się silniejsze (ostatecznie wykorzystując typy zależne do uwzględnienia rzeczywistej wartości), stają się konstruktywnymi dowodami poprawności same w sobie; powodowanie, że niespójne programy kończą się niepowodzeniem.
Pamiętaj, że nie chodzi tylko o głupie błędy. Chodzi również o obronę bazy kodu przed sprytnymi infiltratorami. Będą przypadki, w których musisz odrzucić zgłoszenie bez przekonującego, wygenerowanego maszynowo dowodu ważnych właściwości, takich jak „postępuje zgodnie z formalnie określonym protokołem”.
źródło
Narzędzia analizy statycznej, takie jak Coverity, rzeczywiście mogą znaleźć HeartBleed i podobne wady. Pełne ujawnienie: współtworzyłem Coverity.
Zobacz mój post na blogu na temat naszego dochodzenia w sprawie HeartBleed i tego, w jaki sposób nasza analiza została ulepszona w celu wykrycia:
http://security.coverity.com/blog/2014/Apr/on-detecting-heartbleed-with-static-analysis.html
źródło
automatyczna / formalna weryfikacja oprogramowania jest przydatna i może w niektórych przypadkach pomóc, ale jak zauważyli inni, nie jest to srebrna kula. można zauważyć, że OpenSSL jest podatny na ataki ze względu na to, że jest open source, a mimo to jest używany komercyjnie i w całej branży, szeroko stosowany i nie musi być poddawany szczegółowej recenzji przed wydaniem (zastanawia się, czy w projekcie są jeszcze płatni programiści). wada została odkryta w zasadzie poprzez przegląd kodu po wydaniu, a kod został najwyraźniej sprawdzony przed wydaniem (zauważ jednak, że prawdopodobnie nie ma sposobu na śledzenie, kto dokonał przeglądu wewnętrznego kodu). „możliwy do nauczenia się moment” z sercem serca (między innymi) to zasadniczo lepsze przeglądanie kodu, najlepiej przed wydaniem esp bardzo wrażliwego kodu, być może lepiej śledzone. być może OpenSSL będzie teraz podlegał większej kontroli.
więcej bkg z mediów szczegółowo opisujących jego pochodzenie:
Heartbleed był wypadkiem: Deweloper przyznaje się do spowodowania błędu kodowania i przyznaje, że jego efekt jest „wyraźnie dotkliwy” „Niemiecki programista Dr Robin Seggelmann przyznał, że napisał kod. Następnie został sprawdzony przez innych członków i dodany do oprogramowania OpenSSL”
Jak Codenomicon odkrył błąd Heartbleed, który teraz nęka Internet „Szef firmy ochroniarskiej wyjaśnia, w jaki sposób znaleziono usterkę, jak duże jest ryzyko i co ludzie mogą teraz z tym zrobić”.
3 duże lekcje, które można wyciągnąć z Heartbleed „Dewastująca luka w zabezpieczeniach OpenSSL dowodzi znaczenia organizacji centrów danych, mądrości uruchamiania starszych wersji i potrzeby zwrotu projektu OpenSSL”
źródło