La idea básica de un canario terminador es que cuando un atacante intenta un desbordamiento de búfer, se ve obligado a sobrescribir el valor del canario. El programa puede entonces detectar que el canario ha cambiado de valor y tomar las medidas apropiadas.
El valor 0
es algo especial en programación: muchos lenguajes lo usan como marcador de fin de texto. Si un atacante intenta desbordar un búfer de texto, el uso de 0
como canary terminador significa que el ataque fallará: para evitar que cambie el canary, deben incluir un 0
en la entrada de gran tamaño en una ubicación que hará que se ignore casi todo el exceso de entrada.
Sin embargo, esto tiene un problema: si la entrada se trata como datos binarios en lugar de como texto, el hecho de que el canario tenga un valor fijo conocido significa que el atacante simplemente puede sobreescribir el canario consigo mismo, produciendo un indetectable desbordamiento.
Editar: ejemplos de código
/* 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);
}
En un ejemplo de la vida real, el compilador puede insertar automáticamente el código de verificación de canarios y canarios en lugar de hacerlo manualmente por el programador.