FORTIFY_SOURCE Examples
Theory in FORTIFY_SOURCE
__strcpy_chk : __dest, __src, __bos (__dest)
__builtin___strcpy_chk : __dest, __src, __bos (__dest)
The first and second argument inherits from strcpy. The third argument is the length of __dest and identified by compile time.
+#define strcpy(dst, src) \
+ __builtin___strcpy_chk (dst, src, os (dst))
+char *
+__strcpy_chk (char *d, const char *s, __SIZE_TYPE__ size)
+{
+ /* If size is -1, GCC should always optimize the call into strcpy. */
+ if (size == (__SIZE_TYPE__) -1)
+ abort ();
+ ++chk_calls;
+ if (strlen (s) >= size)
+ __chk_fail ();
+ return strcpy (d, s);
+}
There are 5 cases to analyze:
1. Known correct
int main(int argc,const char *argv[])
{
char buffer[10];
if (argc < 1) {
printf("need one argument");
}
strcpy(buffer, "abcdefg");
return 0;
}
Running Result:
No warning and no runtime check.
2. Known incorrect
int main(int argc,const char *argv[])
{
char buffer[10];
if (argc < 1) {
printf("need one argument");
}
strcpy(buffer, "abcdefghijklmn");
return 0;
}
Running Result:
In function ‘strcpy’,
inlined from ‘main’ at fortify_source2.c:17:2:
/usr/include/x86_64-linux-gnu/bits/string3.h:104:3: warning: call to __builtin___memcpy_chk will always overflow destination buffer
return __builtin___strcpy_chk (__dest, __src, __bos (__dest));
^
Warning and runtime check.
3. Not known if correct, not checkable at runtime
int main(int argc,const char *argv[])
{
char buffer[10];
if (argc < 2) {
printf("need one argument");
}
char *p = argv[2];
char *q = argv[1];
strcpy(p, q);
return 0;
}
Running Result:
No warning and no runtime check. The length of argv[2], argv[1] can’t be detected at compile time.
4. Not known if correct, but checkable at runtime
int main(int argc,const char *argv[])
{
char buffer[10];
if (argc < 1) {
printf("need one argument");
}
strcpy(buffer, argv[1]);
return 0;
}
Running Result:
Warning and runtime check. The length of buffer is 10 at compile time.
5. Difference between 1 and 2(-D_FORTIFY_SOURCE)
struct S {
struct T {
char buf[5];
int x;
} t;
char buf[20];
} var;
int main(int argc,const char *argv[])
{
strcpy(&var.t.buf[1], argv[1]);
return 0;
}
Running Result:
Compare the assembly language with different flags of gcc:
- -D_FORTIFY_SOURCE = 1
0x0000000000000654 <+4>: lea 0x200565(%rip),%rax # 0x200bc0 <var>
0x000000000000065b <+11>: mov 0x8(%rsi),%rsi
0x000000000000065f <+15>: mov $0x4,%edx
0x0000000000000664 <+20>: lea 0x1(%rax),%rdi
0x0000000000000668 <+24>: callq 0x630 <__strcpy_chk@plt>
- -D_FORTIFY_SOURCE = 2
0x0000000000000654 <+4>: lea 0x200565(%rip),%rax # 0x200bc0 <var>
0x000000000000065b <+11>: mov 0x8(%rsi),%rsi
0x000000000000065f <+15>: mov $0x1f,%edx
0x0000000000000664 <+20>: lea 0x1(%rax),%rdi
0x0000000000000668 <+24>: callq 0x630 <__strcpy_chk@plt>
When -D_FORTIFY_SOURCE equals 1, edx is set to 4, the length of buffer : 4 (5 - 1); It separate the members in struct, so the edx is 4; When -D_FORTIFY_SOURCE equals 2, edx is set to 31, the length of buffer : 31 (32 - 1); It regards struct S as a whole, so the edx is 31.