Civiele & structurele engineering
Hoe te om te schrijven Secure C-code om bufferoverstromen te voorkomen
Table of Contents
Buffer overflows blijven een van de meest aanhoudende en gevaarlijke beveiligingskwetsbaarheden in C-programmering. Ondanks dat ze decennialang goed gedocumenteerd zijn, blijven ze ernstige problemen veroorzaken zoals gegevenscorruptie, systeemcrashes en uitvoering van externe code. Het schrijven van beveiligde C-code vereist een diep begrip van hoe buffer overflows optreden en een gedisciplineerde aanpak om ze te voorkomen. Dit artikel biedt een uitgebreide gids voor het schrijven van robuuste, overflow-resistente C-code, die fundamentele concepten, veilige functies, validatietechnieken, compilerbeschermingen en moderne defensieve maatregelen omvat.
Buffer-overstromen begrijpen
Een buffer overflow gebeurt wanneer een programma meer gegevens schrijft naar een aaneengesloten blok geheugen (een buffer) dan de buffer werd toegewezen om te houden. Aangezien buffers in stapel- of stapelgeheugens verblijven, overschrijdt hun grenzen aangrenzende geheugenlocaties. Deze corruptie kan de status van het programma wijzigen, onvoorspelbaar gedrag introduceren of worden uitgebuit door een aanvaller om willekeurige code te injecteren en uit te voeren.
De gevolgen zijn afhankelijk van wat wordt overschreven. Overschrijven van een retouradres op de stack kan uitvoering omleiden naar aanvaller gecontroleerde code. Overschrijven van aanwijzingen kan leiden tot willekeurige geheugen schrijft. Zelfs eenvoudige crashes kunnen worden gebruikt voor denial-of-service aanvallen. Begrijpen van de mechanica is de eerste stap naar preventie.
Stack-based overflows
Lokale variabelen, inclusief buffers die binnen functies zijn aangegeven, worden opgeslagen op de stack. De stack bevat ook het retouradres, opgeslagen framepointers en andere controlegegevens. Wanneer een lineaire buffer als wordt overschreden, worden gegevens gemorst in het retouradres en daarbuiten. Klassieke exploits zoals de Morris worm (1988) gebruikt stack overflows om onbevoegde toegang te krijgen.
Overstromen op basis van hoge kosten
Dynamisch toegewezen buffers (via , , enz.) bevinden zich op de hoop. Overstromen kunnen hier metadata die door de toetator worden gebruikt corrumperen, wat leidt tot crashes of exploitatie via hoop spuiten of gebruik-na-vrije aanvallen. Heap overflows zijn moeilijker te exploiteren maar even gevaarlijk.
Gemeenschappelijke kwetsbare functies en hun veilige alternatieven
De standaard C bibliotheek biedt verschillende functies die niet het uitvoeren van grenzen controleren. Het gebruik ervan is de meest voorkomende oorzaak van buffer overflows. Vervangen van deze door veiliger tegenhangers is een fundamentele beste praktijk.
Tekenreeks kopiëren en concatenderen
- .Onveilig: kopieën tot een nulterminator, geen lengtelimiet.
Veilig alternatief: .De kopieën van ten hoogste n tekens zijn niet nul-terminerend indien de bron langer is dan n, dus altijd handmatig nul-termineren. - Beter nog:
- .Onveilig: concateert zonder grenzen.
Veilig alternatief: [[FLT:]] .De ..bijvoegt ten hoogste n tekens en eindigt altijd nul.
Geformatteerde uitvoer en invoer
- [[FLT:]] .Onveilig: schrijft geformatteerde uitvoer naar een buffer zonder groottecontrole.
Veilig alternatief: ] De uitvoer wordt beperkt tot grootte-1 tekens plus nulterminator. - . Soortgelijk risico; gebruik in plaats daarvan.
- . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
- . .Geen grenzen controle. Gebruik of met veldbreedte-specifier.
Geheugen kopiëren en verplaatsen
- .Alleen veilig als n wordt geverifieerd dat het de buffergrootte van de bestemming niet overschrijdt.]
Veilig alternatief: (verwante handelingen uitvoert) en altijd zorgt voor n ≤ destgrootte. - Sommige platforms voorzien uit bijlage K (facultatief in C11), maar de goedkeuring is beperkt.
Validatie en beheer van de grootte
Zelfs met veilige functies, moet u de invoerlengtes valideren, zorgen voor de juiste buffergroottes, en de potentiële afknotting sierlijk behandelen.
Invoerlengtes controleren
Voordat externe invoer (ingang van de gebruiker, netwerkgegevens, bestandsinhoud) wordt gekopieerd of verwerkt, bepaalt u de maximaal aanvaardbare lengte ervan en wijst u gegevens af of verkort u deze. Bijvoorbeeld:
#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';
Gebruik vaste groottebuffers met bekende limieten
Waar mogelijk, definieer buffers met een constante grootte en af te dwingen door middel van de code. Vermijd variabele lengte arrays (VLA's) die kunnen leiden tot stapel overflows als grote maten worden geleverd. In plaats daarvan, dynamisch toewijzen met expliciete grootte controles.
Handle Truncation Explicitly
Functies als en kunnen gegevens afkappen. Wees bewust van de waarde van de terugkeer om afkapping te detecteren en te beslissen of de afgekapselde gegevens aanvaardbaar zijn of dat er een fout moet worden gemaakt. Het negeren van afkapseling kan buffers in onverwachte toestand achterlaten.
Compiler Security Vlaggen en Runtime Protections
Moderne compilers bieden vlaggen die buffer overflow detectie en mitigatie zonder code wijzigingen toevoegen. Schakel ze in uw build systeem.
- /
- Vervangt oproepen naar onveilige functies zoals en ] met gecontroleerde versies die afbreken als de bestemmingsbuffer te klein is. Vereist of hogere optimalisatie.
- . . . Waarschuwt over het formaat van de kwetsbaarheden van de string die kunnen leiden tot bufferoverstromen of informatielekken.
- .AdresSanitizer (ASan) instrumenten code om buffer overflows, gebruik-na-gebruik en andere geheugenfouten op runtime te detecteren. Vertraagt uitvoering maar is van onschatbare waarde voor het testen.
- Vermijdt het optimaliseren van overflowcontroles (gebruik met voorzichtigheid).
Bescherming van het besturingssysteem
Stack kanaries zijn slechts een laag. Exploit mitigatie technologieën in moderne besturingssystemen omvatten:
- Data Execution Prevention (DEP) / NX bit .Meldt stapel en stapel als niet-uitvoerbaar, waardoor shellcode uitvoering wordt voorkomen.
- Adresruimteindeling Randomisatie (ASLR)
- Relocatie Alleen-lezen (RELRO)
Het inschakelen van deze bescherming (meestal standaard) verhoogt de balk voor exploitatie, maar vervangt geen veilige codering.
Code Audits en Statische Analyse
Menselijke beoordeling gecombineerd met geautomatiseerde statische analyse kan buffer overflow problemen vroeg vangen. Integreer deze in uw ontwikkeling workflow.
- Handmatige codebeoordeling . . Kijk naar toepassingen van onveilige functies, ontbrekende groottecontroles en loops die verder schrijven dan buffergrenzen.
- Statische analysetools . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
- Fuzzing
Praktische voorbeelden van Secure Code
Veilige tekenreekskopie met grenzen controleren
#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
}
Veilige Integer Handling voor Buffer Maten
Buffer overflow kan ook het gevolg zijn van gehele overflows bij het berekenen van de groottes. Controleer altijd rekenen voordat allocatie.
#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);
}
Gebruik van snprintf voor geformatteerde tekenreeksen
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
}
Aanvullende beste praktijken
- Buffers starten
- Vermijd recursie met ongebonden diepte . .Stack overflows kunnen optreden door diepe recursie; gebruik iteratie of limietdiepte.
- Gebruik qualifier
- Prefrequent -correctheid Voorkomt toevallige wijziging van invoerstrings en dwingt intentie.
- Foutbehandeling uitvoeren
Middelen voor verder leren
- SEI CERT C Coding Standard . . Uitgebreide regels voor veilige C-codering.
- CWE-120: Bufferkopie zonder controle van de grootte van de invoer .MITRE
- OWASP Buffer Overflow . . Praktische begeleiding van het Open Web Application Security Project.
- GNU C Library Manual: String and Array Utilities . Documentatie voor veilige stringfuncties.
- AdresSanitizer . . Een snelle geheugenfoutdetector.
Conclusie
Het voorkomen van buffer overflows in C is niet optioneel; het is een fundamentele verantwoordelijkheid van elke ontwikkelaar die met de taal werkt. Door het begrijpen van de mechanismen van overflows, het vervangen van gevaarlijke functies door veiliger alternatieven, het strikt valideren van ingangen en groottes, het mogelijk maken van compilerbeschermingen, en het gebruik van statische analyse en testen, kunt u het risico van deze kwetsbaarheden drastisch verminderen. Geen enkele techniek is voldoende; verdediging in diepte .. compiler-beveiliging combineren, compiler vlaggen, OS mitigations, en grondig testen biedt de sterkste bescherming. Met deze praktijken, kunt u schrijven C-code die zowel krachtig als veilig is, in staat om veilig te werken in kritieke systemen. Onthoud: veilige codering is een lopende praktijk, niet een eenmalige fix. Blijf op de hoogte van nieuwe kwetsbaarheden en voortdurend te beoordelen en verbeteren van uw code.