Any sufficiently advanced bug is indistinguishable from a feature
Rich Kulawiec
At the end of 2023, and early 2024, NSA and the White House respectively came out with recommendations to move away from C and C++, in favor of Rust, because of memory safety concerns. You can find links to these recommendations at the end of this article. There are good reasons for these recommendations, but there are also good reasons for staying with C/C++. This will be a three-part series, where I will be focusing on the reasons behind the recommendations from the NSA and the White House in this one. The next one will then focus on what we can do to mitigate and remove some of the, very real, risk when using C/C++. The last article will look at some of the tooling available to us, when programming in C and C++.
This will not be a Rust bashing article, because I do not have enough knowledge of Rust to have an informed opinion on this language. I do however have a fair bit of knowledge on C/C++, since they are some of my favorite languages, hence this article is in defense of them.
Just as a heads up, you will need to know some programming to follow along! Not necessarily C/C++ programming, but some programming knowledge will greatly benefit you.
C and C++ both provide low-level control over memory, which can be a powerful feature but also leads to a range of memory safety concerns. Although C++ offers additional tools (like RAII, references, smart pointers, and containers) to help manage memory more safely, many of the core issues in C are still relevant in C++ if developers use raw pointers or manual memory management. Below are some of the most common concerns:
1. Buffer Overflows
What is it? Writing or reading beyond the allocated bounds of an array or buffer.
Why it matters: This can overwrite adjacent memory, corrupt data, or allow attackers to inject malicious code.
C vs. C++:
o In both languages, array and pointer arithmetic do not include automatic bounds checking.
o Functions like strcpy, sprintf, or memcpy can cause overflows if not used carefully.
Let’s see an example of this in C++ code:
The buffer has space for 10 characters, including the null terminator (‘ ’), but “12345678910” is 11 characters long (plus the null terminator).
This causes a buffer overflow, corrupting adjacent memory, and we have no idea what might be in this adjacent memory, maybe nothing but it might also be memory used by a critical process in the operating system. In a worst-case scenario, it will give an attacker high level access ☹
2. Pointer and Reference Pitfalls
Dangling Pointers / References
o What is it? Occurs when a pointer or reference points to memory that has already been freed or gone out of scope.
o Why it matters: Dereferencing such pointers results in undefined behavior (could crash, could corrupt data).
Use-After-Free
o What is it? A specific type of dangling pointer where the memory is accessed after being freed.
o Why it matters: Can lead to unexpected behavior, crashes, or security exploits.
Null Pointer Dereferencing
o What is it? Accessing memory through a pointer set to NULL (in C) or nullptr (in C++).
o Why it matters: Typically causes a crash (segmentation fault)
Another example using C++code:
After delete ptr, any accessing ptr is undefined behavior.
A common recommendation is to set ptr = nullptr immediately after deleting ptr (though that doesn’t fix the logic error if you still mistakenly access it).
These are just subsets of the many potential memory issues that can arise when using C/C++ improperly.
As mentioned earlier, the memory issues were the reasoning for the recommendations from NSA and the White House. There are other risks inherent in the C/C++ programming languages. Again, the below list is just a subset.
1. Integer Overflows and Underflows
• What is it?
o When arithmetic operations exceed the range of a given integer type, the value may wrap around (overflow) or become unexpectedly large (underflow).
o For signed integers, this leads to undefined behavior; for unsigned integers, it leads to well-defined modulo arithmetic (but still potentially dangerous).
• Why it matters:
o Attackers can exploit integer overflows to bypass checks (e.g., size calculations before memory allocation).
o This can result in incorrectly sized buffers, leading to subsequent buffer overflows or other vulnerabilities.
2. Command Injection and Unsafe System Calls
• What is it?
o Using system(), popen(), or exec*() family calls with untrusted data can open the door to shell command injections.
o Attackers can append additional shell commands or alter paths.
• Why it matters:
o This can grant attackers the ability to run arbitrary code on the system or escalate privileges.
Let’s look at another example:
How an Attacker Could Exploit It
If the user enters myfile.txt; rm -rf /, the actual command passed to the shell becomes:
This will first attempt to display myfile.txt and then run rm -rf /, potentially deleting all files on the system (if run with appropriate privileges, like root on a Linux/Unix system).
With all these issues, why are we using C/C++? Well, for many years, these were the languages we had, so there are billions of lines of code out there in C/C++, with more being added daily. On top of that, these languages are FAST, when executing, contrary to many other languages, because they are compiled into native machine code running natively on the hardware.
Games are typically developed using C++ for this reason, but device drivers for both Linux and Windows are developed using C/C++ as well, because of the speed of execution. Because of the increased focus from the authorities, there is an increased focus from the C/C++ community on implementing features into the languages that will make them harder to use in an insecure manner.
I will share some links with you in the second part of this series, to some community efforts and videos on these efforts.
- NSA Releases Guidance on How to Protect Against Software Memory Safety Issues > National Security Agency/Central Security Service > Article
- White House www.whitehouse.gov