The high incidence of memory corruption vulnerabilities in software written in C (and C++) is a serious problem that has received considerable media attention, especially in the last few years. According to statistics released by Google, 67% of zero-day vulnerabilities in 2021 were related to memory corruption.
While there has been much debate regarding potential remedies for the situation, a lot of the focus has been on rewriting software in safer languages such as Rust, which eliminates unsafe operations at the cost of flexibility. This has consequences for developer ergonomics; if the compiler cannot prove that a certain abstraction is safe, refactoring may be required even if no problem exists.
While this approach may be fine for a large subset of software which would otherwise have been written in C, it is not popular with all developers. Rust represents a significant departure from C in terms of both syntax and semantics, and the industry-wide push for rewriting software in it has led to some ambivalence irrespective of the potential security gains. Despite my own security engineering background, I admit that I fall into this category, so I decided to search for a better solution.
D is an object-oriented systems programming language developed by Walter Bright and first released in 2001. It implements templates, exceptions, garbage collection and Java-style inheritance. Unlike C++, it is not backwards-compatible with C at the syntactic level and there is no preprocessor. D never caught on as a replacement for either C or C++ and adoption flatlined.
However, in 2022, D introduced a new feature called ImportC, which embeds a C11 compiler directly into the D reference implementation. Exploiting the fact that D is able to match the calling conventions, data types and layouts of a target system's C compiler, it is easy to link against a shared system library and call its functions without requiring wrappers or marshalling. For example, if I wanted to use libsodium, I would create a file called sodium.c containing the definitions and declarations I wish to use. I can then import that file in D as if it was a native module, as long as I tell the compiler-driver to link against libsodium. ImportC automatically runs the system preprocessor to handle the definitions, and the built-in compiler handles the rest.
D's native types, syntax and runtime confer numerous safety benefits:
Garbage collection is conservative and does not relocate objects. It can be disabled on a per-method basis for portions of code with strict timing requirements.
Although this approach may help developers to avoid introducing bugs into their programs, it will not protect those programs against flaws in the libraries that they are linking against.
Since 2020, hardware protections have been made available in new x86 chipsets from Intel and AMD in the form of Control-flow Enforcement Technology (CET). This provides backward-edge protection against ROP attacks using a shadow stack, and forward-edge protection against JOP attacks using indirect branch tracking (Intel only).
In order to take advantage of these protections, libraries must be compiled with a special flag. This flag is already part of the default build configuration for many system libraries on Fedora (and other major Linux distributions), so you may not need to do anything. Dynamically linking against system libraries has one big advantage – security updates can distributed by the OS vendor.
Some people have commented that C's lack of memory safety is a design defect or oversight. This is simply untrue. PL/I, the language from IBM which was used to implement Multics, had exceptions and bounds-checked arrays. It was also a disaster, because it was a complex language and thus compilers were hard to implement and slow to execute. The poor GE645 at MIT that it ran on filled a whole room and was equipped with about 4 MB of RAM and a CPU clocked at roughly 2 MHz.
C was a reaction to this situation. There is a reason why arrays decay into pointers, indexes start at 0 and types are weak: it simplifies the compiler. The mapping between C and the underlying hardware is tighter than what almost any other language offers, and that is why when new platforms are developed, the first compiler they get tends to be a C compiler.
Copyright © 2025 Indraj Gandham