View on GitHub

Getting the maximum of your C compiler, for security

Security-related flags and options for C compilers

GCC

Note: this guide is valid for GCC 11

Understanding GCC flags is a pain. Which ones are enabled by -Wall or -Wextra is not very easy to untangle. The most reliable way is to parse and analyze the commont.opt and c.opt files, which define (partially) the command line options supported by GCC.

The format is described in the GCC internals manual, so I’ve written a partial parser which can help identify what flags are needed. You should also check the compiler-warnings project, which has a real parser for GCC, Clang and XCode.

Warnings

Note that some warnings depend on some optimizations to be enabled, so I recommend to always use -O2.

Generic

Security warnings

Those are not really security options per se, but will catch some logical errors:

Note: You can disable warnings for system includes by using the -isystem option to specify the paths which will be used for “system” includes (#include <file.h>).

Extra flags

Compilation flags

Glibc flags

Linker flags

Runtime sanitizers

GCC supports various runtime sanitizers, which are enabled by the -fsanitize flags, which are often not compatible and thus must be run separately.

kernel-address also exists and enables AddressSanitizer for the Linux kernel.

Code analysis

GCC 10 introduced the -fanalyzer static code analysis tool, which was vastly improved in GCC 11.

It tries to detect memory management issues (double free, use after free, etc.), pointers-related problems, etc.

It is costly and slows down compilation and also exhibits false positives, so its use may not always be practical.

Fuzzing

While fuzzing is out of scope, you should use AFL++ to fuzz your code, with sanitizers enabled.

Test files

Test files are a great way to understand in detail what is and what is not covered by a specific command line flag.

They are located in the gcc/testsuite directory, and in the gcc/testsuite/c-c++-common and gcc/testsuite/gcc.dg subdirectories in particular.

For example, the test suite for the -Walloca-larger-than flag can be found in the following files:

gcc.dg/Walloca-larger-than-2.c
gcc.dg/Walloca-larger-than-3.c
gcc.dg/Walloca-larger-than-3.h
gcc.dg/Walloca-larger-than.c

Walloca-larger-than.c gives some insights on how the option behaves in practice:

/* PR middle-end/82063 - issues with arguments enabled by -Wall
   { dg-do compile }
   { dg-require-effective-target alloca }
   { dg-options "-O2 -Walloca-larger-than=0 -Wvla-larger-than=0 -ftrack-macro-expansion=0" } */

extern void* alloca (__SIZE_TYPE__);

void sink (void*);

#define T(x) sink (x)

void test_alloca (void)
{
  /* Verify that alloca(0) is diagnosed even if the limit is zero.  */
  T (alloca (0));   /* { dg-warning "argument to .alloca. is zero" } */
  T (alloca (1));   /* { dg-warning "argument to .alloca. is too large" } */
}

void test_vla (unsigned n)
{
  /* VLAs smaller than 32 bytes are optimized into ordinary arrays.  */
  if (n < 1 || 99 < n)
    n = 1;

  char a[n];        /* { dg-warning "argument to variable-length array " } */
  T (a);
}

References