Progress on toolchain security features
Over the years, there has been steady progress in adding security features to compilers and other tools to assist with hardening the Linux kernel (and, of course, other programs). In something of a tradition in the toolchains track at the Linux Plumbers Conference, Kees Cook and Qing Zhao have led a session on that progress and further plans; this year, they were joined by Justin Stitt (YouTube video).
Rust
Cook said that he would begin by talking about Rust, rather than sprinkling it throughout the talk, as he has in the past. It seemed easier, he said, to handle all of the Rust information on a single slide (slides). It is important to maintain parity between the security features of the GCC, Clang, and Rust compilers in order to avoid cross-language attacks. To that end, the arm64 software-based shadow call stack for Rust is getting close to being ready to merge into the kernel. Kernel control-flow-integrity (CFI) support for Rust is also in progress; it is forward-edge protection against subverting indirect calls.
There are several things that have not really been looked at yet, including zeroing registers used in calls and making structure layout randomization work with Rust. Cook said that the __counted_by() attribute, which is used to provide bounds information for flexible arrays, needs some investigation to see how it interacts with Rust code. He thinks that the information provided by __counted_by() will already be represented in the Rust bindings, so there will not be a need for any explicit handling on the Rust side, but that should be confirmed.
While Rust has native arithmetic-overflow handling, there is still not
parity with the behavior of the C code. When an overflow occurs, the undefined
behavior sanitizer (UBSAN) gives a different result than Rust does;
"it would be nice to have one result
".
Counted by
Moving on, the "big news from the last year
" was all of the work done for
__counted_by(), which identifies a structure member that is tracking
the size of a flexible array in the structure. Once the support for the
attribute landed in GCC and Clang, annotations needed to be added to the
kernel, which has been done for 391 structures. People are adding more of
these all the time, he said, and he hopes that it is becoming the default
for any new flexible arrays.
Something that was "kind of a footnote
" in last year's update (YouTube video) has
been fixed in both GCC and Clang. Due to some odd differences between the
C language specification and the compilers, unions could
contain flexible arrays, as long as they specified [0] for the
size, because of GCC and Clang extensions to handle that case. "Sort of
accidentally
", flexible
arrays are not supposed to be allowed in unions at all, according to the specification, though. Meanwhile, removing
the zero from the size specification (leaving just []), in
order to modernize the code, has been part of the flexible-array cleanup
work, but doing so in unions would not compile because of the
specification. Now, modern flexible-array declarations will be accepted by
the extensions, which "will simplify some really horrible hacks
" in
the kernel to work around the problem.
Next up was a slide showing the progress on getting flags for setting the
stack-protector-guard location added to GCC and Clang for five different
architectures; this will allow having different stack
canaries for each process. He had planned to talk about the lack of
progress for Clang on the RISC-V and PowerPC architectures over the last
four years, as all of the other architecture-compiler combinations have
been completed, but he had "accidentally nerd-sniped
" Keith Packard
into fixing those holes, so that work is now in progress. "My goal is
to not have to show this slide next year
", he said with a chuckle.
Control-flow integrity
There is work needed on the forward-edge CFI support, since no progress has been made on that over the last year. The support for hardware CFI protection is basically done, and has been for a while, but the more fine-grained, per-function-prototype software-based CFI protection needs to be added to GCC. Zhao said that she had talked with some RISC-V developers at GNU Tools Cauldron who are working on GCC support, starting from the arm64 patch set that Cook had mentioned; some of that work may be applicable beyond RISC-V. Packard suggested that the arm32 pointer authentication code (PAC) extension could be used for CFI protection on that architecture; Cook was not opposed, but arm32 is not a major area of focus for him.
Similarly, backward-edge CFI has stalled since last year. The hardware support exists for both x86 and arm64. Getting x86 hardware support for shadow call stacks to be used internally by the kernel looks difficult, though Peter Zijlstra said that the Intel Flexible Return and Event Delivery (FRED) feature might provide a mechanism for that. So far, Cook said, there has been no work on creating a software-based hash-checking scheme for backward-edge CFI, similar to KCFI that is used for forward-edge protection.
Pointers and bounds
There is work needed in order to add __counted_by() for general pointer values; both GCC and Clang have started working on support. It is somewhat related to the -fbounds-safety proposal from Apple that he would go into more detail about later in the session.
Zhao came forward to talk about the work being done on pointer bounds in
GCC. There are two main cases, pointers inside structures, where the bound
is contained in another structure field, and pointers passed as arguments,
where the bound is also passed as an argument. The second case is already
handled in GCC by the access attribute, so GCC developers will
focus on the first case. She has discussed adding __counted_by()
for pointers in structures with the GCC maintainers, who are amenable to
that approach. Nick Alcock said that access is not currently used
to generate warnings for exceeding the bounds, however; it is, instead,
"a promise to the optimizer
" that the bounds will not be exceeded,
which is somewhat different. Zhao agreed that a different attribute might
be needed for checking the bounds on pointer arguments.
Cook returned to talk about a problem that has been found as the __counted_by() annotations have been added to the kernel. In many cases, the structures holding a flexible array are allocated at run time, but there is a need to ensure that the field being used for bounds checking gets initialized at the same time that the allocation is done. He does not like to have manually repeated information that must be kept in sync in two places in the kernel, so he is working with the GCC and Clang developers to get another to extension to __counted_by() so that the allocators can set the counter without knowing which field, if any, is being used for bounds.
The __builtin_counted_by_ref() intrinsic function will return a
pointer to the field used in __counted_by() or NULL if there is no
bounds-checking annotation. That allows wrappers to be written for
allocators that will initialize the count if there is one at the time the
allocation is done. So the __counted_by() annotations can be made
"without also having to go and check all of the allocation sites to make
sure that the counter has been set before we are accessing the array
".
That means a wrapper can be used for all structures that have flexible
arrays and when __counted_by() gets added to the structure, "it
magically gets the size added as well
"; both GCC and Clang are working
on adding the feature.
Zhao said that the GCC developers have been discussing the return type for
__builtin_counted_by_ref(), in particular for the case where it
returns NULL because there is no __counted_by() information.
Originally the idea was to return a NULL size_t pointer, but it was
decided that a (void *)NULL would ease the use of the feature,
since some counting fields may not be size_t.
That has implications for the example wrapper that he showed, Cook said,
because the void pointer cannot be used to set the count
when there is a __counted_by() attribute. So "a little bit more
trickery
" needs to be added, which has been done, and works; it
"generates better code but it's a little ugly to read
".
The Apple -fbounds-safety proposal that Cook mentioned earlier
has "a huge number of things covering all aspects of gaps in C's bounds
safety
", he said, including annotations for arrays that are counted by elements
or bytes, as well as those terminated by some constant (e.g. NUL-terminated
strings), and
more. The proposal came up
when the request was made to the Clang developers for a way to annotate
flexible arrays. It is much more ambitious than just that; Cook thinks the kernel and
GCC will want to adopt more of those annotations, but he has been focusing on
the low-hanging fruit.
There is also work needed to clarify the
-Warray-bounds warnings issued by GCC, so that the flag can be
used in kernel builds. The kernel "unintentionally constructs code that
the compiler sees as obviously incorrect
", but the warnings do not
really help clarify what the problem is. Zhao said that due to GCC value
tracking (which is done for optimization purposes), the array-bounds
checking can find real problems in the code, but the current diagnostics
make them look like false positives. She has done
some work to make the warnings more understandable for developers.
Arithmetic overflow
There is a question about what to do for unexpected arithmetic wraparound
(or overflow) in the kernel, Stitt said. If you filter CVEs based on overflow
and wraparound problems, there are multiple entries for the kernel; "if
we could turn on the overflow sanitizers, then we're increasing
protection
". But the kernel essentially makes signed integer overflow into
defined behavior with the
-fno-strict-overflow flag, so UBSAN does not flag overflows. In Clang 19, the
signed-integer-overflow sanitizer has
been changed so that it works with -fwrapv (which is
enabled by -fno-strict-overflow), though Zijlstra called that a
bug. Stitt said that one could argue that it is a bug, since overflow is
not an undefined behavior for the kernel, but the change was made with an
eye toward detecting unexpected overflows, which often cause problems.
The kernel has "what I'll call 'overflow-dependent code patterns'
",
such as code that is explicitly checking for overflow (e.g. a + b
< a) in order to handle it in some fashion. The sanitizer will
complain about that, even though it is not a real problem, so there are "idiom exclusions" that have been
added to Clang 19 to ignore certain types of code. For example,
-1UL "will always overflow
", so it would cause a complaint,
making the sanitizer useless for the kernel; the Clang overflow
pattern exclusions will tell the sanitizer to step out of the way for
three specific patterns.
Zijlstra said that he would like to see a qualifier akin to const
or volatile that could be attached to variables that should not wrap.
Stitt said he agreed but that the compiler developers are moving away from
that solution. Cook noted that he and Zijlstra have a fundamental
disagreement on how to get coverage for unexpected overflow; he thinks
"we have to mark the expected places where we're wrapping so that all
the rest will get caught
", though Zijlstra disagrees with that approach.
One way to proceed that works around the (somewhat conflicting) objections from compiler and kernel developers would be to treat certain types, size_t for example, differently in the sanitizer. Zijlstra did not like turning certain types into "magic" types; having a qualifier would allow specifying that behavior more widely. Cook would like to try to make some progress given the current constraints and noted that the type filtering is not mutually exclusive with adding a qualifier later once the usefulness of the feature can be shown. Stitt pointed out that the Clang feature (possibly slated for Clang 20) is not hard-coded in the compiler; it will, instead, be some kind of configuration for the build system. So it will not appear at the source-code level, but will still be controlled by the kernel community.
Track organizer José Marchesi noted that the unexpected overflow items were
listed as "needed" for GCC, so he wondered if they had all been added to
the project's Bugzilla. Cook said that those items had not been added due
to "some existing fundamental disagreements about the word 'undefined',
because 'undefined' has a very well defined meaning
", he said to chuckles. Some of that
needs to be resolved before progress can be made for GCC. He would also
like to get some proof of the
feature's usefulness, which will also help smooth the path.
Stitt said that GCC still needs an unsigned-integer-overflow
sanitizer. Zhao said that she has raised the overflow/sanitizer problems in
the GCC community, so the developers are aware of the issues. The major
problem is with the idiom exclusions; she talked about it at Cauldron and
there was a lot of resistance to the feature. She agrees: "I don't like
it, I think it's
a hack
". She has some ideas for other ways to approach the problem,
but needs to think some more on them. Cook closed the session by noting
that the purpose of these sessions is to get the discussion going; he
does not claim that the right solutions have been found, but hopes to
make some progress on addressing the known problem areas.
[ I would like to thank LWN's travel sponsor, the Linux Foundation, for travel assistance to Vienna for the Linux Plumbers Conference. ]
| Index entries for this article | |
|---|---|
| Kernel | Security/Kernel hardening |
| Security | Linux kernel/Hardening |
| Conference | Linux Plumbers Conference/2024 |