Pełen artykuł
Transkrypt
Pełen artykuł
Dokument pobrany z serwisu: www.Centrum.Bezpieczenstwa.pl Autor artykułu: Marcin Ulikowski (www.nfsec.pl) Integer overflow KaŜdy typ w języku C moŜe być traktowany jako liczba. KaŜdy typ danych ma określoną długość wyraŜoną w bitach lub bajtach. Dla przykładu typ char, czyli typ znakowy ma długość 8 bitów, czyli 1 bajta. Typ integer, który jest typem liczbowym ma długość 32 bitów lub 4 bajtów. Wartość jaką moŜe przenosić dany typ jest ograniczona jego długością oraz od tego czy jest ze znakiem (signed) czy bez (unsigned). Polecam zapoznać się z małym plikiem /usr/include/limits.h, aby dowiedzieć się o limitach dla konkretnych typów danych. PoniŜszy kod pokaŜe rozmiary podstawowych typów. NaleŜy jednak wiedzieć, Ŝe chociaŜ poniŜsze typy mają róŜną długość to na stosie / stercie zajmują zawsze po 32 bity (4 bajty), co moŜna łatwo sprawdzić uŜywając narzędzia gdb. Oczywiście przy załoŜeniu, Ŝe uŜywamy CPU zgodnego z architekturą Intel IA32. [elceef@osiris ~]$ cat sizes.c #include int main(void) { char a; short b; int c; printf("char (%d bits)n", sizeof(a) * 8); printf("short (%d bits)n", sizeof(b) * 8); printf("int (%d bits)n", sizeof(c) * 8); return 0; } [elceef@osiris ~]$ gcc -Wall -o sizes sizes.c [elceef@osiris ~]$ ./sizes char (8 bits) short (16 bits) int (32 bits) W kolejnych przykładach będziemy uŜywać typu char zamiast integer. Łatwiej jest zrozumieć problem kiedy będziemy operować na małych liczbach oraz na małych zakresach. Natomiast w przypadku typu integer i innych typów całkowitych cała zasada "działania" pozostaje bez zmian. Zmienne typu signed (ze znakiem): [elceef@osiris ~]$ cat example1.c #include #include int main(int argc, char *argv[]) { char number; if (argc < 2) exit(1); number = atoi(argv[1]); printf("number = %dn", number); number++; printf("number + 1 = %dn", number); return 0; } [elceef@osiris ~]$ gcc -Wall -o example1 example1.c [elceef@osiris ~]$ ./example1 127 number = 127 number + 1 = -128 Dlaczego po zwiększeniu 127 o 1 - liczba zmieniła znak na przeciwny? PoniewaŜ został przekroczony zakres liczb dodatnich. Zmienna char, którą zadeklarowaliśmy była domyślnie ze znakiem (signed). Kiedy miała wartość 127 binarnie wyglądała w ten sposób: 01111111 Pierwszy bit czyli 0 oznacza znak dodatni (jest to bit znaku). Kolejne jedynki to juŜ wartość samej zmiennej czyli 127. Oto jak wygląda liczba po dodaniu jedynki: 01111111 + 00000001 ---------10000000 Widać, Ŝe pierwszy bit zmienił wartość na 1, czyli liczba ma teraz znak ujemny. Jest to takŜe najmniejsza liczba ujemna jaką moŜna zapisać uŜywając tego typu danych (signed char). NaleŜy zwrócić uwagę, Ŝe teraz siedem zer oznacza liczbę -128 ze względu na inny zakres liczb ujemnych w stosunku do dodatnich. Liczby ujemne mają zakres <-128;-1>, natomiast dodatnie <0;127> dlatego siedem jedynek będzie oznaczało wartość -1. Dla pewności przykłady: 00000000 - 00000001 ---------11111111 = (-1) 01111111 + 00000010 ---------10000001 = (-127) Podsumowując typ signed char moŜne przenosić liczby z zakresu <-128;127>. Jest to 256 liczb czyli 2^8. Zmienne typu unsigned (bez znaku) [elceef@osiris ~]$ cat example2.c #include #include int main(int argc, char *argv[]) { unsigned char number; if (argc < 2) exit(1); number = atoi(argv[1]); printf("number = %dn", number); number++; printf("number + 1 = %dn", number); return 0; } [elceef@osiris ~]$ gcc -Wall -o example2 example2.c [elceef@osiris ~]$ ./example2 255 number = 255 number + 1 = 0 Zmienna typu unsigned char miała wartość 255. Po zwiększeniu o 1 zmieniła wartość na 0, poniewaŜ został przekroczony limit. Limit = 255 lub 11111111 Wartość = 256 lub 100000000 Wynik = 0 lub 00000000 Do obliczenia wyniku takiej operacji moŜemy posłuŜyć się dość znanym wzorem: wartosc % (limit + 1) = wynik czyli 256 % (255 + 1) = 0 lub inny przykład 257 % (255 + 1) = 1 Limit = 255 lub 11111111 Wartosc = 257 lub 100000001 Wynik = 1 lub 00000001 Typ unsigned char moŜe przenosić liczby z zakresu <0;255> Jest to 256 liczb czyli 2^8 Integer overflows chociaŜ trudne do wykrycia z reguły nie stanowią same w sobie duŜego zagroŜenia. Jednak czasami mogą prowadzić do innych błędów, najczęściej przepełnień bufora powodując zapis w obszarach pamięci o krytycznym znaczeniu. Wykorzystanie ujemnej zmiennej jako argumentu malloc() nie spowoduje błędu, a jedynie zaalokowanie kilkubajtowego bufora, który moŜe zostać nadpisany.