Mamy w produkcji kilka systemów kompilacji, o które nikt nie dba, a te maszyny działają na starożytnych wersjach GCC, takich jak GCC 3 lub GCC 2.
I nie mogę przekonać kierownictwa, aby zaktualizował go do nowszego: mówią, „jeśli nie jest zepsuty, nie naprawiaj go”.
Ponieważ utrzymujemy bardzo stary kod (napisany w latach 80-tych), ten kod C89 dobrze kompiluje się na tych kompilatorach.
Ale nie jestem pewien, czy warto używać tych starych rzeczy.
Moje pytanie brzmi:
Czy użycie starego kompilatora C może zagrozić bezpieczeństwu skompilowanego programu?
AKTUALIZACJA:
Ten sam kod jest budowany przez Visual Studio 2008 dla celów Windows, a MSVC nie obsługuje jeszcze C99 ani C11 (nie wiem, czy nowszy MSVC to robi) i mogę go zbudować na moim Linuksie przy użyciu najnowszego GCC. Więc gdybyśmy po prostu wpadli w nowszą GCC, prawdopodobnie zbudowałby tak dobrze, jak poprzednio.
-O3 -Wall -Wextra -fsanitize=undefined
nowoczesne gcc i clang powinno pomóc.Odpowiedzi:
Właściwie argumentowałbym odwrotnie.
Jest wiele przypadków, w których zachowanie jest niezdefiniowane przez standard C, ale jest oczywiste, co by się stało z „głupim kompilatorem” na danej platformie. Przypadki takie jak zezwolenie na przepełnienie liczby całkowitej ze znakiem lub dostęp do tej samej pamięci za pomocą zmiennych dwóch różnych typów.
Najnowsze wersje gcc (i clang) zaczęły traktować te przypadki jako możliwości optymalizacji, które nie dbają o to, czy zmieniają zachowanie pliku binarnego w stanie „niezdefiniowane zachowanie”. Jest to bardzo złe, jeśli twój kod został napisany przez ludzi, którzy traktowali C jak „przenośny asembler”. W miarę upływu czasu optymizatorzy zaczęli przyglądać się coraz większym fragmentom kodu, wykonując te optymalizacje, zwiększając prawdopodobieństwo, że plik binarny skończy robić coś innego niż „to, co zrobiłby plik binarny zbudowany przez głupi kompilator”.
Istnieją przełączniki kompilatora, które przywracają „tradycyjne” zachowanie (-fwrapv i -fno-strict-aliasing dla dwóch, o których wspomniałem powyżej), ale najpierw musisz o nich wiedzieć.
Podczas gdy w zasadzie błąd kompilatora może zmienić zgodny kod w lukę w zabezpieczeniach, uważam, że ryzyko tego jest pomijalne w ogólnym rozrachunku.
źródło
people who treated C like a "portable assembler"
czy to nie jest to, czym jest C?W obu przypadkach istnieje ryzyko.
Starsze kompilatory mają przewagę w postaci dojrzałości i cokolwiek zostało w nich zepsute, prawdopodobnie zostało (ale nie ma gwarancji) obejść się pomyślnie.
W tym przypadku nowy kompilator jest potencjalnym źródłem nowych błędów.
Z drugiej strony nowsze kompilatory mają dodatkowe narzędzia :
Oprzyrządowanie pliku binarnego za pomocą środków dezynfekujących (Address Sanitizer, Memory Sanitizer lub Undefined Behavior Sanitizer), a następnie fuzzowanie go ( na przykład za pomocą American Fuzzy Lop ) ujawniło luki w wielu popularnych programach, zobacz na przykład ten artykuł LWN.net .
Te nowe narzędzia i wszystkie przyszłe narzędzia będą niedostępne, chyba że zaktualizujesz kompilator.
Pozostając na słabo wydajnym kompilatorze, kładziesz głowę w piasek i trzymasz kciuki, aby nie znaleziono żadnej luki. Jeśli Twój produkt jest celem o dużej wartości, zachęcam do ponownego rozważenia.
Uwaga: nawet jeśli NIE uaktualniasz kompilatora produkcyjnego, możesz i tak chcieć użyć nowego kompilatora, aby sprawdzić podatność; pamiętaj, że ponieważ są to różne kompilatory, gwarancje są jednak zmniejszone.
źródło
Twój skompilowany kod zawiera błędy, które można wykorzystać. Błędy pochodzą z trzech źródeł: błędy w kodzie źródłowym, błędy w kompilatorze i bibliotekach oraz niezdefiniowane zachowanie w kodzie źródłowym, które kompilator zamienia w błąd. (Niezdefiniowane zachowanie to błąd, ale jeszcze nie błąd w skompilowanym kodzie. Na przykład i = i ++; w C lub C ++ to błąd, ale w twoim skompilowanym kodzie może zwiększyć i o 1 i być OK lub ustawić i do jakiegoś śmieci i być błędem).
Odsetek błędów w skompilowanym kodzie jest przypuszczalnie niski ze względu na testowanie i naprawianie błędów wynikających z raportów klientów. Więc początkowo mogło być wiele błędów, ale liczba ta spadła.
Jeśli dokonasz aktualizacji do nowszego kompilatora, możesz stracić błędy wprowadzone przez błędy kompilatora. Ale te wszystkie błędy byłyby błędami, których według twojej wiedzy nikt nie znalazł i nikt nie wykorzystał. Ale nowy kompilator może mieć same błędy, a co ważne, nowsze kompilatory mają silniejszą tendencję do przekształcania niezdefiniowanego zachowania w błędy w skompilowanym kodzie.
Będziesz miał więc wiele nowych błędów w skompilowanym kodzie; wszystkie błędy, które hakerzy mogą znaleźć i wykorzystać. A jeśli nie wykonasz wielu testów i nie zostawisz swojego kodu klientom w celu znalezienia błędów przez długi czas, będzie on mniej bezpieczny.
źródło
getaddrinfo()
: access.redhat.com/articles/2161461 . Ten przykład w rzeczywistości nie jest luką w zabezpieczeniach kompilatora, ale ponad 10 lat musi istnieć kilka znanych naprawionych błędów.Jeśli się nie zepsuł, nie naprawiaj tego
Twój szef ma rację, mówiąc to, jednak ważniejszym czynnikiem jest ochrona wejść, wyjść, przepełnienia bufora. Ich brak jest niezmiennie najsłabszym ogniwem w łańcuchu z tego punktu widzenia, niezależnie od używanego kompilatora.
Jeśli jednak baza kodu jest stara i podjęto prace mające na celu złagodzenie słabych punktów używanego K&R C, takich jak brak bezpieczeństwa typów, niezabezpieczone elementy itp., Rozważ pytanie „ Czy uaktualnienie kompilatora do nowocześniejszego C99 / C11 wszystko psują? ”
Zakładając, że istnieje jasna ścieżka migracji do nowszych standardów C, co może wywołać efekty uboczne, najlepiej będzie spróbować rozwidlić stary kod, ocenić go i wprowadzić dodatkowe kontrole typu, testy poprawności i określić, czy aktualizacja do nowszy kompilator ma jakikolwiek wpływ na zbiory danych wejściowych / wyjściowych.
Następnie możesz pokazać go swojemu szefowi: „ Oto zaktualizowana baza kodu, refaktoryzowana, bardziej zgodna z przyjętymi w branży standardami C99 / C11… ”.
To jest ryzyko, które należałoby rozważyć, bardzo ostrożnie , opór przed zmianą mógłby pojawić się w tym środowisku i może odmówić dotknięcia nowszych rzeczy.
EDYTOWAĆ
Po prostu usiadłem na kilka minut i zdałem sobie sprawę, że kod wygenerowany przez K&R może działać na platformie 16-bitowej, są szanse, że aktualizacja do nowocześniejszego kompilatora może faktycznie złamać bazę kodu, myślę w kategoriach architektury, wygenerowany zostanie kod 32-bitowy , może to mieć zabawne skutki uboczne dla struktur używanych w zestawach danych wejściowych / wyjściowych, to kolejny ważny czynnik, który należy dokładnie rozważyć.
Ponadto, ponieważ OP wspomniał o używaniu Visual Studio 2008 do budowania bazy kodu, użycie gcc może spowodować wprowadzenie do środowiska MinGW lub Cygwin, co może mieć wpływ na środowisko, chyba że celem jest Linux, wtedy byłoby to warto spróbować, być może trzeba będzie dołączyć dodatkowe przełączniki do kompilatora, aby zminimalizować szum na starej bazie kodu K&R, inną ważną rzeczą jest przeprowadzenie wielu testów, aby upewnić się, że żadna funkcjonalność nie zostanie zepsuta, może okazać się bolesnym ćwiczeniem.
źródło
Oczywiście, jeśli stary kompilator zawiera znane błędy, o których wiesz, że mogą wpłynąć na Twój program.
Pytanie brzmi, prawda? Aby mieć pewność, musiałbyś przeczytać cały dziennik zmian od swojej wersji do obecnej daty i sprawdzić każdy pojedynczy błąd naprawiony na przestrzeni lat.
Jeśli nie znajdziesz dowodów na błędy kompilatora, które mogłyby wpłynąć na twój program, aktualizacja GCC tylko ze względu na to wydaje się nieco paranoiczna. Należy pamiętać, że nowsze wersje mogą zawierać nowe błędy, które nie zostały jeszcze odkryte. Ostatnio wprowadzono wiele zmian dzięki obsłudze GCC 5 i C11.
To powiedziawszy, kod napisany w latach 80. najprawdopodobniej jest już wypełniony po brzegi lukami w zabezpieczeniach i polega na słabo zdefiniowanym zachowaniu, niezależnie od kompilatora. Mówimy tutaj o standardowym C.
źródło
Istnieje zagrożenie bezpieczeństwa polegające na tym, że złośliwy programista może przemycić tylne drzwi przez błąd kompilatora. W zależności od ilości znanych błędów w używanym kompilatorze, backdoor może wyglądać mniej lub bardziej niepozornie (w każdym przypadku chodzi o to, że kod jest poprawny, nawet jeśli jest zagmatwany, na poziomie źródłowym. Przeglądy i testy kodu źródłowego przy użyciu kompilator bez błędów nie znajdzie backdoora, ponieważ backdoor nie istnieje w tych warunkach). Aby uzyskać dodatkowe punkty zaprzeczenia, złośliwy programista może również samodzielnie szukać wcześniej nieznanych błędów kompilatora. Ponownie, jakość kamuflażu będzie zależeć od wyboru znalezionych błędów kompilatora.
Ten atak został zilustrowany w programie sudo w tym artykule . bcrypt napisał świetną kontynuację dla minifier Javascript .
Oprócz tego niepokoju, ewolucja kompilatory C było wykorzystać niezdefiniowanej zachowanie bardziej i bardziej i bardziej agresywnie, tak stary kod C, który został napisany w dobrej wierze, by rzeczywiście być bardziej bezpieczne skompilowane z kompilatora C od czasu, lub zestawiane w -O0 (ale w nowych wersjach kompilatorów wprowadzono kilka nowych, przełamujących programy optymalizacje wykorzystujące UB, nawet przy -O0 ).
źródło
Starsze kompilatory mogą nie mieć ochrony przed znanymi atakami hakerskimi. Na przykład ochrona przed zniszczeniem stosu została wprowadzona dopiero w GCC 4.1 . Więc tak, kod skompilowany za pomocą starszych kompilatorów może być podatny na zagrożenia, przed którymi chronią nowsze kompilatory.
źródło
Kolejnym aspektem, o który należy się martwić, jest rozwój nowego kodu .
Starsze kompilatory mogą mieć inne zachowanie dla niektórych funkcji języka niż to, co jest ustandaryzowane i oczekiwane przez programistę. Ta niezgodność może spowolnić rozwój i wprowadzić subtelne błędy, które można wykorzystać.
Starsze kompilatory oferują mniej funkcji (w tym funkcje językowe!) I nie są również optymalizowane. Programiści będą omijać te niedociągnięcia - np. Ponownie wdrażając brakujące funkcje lub pisząc sprytny kod, który jest niejasny, ale działa szybciej - tworząc nowe możliwości tworzenia subtelnych błędów.
źródło
nie
Powód jest prosty, stary kompilator może mieć stare błędy i exploity, ale nowy kompilator będzie miał nowe błędy i exploity.
Nie "naprawiasz" żadnych błędów poprzez aktualizację do nowego kompilatora. Twoje przełączanie starych błędów i exploitów na nowe błędy i exploity.
źródło
Cóż, istnieje większe prawdopodobieństwo, że wszelkie błędy w starym kompilatorze są dobrze znane i udokumentowane, w przeciwieństwie do używania nowego kompilatora, więc można podjąć działania w celu uniknięcia tych błędów, kodując je wokół nich. Więc w pewnym sensie to nie wystarczy jako argument za aktualizacją. Prowadzimy te same dyskusje, w których ja pracuję, używamy GCC 4.6.1 na bazie kodu dla oprogramowania wbudowanego i istnieje duża niechęć (wśród kierownictwa) do aktualizacji do najnowszego kompilatora z powodu obawy o nowe, nieudokumentowane błędy.
źródło
Twoje pytanie dzieli się na dwie części:
Być może możesz odpowiedzieć na jedno i drugie, znajdując możliwą do wykorzystania lukę w istniejącej bazie kodu i pokazując, że nowszy kompilator wykryłby ją. Oczywiście kierownictwo może powiedzieć „znalazłeś to w starym kompilatorze”, ale możesz zauważyć, że kosztowało to sporo wysiłku. Lub uruchomisz go za pomocą nowego kompilatora, aby znaleźć lukę, a następnie wykorzystaj ją, jeśli możesz / masz pozwolenie na skompilowanie kodu za pomocą nowego kompilatora. Możesz potrzebować pomocy przyjaznego hakera, ale zależy to od zaufania mu i możliwości / pozwolenia na pokazanie mu kodu (i użycie nowego kompilatora).
Ale jeśli twój system nie jest narażony na ataki hakerów, być może powinieneś być bardziej zainteresowany tym, czy aktualizacja kompilatora zwiększy twoją skuteczność: Analiza kodu MSVS 2013 dość często znajduje potencjalne błędy znacznie wcześniej niż MSVS 2010 i mniej więcej obsługuje C99 / C11 - nie jestem pewien, czy tak jest oficjalnie, ale deklaracje mogą następować po instrukcjach i możesz deklarować zmienne w
for
-loops.źródło