parrot/parrot — Issue #711: Look at FORTIFY_SOURCE in GCC
Author: petdance
Created: 2012-01-31 19:49:01+00:00
State: open
Labels: cage, gcc

From http://gcc.gnu.org/ml/gcc-patches/2004-09/msg02055.html

```
The attached patch (which relies on
http://gcc.gnu.org/ml/gcc-patches/2004-09/msg01993.html
) provides a lightweight buffer overflow protection to
some memory and string functions.
I'm well aware of mudflap, but it has too big runtime overhead
to be used by all programs. The intent of this patch
is to add some checks that have no or non-measurable
runtime overhead, so something that can be enabled for
all programs and libraries in an operating system.
The patch certainly doesn't prevent all buffer overflows,
but should prevent many common ones.
It works by computing a constant (conservative) number
of bytes remaining to the end of object(s) each destination
pointer passed to memory and string functions, if possible
checking for overflows at compile time, if not possible
passing that constant size to special checking alternatives
of the memory/string functions.

http://people.redhat.com/jakub/glibc-chk.patch
contains the glibc counterpart to this patch, although
it doesn't have to be glibc/Linux specific at all.
One could easily take bits/string3.h and part of bits/stdio2.h
from the above patch, rename to resp. ,
#include_next / in these headers and
create a library containing the checking functions.

The intended use in glibc is that by default no protection is
done, when the above GCC 4.0+ and -D_FORTIFY_SOURCE=1 is used
at optimization level 1 and above, security measures that
shouldn't change behaviour of conforming programs are taken.
With -D_FORTIFY_SOURCE=2 some more checking is added, but
some conforming programs might fail.

Buffer overflows can be detected at compile time
or at runtime, if the compiler can detect they will not
happen, normal functions as opposed to their checking
alternatives are used.

Below are four different cases that can happen:

char buf[5];
/* 1) Known correct.
No runtime checking is needed, memcpy/strcpy
functions are called (or their equivalents inline). */
memcpy (buf, foo, 5);
strcpy (buf, "abcd");
/* 2) Not known if correct, but checkable at runtime.
The compiler knows the number of bytes remaining in object,
but doesn't know the length of the actual copy that will happen.
Alternative functions __memcpy_chk or __strcpy_chk are used in
this case that check whether buffer overflow happened. If buffer
overflow is detected, __chk_fail () is called (the normal action
is to abort () the application, perhaps by writing some message
to stderr. */
memcpy (buf, foo, n);
strcpy (buf, bar);
/* 3) Known incorrect.
The compiler can detect buffer overflows at compile
time. It issues warnings and calls the checking alternatives
at runtime. */
memcpy (buf, foo, 6);
strcpy (buf, "abcde");
/* 4) Not known if correct, not checkable at runtime.
The compiler doesn't know the buffer size, no checking
is done. Overflows will go undetected in these cases. */
memcpy (p, q, n);
strcpy (p, q);

In the current implementation mem{cpy,pcpy,move,set},
st{r,p,nc}py, str{,n}cat, {,v}s{,n}printf and gets functions
are checked this way.

The diffence between -D_FORTIFY_SOURCE=1 and -D_FORTIFY_SOURCE=2
is e.g. for
struct S { struct T { char buf[5]; int x; } t; char buf[20]; } var;
With -D_FORTIFY_SOURCE=1,
strcpy (&var.t.buf[1], "abcdefg");
is not considered an overflow (object is whole VAR), while
with -D_FORTIFY_SOURCE=2
strcpy (&var.t.buf[1], "abcdefg");
will be considered a buffer overflow.

Another difference is that with -D_FORTIFY_SOURCE=2, %n
in format strings of the most common *printf family functions
is allowed only if it is stored in read-only memory (usually
string literals, gettext's _("%s string %n") is fine too), but
usually when an attacker attempts to exploit a format string
vulnerability, %n will be somewhere where the attacker could
write it into.

Now about how is this implemented in GCC.
The patch introduces a
size_t __builtin_object_size (ptr, subobject_p)
builtin which computes the number of remaining bytes from PTR
till the end of the object PTR is pointing to.
If there are multiple objects PTR can point to, this builtin
returns maximum number of remaining bytes for them if all
the objects are known at compile time. If it is unknown
what PTR points to, this builtin returns (size_t) -1.
SUBOBJECT_P is a boolean, 0 if whole objects are considered
to be the objects for the remaining bytes computations, if
it is 1, it means the innermost structure field or array
is considered to be the object.

The largest part of the patch is addition of many __*_chk builtins,
checking alternatives to normal string and memory operations.
These builtins are optimized into normal string/memory operations
if compiler can prove there will be no buffer overflow or if
the number of remaining bytes destination pointer points to is
unknown. The builtins issue a warning (some people say these
should be errors) if at compile time the compiler can prove
there will be buffer overflow if the function is called.
Otherwise the builtins expand to calls to the __*_chk functions,
with a constant object size argument.

For routines like mem{cpy,pcpy,move,set},strncpy etc. the runtime
overhead of the checking functions is just a few ticks (comparison
of two arguments, non-predicted branch to __chk_fail function),
for strcpy/stpcpy/strcat/strncat the runtime overhead is slightly
bigger, but as long as they are coded in assembly like the normal
routines, it shouldn't be that big. All functions in the glibc
implementation check for overflows before they happen, but it would
be possible to only detect they happened and make e.g. __strcpy_chk
faster.

Ok, actually the largest part of the patch is testsuite, there is
160k of tests for the added builtins.

Arjan van de Ven built some packages from Fedora Core 3 test 2
distribution with GCC that included this patch, glibc with the patch
mentioned above and -D_FORTIFY_SOURCE=1 and it detected several
buffer overflows at compile time, including e.g.

char *devstr, dest;
strncpy(&dest, (optarg + 1), 3);

struct XXXX *rule;
if (!(rule = malloc(sizeof(rule)))) return NULL;
memset(rule, 0, sizeof(*rule));

typedef struct {
char hash[16];
HASH;
const char *XXXX(const HASH hash) {
unsigned int i;
memcpy(&i, &hash, sizeof(hash));

XXX *sheets[3];
memset (sheets, 0, sizeof (XXX) * 3);

#define strmov(A,B) stpcpy((A),(B))
struct XXXX {
char id[8];
char nr[4];
char text[10];
} YYYY;
strmov(YYYY.text,"Testing...");
```