Tej funkcji brakuje ‘n’

2008-05-29 15:01

W dzisiejszych niebezpiecznych czasach niemal każdy program może stać się przedmiotem ataku, za pomocą którego można – jak to się zwykło mówić w żargonie speców od bezpieczeństwa – “wykonać dowolny kod” (execute arbitrary code). Chyba najbardziej znanym exploitem tego typu jest przepełnienie bufora (buffer overflow), które w najprostszym wypadku może dotyczyć na przykład takiej sytuacji jak poniższa:

  1. int some_function()
  2. {
  3.     char buf[BUF_SIZE];
  4.     do_something_and_give_results (buf, ...);
  5. }

Gdy bowiem bufor rezyduje na stosie, a jakaś funkcja przypadkowo “wyjedzie” poza jego zakres, możliwe jest nadpisanie innej części stosu. Jedną z nich może być odkładany przy wywołaniu każdej funkcji adres powrotu. Po zakończeniu działania kodu funkcji, adres ten jest zdejmowany ze stosu i program skacze pod uzyskane w ten sposób miejsce w pamięci. Dzięki temu wykonanie programu może wrócić do miejsca tuż za wywołaniem funkcji i kontynuować działanie. Jeśli jednak ów adres zostanie nadpisany przez exploit, możemy w rezultacie skoczyć pod zupełnie inny adres i wykonać dowolny – potencjalnie szkodliwy – kod.

Dlatego też nie należy nigdy ślepo zakładać, że przekazywane nam tablice będą zawsze wystarczająco duże na pomieszczenie rezultatów funkcji. Powinniśmy na to zwrócić baczną uwagę zwłaszcza wtedy, gdy mamy zapisać w buforze dane pochodzące z zewnątrz – z pliku, sieci, od użytkownika, itd. Pisząc funkcje operujące na buforach będących tablicami w C, powinniśmy więc zawsze dodawać do nich jeszcze jeden parametr: rozmiar przekazywanego bufora. Oczywiście nalezy potem dbać, aby go nie przekroczyć.
A co z już istniejącymi funkcjami C, jak sprintf, strcpy, gets?… Wszystkie aktualne dokumentacje do kompilatorów, w których funkcje te występują (MSDN, linuksowy man, itp.), solidnie przestrzegają przed potencjalnym przepełnieniem bufora, które może być skutkiem ich używania. Zwykle też podawane są bezpieczne alternatywy, do których przekazuje się rozmiar bufora: w Visual C++ są one oznaczone końcówką _s (np. sprintf_s), a w GNU GCC dodatkową literką n w nazwie (np. snprintf). To ich właśnie należy używać, jeśli występuje potencjalna możliwość przepełnienia bufora.

Wyjątkiem jest gets, której to… nie powinniśmy w ogóle używać :) Co ciekawe, tylko MSDN wspomina o jej bezpiecznym odpowiedniku (gets_s). Pod GCC funkcji gets po prostu permanentnie brakuje n ;-]

Tags: , ,
Author: Xion, posted under Programming »


5 comments for post “Tej funkcji brakuje ‘n’”.
  1. bs.mechanik:
    May 29th, 2008 o 15:47

    czyli gets jest az tak niebezpieczny? ;)

  2. Xion:
    May 29th, 2008 o 15:56

    Cytat z mana:

    “Never use gets(). Because it is impossible to tell without knowing the data in advance how many characters gets() will read, and because gets() will continue to store characters past the end of the buffer, it is extremely dangerous to use. It has been used to break computer security. Use fgets() instead.”

    Tyle że fgets() operuje na plikowych strumieniach C (typ FILE*), więc nie jest to bezpośredni odpowiednik gets(), która to operuje na destryptorach plików (czyli na niższym poziomie).

  3. macabre13:
    May 29th, 2008 o 23:29

    Dla ms windows, zalecane jest uzywanie StringCchXXXXXXX

  4. moriturius:
    May 30th, 2008 o 11:44

    @Xion: nie tyle na plikowych co na strumieniach ;) Jeśli chcesz czytać ze standardowego wejścia za pomocą fgets() to nie ma problemu. Jako parametr FILE* trzeba podać po prostu: stdin.
    Podobnie można pisać na standardowe wyjście za pomocą fprintf():

    fprintf(stdout, “%s\n”, “Przyklad ^^”);

    Jest jeszcze stderr – łatwo domyślić się do czego :P

  5. Xion:
    May 30th, 2008 o 19:05

    moriturius: Powiedziałem ‘strumień plikowy’, żeby odróżnić je od strumieni C++ (IOStreams), a także z tego prostego względu, taki strumień C jest reprezentowany przez typ FILE*. I dotyczy to także stdin, stdout i stderr – też są typu FILE*.
    Rzecz w tym, że nie zawsze chcesz operować na strumieniach (np. dla plików binarnych czasami jest wygodniej używać read()/write(); niekiedy musisz otworzyć plik przez open() z powodu możliwego współdzielenia (wiem, jest fdopen(), no ale.. ;-]); możesz nie chcieć buforowania itp., itd.). A wtedy brak bezpiecznej wersji gets() trochę boli. (Ale znów nie tak bardzo ;)).

Comments are disabled.
 


© 2017 Karol Kuczmarski "Xion". Layout by Urszulka. Powered by WordPress with QuickLaTeX.com.