Perché questo codice è vulnerabile agli attacchi di overflow del buffer?


148
int func(char* str)
{
   char buffer[100];
   unsigned short len = strlen(str);

   if(len >= 100)
   {
        return (-1);
   }

   strncpy(buffer,str,strlen(str));
   return 0;
}

Questo codice è vulnerabile a un attacco di overflow del buffer e sto cercando di capire perché. Sto pensando che abbia a che fare con l' lenessere dichiarato un shortanziché un int, ma non ne sono davvero sicuro.

Qualche idea?


3
Esistono più problemi con questo codice. Ricorda che le stringhe C hanno terminazione nulla.
Dmitri Chubarov,

4
@DmitriChubarov, non null terminare la stringa sarà un problema solo se la stringa viene utilizzata dopo la chiamata a strncpy. In questo caso, non lo è.
R Sahu,

43
I problemi in questo codice derivano direttamente dal fatto che strlenviene calcolato, utilizzato per il controllo di validità e quindi viene nuovamente calcolato assurdamente : è un errore DRY. Se il secondo strlen(str)fosse sostituito con len, non ci sarebbe possibilità di overflow del buffer, indipendentemente dal tipo di len. Le risposte non affrontano questo punto, riescono solo ad evitarlo.
Jim Balter,

3
@CiaPan: quando passa una stringa non terminata con null, strlen mostrerà un comportamento indefinito.
Kaiserludi,

3
@JimBalter Nah, penso che li lascerò lì. Forse qualcun altro avrà la stessa folle idea sbagliata e imparerà da esso. Sentiti libero di segnalarli se ti infastidiscono, qualcuno potrebbe venire ed eliminarli.
Asad Saeeduddin,

Risposte:


192

Sulla maggior parte dei compilatori il valore massimo di an unsigned shortè 65535.

Qualsiasi valore sopra che viene racchiuso, quindi 65536 diventa 0 e 65600 diventa 65.

Ciò significa che lunghe stringhe della giusta lunghezza (ad es. 65600) supereranno il controllo e trabocceranno il buffer.


Utilizzare size_tper archiviare il risultato di strlen(), non unsigned shorte confrontarlo lencon un'espressione che codifica direttamente la dimensione di buffer. Quindi per esempio:

char buffer[100];
size_t len = strlen(str);
if (len >= sizeof(buffer) / sizeof(buffer[0]))  return -1;
memcpy(buffer, str, len + 1);

2
@PatrickRoberts Teoricamente, sì. Ma devi tenere presente che il 10% del codice è responsabile del 90% del tempo di esecuzione, quindi non dovresti lasciare andare le prestazioni prima della sicurezza. E tieni presente che nel tempo il codice cambia, il che può improvvisamente significare che il controllo precedente non è più disponibile.
orlp,

3
Per prevenire l'overflow del buffer, utilizzare semplicemente lencome terzo argomento di strncpy. Usare di nuovo strlen è stupido in ogni caso.
Jim Balter,

16
/ sizeof(buffer[0])- nota che sizeof(char)in C è sempre 1 (anche se un carattere contiene un bit di gazillion) quindi è superfluo quando non è possibile utilizzare un diverso tipo di dati. Ancora ... complimenti per una risposta completa (e grazie per essere sensibile ai commenti).
Jim Balter,

4
@ rr-: char[]e char*non sono la stessa cosa. Ci sono molte situazioni in cui a char[]verrà convertito implicitamente in a char*. Ad esempio, char[]è esattamente lo stesso di char*quando usato come tipo per gli argomenti delle funzioni. Tuttavia, la conversione non avviene per sizeof().
Dietrich Epp,

4
@Controll Perché se si modifica la dimensione di bufferad un certo punto l'espressione si aggiorna automaticamente. Questo è fondamentale per la sicurezza, poiché la dichiarazione di bufferpotrebbe essere abbastanza distante dal controllo nel codice effettivo. Quindi è facile cambiare la dimensione del buffer, ma dimentica di aggiornare in ogni posizione in cui viene utilizzata la dimensione.
orlp

28

Il problema è qui:

strncpy(buffer,str,strlen(str));
                   ^^^^^^^^^^^

