Buffer overflow - terminator canaries

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.