int func(char* str)
{
char buffer[100];
unsigned short len = strlen(str);
if(len >= 100)
{
return (-1);
}
strncpy(buffer,str,strlen(str));
return 0;
}
Ten kod jest podatny na atak przepełnienia bufora i próbuję dowiedzieć się, dlaczego. Myślę, że ma to związek z len
otrzymaniem deklaracji short
zamiast an int
, ale nie jestem pewien.
Jakieś pomysły?
c
security
buffer-overflow
Jason
źródło
źródło
strncpy
. W tym przypadku tak nie jest.strlen
jest obliczany, używany do sprawdzania poprawności, a następnie jest ponownie obliczany absurdalnie - to DRY błąd. Gdyby ten drugistrlen(str)
został wymieniony nalen
, nie byłoby możliwości przepełnienia bufora niezależnie od jego typulen
. Odpowiedzi nie odnoszą się do tego punktu, po prostu udaje im się tego uniknąć.Odpowiedzi:
W większości kompilatorów maksymalna wartość an
unsigned short
to 65535.Każda wartość powyżej jest zawijana, więc 65536 staje się 0, a 65600 staje się 65.
Oznacza to, że długie łańcuchy o odpowiedniej długości (np. 65600) przejdą kontrolę i przepełnią bufor.
Zastosowanie
size_t
do przechowywania wynikstrlen()
, nieunsigned short
, i porównaćlen
do wyrażenia, które bezpośrednio koduje rozmiarbuffer
. Na przykład:źródło
len
jako trzeciego argumentu strncpy. W każdym razie ponowne użycie strlen jest głupie./ sizeof(buffer[0])
- zauważ, żesizeof(char)
w C zawsze wynosi 1 (nawet jeśli znak zawiera miliardy bitów), więc jest to zbędne, gdy nie ma możliwości użycia innego typu danych. Mimo to ... brawa za pełną odpowiedź (i dziękuję za odpowiedzi na komentarze).char[]
ichar*
nie są tym samym. Istnieje wiele sytuacji, w których testchar[]
zostanie niejawnie przekształcony w plikchar*
. Na przykładchar[]
jest dokładnie taki sam, jakchar*
używany jako typ argumentów funkcji. Jednak konwersja nie następuje w przypadkusizeof()
.buffer
w pewnym momencie, wyrażenie zostanie automatycznie zaktualizowane. Jest to krytyczne dla bezpieczeństwa, ponieważ deklaracjabuffer
może znajdować się o kilka wierszy od sprawdzania rzeczywistego kodu. Dlatego łatwo jest zmienić rozmiar bufora, ale zapomnij o aktualizacji w każdym miejscu, w którym rozmiar jest używany.Problem jest tutaj:
Jeśli łańcuch jest większy niż długość bufora docelowego, strncpy nadal go kopiuje. Liczbę znaków łańcucha opierasz na liczbie do skopiowania, a nie na rozmiarze bufora. Prawidłowy sposób na zrobienie tego jest następujący:
To powoduje ograniczenie ilości danych kopiowanych do rzeczywistego rozmiaru bufora minus jeden dla znaku kończącego wartość null. Następnie ustawiamy ostatni bajt w buforze na znak null jako dodatkowe zabezpieczenie. Powodem tego jest to, że strncpy skopiuje do n bajtów, w tym kończący null, jeśli strlen (str) <len - 1. Jeśli nie, to wartość null nie jest kopiowana i masz scenariusz awarii, ponieważ teraz twój bufor ma niezakończony strunowy.
Mam nadzieję że to pomoże.
EDYCJA: Po dalszej analizie i wprowadzeniu informacji przez innych, możliwe kodowanie funkcji jest następujące:
Ponieważ znamy już długość ciągu, możemy użyć memcpy do skopiowania ciągu z lokalizacji, do której odwołuje się str, do bufora. Zauważ, że na stronie podręcznika dla strlen (3) (w systemie FreeBSD 9.3) podano, co następuje:
Co interpretuję jako, że długość łańcucha nie obejmuje wartości null. Dlatego kopiuję len + 1 bajtów, aby uwzględnić wartość null, a test sprawdza, czy długość <rozmiar bufora - 2. Minus jeden, ponieważ bufor zaczyna się na pozycji 0, a minus kolejny, aby upewnić się, że jest miejsce dla null.
EDYCJA: Okazuje się, że rozmiar czegoś zaczyna się od 1, a dostęp zaczyna się od 0, więc -2 przedtem było niepoprawne, ponieważ zwróciłoby błąd dla czegokolwiek> 98 bajtów, ale powinno być> 99 bajtów.
EDYCJA: Chociaż odpowiedź na temat skrótu bez znaku jest ogólnie poprawna, ponieważ maksymalna długość, którą można przedstawić, to 65 535 znaków, nie ma to większego znaczenia, ponieważ jeśli ciąg jest dłuższy, wartość zawinie się. To tak, jakby wziąć 75,231 (co jest 0x000125DF) i zamaskować górne 16 bitów, dając 9695 (0x000025DF). Jedynym problemem, jaki widzę w tym przypadku, jest pierwsze 100 znaków po 65 535, ponieważ sprawdzenie długości pozwoli na kopiowanie, ale we wszystkich przypadkach skopiuje tylko do pierwszych 100 znaków ciągu i zeruje ciąg . Więc nawet w przypadku problemu zawijania, bufor nadal nie zostanie przepełniony.
Może to samo w sobie stanowić zagrożenie bezpieczeństwa, w zależności od zawartości ciągu i tego, do czego go używasz. Jeśli jest to zwykły tekst, który jest czytelny dla człowieka, to generalnie nie ma problemu. Otrzymujesz po prostu obcięty ciąg. Jeśli jednak jest to adres URL lub nawet sekwencja poleceń SQL, możesz mieć problem.
źródło
func
... i każdą inną kiedykolwiek napisaną funkcję C, która przyjmuje jako argumenty ciągi zakończone znakiem NUL. Podawanie możliwości, że wejście nie jest zakończone wartością NUL, jest całkowicie bezmyślne.len >= 100
) został przeprowadzony na jednej wartości, ale długość kopii miała inną wartość ... to jest naruszeniem zasady DRY. Proste wywołaniestrncpy(buffer, str, len)
unika możliwości przepełnienia bufora i wykonuje mniej pracy niżstrncpy(buffer,str,sizeof(buffer) - 1)
... chociaż tutaj jest to po prostu wolniejszy odpowiednikmemcpy(buffer, str, len)
.Nawet jeśli używasz
strncpy
, długość odcięcia jest nadal zależna od przekazanego wskaźnika ciągu. Nie masz pojęcia, jak długi jest ten ciąg (to znaczy lokalizacja terminatora zerowego względem wskaźnika). Więc dzwonienie wstrlen
pojedynkę otwiera cię na słabość. Jeśli chcesz być bezpieczniejszy, użyjstrnlen(str, 100)
.Pełny kod poprawiony byłby:
źródło
strlen
wtedy również nie uzyskałby dostępu poza końcem bufora?strnlen
nie rozwiązuje problemu, jeśli to, co sugeruje orlp, i tak jest rzekomo poprawne.buffer
. „ponieważ str może wskazywać na bufor 2 bajtów, z których żaden nie ma wartości NUL”. - to nie ma znaczenia, tak jak w przypadku każdej implementacjifunc
. Pytanie tutaj dotyczy przepełnienia bufora, a nie UB, ponieważ wejście nie jest zakończone znakiem NUL.Odpowiedź z opakowaniem jest prawidłowa. Ale jest problem, o którym myślę, że nie został wspomniany, jeśli (len> = 100)
Cóż, gdyby Len miał 100, skopiowalibyśmy 100 elementów i nie mielibyśmy końcowych \ 0. To oczywiście oznaczałoby, że każda inna funkcja zależna od prawidłowo zakończonego łańcucha wyszłaby daleko poza oryginalną tablicę.
Ciąg problematyczny z C jest nierozwiązywalny IMHO. Zawsze lepiej jest mieć pewne ograniczenia przed rozmową, ale nawet to nie pomoże. Nie ma sprawdzania granic, więc przepełnienia bufora zawsze mogą i niestety się zdarzają ....
źródło
strncpy()
i przyjaciele, ale funkcje przydzielania pamięci, takie jakstrdup()
i przyjaciele. Są w standardzie POSIX-2008, więc są dość przenośne, chociaż nie są dostępne w niektórych zastrzeżonych systemach.buffer
jest lokalna dla tej funkcji i nie jest używana nigdzie indziej. W prawdziwym programie musielibyśmy zbadać, jak jest używany ... czasami zakończenie nieprzekraczające wartości NUL jest poprawne (pierwotnym zastosowaniem strncpy było utworzenie 14-bajtowych wpisów katalogu UNIX-a - uzupełnionych o NUL i nie zakończonych znakiem NUL). „Ciąg znaków problematyczny z C jest nierozwiązywalny w IMHO” - podczas gdy C jest wrednym językiem, przewyższającym znacznie lepszą technologię, można w nim napisać bezpieczny kod, jeśli zastosuje się odpowiednią dyscyplinę.if (len >= 100)
jest warunkiem, kiedy sprawdzenie kończy się niepowodzeniem , a nie, gdy kończy się pomyślnie, co oznacza, że nie ma przypadku, w którym dokładnie 100 bajtów bez terminatora NUL jest kopiowanych na drugą stronę, ponieważ ta długość jest uwzględniona w warunku niepowodzenia.Poza kwestiami bezpieczeństwa związanymi z wywołaniem
strlen
więcej niż jeden raz, generalnie nie należy używać metod łańcuchowych na łańcuchach, których długość jest dokładnie znana [w przypadku większości funkcji łańcuchowych istnieje tylko bardzo wąski przypadek, w którym należy ich używać - na łańcuchach, dla których maksymalna długość można zagwarantować, ale dokładna długość nie jest znana]. Gdy znana jest długość ciągu wejściowego i znana jest długość bufora wyjściowego, należy dowiedzieć się, jak duży region powinien zostać skopiowany, a następnie użyć gomemcpy()
do faktycznego wykonania danej kopii. Chociaż możliwe jest, żestrcpy
możememcpy()
to osiągnąć lepsze wyniki podczas kopiowania ciągu o wielkości zaledwie 1-3 bajtów, na wielu platformachmemcpy()
może być ponad dwukrotnie szybszy w przypadku większych ciągów.Chociaż istnieją sytuacje, w których bezpieczeństwo kosztowałoby wydajność, jest to sytuacja, w której bezpieczne podejście jest również szybsze. W niektórych przypadkach rozsądne może być napisanie kodu, który nie jest zabezpieczony przed dziwnie zachowującymi się danymi wejściowymi, jeśli kod dostarczający dane wejściowe może zapewnić, że będą one dobrze zachowane i jeśli ochrona przed niewłaściwymi danymi wejściowymi zmniejszy wydajność. Zapewnienie, że długości łańcuchów są sprawdzane tylko raz, poprawia zarówno wydajność, jak i bezpieczeństwo, chociaż można zrobić jedną dodatkową rzecz, aby pomóc chronić bezpieczeństwo nawet podczas ręcznego śledzenia długości ciągu: dla każdego łańcucha, który ma mieć końcowe null, zapisz końcowe null jawnie, a nie niż oczekiwanie, że łańcuch źródłowy go będzie miał. Tak więc, gdyby ktoś pisał
strdup
odpowiednik:Zauważ, że ostatnia instrukcja mogłaby zostać pominięta, jeśli memcpy przetworzyłby
len+1
bajty, ale jeśli inny wątek miałby zmodyfikować łańcuch źródłowy, wynikiem może być ciąg docelowy niezakończony NUL.źródło
strlen
więcej niż raz ?strlen
i podejmie jakąś akcję w oparciu o zwróconą wartość (co prawdopodobnie było powodem wywołania jej w pierwszej kolejności), wówczas powtórzone połączenie (1) zawsze da taką samą odpowiedź jak pierwsza, w takim przypadku jest to po prostu marnowana praca lub (2) może czasami (ponieważ coś innego - być może inny wątek - zmodyfikował w międzyczasie ciąg) daje inną odpowiedź, w takim przypadku kod, który robi pewne rzeczy z długością (np. alokacja bufora) może przybierać inny rozmiar niż kod wykonujący inne czynności (kopiowanie do bufora).