Using compiler and linker defenses
You can use compiler defenses that are available with the QNX compile command (QCC) and 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 app crash. The QCC compiler uses GCC as its back-end, and many of the defenses that are provided here apply to both compilers.
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 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.
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 tries 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 app terminates safely 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 of the GCC source code.
Read-only relocations (RELRO) allow sections of an executable that need to be writable only while an app is loading to be marked read-only before the app starts. These sections typically contain data and function pointers that can't be resolved until runtime. The exact sections that are marked read-only vary based on the architecture, linker, and operating system involved.
You can enable RELRO using the relro parameter in a makefile project:
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 app counter, it would jump to an attacker's payload.
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 is 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 app 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:
PIC and PIE
Position-independent code (PIC) and position-independent executable (PIE) refer to code that executes correctly 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 recommended that all built code be compatible with ASLR.
To generate position-independent code or executables, use the following compiler flags:
- -fPIC or -fpic
- -fPIE or -fpie
-fPIC is considered safer than the other flags. 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.
As of BlackBerry 10 OS version 10.3.0, the -fPIC option is implicitly set. Avoid linking non-PIC code created using a previous release by rebuilding your pre-10.3.0 code using the -fPIC option.
On ARM-based systems, such as BlackBerry 10 devices, there is no difference between using -fPIC or -fpic.
Custom build systems have their own ways of adding these flags, but the following snippet of a makefile shows 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 libraries 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)
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 number of characters in str exceeds the length of the buffer, the function lacks any protection against a buffer overflow and an attacker could exploit this vulnerability.
To avoid this situation, you should use _FORTIFY_SOURCE to detect buffer overflows.
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 by using the -0 option with level 2 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 compiler 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 app 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.
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 app will have a format string vulnerability.
Last modified: 2015-05-07