2009-10-01 11 views
9

Il seguente codice causa un errore e uccide la mia applicazione. Ha senso poiché il buffer ha una lunghezza di soli 10 byte e il testo è lungo 22 byte (buffer overflow).sprintf_s con un buffer troppo piccolo

char buffer[10];  
int length = sprintf_s(buffer, 10, "1234567890.1234567890."); 

Come faccio a prendere questo errore in modo da poter segnalare invece di schiantarsi la mia domanda?

Edit:

Dopo aver letto le commenti qui sotto sono andato con _snprintf_s. Se restituisce un valore -1, il buffer non è stato aggiornato.

length = _snprintf_s(buffer, 10, 9, "123456789"); 
printf("1) Length=%d\n", length); // Length == 9 

length = _snprintf_s(buffer, 10, 9, "1234567890.1234567890."); 
printf("2) Length=%d\n", length); // Length == -1 

length = _snprintf_s(buffer, 10, 10, "1234567890.1234567890."); 
printf("3) Length=%d\n", length); // Crash, it needs room for the NULL char 
+0

Il passaggio della dimensione del buffer e della dimensione del buffer meno uno è ottuso e soggetto a errori. Si dovrebbe preferire la variante che descrivo qui sotto: length = _snprintf_s (buffer, _TRUNCATE, "1234567890.1234567890."); Poiché il parametro della prima dimensione è omesso, il compilatore utilizza il sovraccarico del modello che ne deduce la dimensione. _TRUNCATE è un valore speciale che fa ciò che dice. Nessun numero magico, e ora il tuo codice è sicuro, manutenibile e un buon esempio. Se ti piace questo commento e _snprintf_s allora dovresti selezionare la mia risposta, invece della pericolosa risposta snprintf/_snprintf. –

risposta

5

Invece di sprintf_s, è possibile utilizzare snprintf (a.k.a _snprintf su Windows).

#ifdef WIN32 
#define snprintf _snprintf 
#endif 

char buffer[10];  
int length = snprintf(buffer, 10, "1234567890.1234567890."); 
// unix snprintf returns length output would actually require; 
// windows _snprintf returns actual output length if output fits, else negative 
if (length >= sizeof(buffer) || length<0) 
{ 
    /* error handling */ 
} 
+4

C'è anche uno snprintf_s. – Joe

+2

Nota: per motivi di sicurezza, se non c'è spazio sufficiente, il contenuto del buffer potrebbe non essere terminato con null. – Managu

+2

@ Managu: se la MS rivendicava la conformità a C99 - che non lo fa - quell'affermazione sarebbe falsa; lo standard C99 richiede snprintf() per null terminare la stringa a meno che la lunghezza della stringa sia 0. Sezione 7.19.6.5: Se n è zero, non viene scritto nulla ... Altrimenti, i caratteri di output oltre il n-1 sono scartati piuttosto che che viene scritto nell'array e alla fine viene scritto un carattere nullo dei caratteri effettivamente scritti nell'array. Se la copia avviene tra gli oggetti che si sovrappongono, il comportamento non è definito. –

0

Da MSDN:

L'altra differenza principale tra sprintf_s e sprintf è che sprintf_s un parametro di lunghezza specifica la dimensione del buffer di uscita in caratteri. Se il buffer è troppo piccolo per il testo che viene stampato, il buffer viene impostato su una stringa vuota e viene invocato il gestore parametri non valido. Diversamente da snprintf, sprintf_s garantisce che il buffer sarà terminato con null (a meno che la dimensione del buffer non sia zero).

Quindi idealmente quello che hai scritto dovrebbe funzionare correttamente.

+4

Il "gestore parametri non valido" predefinito termina il processo. –

+0

true, ma è facile installarne uno che non lo fa, il che si traduce in sprintf_s che restituiscono -1 se il buffer è troppo piccolo – stijn

0

Sembra che tu stia scrivendo su MSVC di qualche tipo?

Penso che i documenti MSDN per sprintf_s dicano che asserisce muore, quindi non sono sicuro di poterlo prendere a livello di codice.

Come suggerito da LBushkin, è molto meglio usare le classi che gestiscono le stringhe.

16

