2004.01_Pisanie bezpiecznych programów_[Programowanie].pdf

(402 KB) Pobierz
439033736 UNPDF
dla programistów
Pisanie bezpiecznych
programów
Marek Sawerwain
wane projekty. Błędy, które pojawiają się w tak
złożonym kodzie, są niestety jego naturalną czę-
ś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.
Powstało wiele dodatkowych rozwiązań dla języków
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
jest przeznaczona dla systemów uniksowych, w tym rów-
nież dla Linuksa. W tym artykule chciałbym przedstawić
kilka bardzo cennych rozwiązań, które pozwalają tworzyć
oprogramowanie wydajne oraz – co ważne – znacznie bez-
pieczniejsze i odporne na wyjątkowe sytuacje.
Wszystkie omawiane w tym artykule biblioteki działają
znakomicie pod Linuksem, który nadaje się znacznie lepiej
na platformę do tworzenia bezpiecznych programów niż
choćby Windows, bowiem liczne grono użytkowników
konkretnych bibliotek powoduje, że znacznie szybciej
usuwane są błędy i poszerzane ich możliwości. Co więcej,
niektóre biblioteki – jak dość szeroko znana Electric Fence
- są przeznaczone wyłącznie dla systemów uniksowych.
Rysunek 1. Zawartość stosu przed i po zmianie zawartości
zmiennej b
nych oraz dopracowanych programów, jak Apache , Send-
mail , Wu-ftpd czy nawet OpenSSH . Ten rodzaj błędu nie
dotyczy tylko i wyłącznie aplikacji związanych z obsługą
sieci. Pierwszy z brzegu przykład programu, który można
przytoczyć, to standardowy program Man , w którym już
dość dawno, bo w 1999 roku, wykryto taki błąd.
Ogólny zarys techniki przepełnia bufora jest wręcz try-
wialny. Spójrzmy na taką funkcję:
void funkcja(char *t) {
int i,j;
char b[32];
strcpy(&b, “ciag znaków“);
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
prawdopodobieństwo, że znajdują się w nim błędy. Błędy
wynikające z przepełnienia bufora dotyczą nawet tak zna-
Wewnętrzny bufor b może okazać się zbyt mały, jeśli
parametr t będzie zawierał zbyt dużo znaków. Zapisanie
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
preparując dane można ten adres zmodyfikować – Rysu-
nek 1.
Błąd przepełnienia bufora w powyższej postaci dość
często pojawia się w programach. Przykładem może być
świetny program do odtwarzania plików multimedialnych
O autorze:
Autor zajmuje się tworzeniem oprogramowania dla WIN32
i Linuksa. Zainteresowania: teoria języków programowania
oraz dobra literatura.
Kontakt z autorem: autorzy@linux.com.pl.
58
styczeń 2004
T worzone obecnie programy to duże i skompliko-
439033736.006.png
bezpieczne programy
MPlayer. Całkiem niedawno odkryto w tym programie
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
Listing 1.
Odpowiednio preparując plik w formacie asf można
umieścić kod w adresach URL, a funkcja sprintf może
doprowadzić do nadpisania zmiennej str i w efekcie adresu
powrotnego funkcji, ponieważ reaguje tylko na ogranicze-
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
alokacja pamięci. Innym rozwiązaniem jest zastosowanie
bibliotek do zarządzania ciągami znaków, w których długo-
ści ciągów znaków są dynamiczne. Jest to dość skuteczne
rozwiązanie. Czasem, jak w przypadku sprintf , można
ograniczyć liczbę znaków. Z tego powodu poprawka
MPlayera w niefortunnym kodzie z Listingu 1 jest bardzo
prosta: sprintf( str, "Host: %.220s:%d", url->hostname,
url->port ); .
Istnieją także mechanizmy, które można wbudować
w kompilator, np. do kompilatora GCC w wersji 2.96
dołączano łatkę o nazwie StackGuard , która chroniła stos.
Nie rozwiązuje to jednak wszystkich problemów (głównie
Listing 1. Fragmenty funkcji asf_http_request
HTTP_header_t * asf_http_request ( streaming_ctrl_t
* streaming_ctrl ) {
char str [ 250 ];
sprintf ( str , "Host: %s:%d" , server_url -> hostname ,
server_url -> port );
sprintf ( str , "Host: %s:%d" , url -> hostname , url -> port );
}
związanych z alokacją pamięci), a łatka ta nie jest jeszcze
dostępna dla nowych wersji kompilatora GCC 3.x. Ponadto
StackGuard obniża wydajność – wywołanie funkcji i powrót
z nich pociąga za sobą testy spójności stosu.
SCSL – bezpieczna obsługa ciągów
znaków
Interesującą biblioteką do obsługi ciągów znaków jest Safe
C String Library (SCSL). Oferuje ona ponad dwadzieścia
funkcji związanych z obsługą ciągów znaków ze szcze-
gólnym uwzględnieniem ochrony przed przepełnieniem
bufora. Wprowadzono także pojęcie zaufanego ciągu
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ć
się, iż przekroczymy jakiś założony wcześniej limit.
Biblioteka oferuje nam nowy typ danych o nazwie
safestr_t . Jej autorzy umożliwili konwersję do typu char * ,
dzięki czemu możliwa jest bezproblemowa współpraca ze
standardowymi funkcjami. Autorzy zalecają rozważne sto-
sowanie tego mechanizmu.
Jeśli tworzymy nowy ciąg znaków, to z poziomu SCSL
wykonujemy to następująco:
Instalacja pakietów
Instalacja poszczególnych bibliotek nie jest kłopotliwa, pomimo,
że wymaga kompilacji. Większość dystrybucji niestety nie ofe-
ruje tych bibliotek, ale dzięki obecności skryptu ./configure ich
instalacja sprowadza się do typowych trzech komend.
Jako pierwsze podajemy polecenie: ./configure --prefix=
/usr , aby dokonać konfiguracji. Takie pakiety, jak SCSL czy
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łą-
czone, choć zawsze można wydać serię następujących opcji,
takich jak --enable-alloc , --enable-marker , --enable-location . Po
konfiguracji wystarczy wydać polecenie make , aby rozpocząć
kompilację, a po zakończeniu tego procesu make install , aby
zainstalować biblioteki. Warto wydać również polecenie ldcon-
fig , aby odświeżyć plik zawierający spis dostępnych bibliotek
dynamicznych.
Z tego schematu wyłamuje się pakiet Memwatch, gdyż
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
opisałem w artykule. W przypadku pozostałych bibliotek, ich
dołączenie polega na dodaniu nazw bibliotek dynamicznych,
ewentualnie definicji odpowiednich flag, np. dla Libcwd polece-
nie kompilacji przedstawia się następująco:
safestr_t str;
str = safestr_create("jakiś ciąg znaków", 0);
Dzięki konwersji do char * dopuszczalna jest następująca
konstrukcja:
safestr_t str;
str = safestr_alloc(10, 0);
strcpy((char *)str, "ciąg znaków dłuższy niz 10 znaków");
W takim przypadku jest to jawne „proszenie się o kłopoty”,
ponieważ nad przypisaniem nowej wartości biblioteka SCSL
nie ma już kontroli. Z tego powodu powinniśmy unikać
mieszania ze sobą standardowych funkcji oraz pochodzą-
cych z SCSL.
Wśród typowych funkcji, takich jak przydzielanie
pamięci i wyznaczanie długości ciągów znaków, do
naszej dyspozycji oddano także odpowiedniki printf
w typowych odmianach: fprintf , sprintf oraz vsprintf .
Niestety, brakuje odpowiednika scanf – istnieje tylko
g++ -o app app.cc -lcwd
pod warunkiem, że biblioteki dynamiczne oraz pliki nagłówkowe
zostały zainstalowane w typowych katalogach: /usr/lib oraz
/usr/include , czyli dla prefiksu /usr .
www.linux.com.pl
59
439033736.007.png
dla programistów
Listing 2. Wyświetlenie komunikatu na ekranie
if (safestr_istrusted(cel)){
// ciąg posiada flagę zaufania
}
else {
// ciąg nie posiada flagi zaufania
}
#define VSTR_COMPILE_INCLUDE 1
#include <vstr.h>
#include <unistd.h>
int main ( int argc , char ** argv )
{
Vstr_base * s = NULL ;
if (! vstr_init ())
return - 1 ;
s = vstr_dup_cstr_buf ( NULL , "Hello World!!! \n " );
while ( s -> len ) vstr_sc_write_fd ( s , 1 , s -> len ,
STDOUT_FILENO , NULL );
vstr_free_base ( s );
vstr_exit ();
return 0;
}
Interesującą funkcją jest safestr_memzero . Jej zadanie
jest identyczne z biblioteczną funkcją memset , przy czym
wypełnia ona obszar pamięci zerami. Wprowadzenie takiej
funkcji autorzy umotywowali następująco: stosując silną
optymalizację, kompilator może usunąć wywołanie memset
czyszczące pamięć. Funkcja ta została tak skonstruowana,
aby kompilator nie przeprowadził na niej tego rodzaju opty-
malizacji.
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
znaków w aplikacjach o charakterze sieciowym, gdzie istot-
nym aspektem jest odbiór danych. Nie ma oczywiście prze-
ciwwskazań, aby stosować Vstr w programach ogólnego
przeznaczenia. Wszystkie funkcje API zostały napisane tak,
aby możliwa była praca w trybie nieblokującym. Oznacza
to, że podczas wysyłania ciągu znaków – choćby na ekran
– może odbywać się to fragmentami. Co ważniejsze, funk-
cje odczytujące ciąg znaków z dowolnego urządzenia także
posiadają taką własność. Takich możliwości nie oferowała
poprzednia biblioteka – SCSL jest ukierunkowana na zwięk-
szenie bezpieczeństwa typowych operacji.
Vstr oferuje również ciekawe rozwiązania w zarządza-
niu pamięcią przechowującą poszczególne ciągi znaków.
Co więcej, Vstr nie posiada wyróżnionej reprezentacji
łańcucha znaków – nie są one reprezentowane jako typ
funkcja safestr_read-line , po której można spodziewać
się, że potrafi wczytać linię tekstu zakończoną znakiem
nowej linii bądź EOF.
Jedną z ciekawszych możliwość SCSL jest utworzenie
ciągu znaków tylko do odczytu. Wykonuje to funkcja
safestr_makereadonly . Autorzy oddali do naszej dyspozycji
także funkcję, która sprawdza, czy dany ciąg istotnie jest
przeznaczony tylko do odczytu:
safestr_t str;
str = safestr_create("jakiś ciąg znaków", 0);
safestr_makereadonly(str);
if(safestr_isreadonly(str)) {
// ciąg znaków jest przeznaczony wyłącznie do odczytu }
Funkcją odwrotną jest safestr_makewritable . Powoduje
ona, że dany ciąg można modyfikować. Jeśli będziemy
stosować tylko i wyłącznie funkcje SCSL, możemy mieć
pewność, że dany ciąg znaków nie będzie zmodyfikowa-
ny bez naszej wiedzy.
Drugą przydatną techniką są wspomniane wcześniej
zaufane ciągi. Można nadać flagę zaufania (ang. trusted )
na określony ciąg. Na zaufanym ciągu można oczywiście
wykonywać wszystkie operacje, jednak własność zaufa-
nia będzie aktywna, gdy pozostałe ciągi znaków także
będą miały tę własność. Wykonajmy operację wstawiania
do ciągu zaufanego jakiegoś innego ciągu:
Listing 3. Fragment kodu biblioteki Libmcrypt, zawierający
błąd
static int internal_init_mcrypt ( MCRYPT td , void * key ,
int lenofkey , void * IV ) {
const int * sizes = NULL ;
sizes = mcrypt_enc_get_supported_key_sizes ( td ,
& num_of_sizes );
}
if ( sizes != NULL ) {
// free(sizes); !!! źle
} else {
}
if ( ok == 0 ) {
}
else {
}
free ( sizes ); // OK
safestr_t cel, tmp_str;
safestr_insert(cel, 10, tmp_str);
Jeśli ciąg tmp_str nie posiada flagi zaufania, SCSL samo-
czynnie zdejmie flagę zaufania z ciągu cel . Wykonując
ważne operacje możemy sprawdzać, czy nie doszło do
operacji na ciągach znaków pozbawionych flagi zaufa-
nia:
60
styczeń 2004
439033736.008.png 439033736.009.png
bezpieczne programy
Listing 4. Przykładowy kod poddany analizie przez
Memwatch
odbieramy dane, a w innym musimy je przesłać do wielu
odbiorców. Niemożliwe staje się w takim przypadku prze-
ciążenie programu operacjami wejścia/wyjścia.
#include <stdio.h>
#include <signal.h>
#include "memwatch.h"
int main ( int argc , char ** argv )
{
mwStatistics ( 2 );
char * p , * unalloc ;
p =( char *) malloc ( 5 );
unalloc =( char *) malloc ( 500 );
strcpy ( p , "12345" );
printf ( "%s \n " , p );
strcpy ( p , "1234567" );
printf ( "%s \n " , p );
p =( char *) malloc ( 50 );
free ( p );
return 0 ;
}
Vstr i odbieranie danych
Gdy odczytujemy dowolny ciąg znaków, zazwyczaj posłu-
gujemy się funkcją scanf . Wadą tej funkcji jest oczywiście
zupełny brak kontroli, ile danych zostanie odczytanych.
Rozwiązaniem jest czytanie bezpośrednio funkcją read ,
przy czym i tak samodzielnie trzeba napisać kod spraw-
dzający, ile znaków zostało odczytanych. Biblioteka Vstr
oferuje funkcję, która odczytuje dokładnie tyle bajtów
ile trzeba i podobnie jak w przykładzie z poprzedniego
akapitu, może odczytywać dane fragmentami. Jest to
funkcja vstr_sc_read_iov_fd . Posiada ona aż sześć argu-
mentów. Pierwszy to typ Vstr_base * , reprezentujący ciąg
znaków. Drugi argument to miejsce, w którym ma zostać
dołączony nowy fragment ciągu znaków. Trzeci parametr
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ądź wskazanie na zmienną,
w której zostanie umieszczony ewentualny dodatkowy
kod błędu. Poniższy kod pozwala utworzyć ciąg znaków
ze standardowego wejścia:
char * , ale raczej jako pojedyncze bloki bądź węzły. Idea
jest bardzo podobna do dynamicznej listy. Ciąg znaków
jest przechowywany we fragmentach, które oczywiście
nie stanowią ciągłego obszaru, tak jak w typowej orga-
nizacji ciągu dla języka C. W większości ataków wyko-
rzystuje się ogólnie uznaną organizację łańcucha znaków.
Zmiana organizacji powoduje, że trudniej będzie wyko-
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
zoptymalizować obsługę ciągów znaków. Biblioteka ofe-
ruje także typowe API, jak choćby odpowiedniki funkcji
printf , zgodne z nowym standardem języka C, czyli C99.
Co więcej, można wprowadzić także własne symbole
formatujące.
Vstr_base *read_str;
read_str=vstr_make_base (NULL);
while(vstr_sc_read_iov_fd(read_str, read_str->len,
STDIN_FILENO, 1, 16, NULL));
Listing 5. Wyniki śledzenia programu z Listingu 4
= MEMWATCH 2.71 Copyright (C) 1992-1999 Johan Lindh =
Started at Sun Nov 23 22:03:03 2003
Modes: __STDC__ 64-bit mwDWORD==(unsigned long)
mwROUNDALLOC==8 sizeof(mwData)==32 mwDataSize==32
statistics: now collecting on a line basis
Stopped at Sun Nov 23 22:03:03 2003
unfreed: <2> t1.c(14), 500 bytes at 003D25D0
{FE FE FE FE FE FE FE FE FE FE .......}
unfreed: <1> t1.c(13), 5 bytes at 003D2540
[overflowed] {31 32 33 34 35 .. .. .. 12345}
Memory usage statistics (global):
N)umber of allocations made: 3
L)argest memory usage : 555
T)otal of all alloc() calls: 555
U)nfreed bytes totals : 505
Memory usage statistics (detailed):
Module/Line Number Largest Total Unfreed
t1.c 3 555 555 505
22 1 50 50 0
14 1 500 500 500
13 1 5 5 5
„Hello World” po raz wtóry
Choć wyświetlenie komunikatu na ekranie można zre-
alizować za pomocą Vstr w bardziej „cywilizowany”
sposób, to kod z Listingu 2 oddaje bardzo dobrze sens tej
biblioteki. Wyświetlamy komunikat za pomocą pętli typu
while małymi fragmentami. Przygotowaniem komunikatu
zajmuje się funkcja vstr_dup_cstr_buf, która w jednym
z argumentów przyjmuje typowy ciąg dla języka C, czyli
char * . Wysyłanie zawartości zmiennej s to zadanie funkcji
vstr_sc_write_fd . Jej zaletą jest oczywiście tryb pracy. Poje-
dynczym wywołaniem wysyłamy do urządzenia (w tym
przypadku do strumienia stdout ) fragment ciągu znaków,
po czym aplikacja może wykonywać inne czynności. O ile
wysyłanie pojedynczego ciągu znaków nie stanowi dużego
obciążenia, to wysyłanie do kilku różnych źródeł już może.
Wykonywanie tej czynności małymi fragmentami oznacza
mniejsze obciążenie procesora. Wbrew pozorom ma to
duże znaczenie dla bezpieczeństwa, gdy w jednym miejscu
www.linux.com.pl
61
439033736.001.png 439033736.002.png
dla programistów
Listing 6. Przykładowe komunikaty wygenerowane przez
bibliotekę Libcwd
wiązań tego błędu było wstawienie funkcji free wewnątrz
instrukcji if (sizes != NULL) , co wydaje się logiczne
(oznaczone na Listingu 3 przez "!!!"). Tymczasem wewnątrz
drugiej instrukcji warunkowej if (ok == 0) zmienna sizes
nadal była wykorzystywana (pod warunkiem, że nie przyj-
muje wartości NULL). Oczywiście prawidłowym rozwiąza-
niem jakże oczywistym jest umieszczenie wywołania free
poza pierwszą i drugą instrukcja warunkową.
Ten błąd prowadził ostatecznie do strat pamięci, ale
wyobraźmy sobie, że gdzieś indziej poprzez podobne nie-
dopatrzenie nie byłyby usuwane wskaźniki na klucz. Taki
błąd znacząco obniżałby jakość biblioteki, a bezpieczeń-
stwo szyfrowanych danych byłoby bardzo niskie.
Zastosowanie odpowiednich monitorów pamięci,
choćby takich jak opisane poniżej Memwatch dla języka
C i Libcwd dla C++, ułatwiłoby odszukanie takiego błędu –
każde wywołanie malloc czy free jest zliczane i śledzone.
MALLOC : operator new[]
0x81a123a int [20], (size = 80);
zmienna p to tablica o 20 elementach
MALLOC : delete[] 0x81a123a
t1.cc:24 int [20];
(sz = 80) zmienna p to tablica o 20
elementach <unfinished>
COREDUMP: delete[]: magic number corrupt!
Quit (core dumped)
Nie ma tu ograniczenia, ilu danych się spodziewamy, więc
pętla będzie działać tak długo, jak będą jakieś dane do
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ść.
Zaletą Vstr jest fakt, że wczytanie nowych fragmentów
nie będzie wymagać czasochłonnej alokacji czy realokacji
pamięci.
Pakiet Memwatch
Memwatch to mały pakiet składający się z dwóch plików
źródłowych: memwatch.c i memwatch.h , które można dołą-
czyć do naszej aplikacji, aby uzyskać śledzenie wywołań
funkcji malloc i free .
Listing 4 zawiera przykład kodu, w którym zostały
popełnione błędy w zarządzaniu pamięcią. Dokładnie
cztery błędy, bowiem dwa razy kopiujemy dłuższy ciąg
znaków do zmiennej p , pamięć przydzielona zmiennej
unalloc nie została zwolniona, natomiast zmienna p ma
dwukrotnie przydzielaną pamięć.
API pakietu Memwatch zawiera wiele funkcji przy-
datnych przy bardziej zaawansowanej obsłudze, ale
w naszym programie wystarczy dołączyć plik nagłówko-
wy. Aby uzyskać więcej informacji, wywołujemy funkcję
mwStatistics z parametrem 2. Jak widać, w pozostałej
części kodu operacje na pamięci są przeprowadzane
w standardowy sposób. Dołączenie pakietu Memwatch
powinno wychwycić popełnione przez nas błędy i tak
jest w istocie – podczas kompilacji wystarczy zdefiniować
dwie flagi: MEMWATCH oraz MEMWATCHSTDIO. Pierw-
Obsługa pamięci
Drugim istotnym aspektem na drodze uzyskania bezpiecz-
nej aplikacji jest poprawne zarządzanie pamięcią. Aby
uczynić nasz program lepszym w tym względzie, najlepiej
wprowadzić automatyczne zarządzanie pamięcią wzorem
języka Java. Niestety, powoduje to obniżenie wydajności.
Wiązałoby to się także z koniecznością modyfikacji samego
języka (czytelnikowi zainteresowanemu taką tematyką
polecam odwiedzić strony o językach D oraz Cyclone).
Do problemu można podejść inaczej. Program można
wyposażyć w dodatkową bibliotekę, która będzie zajmo-
wać się monitorowaniem operacji na pamięci. Tego rodzaju
biblioteki są nazywane debugerami pamięci (ang. memory
debuggers ). Śledzą one wywołania standardowych funkcji,
takich jak malloc oraz free , bądź operatorów new i delete
dla języka C++. Tego rodzaju bibliotek powstało bardzo
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ę
mcrypt_enc_get_supported_key_sizes – nigdzie nie była
ona potem zwalniana. Co gorsza, jednym z pierwszych roz-
Rysunek 2. Strona domowa biblioteki Vstr, która zawiera wiele
informacji związanych z bezpieczną obsługą ciągów znaków
62
styczeń 2004
439033736.003.png 439033736.004.png 439033736.005.png
Zgłoś jeśli naruszono regulamin