Towards Safety & Security in C++26 • Daniela Engert • GOTO 2025
Daniela Engert explores the significant push towards memory safety in C++26, detailing how the language is evolving to address long-standing vulnerabilities. The talk covers the critical distinction between security and functional safety, the nuances of undefined behavior, and introduces powerful new features like Standard Library Hardening and a formal Contracts system to help developers write safer, more reliable code.
The Drive for Safer Code
The software industry, spurred by government and administrative bodies, is increasingly focused on reducing vulnerabilities, a significant portion of which stem from memory safety issues in languages like C and C++. A widely cited statistic suggests that around 70% of vulnerabilities are due to memory unsafety. This has led to a push for plans to mitigate these issues, especially for eligibility in governmental projects.
The discussion distinguishes between two critical system properties:
- Security: Focused on protecting information and systems from exploitation, such as safeguarding data in a server farm against known and unknown vulnerabilities.
- Functional Safety: Aims to protect users from technology and vice-versa. This involves physical and mental protection for operators of machinery, with input from numerous stakeholders like owners, insurers, and regulators.
Understanding and Taming Undefined Behavior (UB)
Undefined Behavior (UB) has been a core concept in C++, allowing compilers to make aggressive optimizations under the “as-if” rule, assuming developers write perfect code. However, this can lead to unpredictable and dangerous outcomes where compilers remove defensive code (like null pointer checks) because they “see” downstream UB and assume that code path is unreachable.
A significant step forward is the reclassification of reading from uninitialized variables. What was previously UB for over 25 years is now termed erroneous behavior.
- Undefined Behavior: The compiler can do anything, leading to unpredictable optimizations.
- Erroneous Behavior: The behavior is defined as being wrong, but it is not undefined. This constrains the optimizer, preventing it from removing code. As a result, modern compilers can provide implicit initialization, making code safer by default.
The best practice remains to always initialize variables, for instance, using value initialization ({}).
Constant Evaluation: The Best Defense
The most powerful tool developers have against UB is constant evaluation. By using constexpr, constinit, or static_assert, developers can force code to be executed at compile-time. During this process, the compiler acts as a C++ interpreter within a virtual machine and is mandated to diagnose every instance of UB.
This compile-time C++ interpreter is more effective than runtime sanitizers (address, memory, UB sanitizers) because it combines all checks and applies them everywhere, all the time, before the program is even run.
While compile-time evaluation has expanded to cover most of the language, some features like compile-time fuzzing and multi-threading are not yet available.
Major Safety Features Coming in C++26
C++26 introduces several features aimed directly at improving code safety and correctness.
1. Standard Library Hardening
A common problem is ensuring bound safety when passing arrays to functions. While modern C++ encourages using std::span (since C++20) to bundle a pointer with its size, this doesn’t inherently check for out-of-bounds access on subscripting.
Standard Library Hardening is an opt-in feature that enforces the preconditions already specified in the standard library. When enabled:
- An invalid access, such as an out-of-bounds subscript on a
span, will cause the program to stop immediately. - This prevents any further user-space code from executing after the violation, significantly enhancing security.
- It can also be configured to allow for violation handling, which is crucial for functional safety.
- Implementations like Microsoft’s go further, offering features to help mitigate use-after-free errors.
2. Contracts (P2900)
After a long history, a formal “Design by Contract” system is coming to C++. This moves beyond simple comments or debug-only asserts to provide a robust framework for specifying and checking program correctness.
- Annotations: Preconditions and postconditions are added directly to function declarations.
void process(int* p, int size) [[pre: p != nullptr]] [[pre: size > 0]]; - Implicit
const: Function parameters within contract expressions are implicitlyconstto prevent side effects during checks. - Runtime Assertions: A new
assertfor contracts works in fully optimized release builds. - Violation Handler: When a contract is violated, execution is detoured to a central, customizable violation handler.
- Enforcement Modes: Developers can choose the behavior upon violation:
ignore: No checking is performed (legacy behavior).observe: The violation is logged, but the program continues (useful for legacy code).enforce: The violation is handled, and then the program terminates.quick_enforce: The program stops immediately with no further user code execution.
The Ultimate Principle
While these new language and library features provide powerful tools for building safer software, the most effective strategy remains unchanged: simplicity. Writing simple, clear, and understandable code is the best way to improve its quality, reliability, and security.