Se la stringa è maggiore della lunghezza del buffer di destinazione, strncpy lo copierà comunque. Si sta basando il numero di caratteri della stringa come numero da copiare anziché la dimensione del buffer. Il modo corretto per farlo è il seguente:

strncpy(buffer,str, sizeof(buff) - 1);
buffer[sizeof(buff) - 1] = '\0';

Quello che fa è limitare la quantità di dati copiati alla dimensione effettiva del buffer meno uno per il carattere che termina null. Quindi impostiamo l'ultimo byte nel buffer sul carattere null come protezione aggiuntiva. La ragione di ciò è perché strncpy copierà fino a n byte, incluso il null di terminazione, se strlen (str) <len - 1. In caso contrario, il null non viene copiato e si ha uno scenario di crash perché ora il buffer ha un terminale non terminato corda.

Spero che questo ti aiuti.

EDIT: dopo ulteriori esami e input da altri, segue una possibile codifica per la funzione:

int func (char *str)
  {
    char buffer[100];
    unsigned short size = sizeof(buffer);
    unsigned short len = strlen(str);

    if (len > size - 1) return(-1);
    memcpy(buffer, str, len + 1);
    buffer[size - 1] = '\0';
    return(0);
  }

Dato che conosciamo già la lunghezza della stringa, possiamo usare memcpy per copiare la stringa dalla posizione a cui fa riferimento str nel buffer. Nota che nella pagina del manuale di strlen (3) (su un sistema FreeBSD 9.3), è indicato quanto segue:

 The strlen() function returns the number of characters that precede the
 terminating NUL character.  The strnlen() function returns either the
 same result as strlen() or maxlen, whichever is smaller.

Ciò che interpreto è che la lunghezza della stringa non include il null. Questo è il motivo per cui copio len + 1 byte per includere il valore null e il test verifica che la lunghezza <dimensione del buffer - 2. Meno uno perché il buffer inizia nella posizione 0 e meno un altro per assicurarsi che ci sia spazio per il null.

EDIT: Si scopre che la dimensione di qualcosa inizia con 1 mentre l'accesso inizia con 0, quindi -2 prima non era corretto perché avrebbe restituito un errore per qualcosa> 98 byte ma dovrebbe essere> 99 byte.

EDIT: Sebbene la risposta su un short senza segno sia generalmente corretta poiché la lunghezza massima che può essere rappresentata è di 65.535 caratteri, non importa davvero perché se la stringa è più lunga, il valore andrà a capo. È come prendere 75.231 (che è 0x000125DF) e mascherare i primi 16 bit che ti danno 9695 (0x000025DF). L'unico problema che vedo con questo è i primi 100 caratteri oltre 65.535 poiché il controllo di lunghezza consentirà la copia, ma copierà fino ai primi 100 caratteri della stringa in tutti i casi e termina null la stringa . Quindi, anche con il problema avvolgente, il buffer non verrà comunque traboccato.

Questo può di per sé o meno costituire un rischio per la sicurezza a seconda del contenuto della stringa e del motivo per cui la si sta utilizzando. Se è solo un testo semplice che è leggibile dall'uomo, in genere non ci sono problemi. Ottieni solo una stringa troncata. Tuttavia, se si tratta di qualcosa di simile a un URL o addirittura a una sequenza di comandi SQL, potresti avere un problema.


2
Vero, ma questo va oltre lo scopo della domanda. Il codice mostra chiaramente che alla funzione viene passato un puntatore a caratteri Al di fuori dell'ambito della funzione, non ci interessa.
Daniel Rudy,

"il buffer in cui è memorizzato str" - questo non è un buffer overflow , che è il problema qui. E ogni risposta ha quel "problema", che è inevitabile data la firma di func... e ogni altra funzione C mai scritta che prende le stringhe terminate con NUL come argomenti. Richiamare la possibilità che l'ingresso non venga terminato con NUL è completamente all'oscuro.
Jim Balter,

"questo va oltre lo scopo della domanda" - che purtroppo va oltre la capacità di alcune persone di comprendere.
Jim Balter,

"Il problema è qui" - hai ragione, ma ti manca ancora il problema chiave, ovvero che test ( len >= 100) è stato eseguito su un valore ma alla lunghezza della copia è stato assegnato un valore diverso ... questo è una violazione del principio DRY. La semplice chiamata strncpy(buffer, str, len)evita la possibilità di overflow del buffer e fa meno lavoro di strncpy(buffer,str,sizeof(buffer) - 1)... anche se qui è solo un equivalente più lento di memcpy(buffer, str, len).
Jim Balter,

