Comparing GCC and Clang security features
Cook started by noting that most of the "old-school" security features have long since been supported by both compilers. These include stack canaries, warnings on unsafe format-string use, and more. Rather than look at those, he chose to focus on relatively new security-oriented features.
The first of these is per-function sections — putting each function into
its own ELF section. This behavior is requested with the
-ffunction-sections switch and is well supported by both
compilers. The value of per-function sections is that they enable
fine-grained address-space layout randomization, where the location of each
function can be randomized independently of the others. It is a "bizarre
and wonderful" feature, he said.
Implicit fall-through behavior in switch statements is a common source of bugs, so many projects are trying to eliminate it. To that end, both compilers support the -Wimplicit-fallthrough option. GCC has supported a special attribute making fall-through behavior explicit for some time; Clang has just gained that support as well. There are evidently no plans in the Clang community to support fall-through markers in comments, though, as GCC does. The kernel is now free of implicit fall-throughs; of the roughly 500 patches fixing fall-through warnings in the last year, Cook said, about 10% turned out to be addressing real bugs in the code.
Link-time optimization (LTO) works with both compilers now. It's not primarily a security feature, but it turns out to be necessary to implement control-flow integrity, which requires a view of all of the functions in a program. Both compilers support LTO, but updating the build tooling to make use of it is still painful. There are also, he said, concerns that LTO can expose differences between the C memory model and the model that the kernel uses, but nobody has provided any specifics about where things could go wrong. It is theoretically a problem, but "practicality matters" and these concerns shouldn't hold up adoption of LTO unless somebody can demonstrate a real-world problem.
Stack probing is the practice of reading a newly expanded stack in relatively small increments to defeat any attempt to jump over guard pages. GCC can build in this behavior now, controlled by the -fstack-clash-protection flag; Clang still lacks this capability. This feature is more useful in user space than in the kernel, Cook said, since the kernel has fully eliminated the use of variable-length arrays.
Clang provides a -mspeculative-load-hardening flag to turn on mitigations for Spectre v1; GCC does not have this support. Details about this feature can be found in this LLVM documentation. Enabling this feature has a notable performance impact, but it is still less costly than inserting lfence barriers everywhere. An attribute can be used to restrict hardening to specific functions, avoiding the need to slow down the entire program.
Functions do not need to preserve the contents of caller-saved registers, so they normally return with random data in those registers. Clearing those registers at return time, instead, may be useful to block any number of speculative attacks or side channels. The performance impact, Cook said, is tiny. Peter Zijlstra objected, saying that he would like to see a proper description of just what is being mitigated by this technique; the impact may be small, but the accumulation of such measures adds up to "death by a thousand cuts". Cook responded that there is value in bringing the architecture to a known state at function return; it may not block a specific attack right now, but "we don't know what is coming next". There is a patch for GCC implementing register clearing, but not for Clang.
Another relatively controversial measure is automatically initializing stack variables on function entry. GCC can do that now via a plugin; work is being done to add it to Clang, though the specific behavior is not what the kernel community would like. Clang will initialize variables to a poison pattern, but Linus Torvalds would rather be able to count on them being initialized to zero.
There are a couple of concerns about automatic initialization of stack variables, though. One is that it might mask warnings about the use of uninitialized variables; those warnings are still wanted. The tricks used by the GCC plugin can evidently confuse tools like KASAN. And, more importantly, this behavior is seen as a fundamental change to the semantics of C code, essentially creating a fork of the language. That is a big step that not everybody wants to take.
The next technique is structure layout randomization; GCC has been able to do this for the kernel via a plugin for a couple of years. There is a port of this support for Clang, but it seems to be stalled at the moment. Cook said that this feature is for "really paranoid builds" but is not really needed for most.
Signed integer overflow is technically undefined behavior in C — though Zijlstra quickly interjected that, in the kernel, it is well defined as twos-complement wrapping. Most of the time, the overflow of a signed int is unexpected, Cook said. Both compilers support the -fsanitize=signed-overflow flag, but its behavior is not ideal. If warnings are enabled, the build size grows by about 6%; if they are not enabled, the program just dies instead — not desirable behavior for the kernel. The warning also allows the overflow to happen; Cook would rather see the value saturate and stay there. Best, he said, would be to support a user-defined handler that can decide what to do about signed overflows.
Unsigned integer overflow, instead, is often done intentionally in the kernel. That behavior is well defined in C, but overflows can still lead to exploits. Clang can trap unsigned overflows now, while GCC cannot. Once again, though, he would rather see a mode where the value saturates rather than being allowed to wrap.
Control-flow integrity (CFI) is, to put it briefly, ensuring that code always jumps to a location that was intended to be jumped to. One aspect of that problem is returns from functions, which should go only to the place the function was called from. X86 processors can support this "backward-edge" checking in hardware, so no compiler support is needed. Arm64 processors have the PAC instruction, but those must be inserted by the compiler. Both compilers have support for these instructions. For processors without backward-edge CFI support, software needs to implement a shadow stack to preserve the integrity of function returns. Clang had support for shadow stacks, but problems resulted and the support has been removed; GCC has never had this support.
"Forward-edge" CFI, instead, ensures that indirect jumps go to the intended location; it's a matter of validating the destination as an appropriate target for the jump. Hardware support is limited to verifying that a given location is, indeed, the entry point of a function; that gives a big reduction in the attack surface, Cook said, but still does not provide a lot of real-world protection since attackers can just chain function calls together. X86 implements this feature with the ENDBR instruction, while Arm has BTI; both compilers support this feature. In software, Clang can make things tighter by checking in software that the called function has the correct prototype as well. But what we really need, Cook said, is truly fine-grained forward-edge CFI.
With that last item, Cook's talk concluded. The conversation returned briefly to integer overflow before things wound down; H. Peter Anvin suggested that, if the desire was to change the semantics of the integer type, a better approach might be to switch to a language like C++ where such changes are more readily supported. It is fair to say, though, that this suggestion was not widely accepted by the audience.
[Your editor thanks the Linux Foundation, LWN's travel sponsor, for
supporting his travel to this event.]
Index entries for this article | |
---|---|
Security | Clang |
Security | GCC |
Security | Tools/Compilers |
Conference | Linux Plumbers Conference/2019 |
Posted Sep 13, 2019 0:38 UTC (Fri)
by sami (subscriber, #4163)
[Link]
The problems were specific to x86. Clang supports shadow call stacks on arm64: https://clang.llvm.org/docs/ShadowCallStack.html
Posted Sep 13, 2019 11:52 UTC (Fri)
by nix (subscriber, #2304)
[Link] (4 responses)
Just a thought, and barely even a woolly thought, certainly not anything like a *design* yet (I mean, I'm not even sure if it makes sense to encode even a vague guess at linenos in a backtrace section: it's not worth introducing them just for this, but if we might need them *anyway*, this could serve as another user: it seems unlikely but it might be true, so I'll keep this potential usecase in mind when I get back to the backtrace stuff).
Actually printing the warnings would still add repeated code at every locus, but at least not repeated big strings. Maybe, possibly, an improvement...
Posted Sep 13, 2019 12:49 UTC (Fri)
by eru (subscriber, #2753)
[Link] (3 responses)
Posted Sep 13, 2019 13:01 UTC (Fri)
by nix (subscriber, #2304)
[Link] (1 responses)
Posted Sep 13, 2019 13:31 UTC (Fri)
by eru (subscriber, #2753)
[Link]
Posted Sep 25, 2019 20:38 UTC (Wed)
by anton (subscriber, #25547)
[Link]
Posted Sep 13, 2019 11:58 UTC (Fri)
by nix (subscriber, #2304)
[Link] (1 responses)
Posted Sep 13, 2019 15:40 UTC (Fri)
by sami (subscriber, #4163)
[Link]
Clang's CFI implementation does exactly this.
Posted Sep 13, 2019 13:22 UTC (Fri)
by ballombe (subscriber, #9523)
[Link] (9 responses)
Posted Sep 13, 2019 17:20 UTC (Fri)
by mathstuf (subscriber, #69389)
[Link] (2 responses)
Oh, you also might want __extend where operations return the next larger integer size to guarantee that the result fits.
Posted Sep 14, 2019 16:53 UTC (Sat)
by ballombe (subscriber, #9523)
[Link]
compilation error.
Posted Sep 14, 2019 17:34 UTC (Sat)
by mpr22 (subscriber, #60784)
[Link]
There are two outcomes that for me would honour the principle of least astonishment:
Defined behaviour "compile-time error" as ballombe suggests, or in the alternative, defined behaviour "always follow the overflow semantics of the left-hand operand and ignore the overflow semantics of the right-hand operand".
Posted Sep 16, 2019 5:57 UTC (Mon)
by dvdeug (guest, #10998)
[Link]
I'm not even sure you can have these types for short int, given the type extension rules in C. This type of thing is easy to say, but hard to clearly define, and even if defined, feels like would have been an overcomplex mess.
Posted Sep 16, 2019 7:10 UTC (Mon)
by neilbrown (subscriber, #359)
[Link] (4 responses)
These attributes are attributes of the integer operation, not of the integer itself - a bit like 'volatile' which Linus has ranted about.
Maybe it would be even better to think of them as attributes of the assignment.
a = b + c
traps on overflow.
a @= b+c
wraps if b+c overflowed (the symbol has a wrapping around it)
a #= b + c
saturates on overflow (the remnant of the real value gets hashed out)
if you try (b + c) * x you always get a trap, because overflow is mostly bad.
Posted Sep 16, 2019 14:56 UTC (Mon)
by nybble41 (subscriber, #55106)
[Link] (2 responses)
Posted Sep 16, 2019 17:10 UTC (Mon)
by excors (subscriber, #95769)
[Link] (1 responses)
The rules for promotion and conversion mean that arithmetic in C is already pretty confusing, so I'm not sure it's a good idea to make it many times more complex with a whole new set of types or operators or magic evaluation contexts.
(Incidentally it looks like you can change the overflow behaviour at function scope with "#pragma GCC optimize("-ftrapv")" etc, which seems slightly less confusing than trying to change it at expression scope. Except it doesn't detect the overflow if e.g. the function gets inlined and evaluated at compile time, so -ftrapv is not actually useful for security.)
Posted Sep 16, 2019 22:04 UTC (Mon)
by nybble41 (subscriber, #55106)
[Link]
signed char x, y, z;
Posted Sep 16, 2019 17:54 UTC (Mon)
by rgmoore (✭ supporter ✭, #75)
[Link]
This sounds like a million bugs waiting to happen. When I use an integer in a context where an overflow might result in a bug, I want a guarantee that the integer hasn't overflowed. Assigning an overflow type to the integer when you declare it provides that guarantee. Requiring the overflow type to the assignment means you can only guarantee the integer hasn't overflowed by going back and checking every assignment to make sure they're of the correct type.
More generally, when would you ever want the same variable to have different overflow behavior in different assignments? Why, for example, would you ever want to have one assignment saturate and another one wrap? I can't think of an example for when that would be sensible, so giving you the power to do it at the expense of removing the utility of the overflow types in guaranteeing that a given value will behave predictably seems like a terrible mistake.
Posted Sep 13, 2019 17:38 UTC (Fri)
by rweikusat2 (subscriber, #117920)
[Link] (18 responses)
This is an appeal to ignorance (logical fallacy).
Posted Sep 13, 2019 18:41 UTC (Fri)
by rahulsundaram (subscriber, #21946)
[Link] (15 responses)
Aka proactive security? What about this one?
"The kernel is now free of implicit fall-throughs; of the roughly 500 patches fixing fall-through warnings in the last year, Cook said, about 10% turned out to be addressing real bugs in the code."
Posted Sep 13, 2019 20:11 UTC (Fri)
by rweikusat2 (subscriber, #117920)
[Link] (14 responses)
Posted Sep 13, 2019 20:42 UTC (Fri)
by Cyberax (✭ supporter ✭, #52523)
[Link] (13 responses)
Posted Sep 14, 2019 0:49 UTC (Sat)
by rweikusat2 (subscriber, #117920)
[Link] (12 responses)
Posted Sep 14, 2019 7:06 UTC (Sat)
by pbonzini (subscriber, #60935)
[Link] (10 responses)
Posted Sep 15, 2019 19:37 UTC (Sun)
by rweikusat2 (subscriber, #117920)
[Link]
Posted Sep 15, 2019 19:53 UTC (Sun)
by rweikusat2 (subscriber, #117920)
[Link] (5 responses)
Posted Sep 15, 2019 20:14 UTC (Sun)
by Cyberax (✭ supporter ✭, #52523)
[Link] (2 responses)
That... explains things.
Posted Sep 15, 2019 21:00 UTC (Sun)
by rweikusat2 (subscriber, #117920)
[Link] (1 responses)
Posted Sep 15, 2019 21:06 UTC (Sun)
by Cyberax (✭ supporter ✭, #52523)
[Link]
It doesn't seem that you understand the issue. It's possible to guarantee that ONE particular and easy function has no adverse effects from clobbered registers, it's not possible to prove that ALL functions are equally safe.
Posted Sep 15, 2019 21:11 UTC (Sun)
by mjg59 (subscriber, #23239)
[Link]
Posted Sep 19, 2019 11:28 UTC (Thu)
by jschrod (subscriber, #1646)
[Link]
How valuable.
Posted Sep 15, 2019 20:09 UTC (Sun)
by ken (subscriber, #625)
[Link]
And without even a single example of when that would have help it does sound a bit excessive.
Posted Sep 15, 2019 20:34 UTC (Sun)
by ballombe (subscriber, #9523)
[Link] (1 responses)
Posted Sep 19, 2019 10:39 UTC (Thu)
by dvdeug (guest, #10998)
[Link]
Posted Sep 20, 2019 1:46 UTC (Fri)
by kmweber (guest, #114635)
[Link]
There's a difference between "we don't know with certainty that this is a safe state, so there is a chance that this is unsafe so let's not take any chances" (which is the argument actually being made) and "we dony know it's safe; therefore it is definitely unsafe" (which would indeed be an argument to ignorance).
Posted Sep 13, 2019 22:14 UTC (Fri)
by Paf (subscriber, #91811)
[Link] (1 responses)
I’ll expand the sentence in to the context you know exists and you can tell me if it’s still “arguing from ignorance”.
“Many récent security vulnerabilities have come from leaking internal state that was not previously recognized as important. Therefore, it is worth scrubbing potential sources of this internal state information, even without specific attacks identified for that particular vector.”
Posted Sep 15, 2019 20:47 UTC (Sun)
by flussence (guest, #85566)
[Link]
And IIRC, doing so is such a tired logical fallacy that it even has its own pretentious latin label.
Posted Sep 14, 2019 2:23 UTC (Sat)
by willy (subscriber, #9762)
[Link] (3 responses)
https://github.com/fishinabarrel/linux-kernel-module-rust
(click the link to the LSSNA presentation for some of their reasoning)
Posted Sep 14, 2019 8:49 UTC (Sat)
by alonz (subscriber, #815)
[Link] (2 responses)
(I agree that Rust would provide more value—but at a hugely higher cost.)
Posted Sep 17, 2019 2:48 UTC (Tue)
by roc (subscriber, #30627)
[Link] (1 responses)
Posted Sep 17, 2019 13:10 UTC (Tue)
by foom (subscriber, #14868)
[Link]
Additionally, if you're ok using a compiler extension, Clang has an attribute that can be used to force trivial abi, if you do have a type which can be passed in registers despite having non-trivial constructors/destructors: https://clang.llvm.org/docs/AttributeReference.html#trivi...
Posted Sep 16, 2019 15:04 UTC (Mon)
by jgh (subscriber, #92451)
[Link]
Registers and stack locs would be written with a poisoned value at function head, and similar to an FP NaN, this should propagate on copies; it should trap on arithmetic or indirection use. It would be silently overwritten by a valid value.
For best protection the full memory hierachy would support it (the DRAM layer, eg, could use a know-bad ECC). A cpu-chip-only implementation could get partial protection by not writing farther out than the last-level cache.
Posted Sep 21, 2019 12:52 UTC (Sat)
by robert_s (subscriber, #42402)
[Link]
Comparing GCC and Clang security features
Comparing GCC and Clang security features
Comparing GCC and Clang security features
Comparing GCC and Clang security features
Comparing GCC and Clang security features
How about something like what coverage instrumentation does? (Which includes adding some initialized constant data structures for each function).
For every function you would store the name, and an array mapping from the offsets of the overflow checks to line numbers. Not very much data there, since not every line needs to be mapped. I assume that after the trap there is some way to figure out the function where it happened and then find the map.
INTO is not in AMD64. It has been replaced with one of the REX prefixes. However, a JO with a single-byte offset is also pretty small.
Comparing GCC and Clang security features
Comparing GCC and Clang security features
Comparing GCC and Clang security features
Comparing GCC and Clang security features
declare each integer type separately
__wrap long int i;
__saturate unsigned long int j;
__overflow short int k;
etc.
instead of having a global setting.
Comparing GCC and Clang security features
Comparing GCC and Clang security features
Comparing GCC and Clang security features
Comparing GCC and Clang security features
Comparing GCC and Clang security features
> declare each integer type separately
Of course, you could always do
(_tmp @= b+ c) * x
if you really want to wrap a nested expression.
Comparing GCC and Clang security features
Comparing GCC and Clang security features
Comparing GCC and Clang security features
__builtin_wrapv(x = y + z);
Comparing GCC and Clang security features
Maybe it would be even better to think of them as attributes of the assignment.
Comparing GCC and Clang security features
> attack right now, but "we don't know what is coming next".
Comparing GCC and Clang security features
Comparing GCC and Clang security features
Comparing GCC and Clang security features
Comparing GCC and Clang security features
Comparing GCC and Clang security features
Comparing GCC and Clang security features
For a practical example. This ARM64 (mnemonical) machine code for a function supposed to strip trailing newlines from a string.
Comparing GCC and Clang security features
46b710: 8b214001 add x1, x0, w1, uxtw
46b714: eb01001f cmp x0, x1
46b718: 540000a3 b.cc 46b72c
46b71c: 14000007 b 46b738
46b720: d1000421 sub x1, x1, #0x1
46b724: eb01001f cmp x0, x1
46b728: 540000c0 b.eq 46b740
46b72c: 385ff022 ldurb w2, [x1,#-1]
46b730: 7100285f cmp w2, #0xa
46b734: 54ffff60 b.eq 46b720
46b738: 3900003f strb wzr, [x1]
46b73c: d65f03c0 ret
46b740: aa0003e1 mov x1, x0
46b744: 3900003f strb wzr, [x1]
46b748: d65f03c0 ret
This is called with a pointer to the string in x0 and its length in w1. After the function has finished, x1 points to a null byte written over the the first trailing newline or immediately behind the string if there were no newlines. w2 contains the rightmost character which wasn't a newline or 10 if there were only newlines in the string. There's no information in these registers which isn't already available at the call site, hence, there's absolutely no point in changing the values of these two registers before returning from the function.
Comparing GCC and Clang security features
Comparing GCC and Clang security features
Comparing GCC and Clang security features
Comparing GCC and Clang security features
Comparing GCC and Clang security features
Comparing GCC and Clang security features
Comparing GCC and Clang security features
Paranoia cuts both way.
Comparing GCC and Clang security features
Comparing GCC and Clang security features
Comparing GCC and Clang security features
Comparing GCC and Clang security features
Comparing GCC and Clang security features
That's quite different: switching to Rust implies a full rewrite of relevant code. Switching the kernel to compile as C++ is expected to be several orders of magnitude easier - at least in theory, much of the code should just compile as-is, and much of the rest can be fixed mechanically.
Comparing GCC and Clang security features
Comparing GCC and Clang security features
Comparing GCC and Clang security features
Comparing GCC and Clang security features
Comparing GCC and Clang security features