"Dennis Ritchie designed C to be close to the hardware with an assumption in its design was that the programmers using the language were well trained and professionals, using processes that professional do, such as code reviews."  Clem Cole[1]


Definicje

edytuj

Błąd (oprogramowania, programu) (ang. bug) jest to usterka programu komputerowego powodująca jego nieprawidłowe działanie[2]

Sygnał (ang. signal) jest to informacja przesyłana do procesu.

Wyjątek (ang. exception) jest to przewidziany przez programistę błąd programu [3]

Niezdefiniowane zachowanie ( ang. undefined behavior UB)[4][5]

Unspecified behavior[6]

Skutek uboczny, efekt uboczny (ang. side effect) – dowolny efekt wyrażenia lub wywołania funkcji, który wykracza poza zwrócenie wartości, np. interakcja z systemem operacyjnym lub zmiana wartości zmiennej globalnej. Innymi słowy skutek uboczny następuje, gdy modyfikowany zostaje jakiś stan programu poza swoim lokalnym środowiskiem, to znaczy następuje zauważalna interakcja ze „światem zewnętrznym”, oprócz zwracania wartości.[7]

Ze względu na przyczynę błędy dzieli się na trzy główne typy:[8]

  • błędy składniowe – nie pozwalają na kompilację programu, jak np. literówka w wywołaniu zmiennej. Są one najczęściej dość łatwe do usunięcia, zazwyczaj wynikają z drobnych pomyłek programisty.
  • błędy semantyczne (znaczeniowe) – część błędów semantycznych można wychwycić już w momencie kompilacji, np. próbę odwołania się do nieistniejącej funkcji, lecz inne mogą ujawnić się dopiero w trakcie wykonywania.
  • błędy logiczne – nie przerywają kompilacji, lecz powodują niewłaściwe działanie warstwy logicznej jak np. niepoprawne wyznaczanie pozycji gracza (które także może być spowodowane literówką – jak np. wpisanie znaku „+” zamiast „-”). Ten typ błędów jest znacznie trudniejszy do wykrycia i usunięcia; często błąd tkwi w jednym źle zapisanym znaku, lecz programista musi do tego znaku sam dojść (w przypadku błędu składniowego znak jest wskazywany przez komunikat kompilatora).

Ze względu na moment wykrycia błędy dzielimy na:

  • błędy wykryte przy kompilacji ( buildtime)
  • błędy wykryte przy uruchomieniu ( runtime)
  • błędy niewykryte, czyli ukryte ( program kompiluje się poprawnie i uruchamia się bez komunikatów o błędach)

Przykłady typowych błędów[9][10]

Błędy przy kompilacji

edytuj

opis błędów kompilacji

Błędy przy uruchamianiu

edytuj

Błędy przy uruchamianiu programu (błędy wykonania, ang. run-time errors)

Naruszenie ochrony pamięci

edytuj

Przepełnienie stosu

edytuj

Może być spowodowane:

  • rekursją: nieskończoną lub zbyt głęboką
  • rozmiarem zmiennych statycznych (stosu)
Rekursja
edytuj

Program powoduje nieskończoną rekursję:

#include <stdio.h>


int foo() {
      return foo();
}
 
 
int main ()
{
  foo();


return 0;
}

Program kompiluje się bez problemów:

 gcc c.c -Wall

ale jego uruchomienie powoduje błąd:

 ./a.out
 Naruszenie ochrony pamięci
Rozmiar zmiennych
edytuj

Poniższy program tworzy styczne tablice o coraz większym rozmiarze aż do powstania błędu związanego z dostępem do pamięci:

#include <stdio.h>


void TestArray(int length)
{

 double n[length]; /* n is an array of 10 integers */
   int i;
 
   /* initialize elements of array n to 0 */        
   for ( i = 0; i < length; i++ )
   {
      n[ i ] = i *100.0; /* set element at location i to i + 100 */
   }

   n[1]=n[1]; // to remove warning: variable ‘n’ set but not used [-Wunused-but-set-variable

   // http://stackoverflow.com/questions/37538/how-do-i-determine-the-size-of-my-array-in-c
   // length = sizeof(n)/sizeof(n[0])
   printf(" length = %d , size = %ld bytes = %ld kB  \n",length, length*sizeof(double), length*sizeof(double)/1000);
}
  
 
int main ()
{
  int i;
  for (i=1; i<1000000; i++)
      TestArray(1000*i);
  return 0;
}

Tablice statyczne są zapisywane w stosie więc następuje przepełnienie stosu[18] : Wynik:

length = 1043000 , size = 8344000 bytes = 8344 kB 
 length = 1044000 , size = 8352000 bytes = 8352 kB 
 length = 1045000 , size = 8360000 bytes = 8360 kB 
 length = 1046000 , size = 8368000 bytes = 8368 kB 
 length = 1047000 , size = 8376000 bytes = 8376 kB 
Naruszenie ochrony pamięci


