Did you know that when you compile your C or C++ programs, the GCC compiler will not enable all exceptions by default? Do you know which build flags you need to specify in order to obtain the same level of security hardening that GNU/Linux distributions use (such as Red Hat Enterprise Linux and Fedora)? This article walks through a list of recommended build flags.
The GNU-based toolchain in Red Hat Enterprise Linux and Fedora (consisting of GCC programs such as gcc
, g++
, and Binutils programs such as as
and ld
) are very close to upstream defaults in terms of build flags. For historical reasons, the GCC and Binutils upstream projects do not enable optimization or any security hardening by default. While some aspects of the default settings can be changed when building GCC and Binutils from source, the toolchain we supply in our RPM builds does not do this. We only align the architecture selection to the minimum architecture level required by the distribution.
Consequently, developers need to pay attention to build flags, and manage them according to the needs of their project for optimization, level of warning and error detection, and security hardening.
Recommended build flags
During the build process to create distributions such as Fedora and Red Hat Enterprise Linux, compiler and linker flags have to be injected, as discussed below. When you are using one of these distributions with the included compiler, this environment is recreated, requiring an extensive list of flags to be specified. Recommended flags vary between distribution versions because of toolchain and kernel limitations. The following table lists recommended build flags (as seen by the gcc
and g++
compiler drivers), along with a brief description of which version of Red Hat Enterprise Linux and Fedora are applicable.
A note about GCC versions for Red Hat Enterprise Linux
GCC 4.8 was included in Red Hat Enterprise Linux 7. GCC 4.4 was included in Red Hat Enterprise Linux 6. You can easily install GCC 8 or later using Red Hat Developer Toolset (DTS). The November 2018 release of DTS 8 included GCC 8.2. RHEL 8 Beta uses GCC 8 as the default compiler.
Flag | Purpose | Applicable Red Hat Enterprise Linux versions | Applicable Fedora versions |
---|---|---|---|
-D_FORTIFY_SOURCE=2 | Run-time buffer overflow detection | All | All |
-D_GLIBCXX_ASSERTIONS | Run-time bounds checking for C++ strings and containers | All (but ineffective without DTS 6 or later) | All |
-fasynchronous-unwind-tables | Increased reliability of backtraces | All (for aarch64, i386, s390, s390x, x86_64) | All (for aarch64, i386, s390x, x86_64) |
-fexceptions | Enable table-based thread cancellation | All | All |
-fpie -Wl,-pie | Full ASLR for executables | 7 and later (for executables) | All (for executables) |
-fpic -shared | No text relocations for shared libraries | All (for shared libraries) | All (for shared libraries) |
-fplugin=annobin | Generate data for hardening quality control | Future | Fedora 28 and later |
-fstack-clash-protection | Increased reliability of stack overflow detection | Future (after 7.5) | 27 and later (except armhfp) |
-fstack-protector or -fstack-protector-all | Stack smashing protector | 6 only | n/a |
-fstack-protector-strong | Likewise | 7 and later | All |
-g | Generate debugging information | All | All |
-grecord-gcc-switches | Store compiler flags in debugging information | All | All |
-mcet -fcf-protection | Control flow integrity protection | Future | 28 and later (x86 only) |
-O2 | Recommended optimizations | All | All |
-pipe | Avoid temporary files, speeding up builds | All | All |
-Wall | Recommended compiler warnings | All | All |
-Werror=format-security | Reject potentially unsafe format string arguents | All | All |
-Werror=implicit-function-declaration | Reject missing function prototypes | All (C only) | All (C only) |
-Wl,-z,defs | Detect and reject underlinking | All | All |
-Wl,-z,now | Disable lazy binding | 7 and later | All |
-Wl,-z,relro | Read-only segments after relocation | 6 and later | All |
This table does not list flags for managing an executable stack or the .bss
section, under the assumption that these historic features have been phased out by now.
Documentation for compiler flags is available in the GCC manual. Those flags (which start with -Wl
) are passed to the linker and are described in the documentation for ld.
For some flags, additional explanations are in order:
-D_GLIBCXX_ASSERTIONS
enables additional C++ standard library hardening. It is implemented in libstdc++ and described in the libstdc++ documentation. Unlike the C++ containers with full debugging support, its use does not result in ABI changes.-fasynchronous-unwind-tables
is required for many debugging and performance tools to work on most architectures (armhfp, ppc, ppc64, ppc64le do not need these tables due to architectural differences in stack management). Even though it is necessary on aarch64, upstream GCC does not enable it by default. The compilers for Red Hat Enterprise Linux and Fedora carry a patch to enable it by default.-fexceptions
is recommended for hardening of multi-threaded C and C++ code. Without it, the implementation of thread cancellation handlers (introduced bypthread_cleanup_push
) uses a completely unprotected function pointer on the stack. This function pointer can simplify the exploitation of stack-based buffer overflows even if the thread in question is never canceled.-fstack-clash-protection
prevents attacks based on an overlapping heap and stack. This is a new compiler flag in GCC 8, which has been backported to the system compiler in Red Hat Enterprise Linux 7.5 and Fedora 26 (and later versions of both). We expect this compiler feature to reach maturity in Red Hat Enterprise Linux 7.6. The GCC implementation of this flag comes in two flavors: generic and architecture-specific. The generic version shares many of its problems with the older-fstack-check
flag (which is not recommended for use). For the architectures supported by Red Hat Enterprise Linux, improved architecture-specific versions are available. This includes aarch64, for which only problematic generic support is available in upstream GCC (as of mid-February 2018). The Fedora armhfp architecture also lacks upstream and downstream support, so the flag cannot be used there.-fstack-protector-strong
completely supersedes the earlier stack protector options. It only instruments functions that have addressable local variables or usealloca
. Other functions cannot be subject to direct stack buffer overflows and are not instrumented. This greatly reduces the performance and code size impact of the stack protector.- To enable address space layout randomization (ASLR) for the main program (executable),
-fpie -Wl,-pie
has to be used. However, while the code produced this way is position-independent, it uses some relocations which cannot be used in shared libraries (dynamic shared objects). For those, use-fpic
, and link with-shared
(to avoid text relocations on architectures which support position-dependent shared libraries). Dynamic shared objects are always position-independent and therefore support ASLR. Furthermore, the kernel in Red Hat Enterprise Linux 6 uses an unfortunate address space layout for PIE binaries under certain circumstances (bug 1410097) which can severely interfere with debugging (among other things). This is why it is not recommended to build PIE binaries on Red Hat Enterprise Linux 6. -fplugin=annobin
enables the annobin compiler plugin, which captures additional metadata to allow a determination of which compiler flags were used during the build. Annobin is currently available only on Fedora, and it is automatically enabled as part of the Fedora 28 build flags , where it shows up as-specs=/usr/lib/rpm/redhat/redhat-annobin-cc1
.- To generate debugging information we recommend (using
-g
), even for optimized production builds. Having only partly usable debugging information (due to optimization) certainly beats having none at all. With GCC, generating debugging information does not alter code generation. It is possible to use tools such aseu-strip
to separate debugging information before distributing binaries (which automatically happens during RPM builds). -grecord-gcc-switches
captures compiler flags, which can be useful to determine whether the intended compiler flags are used throughout the build.-mcet -fcf-protection
enables support for the Control-Flow Enforcement Technology (CET) feature in future Intel CPUs. This involves the generation of additional NOPs, which are ignored by the current CPUs. It is recommended that you enable this flag now, to detect any issues caused by them (e.g., interactions with dynamic instrumentation frameworks, or performance issues).- For many applications,
-O2
is a good choice because the additional inlining and loop unrolling introduced by-O3
increases the instruction cache footprint, which ends up reducing performance.-O2
or higher is also required by-D_FORTIFY_SOURCE=2
. - By default, GCC allows code to call undeclared functions, treating them as returning
int
.-Werror=implicit-function-declaration
turns such calls into errors. This avoids difficult-to-track-down run-time errors because the defaultint
return type is not compatible withbool
or pointers on many platforns. For C++, this option is not needed because the C++ compiler rejects calls to undeclared functions. -Wl,-z,defs
is required to detect underlinking, which is a phenomenon caused by missing shared library arguments when invoking the linked editor to produce another shared library. This produces a shared library with incomplete ELF dependency information (in the form of missingDT_NEEDED
tags), and the resulting shared object may not be forward compatible with future versions of libraries which use symbol versioning (such as glibc), because symbol versioning information is missing from it.-Wl,-z,now
(also referred to asBIND_NOW
) is not recommended for use on Red Hat Enterprise Linux 6 because the dynamic linker processes non-lazy relocations in the wrong order (bug 1398716), causing IFUNC resolvers to fail. IFUNC resolver interactions remain an open issue even for later versions, but-Wl,-z,defs
will catch the problematic cases involving underlinking.
In RPM builds, some of these flags are injected using -specs=/usr/lib/rpm/redhat/redhat-hardened-cc1
and -specs=/usr/lib/rpm/redhat/redhat-hardened-ld
because the option selection mechanism in GCC specs allows one to automatically drop the PIE-related flags (for static linking) for PIC builds (for dynamic linking). For historic reasons, -Wl,-z,now
is included in -specs=/usr/lib/rpm/redhat/redhat-hardened-ld
, and not on the command line, so it will not show up directly in build logs.
Injecting flags during RPM builds
RPM spec files need to inject build flags in the %build
section, as part of the invocation of the build tools.
The most recent versions of the redhat-rpm-config
package documents how to obtain the distribution compiler and linker flags. Note that the link goes to the most recent version of the Fedora package. For older distributions, only the following methods for obtaining flags are supported:
- The
%{configure}
RPM macro, which runs./configure
, but also sets theCFLAGS
andLDFLAGS
macros. - The
%{optflags}
RPM macro and the$RPM_OPT_FLAGS
environment variable, which provide compiler flags for C and C++ compilers. - The
$RPM_LD_FLAGS
environment variable, which provides linker flags.
Note that Red Hat Enterprise Linux 7 and earlier do not enable fully hardened builds for all packages, and it is necessary to specify:
%global _hardened_build 1
BIND_NOW
-Wl,-z,defs
Other flags to consider
-fwrapv
tells the compiler that the application assumes that signed integer overflow has the usual modulo behavior (like it has in Java, for example). By default, integer overflow is treated as undefined, which helps with certain loop optimizations. This can cause problems with legacy code which assume the Java behavior even for C/C++.-fno-strict-aliasing
instructs the compiler to make fewer assumptions about how pointers are used and which pointers can point to the same data (aliasing). This can be required to compile legacy code.-flto
and various other flags can be used to switch on link-time optimization (LTO). This can result in improved performance and smaller code, but may interfere with debugging. It may also reveal conformance issues in the source code that were previously hidden by separate compilations.- In some cases,
-Os
(optimize for small code) may result in faster code than-O2
due to reduced instruction cache pressure. - For some applications
-O3
or-O2 -free-loop-vectorize
will provide a significant speed boost. By default, GCC does not perform loop vectorization. Be aware that-O3
will change the way code reacts to ELF symbol interposition, so this option is not entirely ABI-compatible. Overall, we still consider-O2
the choice for the default. -mstackrealign
may be needed for compatibility with legacy applications (particularly on i686) which does not preserve stack alignment before calling library functions compiled with recent GCC versions.
Problematic flags
Some flags are used fairly often, but cause problems. Here is a list of a few of those:
-ffast-math
can have very surprising consequences because many identities which usually hold for floating-point arithmetic no longer apply. The effect can extend to code not compiled with that option.-mpreferred-stack-boundary
and-mincoming-stack-boundary
alter ABI and can break interoperability with other code and future library upgrades.-O0
may improve the debugging experience while it disables all optimization, it also eliminates any hardening which depends on optimizations (such as source fortifictation/-D_FORTIFY_SOURCE=2
).- Likewise, the sanitizer options (
-fsanitize=address
and so on) can be great debugging tools, but they can have unforeseen consequences when used in production builds for long-term use across multiple operating system versions. For example, the Address Sanitizer interceptors disable ABI compatibility with future library versions.
Flags for Red Hat Developer Toolset (DTS)
Red Hat Developer Toolset provides the latest stable GNU toolchain for Red Hat Enterprise Linux. As of November 2018, DTS includes GCC 8.2. See this article to install GCC 8 on Red Hat Enterprise Linux via yum
.
The -fstack-protector-strong
flag is available in DTS 2.0 and later. DTS 6 and later versions support -D_GLIBCXX_ASSERTIONS
. DTS 7.1 supports the -fstack-clash-protection
flag. The other version specific limitations are due to system components which are not enhanced by DTS (such as glibc or the kernel), so these restrictions apply to DTS builds as well.
Language standard versions
The flags discussed so far mostly affect code generation and debugging information. An important matter specific to the C and C++ languages in particular is the selection of:
- The system compilers in Red Hat Enterprise Linux 7 and earlier defaults to C90 for C and C++98 for C++, with many GNU extensions, some of which made it into later standards versions.
- The Red Hat Enterprise Linux 7 system compiler is based on GCC 4.8 and supports the
-std=gnu11
option for C and-std=gnu++11
option for C++. However, both C11 and C++11 support are experimental. - Developer Toolset (DTS) provides extensive support for newer versions of the standards, ensuring compatibility with the system libstdc++ library using a hybrid linkage model.
- The Fedora 27 system compiler defaults to C11 and C++14 (which will change again in future Fedora versions).
In general, it is recommended to use the most recent standard version support by the toolchain, which is C99 (-std=gnu99
) and C++98 (enabled by default default) for the Red Hat Enterprise Linux system compilers. For the Developer Toolset, the more recent defaults should be used. Some changes in the standards do not have perfect backwards compatibility. As a result, a porting effort may be required to use the settings for the newer standards.
Note that even the most recent version of the GNU toolchain does not support some optional C features (such as C11 threads or Annex K and its _s
functions), and C++ support is continuously evolving, especially for recent or upcoming versions of the C++ standard.
See also
- How to install GCC 8 on Red Hat Enterprise Linux—Use
yum
to install GCC 8 as well as Clang/LLVM 6. The November 2018 Developer Toolset 8 release included GCC 8.2, GDB 8.2, and updated versions of SystemTap, Valgrind, OProfile, and other tools - Read about improvements to the GNU Compiler Collection since the 4.x version that shipped in Red Hat Enterprise Linux 6 and 7.
Last updated: July 2, 2024