Historia z piekła rodem czyli najgorszy koszmar programisty

dodał 6 grudnia 2015 o 18:05 w kategorii Błędy  z tagami:
Historia z piekła rodem czyli najgorszy koszmar programisty

Poprawianie cudzego kodu samo z siebie wydaje się już być wystarczającym koszmarem. Co jednak powiedzieć o sytuacji, kiedy przejrzany i poprawiony kod robi rzeczy, których zdecydowanie nie są w nim zaprogramowane?

W serwisie Quora furorę robi pewna odpowiedź na pytanie Jaki jest najgorszy koszmar programisty. Choć historia w niej opisana sprawia wrażenie wręcz nierealnej, to renoma jej autora sugeruje, że wydarzyła się naprawdę. Przeczytajcie.

To tylko drobne poprawki

Mick Stute, autor opowieści, został kiedyś poproszony przez pewnego psychologa o naprawienie programu autorstwa byłego studenta naukowca. Program służył do wczytania danych z bazy, zadania użytkownikowi 50 pytań i wyświetlenia wyników. Pozornie działał prawidłowo, jednak w trakcie wyświetlania pytań na ekranie pojawiały się momentami różne dziwne słowa, których być tam nie powinno. Z pozoru banalne zadanie usunięcia tego elementu z kodu okazało się prowadzić do wielodniowej batalii. Walka prowadzona była na komputerze 3B2 na początku lat 80. XX wieku.

Zestaw z 3B2 w roli głównej

Zestaw z 3B2 w roli głównej

Mick podjął się zadania. Już pierwszy rzut oka na kod źródłowy zapowiadał problemy. Program, napisany w C, rozbity był na 15 różnych plików i każdy plik zawierał pojedynczy wiersz obejmujący +/- trzy różne funkcje. Wszystkie nazwy zmiennych były losowymi, trzyliterowymi ciągami. Mick postanowił umówić się z autorem zlecenia na stawkę godzinową i po ustaleniu warunków zasiadł do rozplątywania kodu.

Program używał biblioteki curses by ustawić na ekranie kursor w odpowiednim miejscu i wyświetlić treść pytania. Zanim jednak pojawiało się pytanie, na pół sekundy wyświetlany był rasistowski komentarz. Mick wyczyścił i uporządkował cały kod. Zlokalizował 5 miejsc, w których znajdowały się polecenia wyświetlające na ekranie zawartość pytań – w każdym z nich był zapisany fragment z dodatkowym komunikatem. Usunął niepotrzebne polecenia, skompilował kod i myślał że już skończył pracę. Program był jednak innego zdania i znowu wyświetlił rasistowskie komentarze, choć tym razem o innej treści.

Gdy Mick spojrzał na kod źródłowy lekko się załamał. Wszystko wróciło do stanu wyjściowego – trzyliterowe zmienne, 15 plików i nieczytelne długie wiersze. Niestety nie zrobił kopii zapasowej wyczyszczonego kodu, zatem musiał powtórzyć od początku całą procedurę. Tym razem przed skompilowaniem programu zrobił kilka kopii, lecz efekt był identyczny. Oprócz jego kodu – pozornie czystego, choć ciągle produkującego niewłaściwy kod wyjściowy, miał na dysku znowu kopię kodu oryginalnego. To jednak nie było problemem – problemem było ustalenie, skąd biorą się polecenia wyświetlania rasistowskich komentarzy.

Wyszukiwanie wybranego fragmentu wyświetlanego komunikatu w /usr/include nie przyniosło spodziewanych rezultatów. Drugi dzień pracy spędzony na analizie plików źródłowych dołączanych do kodu nie posunął pracy do przodu. Ciągi musiały być gdzieś zaszyfrowane. Mick postanowił zrekompilować wszystkie biblioteki. Szóstego dnia kolejna kompilacja programu w oparciu o sprawdzony kod źródłowy wszystkich elementów przyniosła identyczny efekt jak poprzednie próby – złośliwy kod znowu dodał się do pliku wynikowego.

Musimy zejść głębiej

Mick był już bliski decyzji o napisaniu całego programu od nowa, jednak postanowił się nie poddawać. Zleceniodawcę poinformował, że prace jeszcze trochę potrwają, ale nie będzie go już obciążał dodatkowymi kosztami – to była dla niego sprawa honoru. Po kolejnych sześciu dniach deasemblacji biblioteki curses Mick był znowu w punkcie wyjścia – ani śladu złośliwego kodu. Dopiero 15go dnia trwania zlecenia Micka olśniło – problemem musiał być kompilator.

Analiza kodu źródłowego kompilatora przyniosła pierwszy sukces. Mick znalazł w nim następującą sekwencję poleceń:

  • kompilator sprawdzał każdy wywołanie fopen() i weryfikował, czy wczytywany plik zawierał fragment pytań używanych w programie,
  • jeśli go znajdował, tworzył kopię zainfekowanego kodu źródłowego na dysku,
  • kompilował zainfekowany kod i zapisywał go tam, gdzie wylądować miał wynik działania kompilatora.

Z pomocą ruszył inżynier AT&T, producenta komputera, który dostarczył nietkniętą kopię kodu źródłowego kompilatora. Niestety to także nie rozwiązało problemu. Okazało się bowiem, że na feralnej maszynie kompilator był zainfekowany jeszcze innym fragmentem kodu, wykrywającym kompilowanie kompilatora. W takim przypadku zainfekowany kompilator dopisywał do kodu źródłowego swoje ukryte funkcje i dopiero wtedy się kompilował z zainfekowanego kodu. Na szczęście pomogło rozwiązanie ostateczne, czyli przeniesienie binarnej wersji czystego kompilatora z innej identycznej maszyny.

Nie był to koniec niespodzianek – na deser odkryto także że próba kompilacji /sbin/login powoduje dodanie do pliku wykonywalnego tylnej furtki umożliwiającej logowanie do systemu na konto roota za pomocą uniwersalnego hasła. Trzeba przyznać, że autor modyfikacji był nie tylko złośliwy, ale także utalentowany (pamiętajcie, że był to początek lat 80).

Wam życzymy w nadchodzącym tygodniu problemów o mniejszym poziomie złożoności. W razie kłopotów pamiętajcie o kompilatorze.