Co dziwne największa możliwa do utworzenia tablica ma rozmiar 8376 kB, co jest większe niż ustalony maksymalny rozmiar stosu:

ulimit -s

otrzymujemy

8192


Potencjalne rozwiązania problemu:

  • zmiana zmiennych na dynamiczne
  • zmiana limitu wielkości stosu
  • zmiana algorytmu

Przepełnienie bufora

edytuj

Program wczytuje znaki

/*
  http://www.tenouk.com/Bufferoverflowc/Bufferoverflow1.html
  The output, when the input is: 12345678 (8 bytes), the program run smoothly.

  When the input is: 123456789 (9 bytes), then :
  * the following will be displayed when compiled with Microsoft Visual C++ 6.0. 
  * In Linux the “Segmentation fault” message will be displayed and the program terminates.
*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int main(int argc, char *argv[])
{
  // theoretically reserve 5 byte of buffer plus the
  // terminating NULL....should allocate 8 bytes = 2 double words,
  // to overflow, need more than 8 bytes...
  // so, if more than 8 characters input by user,
  // there will be access violation, segmentation fault etc.
  char mybuffer[5];
  // a prompt how to execute the program...
  if (argc < 2)
    {
      printf("strcpy() NOT executed....\n");
      printf("Syntax: %s <characters>\n", argv[0]);
      exit(0);
    }
  // copy the user input to mybuffer, without any bound checking
  // a secure version is srtcpy_s()
  strcpy(mybuffer, argv[1]);
  printf("mybuffer content= %s\n", mybuffer);
  // you may want to try strcpy_s()
  printf("strcpy() executed...\n");
  return 0;
}

Kompilujemy program bez problemów:

 gcc b.c -Wall

Uruchamiamy:

 ./a.out

Wynik:

 strcpy() NOT executed....
 Syntax: ./a.out <characters>

Zapomnieliśmy wprowadzić dane, program dopomina się o nie. Ponownie uruchamiamy:

./a.out 1234

Wynik:

 mybuffer content= 1234
 strcpy() executed...

bez błędów.

./a.out 12345678

mybuffer content= 12345678
 strcpy() executed...

Wprowadzenie więcej niż 8 znaków:

 ./a.out 123456789

powoduje błąd:

 mybuffer content= 123456789
 strcpy() executed...
 *** stack smashing detected ***: ./a.out terminated
 Przerwane


Zobacz również:

Przykłady

edytuj
  • TOCTOU[19]
  • lista błędów programowych[20]

Zapobieganie

edytuj
  • standardy kodowania, formatowanie kodu
  • MISRA_C – standard budowania aplikacji w języku C stworzony przez Motor Industry Software Reliability Association (MISRA) utworzony w celu zapewnienia przejrzystości i przenośności kodu źródłowego, głównie z myślą o systemach mikroprocesorowych programowanych w C. Standard MISRA C został stworzony z myślą o przemyśle samochodowym. Obecnie jednak jest podstawowym standardem tworzenia oprogramowania we wszelkich systemach, które wymagają dużej niezawodności m.in. przemysł lotniczy, kolejowy, medyczny, telekomunikacyjny oraz militarny.
  • CERT Coding Standards

Przestrzeń nazw

edytuj

Przestrzeń nazw (ang. namespace) nie istnieje w c w taki sposób jak w C++, ale można używać prefiksów[21]

Przykłady uzycia:

  • biblioteka stb[22]
  • stb_image_write.h ( file name )
  • STBI_THREAD_LOCAL ( name )
  • static void *stbi__malloc_mad4(int a, int b, int c, int d, int add)

Dokumentacja

edytuj

Dokumentacja


Zobacz również

Usuwanie niepotrzebnego kodu

edytuj

Znajdowanie martwego kodu

  • cppcheck
  • Squish Coco[27]
  • kompilacja z gcc -Wunreachable-code ( usunięta opcja w nowym kopilatorze)
  • Frama-C

Zmniejszanie zakresu zmiennej

edytuj
  • ang. Reduce Scope Of Variable

Bezpieczne programowanie

edytuj

W C nie ma automatycznego:

  • sprawdzania zakresów indeksu tablicy (ang. no bounds checks on array) to prowadzi do błędu Off by one
  • wskaźników (ang. pointer references)

Funkcje, których należy unikać dotyczą: [28]

  • operacji na łańcuchach


Niebezpieczne funkcje i ich bezpieczne zamienniki[28]
niebezpieczna opis ryzyka bezpieczna
gets() najbardziej niebezpieczna fgets(buf, size, stdin)
strcpy bardzo niebezpieczna strncpy, strlcpy
strcat bardzo niebezpieczna strncat
sprintf bardzo niebezpieczna snprintf asprintf
scanf bardzo niebezpieczna Use precision specifiers, or do your own parsing.
sscanf bardzo niebezpieczna Use precision specifiers, or do your own parsing.
fscanf bardzo niebezpieczna Use precision specifiers, or do your own parsing.
vfscanf bardzo niebezpieczna Use precision specifiers, or do your own parsing.
vsprintf bardzo niebezpieczna Use vsnprintf instead, or use precision specifiers.
vscanf bardzo niebezpieczna Use precision specifiers, or do your own parsing.
vsscanf bardzo niebezpieczna Use precision specifiers, or do your own parsing.
streadd bardzo niebezpieczna Make sure you allocate 4 times the size of the source parameter as the size of the destination.
strecpy bardzo niebezpieczna Make sure you allocate 4 times the size of the source parameter as the size of the destination.
strtrns Risky Manually check to see that the destination is at least the same size as the source string.
realpath Very risky (or less, depending on the implementation) Allocate your buffer to be of size MAXPATHLEN. Also, manually check arguments to ensure the input argument is no larger than MAXPATHLEN.
syslog Very risky (or less, depending on the implementation) Truncate all string inputs at a reasonable size before passing them to this function.
getopt Very risky (or less, depending on the implementation) Truncate all string inputs at a reasonable size before passing them to this function.
getopt_long Very risky (or less, depending on the implementation) Truncate all string inputs at a reasonable size before passing them to this function.
getpass Very risky (or less, depending on the implementation) Truncate all string inputs at a reasonable size before passing them to this function.
getchar Moderate risk Make sure to check your buffer boundaries if using this function in a loop.
fgetc Moderate risk Make sure to check your buffer boundaries if using this function in a loop.
getc Moderate risk Make sure to check your buffer boundaries if using this function in a loop.
read Moderate risk Make sure to check your buffer boundaries if using this function in a loop.
bcopy Low risk Make sure that your buffer is as big as you say it is.
fgets Low risk Make sure that your buffer is as big as you say it is.
memcpy Low risk Make sure that your buffer is as big as you say it is.
snprintf Low risk Make sure that your buffer is as big as you say it is.
strccpy Low risk Make sure that your buffer is as big as you say it is.
strcadd Low risk Make sure that your buffer is as big as you say it is.
strncpy Low risk Make sure that your buffer is as big as you say it is.
vsnprintf Low risk Make sure that your buffer is as big as you say it is.


słowa kluczowe:

Wykrywanie i usuwanie

edytuj

Ogólne zasady:

  • wg Erica Lipperta[29]

testy programu

edytuj

Liczbę błędów można też ograniczyć przeprowadzając testy programu.

Komunikaty o błędach

edytuj


Analiza kodu

edytuj

Obsługa błędów i wyjątków

edytuj

Obsługa błędów w języku C ( ang. error handling)[30]

  • funkcje
  • makra
  • globalne zmienne
    • errno
  • kontrola zwracancyh przez funkcje wartosci ( ang. function return values )
    • EXIT_STATUS


Funkcje i makra do kontroli błędów:[31][32]

Źródła

edytuj
  1. quora: Why-are-programs-written-in-C-and-C-so-frequently-vulnerable-to-overflow-attacks
  2. błąd w wikipedii
  3. Z Lipiński : Programowanie w Cpp
  4. undefined behavior (UB) w ang. wikipedii
  5. nayuki : undefined-behavior-in-c-and-cplusplus-programs
  6. Unspecified behaviorw ang. wikipedii
  7. Skutek uboczny w wikipedii
  8. Błąd_(informatyka) w wikipedii, zobacz również wersję ang
  9. programowanie-pułapki-jezyka-c
  10. c errors
  11. wikipedia: Naruszenie ochrony pamięci
  12. W Sikora-Kobyliński, M Czępiński, G Kulewski : Ostatnia linia obrony przed atakiem
  13. ...definition of "Segmentation Fault" - Where is...
  14. Przepełnienie bufora w wikipedii
  15. Open Web Application Security Project
  16. Smashing The Stack For Fun And Profit by Aleph One
  17. Journey to the Stack, Part I by Gustavo Duarte Mar 10th, 2014
  18. Przepełnieniestosu w ang wikipedii
  19. Time-of-check_to_time-of-use w ang. wikipedii
  20. List_of_software_bugs in eng. wikipedia
  21. stackoverflow question: namespaces-in-c
  22. stb single-file public domain libraries for C/C++ by Sean T. Barrett.
  23. Explaining Code using ASCII Art by John Regehr, Professor of Computer Science, University of Utah, USA
  24. Doxygen Magically Turns Source Code into Documentation by Carla Schroder
  25. sphinx-doc
  26. citing-papers-in-software by Fredrik Johansson
  27. find-unused-functions-c-code-coverage using Squish Coco
  28. 28,0 28,1 Protect your code through defensive programming by Gary McGraw, John Viega
  29. eric lippert : how-to-debug-small-programs
  30. delftstack : c-handling-errors by Naila Saad Siddiqui Oct-20, 2022
  31. Simiński: Programowanie w C++
  32. libc: Errors in Floating-Point Calculations
  33. opengroup: fenv.h
  34. libc: FP Exceptions