@JimBalter Va oltre lo scopo della domanda, ma sto divagando. Comprendo che i valori utilizzati dal test e ciò che viene utilizzato in strncpy sono due diversi. Tuttavia, la pratica di codifica generale dice che il limite di copia dovrebbe essere sizeof (buffer) - 1, quindi non importa quale sia la lunghezza di str sulla copia. strncpy interromperà la copia dei byte quando raggiunge un valore nullo o copia n byte. La riga successiva garantisce che l'ultimo byte nel buffer sia un carattere nullo. Il codice è sicuro, sostengo la mia precedente dichiarazione.
Daniel Rudy,

11

Anche se stai usando strncpy, la lunghezza del taglio dipende ancora dal puntatore della stringa passata. Non hai idea di quanto sia lunga quella stringa (la posizione del terminatore null rispetto al puntatore, cioè). Quindi chiamare strlenda solo ti apre alla vulnerabilità. Se vuoi essere più sicuro, usa strnlen(str, 100).

Il codice completo corretto sarebbe:

int func(char *str) {
   char buffer[100];
   unsigned short len = strnlen(str, 100); // sizeof buffer

   if (len >= 100) {
     return -1;
   }

   strcpy(buffer, str); // this is safe since null terminator is less than 100th index
   return 0;
}

@ user3386109 Non accederà strlenquindi oltre la fine del buffer?
Patrick Roberts,

2
@ user3386109 ciò che stai sottolineando rende la risposta di orlp non valida quanto la mia. Non riesco a capire perché strnlennon risolva il problema se ciò che orlp sta suggerendo è comunque presumibilmente corretto.
Patrick Roberts,

1
"Non credo che Strnlen risolva nulla qui" - certo che lo fa; impedisce lo straripamento buffer. "poiché str potrebbe puntare a un buffer di 2 byte, nessuno dei quali è NUL." - è irrilevante, come è vero per qualsiasi implementazione di func. La domanda qui riguarda l'overflow del buffer, non UB perché l'input non è terminato con NUL.
Jim Balter,

1
"Il secondo parametro passato a strnlen deve essere la dimensione dell'oggetto a cui punta il primo parametro, o strnlen è inutile" - questa è una completa e totale assurdità. Se il secondo argomento di strnlen è la lunghezza della stringa di input, allora strnlen è equivalente a strlen. Come potresti ottenere quel numero, e se lo avessi, perché dovresti chiamare str [n] len? Non è per questo che strnlen è affatto.
Jim Balter,

1
+1 Sebbene questa risposta è imperfetta perché non è equivalente al codice del PO - strncpy NUL-pad e non fa NUL terminare, considerando strcpy NUL-termina e non NUL-pad, esso fa risolvere il problema, contrariamente alla commenti ridicoli e ignoranti sopra.
Jim Balter,

4

La risposta con la confezione è corretta. Ma c'è un problema che penso non sia stato menzionato se (len> = 100)

Bene, se Len fosse 100, avremmo copiato 100 elementi e non avremmo trascinato \ 0. Ciò significherebbe chiaramente che qualsiasi altra funzione, a seconda della stringa corretta, andrebbe ben oltre l'array originale.

La stringa problematica di C è IMHO irrisolvibile. Faresti sempre meglio ad avere dei limiti prima della chiamata, ma anche questo non ti aiuterà. Non c'è controllo dei limiti e quindi gli overflow del buffer possono sempre e purtroppo accadranno ....


La stringa problematica è risolvibile: basta usare le funzioni appropriate. I. e. non strncpy() e amici, ma la memoria che assegna funzioni come strdup()e amici. Sono nello standard POSIX-2008, quindi sono abbastanza portatili, sebbene non disponibili su alcuni sistemi proprietari.
cmaster - ripristina monica il

