L'idea di base di un terminatore è che quando un utente malintenzionato tenta un buffer overflow, è costretto a sovrascrivere il valore delle canarie. Il programma può quindi rilevare che il canarino ha cambiato valore e intraprendere le azioni appropriate.
Il valore 0
è un po 'speciale nella programmazione: molte lingue lo usano come marcatore di fine testo. Se un utente malintenzionato sta tentando di sovraccaricare un buffer di testo, l'uso di un 0
come un terminatore può significare che l'attacco fallirà: per impedire al canarino di cambiare, è necessario includere un 0
nell'input sovradimensionato in una posizione che causerà la quasi totalità dell'ingresso in eccesso da ignorare.
Questo ha un problema, tuttavia: se l'input è gestito come dati binari anziché come testo, il fatto che il canarino abbia un valore fisso noto significa che l'attaccante può semplicemente sovrascrivere il canarino con se stesso, producendo un non rilevabile trabocco.
Modifica: esempi di codice
/* This reads a length-tagged packet of up to 16 bytes length from an input stream.
*
* Note that since the programmer forgot to check the length of the input,
* a packet of more than 20 bytes (give or take alignment) will overflow onto
* sensitive parts of the stack. If bytes 17 through 20 of the outsized packet
* are 0s, this overflow won't be detected.
*/
size_t readPacket(char *stream)
{
size_t length;
char packet[16];
uint32_t canary = 0;
length = (size_t)(*stream++);
memcpy(packet, stream, length);
processPacket(packet, length);
if(canary != 0)
exit(0);
return length;
}
/* This reads a username from an input stream.
*
* Note that since the programmer used strcpy() rather than strlcpy(), a
* string of more than 20 bytes (give or take alignment) will overflow onto
* sensitive parts of the stack. However, since strcpy() stops copying once
* it encounters a byte with the value 0, in order for overflow to reach a
* sensitive part of the stack, it must change the value of the canary. If
* this happens, exit() is called and the changed stack is never used.
*/
size_t readName(char *stream)
{
char userName[16];
uint32_t canary = 0;
strcpy(userName, stream);
processUserName(userName);
if(canary != 0)
exit(0);
return strlen(userName);
}
In un esempio di vita reale, i canarini e il codice di controllo del canarino possono essere inseriti automaticamente dal compilatore anziché manualmente dal programmatore.