Bufferoverfloder forblir en av de mest vedvarende og farlige sikkerhetsproblemene i C programmering. Til tross for å være godt dokumentert i tiår, fortsetter de å forårsake alvorlige problemer som datakorrupsjon, systemkrasj og ekstern kodeutførelse. Skriving sikker C-kode krever en dyp forståelse av hvordan bufferoverfloder oppstår og en disiplinert tilnærming for å hindre dem. Denne artikkelen gir en omfattende guide til å skrive robust, overflødig C-kode, dekker grunnleggende konsepter, trygge funksjoner, valideringsteknikker, kompilatorbeskyttelse og moderne defensive tiltak.

Forståe buffer Overstrømninger

En bufferoverflyt skjer når et program skriver mer data til en sammenhengende minneblokk (en buffer) enn bufferen ble tildelt til å holde. Siden buffere bor i stabel eller haugminne, overskriver grensene sine minnesteder. Denne korrupsjonen kan endre programtilstand, introdusere uforutsigbar oppførsel eller bli utnyttet av en angriper for å injisere og utføre vilkårlig kode.

Konsekvensene avhenger av hva som blir overskrevet. Overskriving av en returadresse på stabelen kan omdirigere utførelse til angriperstyrt kode. Overskriving av pekere kan føre til vilkårlige minneskrivelser. Selv enkle krasj kan utnyttes for å nekte angrep. Forstå mekanikken er det første skrittet til å forebygge.

Stack-baserte overstrømninger

Lokale variabler, inkludert buffere som er deklarert inne funksjoner, lagres på stabelen. Stabelen holder også returadressen, lagrede rammepekere og andre kontrolldata. Når en lineær buffer som er overkjørt, sløser data ut i returadressen og videre. Klassisk utnytter som Morris-ormen (1988) brukte stabeloverfloder for å få uautorisert tilgang.

Heap-baserte overstrømninger

Dynamisk tildelte buffere (via ], etc.) bor på haugen. Overstrømninger her kan korrupte metadata som brukes av allokatoren, noe som fører til krasj eller utnyttelse via haugsprøyting eller bruksfrie angrep. Heap overfloder er vanskeligere å utnytte, men like farlig.

Felles sårbare funksjoner og deres trygge alternativer

C standardbiblioteket gir flere funksjoner som ikke utfører grensekontroll. Ved å bruke dem er den vanligste årsaken til bufferoverflytninger. Å erstatte dem med tryggere motstykker er en grunnleggende beste praksis.

String Kopier og konkatenasjon

  • ] — Usikker: kopier til en nullterminator, ingen lengdegrense.
    ]Safe alternativ:
    ] — kopier på de fleste n tegn; Merk at det ikke nullterminererer hvis kilden er lengre enn n, så alltid manuelt null-terminer.
  • Bedre enda: - tilgjengelig på BSD og mange Linux-systemer; alltid null-terminerer og returnerer lengden på kildestrengen for trunkasjon deteksjon.
  • ] ⁇ Usikker: konkatenerer uten grenser.
    ]Safe alternativ: ] ⁇ legger til på de fleste n tegn og alltid null-terminererer.

Formatert utgang og inngang

  • ] — Usikker: skriver formatert utgang til en buffer uten størrelseskontroll.
    ]Safe alternativ: ] - begrenser utgangen til størrelse-1 tegn pluss null terminator.
  • ]] — Lignende risiko; bruk i stedet .
  • ]] — Ekstremt farlig; fjernet fra C11 standard. Bruk i stedet .
  • ]] - Ingen grenser sjekk. Bruk eller med feltbredde spesifier.

Minne Kopier og Flytt

  • ] — Safe bare hvis n er verifisert for å ikke overstige destinasjonsbufferstørrelsen.
    ]Safer alternativ: (håndledd overlappende) og alltid sikre n ≤ dest størrelse.
  • Noen plattformer tilbyr fra vedlegg K (valgfritt i C11), men adopsjon er begrenset.

Validering og størrelsesstyring

Selv med trygge funksjoner, må du validere inngangslengder, sikre riktig bufferstørrelser og håndtere potensiell trunkasjon graciøst.

Sjekk inndatalengder

Før du kopierer eller behandler ekstern inngang (brukerinndata, nettverksdata, filinnhold), bestemmer dens maksimale akseptable lengde og avviser eller trekker data som overstiger den. For eksempel:

#define MAX_INPUT 255
char buffer[MAX_INPUT + 1]; // +1 for null
if (strlen(user_input) > MAX_INPUT) {
 // Handle error: reject or truncate
 fputs("Input too long", stderr);
 return -1;
}
strncpy(buffer, user_input, sizeof(buffer) - 1);
buffer[sizeof(buffer) - 1] = '\0';

Bruk Fast størrelse buffere med kjente grenser

Når det er mulig, definere buffere med en konstant størrelse og håndheve den gjennom hele koden. Unngå variabel lengde-arrangementer (VLAs) som kan forårsake stabeloverfloder hvis store størrelser leveres. I stedet tildele dynamisk med eksplisitte størrelseskontroller.

Håndtere Truncation Explicitly

Funksjoner som og kan trenkende data. Vær oppmerksom på returverdien for å oppdage trunkering og bestemme om de avkortete dataene er akseptable eller om en feil bør heves. Overser trunkering kan forlate buffere i en uventet tilstand.

Kompilatorens sikkerhetsflagg og rengjøringsvern