"qualsiasi altra funzione che dipende dalla stringa terminata corretta" - bufferè locale per questa funzione e non viene utilizzata altrove. In un vero programma dovremmo esaminare come viene usato ... a volte non la terminazione NUL è corretta (l'uso originale di strncpy era di creare le voci della directory a 14 byte di UNIX - NUL-padded e non NUL-terminated). "La stringa problematica di C è IMHO irrisolvibile" - mentre C è un linguaggio strabiliante che è stato superato da una tecnologia di gran lunga migliore, se si usa abbastanza disciplina si può scrivere un codice sicuro in esso.
Jim Balter,

La tua osservazione mi sembra sbagliata. if (len >= 100)è la condizione per quando il controllo ha esito negativo , non quando passa, il che significa che non esiste un caso in cui vengono copiati esattamente 100 byte senza terminatore NUL, poiché tale lunghezza è inclusa nella condizione di errore.
Patrick Roberts,

@ cmaster. In questo caso ti sbagli. Non è risolvibile, perché si può sempre scrivere senza limiti. Sì, è un comportamento indefinito, ma non c'è modo di impedirlo completamente.
Friedrich,

@Jim Balter. Non importa. Potrei potenzialmente scrivere oltre i limiti di questo buffer locale e quindi sarà sempre possibile corrompere alcune altre strutture di dati.
Friedrich,

3

Al di là dei problemi di sicurezza legati alla chiamata strlenpiù di una volta, in genere non si dovrebbero usare metodi di stringa su stringhe la cui lunghezza è nota con precisione [per la maggior parte delle funzioni di stringa, esiste solo un caso molto ristretto in cui dovrebbero essere usati - su stringhe per le quali un massimo la lunghezza può essere garantita, ma la lunghezza precisa non è nota]. Una volta che la lunghezza della stringa di input è nota e la lunghezza del buffer di output è nota, si dovrebbe capire quanto una regione deve essere copiata e quindi utilizzare memcpy()per eseguire effettivamente la copia in questione. Sebbene sia possibile che si verifichino strcpyprestazioni superiori memcpy()quando si copia una stringa di solo 1-3 byte o giù di lì, su molte piattaforme memcpy()è probabile che sia più del doppio rispetto alle stringhe più grandi.

Sebbene ci siano alcune situazioni in cui la sicurezza verrebbe a scapito delle prestazioni, questa è una situazione in cui l'approccio sicuro è anche quello più veloce. In alcuni casi, può essere ragionevole scrivere un codice che non è sicuro contro input che si comportano in modo strano, se il codice che fornisce gli input può garantire che siano ben educati e se la protezione da input maleducati ostacolerebbe le prestazioni. Garantire che le lunghezze delle stringhe siano controllate solo una volta migliora sia le prestazioni che la sicurezza, anche se è possibile fare qualcosa in più per proteggere la sicurezza anche quando si traccia manualmente la lunghezza delle stringhe: per ogni stringa che dovrebbe avere un null finale, scrivere esplicitamente il null finale piuttosto che aspettarsi che la stringa di origine l'abbia. Quindi, se uno scrivesse un strdupequivalente:

char *strdupe(char const *src)
{
  size_t len = strlen(src);
  char *dest = malloc(len+1);
  // Calculation can't wrap if string is in valid-size memory block
  if (!dest) return (OUT_OF_MEMORY(),(char*)0); 
  // OUT_OF_MEMORY is expected to halt; the return guards if it doesn't
  memcpy(dest, src, len);      
  dest[len]=0;
  return dest;
}

Si noti che l'ultima istruzione potrebbe generalmente essere omessa se memcpy avesse elaborato len+1byte, ma se un altro thread dovesse modificare la stringa di origine, il risultato potrebbe essere una stringa di destinazione non terminata con NUL.


3
Potete per favore spiegare i problemi di sicurezza legati alla chiamata strlenpiù di una volta ?
Bogdan Alexandru,

1
@BogdanAlexandru: una volta che uno ha chiamato strlene intrapreso alcune azioni in base al valore restituito (che era presumibilmente il motivo per chiamarlo in primo luogo), allora una chiamata ripetuta (1) produrrà sempre la stessa risposta della prima, nel qual caso è semplicemente un lavoro sprecato o (2) a volte (perché qualcos'altro - forse un altro thread - nel frattempo ha modificato la stringa) può dare una risposta diversa, nel qual caso il codice che fa alcune cose con la lunghezza (ad es. allocare un buffer) può assumere una dimensione diversa rispetto al codice che fa altre cose (copia nel buffer).
supercat
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.