Under Snow Leopard, sprintf calls are automatically replaced by calls to sprintf_chk, a functionally equivalent function that performs bounds checking on the sprintf argument.
This means the following program (that deliberately overwrites the end of t):
produces assembler code where the sprintf call is replaced to a sprintf_chk call:
sprintf_chk has the following prototype:
that implies that the compiler is aware of the storage size of the destination buffer used by sprintf.
If the compiler knows the storage size of the destination buffer it uses it in the sprintf_chk call, which in turn calls vsnprintf and checks that the number of characters written in the buffer is less or equal to the size of the buffer (if this is not the case, it aborts).
sprintf_chk will not work with buffers that have been allocated on the heap (it will be called with length set to -1).
This means the following program (that deliberately overwrites the end of t):
#include <stdio.h>
int main()
{
char t[1] = {0x41};
char v[1] = {0x42};
sprintf(t, "%d%d", 0x43, 0x44);
}
produces assembler code where the sprintf call is replaced to a sprintf_chk call:
(gdb) disass
Dump of assembler code for function main:
0x0000000100000eec <main+0>: push %rbp
0x0000000100000eed <main+1>: mov %rsp,%rbp
0x0000000100000ef0 <main+4>: sub $0x10,%rsp
0x0000000100000ef4 <main+8>: movb $0x41,-0x1(%rbp)
0x0000000100000ef8 <main+12>: movb $0x42,-0x2(%rbp)
0x0000000100000efc <main+16>: lea -0x1(%rbp),%rdi # buffer argument
0x0000000100000f00 <main+20>: mov $0x44,%r9d # second va_arg argument
0x0000000100000f06 <main+26>: mov $0x43,%r8d # first va_arg argument
0x0000000100000f0c <main+32>: lea 0x47(%rip),%rcx # format string ("%d%d")
0x0000000100000f13 <main+39>: mov $0x1,%edx # storage size as detected by the compiler
0x0000000100000f18 <main+44>: mov $0x0,%esi # flags argument (0)
0x0000000100000f1d <main+49>: mov $0x0,%eax # MMS registers are not used to pass va_args
0x0000000100000f22 <main+54>: callq 0x100000f2a <dyld_stub___sprintf_chk>
0x0000000100000f27 <main+59>: leaveq
0x0000000100000f28 <main+60>: retq
End of assembler dump.
sprintf_chk has the following prototype:
int __sprintf_chk (char *s, int flags, size_t len, const char *format, ...)
that implies that the compiler is aware of the storage size of the destination buffer used by sprintf.
If the compiler knows the storage size of the destination buffer it uses it in the sprintf_chk call, which in turn calls vsnprintf and checks that the number of characters written in the buffer is less or equal to the size of the buffer (if this is not the case, it aborts).
sprintf_chk will not work with buffers that have been allocated on the heap (it will be called with length set to -1).