dla programistów
Pisanie bezpiecznych
programów
Marek Sawerwain
worzone obecnie programy to duże i skompliko-
wane projekty. Błędy, które pojawiają się w tak
złożonym kodzie, są niestety jego naturalną czę-
Tścią. Zazwyczaj mija sporo czasu, zanim zostaną
wyeliminowane. Obecność czy nawet podejrzenie jakiego-
kolwiek błędu w programie jest niepokojące, jednak co
gorsza błędy w programach są często wykorzystywane do
przejęcia kontroli nad systemem operacyjnym. Najczęściej
stosuje się tzw. przepełnienie bufora oraz błędy w obsłu-
dze pamięci. Jednym ze sposobów eliminacji tego rodzaju
błędów jest zastosowanie odpowiedniego języka progra-
mowania, np. Javy. W większości przypadków programy
tworzone są jednak w językach C i C++, które co prawda
nie oferują tak dużego bezpieczeństwa wykonania jak Java,
ale są wydajniejsze.
Rysunek 1. Zawartość stosu przed i po zmianie zawartości
Powstało wiele dodatkowych rozwiązań dla języków
zmiennej b
C/C++, oferujących lepsze zabezpieczenie przed błędami
związanymi z obsługą pamięci, jak również z bezpiecznym
obsługiwaniem ciągów znaków. Większość tych bibliotek nych oraz dopracowanych programów, jak Apache, Send-
jest przeznaczona dla systemów uniksowych, w tym rów- mail, Wu-ftpd czy nawet OpenSSH. Ten rodzaj błędu nie
nież dla Linuksa. W tym artykule chciałbym przedstawić dotyczy tylko i wyłącznie aplikacji związanych z obsługą
kilka bardzo cennych rozwiązań, które pozwalają tworzyć sieci. Pierwszy z brzegu przykład programu, który można
oprogramowanie wydajne oraz co ważne znacznie bez- przytoczyć, to standardowy program Man, w którym już
pieczniejsze i odporne na wyjątkowe sytuacje. dość dawno, bo w 1999 roku, wykryto taki błąd.
Wszystkie omawiane w tym artykule biblioteki działają Ogólny zarys techniki przepełnia bufora jest wręcz try-
znakomicie pod Linuksem, który nadaje się znacznie lepiej wialny. Spójrzmy na taką funkcję:
na platformę do tworzenia bezpiecznych programów niż
choćby Windows, bowiem liczne grono użytkowników void funkcja(char *t) {
konkretnych bibliotek powoduje, że znacznie szybciej int i,j;
usuwane są błędy i poszerzane ich możliwości. Co więcej, char b[32];
niektóre biblioteki jak dość szeroko znana Electric Fence strcpy(&b, ciag znaków );
- są przeznaczone wyłącznie dla systemów uniksowych. strcpy(&b, t);
// operacje na zmiennej b
Czy nasze programy są bezpieczne? }
Na to pytanie można odpowiedzieć bardzo szybko: nieste-
ty nie. Im bardziej złożone oprogramowanie, tym większe Wewnętrzny bufor b może okazać się zbyt mały, jeśli
prawdopodobieństwo, że znajdują się w nim błędy. Błędy parametr t będzie zawierał zbyt dużo znaków. Zapisanie
wynikające z przepełnienia bufora dotyczą nawet tak zna- większej ilości danych spowoduje zmodyfikowanie stosu
(umieszczane są na nim również zmienne lokalne), na
którym znajduje się adres powrotu z funkcji. Odpowiednio
O autorze:
preparując dane można ten adres zmodyfikować Rysu-
Autor zajmuje się tworzeniem oprogramowania dla WIN32
nek 1.
i Linuksa. Zainteresowania: teoria języków programowania
Błąd przepełnienia bufora w powyższej postaci dość
oraz dobra literatura.
Kontakt z autorem: autorzy@linux.com.pl. często pojawia się w programach. Przykładem może być
świetny program do odtwarzania plików multimedialnych
58 styczeń 2004
bezpieczne programy
MPlayer. Całkiem niedawno odkryto w tym programie
Listing 1. Fragmenty funkcji asf_http_request
możliwość przepełnienia bufora poprzez funkcję sprintf.
Błąd znajdował się w funkcji asf_http_request, w pliku
asf_streaming.c jej najistotniejsze fragmenty prezentuje HTTP_header_t * asf_http_request(streaming_ctrl_t
Listing 1. *streaming_ctrl) {
Odpowiednio preparując plik w formacie asf można char str[250];
umieścić kod w adresach URL, a funkcja sprintf może sprintf( str, "Host: %s:%d", server_url->hostname,
doprowadzić do nadpisania zmiennej str i w efekcie adresu server_url->port );
powrotnego funkcji, ponieważ reaguje tylko na ogranicze- sprintf( str, "Host: %s:%d", url->hostname, url->port );
nie w postaci znaku zero. Jej stosowanie jest więc tak samo }
niebezpieczne jak pozostałe funkcje działające na ciągach
znaków.
Rozwiązaniem tego rodzaju błędów jest odpowiednia związanych z alokacją pamięci), a łatka ta nie jest jeszcze
alokacja pamięci. Innym rozwiązaniem jest zastosowanie dostępna dla nowych wersji kompilatora GCC 3.x. Ponadto
bibliotek do zarządzania ciągami znaków, w których długo- StackGuard obniża wydajność wywołanie funkcji i powrót
ści ciągów znaków są dynamiczne. Jest to dość skuteczne z nich pociąga za sobą testy spójności stosu.
rozwiązanie. Czasem, jak w przypadku sprintf, można
ograniczyć liczbę znaków. Z tego powodu poprawka SCSL bezpieczna obsługa ciągów
MPlayera w niefortunnym kodzie z Listingu 1 jest bardzo znaków
prosta: sprintf( str, "Host: %.220s:%d", url->hostname, Interesującą biblioteką do obsługi ciągów znaków jest Safe
url->port );. C String Library (SCSL). Oferuje ona ponad dwadzieścia
Istnieją także mechanizmy, które można wbudować funkcji związanych z obsługą ciągów znaków ze szcze-
w kompilator, np. do kompilatora GCC w wersji 2.96 gólnym uwzględnieniem ochrony przed przepełnieniem
dołączano łatkę o nazwie StackGuard, która chroniła stos. bufora. Wprowadzono także pojęcie zaufanego ciągu
Nie rozwiązuje to jednak wszystkich problemów (głównie znaków, czyli techniki podobnej do trybu taint w Perlu.
Podczas operacji na ciągach znaków, samoczynnie zmie-
niają one swoją wielkość. Z tego powodu nie trzeba obawiać
Instalacja pakietów się, iż przekroczymy jakiś założony wcześniej limit.
Biblioteka oferuje nam nowy typ danych o nazwie
Instalacja poszczególnych bibliotek nie jest kłopotliwa, pomimo,
safestr_t. Jej autorzy umożliwili konwersję do typu char *
,
że wymaga kompilacji. Większość dystrybucji niestety nie ofe-
dzięki czemu możliwa jest bezproblemowa współpraca ze
ruje tych bibliotek, ale dzięki obecności skryptu ./configure ich
standardowymi funkcjami. Autorzy zalecają rozważne sto-
instalacja sprowadza się do typowych trzech komend.
sowanie tego mechanizmu.
Jako pierwsze podajemy polecenie: ./configure --prefix=
Jeśli tworzymy nowy ciąg znaków, to z poziomu SCSL
/usr, aby dokonać konfiguracji. Takie pakiety, jak SCSL czy
wykonujemy to następująco:
Vstr, nie wymagają dodatkowych opcji, natomiast dla Libcwd
warto dodać opcję --enable-magic. Pozostałe możliwości
w zakresie zarządzania pamięcią powinny być domyślnie włą- safestr_t str;
czone, choć zawsze można wydać serię następujących opcji,
str = safestr_create("jakiś ciąg znaków", 0);
takich jak --enable-alloc, --enable-marker, --enable-location. Po
konfiguracji wystarczy wydać polecenie make, aby rozpocząć
Dzięki konwersji do char * dopuszczalna jest następująca
kompilację, a po zakończeniu tego procesu make install, aby
konstrukcja:
zainstalować biblioteki. Warto wydać również polecenie ldcon-
fig, aby odświeżyć plik zawierający spis dostępnych bibliotek
safestr_t str;
dynamicznych.
str = safestr_alloc(10, 0);
Z tego schematu wyłamuje się pakiet Memwatch, gdyż
strcpy((char *)str, "ciąg znaków dłuższy niz 10 znaków");
są to zaledwie dwa pliki: memwatch.c oraz memwatch.h,
które należy dołączyć do własnego projektu w sposób, który
W takim przypadku jest to jawne proszenie się o kłopoty ,
opisałem w artykule. W przypadku pozostałych bibliotek, ich
dołączenie polega na dodaniu nazw bibliotek dynamicznych, ponieważ nad przypisaniem nowej wartości biblioteka SCSL
ewentualnie definicji odpowiednich flag, np. dla Libcwd polece- nie ma już kontroli. Z tego powodu powinniśmy unikać
nie kompilacji przedstawia się następująco:
mieszania ze sobą standardowych funkcji oraz pochodzą-
cych z SCSL.
g++ -o app app.cc -lcwd
Wśród typowych funkcji, takich jak przydzielanie
pamięci i wyznaczanie długości ciągów znaków, do
pod warunkiem, że biblioteki dynamiczne oraz pliki nagłówkowe
naszej dyspozycji oddano także odpowiedniki printf
zostały zainstalowane w typowych katalogach: /usr/lib oraz
w typowych odmianach: fprintf, sprintf oraz vsprintf.
/usr/include, czyli dla prefiksu /usr.
Niestety, brakuje odpowiednika scanf istnieje tylko
www.linux.com.pl 59
dla programistów
if (safestr_istrusted(cel)){
Listing 2. Wyświetlenie komunikatu na ekranie
// ciąg posiada flagę zaufania
}
#define VSTR_COMPILE_INCLUDE 1 else {
#include
// ciąg nie posiada flagi zaufania
#include }
int main(int argc, char **argv)
{ Interesującą funkcją jest safestr_memzero. Jej zadanie
Vstr_base *s=NULL; jest identyczne z biblioteczną funkcją memset, przy czym
if(!vstr_init()) wypełnia ona obszar pamięci zerami. Wprowadzenie takiej
return -1; funkcji autorzy umotywowali następująco: stosując silną
s=vstr_dup_cstr_buf(NULL, "Hello World!!!\n"); optymalizację, kompilator może usunąć wywołanie memset
while (s->len) vstr_sc_write_fd(s, 1, s->len, czyszczące pamięć. Funkcja ta została tak skonstruowana,
STDOUT_FILENO, NULL); aby kompilator nie przeprowadził na niej tego rodzaju opty-
vstr_free_base(s); malizacji.
vstr_exit();
return 0; Vstr inne spojrzenie na ciągi znaków
} O ile poprzednia biblioteka była przeznaczona dla każdego,
kto chce poprawić bezpieczeństwo obsługi ciągów znaków,
to biblioteka Vstr jest przeznaczona do obsługi ciągów
funkcja safestr_read-line, po której można spodziewać znaków w aplikacjach o charakterze sieciowym, gdzie istot-
się, że potrafi wczytać linię tekstu zakończoną znakiem nym aspektem jest odbiór danych. Nie ma oczywiście prze-
nowej linii bądz EOF. ciwwskazań, aby stosować Vstr w programach ogólnego
Jedną z ciekawszych możliwość SCSL jest utworzenie przeznaczenia. Wszystkie funkcje API zostały napisane tak,
ciągu znaków tylko do odczytu. Wykonuje to funkcja aby możliwa była praca w trybie nieblokującym. Oznacza
safestr_makereadonly. Autorzy oddali do naszej dyspozycji to, że podczas wysyłania ciągu znaków choćby na ekran
także funkcję, która sprawdza, czy dany ciąg istotnie jest może odbywać się to fragmentami. Co ważniejsze, funk-
przeznaczony tylko do odczytu: cje odczytujące ciąg znaków z dowolnego urządzenia także
posiadają taką własność. Takich możliwości nie oferowała
safestr_t str; poprzednia biblioteka SCSL jest ukierunkowana na zwięk-
str = safestr_create("jakiś ciąg znaków", 0); szenie bezpieczeństwa typowych operacji.
safestr_makereadonly(str); Vstr oferuje również ciekawe rozwiązania w zarządza-
if(safestr_isreadonly(str)) { niu pamięcią przechowującą poszczególne ciągi znaków.
// ciąg znaków jest przeznaczony wyłącznie do odczytu } Co więcej, Vstr nie posiada wyróżnionej reprezentacji
łańcucha znaków nie są one reprezentowane jako typ
Funkcją odwrotną jest safestr_makewritable. Powoduje
ona, że dany ciąg można modyfikować. Jeśli będziemy
Listing 3. Fragment kodu biblioteki Libmcrypt, zawierający
stosować tylko i wyłącznie funkcje SCSL, możemy mieć
błąd
pewność, że dany ciąg znaków nie będzie zmodyfikowa-
ny bez naszej wiedzy.
Drugą przydatną techniką są wspomniane wcześniej static int internal_init_mcrypt(MCRYPT td, void *key,
zaufane ciągi. Można nadać flagę zaufania (ang. trusted) int lenofkey, void *IV) {
na określony ciąg. Na zaufanym ciągu można oczywiście const int *sizes = NULL;
wykonywać wszystkie operacje, jednak własność zaufa- sizes = mcrypt_enc_get_supported_key_sizes(td,
nia będzie aktywna, gdy pozostałe ciągi znaków także &num_of_sizes);
będą miały tę własność. Wykonajmy operację wstawiania }
do ciągu zaufanego jakiegoś innego ciągu: if (sizes != NULL){
// free(sizes); !!! zle
safestr_t cel, tmp_str; } else {
safestr_insert(cel, 10, tmp_str); }
if (ok == 0){
Jeśli ciąg tmp_str nie posiada flagi zaufania, SCSL samo- }
czynnie zdejmie flagę zaufania z ciągu cel. Wykonując else {
ważne operacje możemy sprawdzać, czy nie doszło do }
operacji na ciągach znaków pozbawionych flagi zaufa- free(sizes); // OK
nia:
60 styczeń 2004
bezpieczne programy
odbieramy dane, a w innym musimy je przesłać do wielu
Listing 4. Przykładowy kod poddany analizie przez
odbiorców. Niemożliwe staje się w takim przypadku prze-
Memwatch
ciążenie programu operacjami wejścia/wyjścia.
#include
#include Vstr i odbieranie danych
#include "memwatch.h" Gdy odczytujemy dowolny ciąg znaków, zazwyczaj posłu-
int main(int argc, char **argv) gujemy się funkcją scanf. Wadą tej funkcji jest oczywiście
{ zupełny brak kontroli, ile danych zostanie odczytanych.
mwStatistics(2); Rozwiązaniem jest czytanie bezpośrednio funkcją read,
char *p, *unalloc; przy czym i tak samodzielnie trzeba napisać kod spraw-
p=(char *) malloc(5); dzający, ile znaków zostało odczytanych. Biblioteka Vstr
unalloc=(char *) malloc(500); oferuje funkcję, która odczytuje dokładnie tyle bajtów
strcpy(p, "12345"); ile trzeba i podobnie jak w przykładzie z poprzedniego
printf("%s\n", p); akapitu, może odczytywać dane fragmentami. Jest to
strcpy(p, "1234567"); funkcja vstr_sc_read_iov_fd. Posiada ona aż sześć argu-
printf("%s\n", p); mentów. Pierwszy to typ Vstr_base *
, reprezentujący ciąg
p=(char*)malloc(50); znaków. Drugi argument to miejsce, w którym ma zostać
free(p); dołączony nowy fragment ciągu znaków. Trzeci parametr
return 0; do deskryptor plików. Następne dwa argumenty to mini-
} malna i maksymalna ilość węzłów, które przeznaczamy
na odczytywany ciąg znaków. W ostatnim parametrze
możemy podać adres bądz wskazanie na zmienną,
char *
, ale raczej jako pojedyncze bloki bądz węzły. Idea w której zostanie umieszczony ewentualny dodatkowy
jest bardzo podobna do dynamicznej listy. Ciąg znaków kod błędu. Poniższy kod pozwala utworzyć ciąg znaków
jest przechowywany we fragmentach, które oczywiście ze standardowego wejścia:
nie stanowią ciągłego obszaru, tak jak w typowej orga-
Vstr_base *read_str;
nizacji ciągu dla języka C. W większości ataków wyko-
read_str=vstr_make_base (NULL);
rzystuje się ogólnie uznaną organizację łańcucha znaków.
while(vstr_sc_read_iov_fd(read_str, read_str->len,
Zmiana organizacji powoduje, że trudniej będzie wyko-
STDIN_FILENO, 1, 16, NULL));
rzystać sposób działania funkcji działających na ciągach
znaków. Dodatkowym walorem innej organizacji jest fakt,
że dzięki temu autorzy biblioteki Vstr mogli dość mocno
Listing 5. Wyniki śledzenia programu z Listingu 4
zoptymalizować obsługę ciągów znaków. Biblioteka ofe-
ruje także typowe API, jak choćby odpowiedniki funkcji = MEMWATCH 2.71 Copyright (C) 1992-1999 Johan Lindh =
printf, zgodne z nowym standardem języka C, czyli C99. Started at Sun Nov 23 22:03:03 2003
Co więcej, można wprowadzić także własne symbole Modes: __STDC__ 64-bit mwDWORD==(unsigned long)
formatujące. mwROUNDALLOC==8 sizeof(mwData)==32 mwDataSize==32
statistics: now collecting on a line basis
Hello World po raz wtóry Stopped at Sun Nov 23 22:03:03 2003
Choć wyświetlenie komunikatu na ekranie można zre- unfreed: <2> t1.c(14), 500 bytes at 003D25D0
alizować za pomocą Vstr w bardziej cywilizowany {FE FE FE FE FE FE FE FE FE FE .......}
sposób, to kod z Listingu 2 oddaje bardzo dobrze sens tej unfreed: <1> t1.c(13), 5 bytes at 003D2540
biblioteki. Wyświetlamy komunikat za pomocą pętli typu [overflowed] {31 32 33 34 35 .. .. .. 12345}
while małymi fragmentami. Przygotowaniem komunikatu Memory usage statistics (global):
zajmuje się funkcja vstr_dup_cstr_buf, która w jednym N)umber of allocations made: 3
z argumentów przyjmuje typowy ciąg dla języka C, czyli L)argest memory usage : 555
char *
. Wysyłanie zawartości zmiennej s to zadanie funkcji T)otal of all alloc() calls: 555
vstr_sc_write_fd. Jej zaletą jest oczywiście tryb pracy. Poje- U)nfreed bytes totals : 505
dynczym wywołaniem wysyłamy do urządzenia (w tym Memory usage statistics (detailed):
przypadku do strumienia stdout) fragment ciągu znaków, Module/Line Number Largest Total Unfreed
po czym aplikacja może wykonywać inne czynności. O ile t1.c 3 555 555 505
wysyłanie pojedynczego ciągu znaków nie stanowi dużego 22 1 50 50 0
obciążenia, to wysyłanie do kilku różnych zródeł już może. 14 1 500 500 500
Wykonywanie tej czynności małymi fragmentami oznacza 13 1 5 5 5
mniejsze obciążenie procesora. Wbrew pozorom ma to
duże znaczenie dla bezpieczeństwa, gdy w jednym miejscu
www.linux.com.pl 61
dla programistów
wiązań tego błędu było wstawienie funkcji free wewnątrz
Listing 6. Przykładowe komunikaty wygenerowane przez
instrukcji if (sizes != NULL), co wydaje się logiczne
bibliotekę Libcwd
(oznaczone na Listingu 3 przez "!!!"). Tymczasem wewnątrz
drugiej instrukcji warunkowej if (ok = = 0) zmienna sizes
MALLOC : operator new[] nadal była wykorzystywana (pod warunkiem, że nie przyj-
0x81a123a int [20], (size = 80); muje wartości NULL). Oczywiście prawidłowym rozwiąza-
zmienna p to tablica o 20 elementach niem jakże oczywistym jest umieszczenie wywołania free
MALLOC : delete[] 0x81a123a poza pierwszą i drugą instrukcja warunkową.
t1.cc:24 int [20]; Ten błąd prowadził ostatecznie do strat pamięci, ale
(sz = 80) zmienna p to tablica o 20 wyobrazmy sobie, że gdzieś indziej poprzez podobne nie-
elementach dopatrzenie nie byłyby usuwane wskazniki na klucz. Taki
COREDUMP: delete[]: magic number corrupt! błąd znacząco obniżałby jakość biblioteki, a bezpieczeń-
Quit (core dumped) stwo szyfrowanych danych byłoby bardzo niskie.
Zastosowanie odpowiednich monitorów pamięci,
choćby takich jak opisane poniżej Memwatch dla języka
Nie ma tu ograniczenia, ilu danych się spodziewamy, więc C i Libcwd dla C++, ułatwiłoby odszukanie takiego błędu
pętla będzie działać tak długo, jak będą jakieś dane do każde wywołanie malloc czy free jest zliczane i śledzone.
odczytu. Określimy tylko ilość węzłów, z których składa
się ciąg, przy czym węzły mogą mieć dowolną długość. Pakiet Memwatch
Zaletą Vstr jest fakt, że wczytanie nowych fragmentów Memwatch to mały pakiet składający się z dwóch plików
nie będzie wymagać czasochłonnej alokacji czy realokacji zródłowych: memwatch.c i memwatch.h, które można dołą-
pamięci. czyć do naszej aplikacji, aby uzyskać śledzenie wywołań
funkcji malloc i free.
Obsługa pamięci Listing 4 zawiera przykład kodu, w którym zostały
Drugim istotnym aspektem na drodze uzyskania bezpiecz- popełnione błędy w zarządzaniu pamięcią. Dokładnie
nej aplikacji jest poprawne zarządzanie pamięcią. Aby cztery błędy, bowiem dwa razy kopiujemy dłuższy ciąg
uczynić nasz program lepszym w tym względzie, najlepiej znaków do zmiennej p, pamięć przydzielona zmiennej
wprowadzić automatyczne zarządzanie pamięcią wzorem unalloc nie została zwolniona, natomiast zmienna p ma
języka Java. Niestety, powoduje to obniżenie wydajności. dwukrotnie przydzielaną pamięć.
Wiązałoby to się także z koniecznością modyfikacji samego API pakietu Memwatch zawiera wiele funkcji przy-
języka (czytelnikowi zainteresowanemu taką tematyką datnych przy bardziej zaawansowanej obsłudze, ale
polecam odwiedzić strony o językach D oraz Cyclone). w naszym programie wystarczy dołączyć plik nagłówko-
Do problemu można podejść inaczej. Program można wy. Aby uzyskać więcej informacji, wywołujemy funkcję
wyposażyć w dodatkową bibliotekę, która będzie zajmo- mwStatistics z parametrem 2. Jak widać, w pozostałej
wać się monitorowaniem operacji na pamięci. Tego rodzaju części kodu operacje na pamięci są przeprowadzane
biblioteki są nazywane debugerami pamięci (ang. memory w standardowy sposób. Dołączenie pakietu Memwatch
debuggers). Śledzą one wywołania standardowych funkcji, powinno wychwycić popełnione przez nas błędy i tak
takich jak malloc oraz free, bądz operatorów new i delete jest w istocie podczas kompilacji wystarczy zdefiniować
dla języka C++. Tego rodzaju bibliotek powstało bardzo dwie flagi: MEMWATCH oraz MEMWATCHSTDIO. Pierw-
wiele i można dosłownie przebierać wśród dostępnych roz-
wiązań. Wspólną cechą wielu z nich jest to, że można pisać
program przy pomocy standardowych poleceń, takich jak
malloc i free. Podczas kompilacji odpowiednią flagą naka-
zujemy podmianę oryginalnych poleceń na pochodzące
z określonej biblioteki, uzyskując w ten sposób śledzenie
obsługi pamięci. Usuwając definicję flagi podczas ponow-
nej kompilacji przywracane są standardowe funkcje.
Wykrycie błędu w obsłudze pamięci jest znacznie trud-
niejsze niż w przypadku bufora, nawet jeśli kod jest dość
oczywisty, czego przykładem może być błąd w bibliotece
Libmcrypt, która (uwaga!) służy do obsługi szyfrów syme-
trycznych.
Błąd, który popełniono w tej bibliotece, wiązał się
z przydzieleniem pamięci zmiennej sizes przez funkcję
Rysunek 2. Strona domowa biblioteki Vstr, która zawiera wiele
mcrypt_enc_get_supported_key_sizes nigdzie nie była
ona potem zwalniana. Co gorsza, jednym z pierwszych roz- informacji związanych z bezpieczną obsługą ciągów znaków
62 styczeń 2004
bezpieczne programy
sza definicja naturalnie uruchamia cały system śledzenia,
Listing 7. Badanie strat w pamięci
natomiast druga wprowadza możliwość odpowiedzi na
niespełnione asercje. Wobec tego, kompilacja przedstawia
się następująco: libcw::debug::marker_ct *mkr =
new libcw::debug::marker_ct( znacznik 1 );
gcc -DMEMWATCH -DMEMWATCH_STDIO t1.c memwatch.c -o t1 I. // operacje na pamięci
Debug(move_outside(mkr, p));
Gdy skompilujemy i uruchomimy program, pojawi się if (libcw::debug::mem_blocks() > 0)
plik z logiem, w którym znajdą się informacje, jakie błędy Dout(dc::warning, "Nie zwolniono pamięci "
zostały wychwycone oraz opis zużycia pamięci przez nasz else
program. Dla kodu z Listingu 4 plik z logiem wygląda tak Dout(dc::malloc, "Brak strat w pamieci\n");
jak na Listingu 5. Po słowie overflow zostały wymienione delete mkr;
linie, w których nastąpiło przekroczenie wielkości bufora.
Została tam także umieszczona informacja, o ile bajtów
przekroczony został bufor. Dodatkowo, po słowie unfre- mogą funkcjonować tak jak oryginalne asercje, czyli zatrzy-
ed zostały wyszczególnione zmienne, dla których pamięć mywać natychmiast działanie programu. Wersja asercji
nie została zwolniona. Pakiet Memwatch wskazuje także z pakietu Memwatch umożliwia jednak zadanie pytania,
numery linii w kodzie zródłowym, w których następuje czy wykonanie danej asercji ma być powtórzone bądz
przydzielenie pamięci. zignorowane, czy też ostatecznie przerwać wykonanie
Zachęcam, aby do jednego ze swoich własnych pro- programu.
gramów pisanych w C podłączyć ten pakiet i prześledzić
zarządzanie pamięcią. Może się okazać, że gdzieś pogubiły Libcwd coś dla miłośników C++
się cenne bajty. Rozwiązania podobne do Memwatch istnieją także dla
Oczywiście, możliwość uzyskania logu nie jest końcem języka C++. Jednym z bardziej rozbudowanych jest biblio-
możliwości Memwatch. Pakiet oddaje nam do dyspozycji teka Libcwd. Nie jest to tylko i wyłącznie pakiet do moni-
funkcję mwIsSafeAddr, którą sprawdzimy, czy dany adres torowania operacji na pamięci, ale również cały system
jest bezpieczny. Poniższym kodem badamy, czy istnieje przeznaczony do śledzenia pracy aplikacji.
możliwość zapisu danych pod zmienną wskaznikową p, Śledzenie operacji zawiązanych z dynamicznym przy-
lecz 100kB dalej względem jej początku: dzielaniem pamięci w przypadku C++ jest ułatwione dzięki
własnościom samego języka. Przeciążenie operatorów
if( mwIsSafeAddr(p+(1024*100),1) ) { new oraz delete (oraz ich odmian nazywanych operato-
*(p+(1024*100)) = 0; rami umieszczającymi) powoduje, że wszelkie operacje
} na pamięci można łatwo monitorować. Libcwd oferuje
śledzenie dosłownie wszystkich operacji na pamięci, doko-
W logu możemy także wprowadzać własne komentarze nywanych przez naszą aplikację nawet na poziome funkcji
makrem TRACE. bibliotecznych C++. Takich możliwości nie dawał nam
Pakiet Memwatch pomimo niewielkich rozmiarów pakiet Memwatch świadczy to dobrze o jakości biblioteki
posiada wiele cennych możliwości w zakresie ochrony Libcwd.
pamięci. Bardzo cennym rozszerzeniem są asercje, które Libcwd oferuje także wykrywanie błędów charaktery-
stycznych dla C++ , np. pomyłki w używaniu operatorów
do alokacji pamięci zostanie zastosowany operator new,
a do usunięcia delete [].
O ile stosowanie Memwatch ograniczało się do uży-
wania malloc i free, to libcwd pozwala na wprowadza-
nia dodatkowych komentarzy poprzez funkcje AllocTag.
Informacje te są używane przez bibliotekę w momencie
wykrycia nieprawidłowości, więc oprócz informacji,
w której linii nastąpił błąd, możemy także podać komen-
tarz, np.:
int N=20;
int* p = new int[N];
AllocTag(p, "zmienna p to tablica o << n
Fakt, że została zadeklarowana zmienna zostanie odno-
Rysunek 3. Strona domowa biblioteki Libcwd
towany w logu oraz umieszczony w komentarzu. Wykry-
www.linux.com.pl 63
dla programistów
rzystać do sprawdzenia, czy nastąpiła utrata pamięci. Po
Tabela 1. Spis funkcji operujących na ciągach znaków
utworzeniu znacznika można wykonywać operacje przy-
Funkcja Przeznaczenie
dzielenia i zwalniania pamięci, ale gdy chcemy spraw-
Safe C String Library
dzić, czy mamy jakieś straty, należy wywołać funkcję
safestr_create utworzenie ciągu znaków
move_outside i wskazać zmienną, od której rozpoczęto
safestr_alloc alokacja pamięci dla ciągu znaków operacje z pamięcią. Umożliwia to sprawdzenie za
pomocą mem_blocks (Listing 7), czy istnieją jakieś nie-
safestr_makereadonly ciąg tylko do odczytu
zwolnione bloki. Trzeba pamiętać, że funkcje mem_blocks
safestr_isreadonly test, czy ciąg jest przeznaczony tylko
oraz mem_size nie tylko biorą pod uwagę zmienne aloko-
dla odczytu
wane przez bibliotekę standardową, a także ciągi znaków
safestr_makewritable ustala, że ciąg może być modyfiko-
podawane w cudzysłowach.
wany
Biblioteka Libcwd oferuje jeszcze więcej możliwości
safestr_memzero czyści pamięć
związanych z samym procesem śledzenia, np. można śle-
safestr_trust ustala flagę zaufania dzić proces ładowania bibliotek dynamicznych, odczytywać
zdefiniowane symbole oraz na podstawie adresu biblioteki
safestr_istrusted sprawdza, czy ciąg posiada flagę
sprawdzić, ile pamięci pod ten adres zostało przydzielo-
zaufania
nych. Zachęcam do dalszej analizy Libcwd osobom tworzą-
safestr_insert wstawia ciąg znaków
cym w C++, a także początkującym, którym z pewnością
VStr
pomoże tworzyć lepsze oprogramowanie.
vstr_init inicjalizacja biblioteki
vstr_exit zakończenie pracy biblioteki Podsumowanie
Temat tworzenia bezpiecznego oprogramowania jest
vstr_sc_write_fd zapis do pliku/gniazdka o podanym
bardzo obszerny i mam nadzieję, że udało mi się pokazać,
uchwycie
iż dotyczy wielu aspektów związanych z obsługą pamięci
vstr_sc_read_iov_fd odczyt z pliku/gniazdka o podanym
czy ciągów znaków, a także przesyłaniem danych.
uchwycie
Wystarczy uświadomić sobie, że zwykła operacja
vstr_dup_cstr_buf kopia ciągu znaków
odczytywania znaków z wejścia jak krótko to pokaza-
vstr_make_base utworzenie typu reprezentującego
łem przy okazji biblioteki Vstr powinna być odpowied-
ciąg znaków
nio zaimplementowana. Istnieje jeszcze wiele bibliotek,
vstr_free_base usunięcie z pamięci ciągu znaków np. Electric Fence, gdzie test, czy nie przekroczono zakre-
su przydzielonej pamięci jest wykonywany z użyciem
wanie przez Libcwd przepełnienia bufora również jest fizycznych możliwości procesora do chronienia pamięci
możliwe. Jak podaje autor, należy podczas konfiguracji powoduje to, że ta biblioteka staje się bardzo skuteczna,
pamiętać o opcji --enable-magic, która powoduje, że choć nie oferuje tak szerokich statystyk jak Memwatch
każdy przydzielony blok jest wyposażany w specjalne czy Libcwd.
dodatkowe wartości, tzw. magiczne liczby (ang. magic
number) na jego początku i końcu. Jeśli zostaną one
zamazane, to oczywiście oznacza to, że nastąpiła próba
W Sieci:
przepełnieniu bufora. Uzupełnijmy kod o następujące
linie:
" Biblioteka Safe C String Library:
http://www.zork.org/safestr/
p[20]=0;
" Biblioteka Vstr:
p[21]=0; http://www.and.org/vstr/
" Strona autora pakietu Memwatch:
p[22]=0;
http://www.linkdata.se/
delete [] p;
" Biblioteka Libcwd:
http://libcwd.sourceforge.net/
W logu powinny znalezć się informacje podobne do
" Język D rozwijany przez firmę Digital Mars:
tych z Listingu 6. W pierwszej linii zostaliśmy poinfor-
http://www.digitalmars.com/d/
mowani o przydzieleniu pamięci. Druga linia dotyczy
" Bezpieczny dialekt języka C Cyclone:
wywołania operatora delete. Proces ten jednak nie
http://www.cs.cornell.edu/projects/cyclone/
został zakończony z powodu zmodyfikowania dodat-
" Serwis zajmujący się aspektami bezpieczeństwa (zawiera
kowych wartości.
archiwa listy dyskusyjnej Bugtraq):
Do innych możliwości Libcwd należy np. sprawdza- http://www.securityfocus.com/
nie, czy zmienna może zostać pomyślne usunięta. Taki " Opis błędu w programie MPlayer:
http://www.securityfocus.com/archive/1/339330
test przeprowadza się funkcją test_delete. Biblioteka
oferuje również znaczniki pamięci, które można wyko-
64 styczeń 2004
Wyszukiwarka
Podobne podstrony:
01 Wprowadzenie do programowania w jezyku C
21 Pisanie i uruchamianie programów w asemblerze
C i C Bezpieczne programowanie(1)
01 mój pierwszy program kod
01 Projektowanie i tworzenie programów
01 Intro Coding Programming
Razem bezpieczniej Program RM 2007 15 prezentacja
2004 01 Loop AES – szyfrowane systemy plików [Bezpieczenstwo]
2004 08 Katalog filmów [Programowanie]
2004 01 Praca z OpenSSH [Administracja]
więcej podobnych podstron