Perché è gets()
pericoloso
Il primo worm Internet ( Morris Internet Worm ) è fuggito circa 30 anni fa (1988-11-02) e ha usato gets()
e un buffer overflow come uno dei suoi metodi di propagazione da un sistema all'altro. Il problema di base è che la funzione non sa quanto è grande il buffer, quindi continua a leggere fino a quando non trova una nuova riga o incontra EOF, e può traboccare i limiti del buffer che gli è stato dato.
Dovresti dimenticare di aver mai sentito dire che gets()
esisteva.
Lo standard C11 ISO / IEC 9899: 2011 eliminato gets()
come funzione standard, che è A Good Thing ™ (è stato formalmente contrassegnato come "obsoleto" e "deprecato" in ISO / IEC 9899: 1999 / Cor.3: 2007 - Rettifica tecnica 3 per C99, quindi rimosso in C11). Purtroppo, rimarrà nelle biblioteche per molti anni (che significa "decenni") per motivi di compatibilità con le versioni precedenti. Se dipendesse da me, l'implementazione di gets()
diventerebbe:
char *gets(char *buffer)
{
assert(buffer != 0);
abort();
return 0;
}
Dato che il tuo codice andrà in crash comunque, prima o poi, è meglio eliminare il problema prima che dopo. Sarei pronto ad aggiungere un messaggio di errore:
fputs("obsolete and dangerous function gets() called\n", stderr);
Le versioni moderne del sistema di compilazione Linux generano avvisi se si collega gets()
- e anche per alcune altre funzioni che hanno anche problemi di sicurezza ( mktemp()
, ...).
Alternative a gets()
fgets ()
Come tutti gli altri hanno detto, l'alternativa canonica a gets()
sta fgets()
specificando stdin
come flusso di file.
char buffer[BUFSIZ];
while (fgets(buffer, sizeof(buffer), stdin) != 0)
{
...process line of data...
}
Ciò che nessun altro ancora menzionato è che gets()
non include la newline ma lo fgets()
fa. Pertanto, potrebbe essere necessario utilizzare un wrapper per fgets()
eliminare la nuova riga:
char *fgets_wrapper(char *buffer, size_t buflen, FILE *fp)
{
if (fgets(buffer, buflen, fp) != 0)
{
size_t len = strlen(buffer);
if (len > 0 && buffer[len-1] == '\n')
buffer[len-1] = '\0';
return buffer;
}
return 0;
}
O meglio:
char *fgets_wrapper(char *buffer, size_t buflen, FILE *fp)
{
if (fgets(buffer, buflen, fp) != 0)
{
buffer[strcspn(buffer, "\n")] = '\0';
return buffer;
}
return 0;
}
Inoltre, come sottolinea caf in un commento e paxdiablo mostra nella sua risposta, fgets()
potresti avere dei dati lasciati su una riga. Il mio codice wrapper lascia che i dati vengano letti la prossima volta; puoi prontamente modificarlo per inghiottire il resto della linea di dati se preferisci:
if (len > 0 && buffer[len-1] == '\n')
buffer[len-1] = '\0';
else
{
int ch;
while ((ch = getc(fp)) != EOF && ch != '\n')
;
}
Il problema residuo è come riportare i tre diversi stati di risultato: EOF o errore, riga letta e non troncata e riga parziale letta ma i dati sono stati troncati.
Questo problema non si pone gets()
perché non sa dove finisce il buffer e calpesta allegramente oltre la fine, provocando il caos sul layout della memoria ben curato, spesso incasinando lo stack di ritorno (uno Stack Overflow ) se il buffer è allocato su lo stack, o calpestando le informazioni di controllo se il buffer è allocato dinamicamente, o copiando i dati su altre preziose variabili globali (o modulo) se il buffer è allocato staticamente. Nessuna di queste è una buona idea: incarnano la frase "comportamento indefinito".
Esiste anche il TR 24731-1 (Rapporto tecnico del Comitato Standard C) che offre alternative più sicure a una varietà di funzioni, tra cui gets()
:
§6.5.4.1 La gets_s
funzione
Sinossi
#define __STDC_WANT_LIB_EXT1__ 1
#include <stdio.h>
char *gets_s(char *s, rsize_t n);
Runtime-vincoli
s
non deve essere un puntatore nullo. n
non deve essere uguale a zero né maggiore di RSIZE_MAX. Un carattere di nuova riga, fine del file o errore di lettura deve verificarsi nella lettura dei
n-1
caratteri da stdin
. 25)
3 Se si verifica una violazione del vincolo di runtime, s[0]
viene impostato sul carattere null e i caratteri vengono letti e scartati stdin
fino alla lettura di un carattere di nuova riga o alla fine del file o si verifica un errore di lettura.
Descrizione
4 La gets_s
funzione legge al massimo uno in meno del numero di caratteri specificato n
dal flusso puntato da stdin
, nella matrice puntata da s
. Nessun carattere aggiuntivo viene letto dopo un carattere di nuova riga (che viene scartato) o dopo la fine del file. Il carattere di nuova riga scartato non conta per il numero di caratteri letti. Un carattere null viene scritto immediatamente dopo l'ultimo carattere letto nell'array.
5 Se viene rilevata la fine del file e non sono stati letti caratteri nell'array o se si verifica un errore di lettura durante l'operazione, s[0]
viene impostato sul carattere null e gli altri elementi di s
accettano valori non specificati.
Pratica consigliata
6 La fgets
funzione consente ai programmi scritti correttamente di elaborare in modo sicuro linee di input troppo lunghe per essere memorizzate nell'array dei risultati. In generale ciò richiede che i chiamanti fgets
prestino attenzione alla presenza o all'assenza di un carattere di nuova riga nella matrice dei risultati. Prendi in considerazione l'utilizzo fgets
(insieme a qualsiasi elaborazione necessaria basata su caratteri di nuova riga) anziché
gets_s
.
25) La gets_s
funzione, a differenza gets
, la rende una violazione del vincolo di runtime per una linea di input che trabocca il buffer per memorizzarlo. Diversamente fgets
, gets_s
mantiene una relazione uno a uno tra le linee di input e le chiamate riuscite a gets_s
. I programmi che usano si gets
aspettano una simile relazione.
I compilatori di Microsoft Visual Studio implementano un'approssimazione allo standard TR 24731-1, ma ci sono differenze tra le firme implementate da Microsoft e quelle nella TR.
Lo standard C11, ISO / IEC 9899-2011, include TR24731 nell'allegato K come parte opzionale della libreria. Sfortunatamente, è raramente implementato su sistemi simili a Unix.
getline()
- POSIX
POSIX 2008 offre anche un'alternativa sicura a gets()
chiamata getline()
. Alloca lo spazio per la linea in modo dinamico, quindi alla fine devi liberarla. Rimuove quindi la limitazione sulla lunghezza della linea. Restituisce anche la lunghezza dei dati letti o -1
(e non EOF
!), Il che significa che i byte null nell'input possono essere gestiti in modo affidabile. C'è anche una variante 'scegli il tuo delimitatore a carattere singolo' chiamata getdelim()
; questo può essere utile se hai a che fare con l'output da find -print0
cui le estremità dei nomi dei file sono contrassegnate con un '\0'
carattere ASCII NUL , ad esempio.
gets()
Buffer_overflow_attack