È di progettazione. L'intero punto di sprintf_s e altre funzioni della famiglia *_s consiste nel rilevare gli errori di sovraccarico del buffer e trattarli come violazioni di precondizione . Ciò significa che non sono pensati per essere recuperabili. Questo è progettato per catturare solo gli errori - non si dovrebbe mai chiamare sprintf_s se si sa che la stringa può essere troppo grande per un buffer di destinazione. In tal caso, utilizzare prima strlen per verificare e decidere se è necessario tagliare.

+0

Non sono d'accordo. È del tutto ragionevole chiamare sprintf_s con un buffer di destinazione troppo piccolo, purché si utilizzi il flag _TRUNCATE per indicarlo. Ok, tecnicamente _TRUNCATE richiede l'uso di snprintf_s invece di sprintf_s, ma il mio punto di vista è principalmente valido. L'utilizzo di strlen è spesso inapplicabile o scomodo, ma l'uso di _TRUNCATE è spesso banale e appropriato. –

+0

Penso che l'uso di 'snprintf_s' sia la differenza cruciale, e non proprio un mero tecnicismo. –

+0

È una differenza cruciale, sicuramente. Ma penso che la tua risposta faccia sembrare che la famiglia di funzioni di _s non possa troncare, il che potrebbe essere fuorviante. –

0

Vedere la sezione 6.6.1 di TR24731 che è la versione ISO C Committee della funzionalità implementata da Microsoft. Fornisce funzioni set_constraint_handler(), abort_constraint_handler() e ignore_constraint_handler() funzioni.

Ci sono commenti da Pavel Minaev che suggeriscono che l'implementazione di Microsoft non aderisce alla proposta TR24731 (che è un "Rapporto tecnico di tipo 2"), quindi potresti non essere in grado di intervenire, o potresti dover fare qualcosa diverso da quello che il TR indica dovrebbe essere fatto. Per questo, scruta MSDN.

+1

Sfortunatamente, MSVC non implementa completamente TR24731 - in particolare, non implementa specificamente le funzioni a cui si fa riferimento (inoltre, anche i loro nomi terminano con '_s' - cioè" set_constraint_handler_s'). –

+1

Ma secondo http://msdn.microsoft.com/en-us/library/ksazx244%28VS.80%29.aspx esiste una funzione _set_invalid_parameter_handler() che può essere utilizzata per modificare il comportamento predefinito di interrompere il programma . –

5

Questo funziona con VC++ ed è ancora più sicuro che usare snprintf (e certamente più sicuro di _snprintf):

void TestString(const char* pEvil) 
{ 
    char buffer[100]; 
    _snprintf_s(buffer, _TRUNCATE, "Some data: %s\n", pEvil); 
} 

Il _TRUNCATE flag indica che la stringa dovrebbe essere troncato. In questa forma la dimensione del buffer non viene effettivamente passata, cosa che (paradossalmente!) è ciò che lo rende così sicuro. Il compilatore utilizza la magia del template per dedurre la dimensione del buffer, il che significa che non può essere specificato in modo errato (un errore sorprendentemente comune). Questa tecnica può essere applicata per creare altri wrapper di string sicure, come descritto nel post del mio blog qui: https://randomascii.wordpress.com/2013/04/03/stop-using-strncpy-already/

+0

guardando la documentazione MSDN di _snprintf_s, sembra che tu abbia dimenticato un argomento nella chiamata a _snprintf_s. Questo argomento dovrebbe comparire tra buffer e _TRUNCATE e si chiama sizeOfBuffer – user1741137

+1

Non ho dimenticato un argomento: il codice viene compilato ed è perfettamente sicuro. È necessario rileggere la documentazione. Sto facendo uso di un template override a _snprintf_s che dice al compilatore di dedurre la dimensione del buffer. Ho visto centinaia di posti in cui i programmatori sono passati esplicitamente in una dimensione del buffer e hanno superato la dimensione * errata *. Solo se il compilatore deduce la dimensione del buffer, questo serio errore può essere evitato. Ho fatto riferimento a questa tecnica nell'articolo a cui mi sono collegato nella mia soluzione. Fortemente raccomandato. –

Problemi correlati