Using compiler and linker defenses

You can use compiler defenses that are available with the QNX compile command (QCC) and the GCC to decrease the ability of an attacker to exploit software vulnerabilities. With the correct configuration, you can remove some vulnerabilities that would allow attackers to take control of a system, and instead only cause an application crash. The QCC compiler uses GCC as its back-end, and many of the defenses that are provided here apply to both compilers.

Stack cookies

You should use stack cookies to harden the compiled code against stack-based buffer overflow vulnerabilities. When you use a makefile project, enable stack cookies using one (and only one) of the following CCFLAGS compiler flags:

  • -fstack-protector-all - All functions use stack cookies.
  • -fstack-protector-strong - Functions use stack cookies if they have arrays on the stack, take memory references to their own stack variables, call alloca(), or use variable-length arrays (VLA).

It is preferred that you use -fstack-protector-all or -fstack-protector-strong. -fstack-protector (which causes functions to use stack cookies if alloca() or VLAs are used, or if there are character-based buffers on the stack) provides some protection, but is not as strong as the -fstack-protector-all and -fstack-protector-strong flags. When using -fstack-protector or -fstack-protector-strong, functions calling alloca() or that use VLAs receive a stack cookie only if you enable optimization.

For example:

CCFLAGS+=-fstack-protector-all

All of these flags provide some protection against stack smashing. Stack smashing is a technique used to exploit stack-based buffer overflows. These compiler flags add a random number, called a stack cookie, below the return address that's stored on the stack during a function call, and reorder local variables so that arrays are directly below the cookie on the stack.

If an attacker attempts to exploit a buffer overflow, the attacker usually has to overwrite the cookie before overwriting the return address. When the function returns, the cookie is verified. If the cookie has been modified, the application will safely terminate instead of jumping into an attacker's payload through the return address that is stored on the stack. You can find the rules for stack cookie generation in the cfgexpand.c file in the GCC source code.

RELRO

Read-only relocations (RELRO) allow sections of an executable that need to be writable only while a program is loading to be marked read-only before the program starts. These sections typically contain data and function pointers that can't be resolved until runtime. The exact sections that are marked read-only will vary based on the architecture, linker, and operating system involved.

You can enable RELRO using the relro parameter in a makefile project:

LDFLAGS+=-Wl,-z,relro

You should always use RELRO so the pointers are stored in a read-only page of memory and to prevent them from being targeted. In this type of attack, an attacker would modify one of the function pointers. After the modified pointer is loaded into the program counter, it would jump to an attacker's payload.

BIND_NOW

When used in combination with RELRO, BIND_NOW prevents the full global offset table (GOT) from being overwritten. The GOT stores the offsets of data and code within each library that will be loaded into an executable's address space. BIND_NOW causes all of the executable's sections to be loaded when the executable is first started. This approach allows the full GOT, including the parts of the GOT referenced by the procedure linkage table (PLT), to be marked read-only before the program starts. Without the BIND_NOW option, the offsets are initialized on demand, thereby moving load time calculations to runtime calculations.

The parts of the GOT that are associated with the PLT are easy targets for an attack, because some vulnerabilities allow trivial modification of PLT-related GOT entries in order to jump into an attacker's payload. You should use BIND_NOW because it's the only way to fully protect the GOT.

You can enable BIND_NOW using the now parameter in a makefile project:

LDFLAGS+=-Wl,-z,now

PIC and PIE

Position-independent code (PIC) and position-independent executable (PIE) refer to code that executes properly regardless of its location in memory. PIC is necessary so that executables can take full advantage of Address Space Layout Randomization (ASLR). ASLR randomizes the location of the executable, shared libraries, stack, and other data in memory, and makes developing reliable exploits significantly more challenging. It is strongly recommend that all built code be compatible with ASLR.

To generate position-independent code or executables, use the following compiler flags:

  • -fpic
  • -fpie
  • -fPIC
  • -fPIE

-fPIC is considered safer than the other flags and should be used when possible. Use only one of the flags for any one object used to build an executable or library. Mixing them can cause an application to crash when executed.

The lower-case flags, -fpic and -fpie, can optimize the usage of the global offset table (GOT) and potentially produce smaller executables for some machine architectures. These flags require all GOT entries to be within a certain range of the GOT base address, limiting its total size.

The upper-case flags, -fPIC and -fPIE, do not have a limitation on the size of the GOT. For BlackBerry 10 Native SDK projects (including Cascades), use -fPIE when building an executable and -fPIC when building a shared object. Any other flags might cause crashes that are extremely difficult to diagnose.

Custom build systems have their own ways of adding these flags, but the following snippet of a makefile is one example of how to add the correct flags:

CCFLAGS+=$(if $(filter g,$(VARIANTS)),,$(if $(filter so shared,$(VARIANTS)),-fPIC,-fPIE))

When building a PIE, you must also use the -pie flag along with one of the four flags above. The -pie flag isn't required (or permitted) when building a shared library, because these are always position-independent. The BlackBerry C/C++ Project wizard that is available in the Momentics IDE for BlackBerry configures projects to generate release builds with PIE binaries for applications and archives. Makefile projects also have PIE enabled by default using the following LDFLAGS:

LDFLAGS+=$(if $(filter g so shared,$(VARIANTS)),,-pie)

_FORTIFY_SOURCE

Functions such as strcpy(), strcat(), and memcpy() are prone to misuse and can be the source of many security vulnerabilities due to buffer overflows. You can use the _FORTIFY_SOURCE compiler flag to instruct the compiler to protect memory and string functions.

To illustrate the vulnerabilities of the functions above, consider the following code sample. If the variable str string is too long, the function lacks any protection against a buffer overflow and an attacker could exploit this vulnerability.

void foo(char* str)
{
     char buffer[10];
     strcpy(buffer,str);
}

To avoid this, you should use _FORTIFY_SOURCE to detect buffer overflows.

-D_FORTIFY_SOURCE=2

In the code sample above, using _FORTIFY_SOURCE instructs the compiler to halt the application if the string being passed in is too large (assuming that the size of the buffer is known at compile time).

To fully use this defense, you must optimize your code with -02 or higher.

Format string warnings as errors

When you compile your app, you should treat format string warnings as errors to avoid introducing format string vulnerabilities. If enabling this option introduces new compile errors, you can usually modify the code to make sure that format strings are handled safely.

To illustrate this vulnerability, consider the following code sample and an attacker that controls str. By carefully constructing the string that's passed in, an attacker could easily cause the program to crash. If an attacker has access to the output of the call with the format string vulnerability, the attacker could also easily bypass defenses such as Address Space Layout Randomization (ASLR), No eXecute (NX), and stack cookies.

void foo(char* str) {
     printf(str);
}

You can resolve format string vulnerabilities as errors by using the following options in the command line compiler:

-Wformat -Wformat-security -Werror=format-security

Using these options can give false warnings when an attacker can't actually control the format string, but treating format string warnings as errors makes it far less likely that your application will have a format string vulnerability.

Last modified: 2013-12-21

comments powered by Disqus