Decreasing undefined conduct within the C language [LWN.net]

As a professor of biomedical engineering, Martin Uecker maybe doesn’t
match the profile of a typical presenter at Kernel Recipes. He is,
nevertheless, a longtime Linux consumer, and works on free software program for controlling
magnetic resonance imaging (MRI) scanners. He was on the convention to
speak concerning the C programming language, the precise downside of undefined
conduct in C, and whether or not it may well finally be made right into a memory-safe
language.
Why trouble with C in 2026? It is, he stated, nonetheless an important language. C is
transportable, secure over the long run, gives quick compilation, and the
ensuing binary code is quick. “What you see is what you get
“; it
is straightforward to have a look at C code and have some thought of what the pc will
really do. There are quite a lot of instruments for working with the language, and C
will get out of the way in which when obligatory.
$ sudo subscribe at present
Subscribe at present and elevate your LWN privileges. You’ll have
entry to all of LWN’s high-quality articles as quickly as they’re
printed, and assist help LWN within the course of. Act now and you can begin with a free trial subscription.
C does have a protracted historical past, and that impacts the language as we see it
at present, he stated. The C89 commonplace had to deal with all kinds of
{hardware}, together with machines with signed-magnitude or one’s-complement
integer representations, segmented reminiscence, unique pointer representations,
and shocking sizes for varieties. Some Honeywell machines, for instance, had
nine-bit bytes. That tremendously difficult the duty of writing an ordinary
that may allow the writing of transportable code.
The method that was taken was to outline the semantics of the language in
phrases of an summary machine. All operations are to be executed as in the event that they
had run on that summary machine, which can not precisely match the precise
{hardware}. The observable conduct of this system should be what the summary
machine would have accomplished. The “observable” half issues: entry to
risky variables, being outlined as observable, should occur
precisely based on the summary machine; every thing else simply has to
produce the identical eventual end result.
The commonplace provides quite a lot of freedom to compiler implementers; solely the
observable conduct must be preserved. There are many elements of that
conduct which are both undefined or implementation-defined. These are
not observable conduct, and thus don’t constrain what compiler
implementers can do. There are, after all, different specs that
can constrain compiler builders the place the C commonplace doesn’t;
these embrace ABI necessities, requirements like POSIX, or the necessity for
backward compatibility.
Undefined conduct comes about when a program does one thing that’s both
not transportable or not outlined by the usual in any respect. In such instances, the C89
commonplace states that it “imposes no necessities
” on the
implementation. Undefined conduct exists for quite a lot of causes.
It permits implementations to help extensions, handle
interactions with hardware-based security mechanisms, and carry out aggressive
optimization, all whereas permitting difficult-to-detect errors to be ignored.
It explicitly provides the compiler the proper to disregard entire lessons of
hard-to-detect errors.
Nasal demons
The downside, Uecker stated, is that the usual permits a compiler to do
something in response to undefined conduct, as much as the purpose of
invoking nasal
demons. If a program accommodates any undefined conduct in any respect, in accordance
to compiler writers, then it has no anticipated semantics. The C++23 commonplace
goes additional to explicitly state that the usual imposes no necessities
for these packages. That has led to widespread disagreements between
builders about what may be anticipated from the language.
For instance, for those who zero a complete construction (maybe with a name to memset()),
then write to particular fields, what is going to occur for those who learn from any
padding bytes in that construction? Might they comprise security-relevant
information? A
2015 survey confirmed that there was no consensus on what ought to occur in
that case. Or take into account this easy code:
extern int x;
int f(int a, int b)
{
x = b ? 42 : 43;
return a/b;
}
If b is zero, then the return assertion is a division by
zero, which is undefined conduct. In this case, is the compiler entitled
to omit the take a look at completely and simply execute x = 42? After all, the
b = 0 case has no anticipated semantics, and may thus be ignored.
There are compilers that can do precisely that. In the undefined-behavior
case, the shop to x shouldn’t be observable conduct. But now take into account
this case:
extern void g(int x);
int f(int a, int b)
{
g(b ? 42 : 43);
return a/b;
}
This may appear to be the identical scenario, with the compiler being entitled
to take away the take a look at and simply cross 42 to g(), and a few compilers
have handled that manner — however that compiler conduct was a bug. Imagine a
definition of g() that calls exit() if b is
zero. In that case, the division won’t ever occur and this system’s
conduct shouldn’t be undefined. So eliding the take a look at and easily passing 42 to
g() is wrong.
One extra fascinating case:
risky int x;
int foo(int a, int b, bool store_to_x)
{
if (! store_to_x)
return a/b;
x = b;
return a/b;
}
The query right here is: can the compiler hoist the ultimate division operation
above task to x? If there aren’t any semantics related to
the b = 0 case, then there isn’t any change in observable conduct.
This, too, is one thing compilers have accomplished, however the C23 commonplace added a
“no time journey” stipulation to disallow it. In C++, as an alternative, hoisting
should be explicitly prevented by inserting a name to
std::observable_checkpoint().
Time-travel bugs ought to finally go away, however there are quite a lot of different
conditions the place, even when the usual is obvious, compiler writers usually
disagree. These embrace studying of uninitialized variables (which is
nearly all the time outlined), and equality comparisons of pointers, which
is all the time outlined, however can also be miscompiled by each Clang and GCC.
Fighting undefined conduct
To attempt to tackle all of those issues and extra, the C committee runs
three examine teams centered particularly on the reminiscence object mannequin, reminiscence
security, and undefined conduct. There are presently about 100 cases of
undefined conduct within the C commonplace, however the in-progress C2y draft has
eliminated 45 of them. The scenario is certainly getting higher.
There is an more and more wealthy set of instruments geared toward discovering points:
compiler warnings, static analyzers, sanitizers, LLM-based instruments, formal
verification, and extra. The variety of conditions the place a compiler will
emit a warning the place doable undefined conduct is detected is rising;
current examples embrace higher warnings for integer overflows and potential
use-after-free conditions. Static analyzers can be found as standalone
instruments, however are additionally more and more being constructed into the compilers themselves;
GCC can now warn about quite a lot of potential buffer-overflow conditions,
for instance. Sanitizers work by inserting run-time checks; they’ll catch
quite a lot of undefined conduct and, in trapping mode, be used for hardening as
effectively.
Memory security has by no means been certainly one of C’s robust factors, however Uecker needed to
make the purpose that it may be improved. That downside breaks down into
three sub-problems: kind security, spatial reminiscence security, and temporal reminiscence
security.
C, he stated, has a powerful kind system, and the remaining issues are
fixable. Tagless unions, for instance, can create kind confusion, however the
compiler can implement varieties with some further annotations. New
diagnostics can catch unsafe casts from void. Type checking
throughout translation models is historically not an enormous downside in C, since
header recordsdata are used to make sure constant varieties, however the scenario might
be improved with a link-time checker.
Spatial reminiscence security — bounds checking — is {a partially} solved downside;
the compilers can carry out array-bounds checking in lots of conditions now. In
some instances, some code modifications are wanted to completely profit from this
checking. Use of the counted_by attribute
can allow checking for versatile array members, for instance.
Temporal reminiscence security — avoiding use-after-free bugs and the like — is
more durable, Uecker stated, and Rust undoubtedly has a bonus there. Still,
higher temporal memory-safety enforcement is feasible. Architectures like
CHERI
can assist right here is effectively. Fil-C can discover a
lot of temporal-safety bugs.
Can all of those instruments and language modifications get us to full reminiscence security?
Completely fixing the issue would require both costly run-time
checking or formal verification, he stated. In the close to future, probably the most
full outcomes will likely be had with the mixture of a restricted language
and formal verification instruments.
Overall, he concluded, C continues to be a residing language and continues to be enhancing.
The C23 commonplace eliminated quite a lot of problematic options, together with
old-style (Ok&R) perform definitions, help for sign-magnitude and
one’s-complement machines, and trigraphs. It added bit-precise integer
varieties, checked integer operations, and extra. C2y will go additional, including
case ranges, named for loops, the _Countof() macro to
decide array lengths, and quite a lot of “demon removing
“. It won’t
obtain full reminiscence security for C, however that’s an eventual risk, and
will turn into extra sensible over time. He ended by encouraging
individuals to take part within the working teams.
The video and slides from this
speak can be found.
[Thanks to the Linux Foundation, LWN’s travel sponsor, for supporting my
travel for this event.]
