3

I am reading an article on buffer overflow protection here.

For terminator canaries, I follow the part that if a terminator like a zero is used for a canary, the attacker would have a terminator in the overflow, which will cause the overflow to stop, preventing overflow of return address.

But the next sentence is not clear:

The undesirable result is that the canary is known. Even with the protection, 
an attacker could potentially overwrite the canary with its known value, and 
control information with mismatched values, thus passing the canary check code, 
this latter being executed soon before the specific processor's 
return-from-call instruction.

I am not able to follow this argument.

Does it imply that terminator canary can be bypassed in any way ?

7

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.

  • Can you please elaborate the meaning of "if the input is handled as binary data than as text" .. ? What copy mechanism does this include like strcpy, memcpy .. ? – Jake Oct 3 '14 at 20:25
  • 3
    Text is normally handled as "a series of non-0 bytes followed by the value 0". Binary data is handled as "a sequence of bytes of a certain length", or for input, it's sometimes "a sequence of bytes extending to the end of the file". Binary data doesn't have a special value indicating the end of the data, while text does. – Mark Oct 3 '14 at 20:31
  • 1
    @Jake consider memcpy or memset, or simply writing 'char' values at each offset of a region one by one, rather than str* functions which would only modify the memory region until a 0 is encountered. – Steve Dodier-Lazaro Oct 3 '14 at 20:36
  • 1
    @Jake I think this is what Mark means yes. With memcpy you might actually have an attack that changes the buffer size to be copied (any invalid write of 1 byte or more at a chosen location should suffice), and then be allowed to copy more of your arbitrary data than the program expected/had previously checked. – Steve Dodier-Lazaro Oct 4 '14 at 10:28
  • 1
    @Jake, that's a tricky question to answer. The compiler will usually lay out memory in the order the variables are declared, in which case canary will come after buffer in memory and be overwritten by an overflow, but it's not guaranteed. That's one of the reasons the canaries should be inserted by the compiler rather than the programmer. – Mark Oct 4 '14 at 21:12

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.