The basic idea of a terminator canary is that when an attacker attempts a buffer overflow, they're forced to over-write the canary value. The program can then detect that the canary has changed value and take appropriate actions.
The value 0
is somewhat special in programming: many languages use it as an end-of-text marker. If an attacker is trying to overflow a text buffer, the use of a 0
as a terminator canary means the attack will fail: in order to keep the canary from changing, they need to include a 0
in the oversized input in a location that will cause almost all of the excess input to be ignored.
This has a problem, though: if the input is handled as binary data rather than as text, the fact that the canary has a known, fixed value means the attacker can simply over-write the canary with itself, producing an undetectable overflow.
Edit: code examples
/* 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 a real-life example, the canaries and canary-checking code may be inserted automatically by the compiler rather than manually by the programmer.