Moderne kompilatorer tilbyr flagg som legger til bufferoverflytdeteksjon og redusering uten kodeendringer. Aktiver dem i byggesystemet.

  • ] / ] - Setter inn stabelkanarier (random verdier) før returadresser. Hvis en buffer overskriver kanariet før endring av returadressen, avbryter programmet før utnyttelsen fullføres.
  • ] - Bytter ut samtaler til usikre funksjoner som og med kontrollerte versjoner som avbryter hvis destinasjonsbufferen er for liten. Krever eller høyere optimalisering.
  • ]] - Varsler om formatstrengs sårbarheter som kan føre til bufferoverfloder eller informasjonslekkasjer.
  • ] — AddressSanitizer (ASan) instrumentkode for å oppdage bufferoverfloder, bruk-etter-fri, og andre minnefeil ved kjøring. Sakter utførelsen men er uvurderlig for testing.
  • ]] ⁇ unngår optimalisering av overflodskontrollene (bruk med forsiktighet).

Operativsystembeskyttelse

Stack kanarier er bare ett lag. Utnytte reduksjonsteknologier i moderne OSes inkluderer:

  • Datautførelsesforebygging (DEP) / NX bit - Marker stabel og haug som ikke-eksekuerbar, hindrer skallkodeutførelse.
  • Adresse Space Layout Randomization (ASLR)] - Randomizes minneadresser (stack, haug, delte biblioteker) for å gjøre det vanskeligere å forutsi mål.
  • Relacation Read-Only (RELRO)] ⁇ Beskytter GOT (Global Offset Table) fra overskriving.

Aktivering av disse beskyttelsene (vanligvis standard) hever streken for utnyttelse, men erstatter ikke sikker koding.

Koderevider og statistikkanalyse

Menneskers gjennomgang kombinert med automatisert statisk analyse kan fange bufferoverflod problemer tidlig. Integrer disse i utviklingsarbeidsflyten.

  • Manuel kode gjennomgang] - Se etter bruk av usikre funksjoner, manglende størrelseskontroller og sløyfer som skriver utover buffergrenser.
  • Statiske analyseverktøy ⁇ Verktøy som , , og oppdage potensielle overfloder, bruk av farlige funksjoner og feil ved én feil. De kan kjøres i CI-rørledninger.
  • Fuzzing - Bruk libFuzzer, AFL eller andre fuzzers til å automatisk teste inndatahåndtering med uventede data som kan utløse overfloder.

Praktiske eksempler på sikker kode

Sikker strengkopi med grenser Sjekk

#include <stdio.h>
#include <string.h>

int safe_string_copy(char *dest, size_t dest_size, const char *src) {
 if (!dest || !src || dest_size == 0) {
 return -1; // Invalid parameters
 }
 size_t src_len = strlen(src);
 if (src_len >= dest_size) {
 // Source too large; truncation or error
 // Option: copy what fits and null-terminate
 strncpy(dest, src, dest_size - 1);
 dest[dest_size - 1] = '\0';
 return 1; // Truncation occurred
 }
 strncpy(dest, src, dest_size);
 // strncpy fills remaining with null, so dest_size fits; no need to null-terminate if src shorter
 return 0; // Success, no truncation
}

Sikker Heltalshåndtering for bufferstørrelser

Bufferoverflyt kan også skyldes heltallsoverflod når datastørrelser er i bruk. Kontroller alltid aritmetisk før tildeling.

#include <stdlib.h>
#include <limits.h>
#include <errno.h>

void *safe_malloc_array(size_t nmemb, size_t size) {
 if (nmemb == 0 || size == 0) {
 return NULL; // Or handle zero-size allocation
 }
 if (nmemb > SIZE_MAX / size) {
 // Integer overflow would occur
 errno = ENOMEM;
 return NULL;
 }
 return malloc(nmemb * size);
}

Bruke snprintf for formaterte strenger

char log_message[256];
int ret = snprintf(log_message, sizeof(log_message),
 "User %s logged in from %s", username, ip_address);
if (ret < 0) {
 // Output error
} else if ((size_t)ret >= sizeof(log_message)) {
 // Truncation occurred; handle if needed
}

Andre beste praksis

  • Initialisere buffere - Alltid null-initialisere buffere for å unngå å lekke uinitialisert minne.
  • Avoid recitering med ubundet dybde] - Stack overfloder kan forekomme fra dyp recitering; bruk iterasjon eller grensedybde.
  • Bruk kvalifiseringssetter] - Hjelper kompilatoren å optimalisere og kan fange aliaseringsproblemer, men ikke direkte hindre overfloder.
  • Prefer -rettighet] - Forhindrer utilsiktet modifikasjon av inngangsstrenger og håndhever intensjon.
  • Implement feilhåndtering - Ikke oversett returverdier fra funksjoner som ], ], , etc.

Ressurser til videre læring

Konklusjon

Forebygge bufferoverfloder i C er ikke valgfritt; det er et grunnleggende ansvar for enhver utvikler som arbeider med språket. Ved å forstå mekanismer for overfloder, erstatte farlige funksjoner med tryggere alternativer, strengt validere innganger og størrelser, muliggjøre kompilatorbeskyttelser, og bruke statisk analyse og testing, kan du dramatisk redusere risikoen for disse sårbarhetene. Ingen enkelt teknikk er tilstrekkelig; forsvar i dybden - kombinere kode disiplin, kompilatorflagg, OS-reduserende midler og grundig testing - gir den sterkeste beskyttelsen. Med disse praksisene kan du skrive C-kode som er både kraftig og sikker, i stand til å kjøre trygt i kritiske systemer. Husk: sikker koding er en pågående praksis, ikke en engangsreparasjon. Hold deg informert om nye sårbarheter og kontinuerlig gjennomgang og forbedre koden din.