This is a joint proposal from: @AaronBallman, @shafik, @Endill, and @cor3ntin (with helpful input from others!)
Safety and security of C and C++ programs has been an important issue in the ecosystem for a while. Both WG21 and WG14 are making plans on how to improve these aspects of the language from their end, but the standard is constrained by what it can talk about and the speed at which it can move. Implementations need to be the driving force behind improving this situation; we’re ultimately responsible for the quality of what we provide our users. To that end, we should seriously consider what the Clang approach to hardening will be.
Thankfully, our implementation already provides a ton of mechanisms to help users improve the safety and security of their code. Unfortunately, those mechanisms are scattered throughout the implementation (some are feature flags, some are machine flags, some are macros, some are warnings, etc), poorly documented or not documented at all, and are not always easy to use. There are guides online to help users, but this requires more proactive consideration from users than we think is reasonable.
We’re proposing a path forward to unify a lot of our existing mechanisms in an easy-to-use way for users.
Setting User Expectations
One difficult decision we frequently face is “will this change break existing code?”. To date, user expectations have been set that we will work hard to avoid breaking their existing, working, correct code. For example, when deciding whether to emit a diagnostic or not, if we cannot prove something is wrong, we will frequently suppress the diagnostic (reduce false positives). However, this is in direct conflict with the needs for safety and security. In that scenario, if we cannot prove something is right, we should generally diagnose the construct (increase false positives). So this mode needs to set user expectations appropriately: your code breaking between compiler releases is a feature, not a bug. Obviously, we still want to strive for a low false positive rate in hardened code; it’s just that we should feel comfortable with rejecting correct, but suspicious, code. This includes a need for potentially ABI breaking changes; when the next spectre vulnerability hits, we want users to get the mitigation automatically rather than having to learn that they need the mitigation at all.
Prior Art in GCC
GCC has added a -fhardened mode (Instrumentation Options (Using the GNU Compiler Collection (GCC))). This mode enables several feature flags ( -ftrivial-auto-var-init , -fstack-protector-strong , etc) and predefines some macros ( _FORTIFY_SOURCE=3 , _GLIBCXX_ASSERTIONS ). It does not currently modify any diagnostic behavior or set any machine flags.
We could follow GCC’s lead and do exactly as they do and add -fhardened which aims for compatibility with GCC. However, we do not recommend aiming for an identical implementation between Clang and GCC. Differences in behavior between the two compilers already makes matching features difficult. When it comes to omnibus options, it becomes even harder. So we will likely never behave the same as GCC anyway – better to set user expectations up front that Clang and GCC hardening aim to solve the same problem, but may do so in different ways sometimes.
Goals
We want to enable various -f , -m , -D , and -W flags in this hardened mode, but we do not want users to have to manually figure out the set to enable. So we’re looking to add some one-shot way for the user to enable various flags, for example:
It can enable various -f flags by default: -ftrivial-auto-var-init , -fPIE , -fcf-protection , etc.
flags by default: , , , etc. It can enable various -m flags by default: -mspeculative-load-hardening , -mlvi-hardening , etc.
flags by default: , , etc. It can enable various -W flags by default: -Wall , -Wextra , -Werror=return-type , etc.
flags by default: , , , etc. It can enable any hardening modes available for the standard library
It can predefine various macros: _FORTIFY_SOURCE , _GLIBCXX_ASSERTIONS , etc.
, , etc. It can require the user to specify an explicit language standard mode
It can refuse to compile code against older language standards we feel are inherently unsafe, like C89 or C++98
It can pass along linker flags like enabling ASLR
The exact flags and behaviors can be determined as we go and can be extended as we introduce new functionality or diagnostics into the compiler.
Proposal
There are multiple ways we could surface this functionality. We’re presenting options to see what direction the community thinks will best meet the goals outlined. Other alternatives may also exist which are worth considering.
Config File
A configuration file could be shipped alongside Clang. Users would set the hardened mode by passing --config=hardened (or some other single flag that effectively translates into –config under the hood). A benefit of this approach is that maintenance of hardened mode is very easy and downstreams can modify the behavior easily to meet their own needs.
Driver
By using a new driver mode, we reset user expectations about how compiler upgrades will go. They’re no longer running clang , it’s now a “different” compiler. The significant downside to a new driver is that it can be a challenge to integrate it into existing build systems like CMake. However, clang --driver-mode= can still be used as a stopgap solution until build systems catch up.
Orthogonal Flags
We could have orthogonal -fhardened , -mhardened , and -Whardened options to specify language dialect flags and macros, machine flags, and diagnostic flags respectively. It’s not a single flag for a user to pass to get all the hardening, but it does let the user have an a la carte approach to opting into hardening.
Single Flag
We could have a single flag such as -fhardened which enables all the macros, language dialect, machine, and diagnostic options so that we have a single flag for users to opt into everything. However, it would be a novel direction; we don’t have any other flags with such wide-reaching impacts across -f , -m , -W , etc behaviors. Also, if we name the flag -fhardened , there will be pressure to be compatible across GCC and Clang, which is a burden on both communities, so if we go with a single flag, we should pick a unique name for it.
What Are We Deciding Right Now?
We are looking for high-level direction from the community on how to proceed. Once we know that the community supports the notion of a hardened mode, and we know the general shape of how the community wants that mode surfaced, we intend to come back with a separate proposal for that particular path forward as well as the initial set of functionality enabled by that mode.