Compile libraries with cf-protection=branch on x64#156612
Conversation
The CET control-flow enforcement technology feature on x64 prevents control-flow hijacking via corrupted vtables and similar. It requires a special NOP at all entry points that are targeted by indirect branches/calls. For an executable to be protected, all object files linked into it have to have support. Until now, any rust object file would disable support for the entire executable. This adds the flag (if available) so that rust system libraries do not poison the protection when linked in. Note: This doesn't do anything for shadow stack support. The two features can be enabled separately, so it's valuable in itself. tracking issue: rust-lang#93754
|
cc @tgross35 |
|
r? @jieyouxu rustbot has assigned @jieyouxu. Use Why was this reviewer chosen?The reviewer was selected based on:
|
|
Perhaps @abrown is a better reviewer for this? |
|
V8 and LLVM have support for -fcf-protection=branch, which is a great security feature, and actually very simple. It merely bans indirect jumps to locations that are not marked with the ENDBR64 instruction (which is just a NOP if the feature is not enabled on your CPU). This prevents a whole class of program-counter hijacking attacks (ROP). However, currently you have to disable the parts of V8 written in rust for it to work, because the rust code pulls in artifacts that don't have the support (those NOP/ENDBR64 instructions at entry points). As more of V8 gets written in rust this problem is only going to get worse. Effectively, having part of your program written in rust means you can't get this performance-neutral defense-in-depth feature. I also work on Cloudflare Workers, which embed V8 and have the same problem. The LLVM support for cf-protection=branch is not considered experimental, but in rust the discussion (see linked bug) has been mostly about how to activate it. A flag is available in nightly, but currently doesn't work because of this issue. |
|
I fixed it so that it only changes the artifacts on the non-stable build, but not sure how to do the same for the change in build.rs. This is for the optimized builtins which are in C, so it's a flag for the C compiler. I could perhaps do something tricky with environment, unless you have a better idea? |
|
Can't you build std yourself with the unstable flag for now? If you have spare cycles to spend on improving the situation, it's probably better to work toward moving the relevant flags toward stabilization. That way once we have build-std, which is in progress, you'll be able to do what is needed from stable Rust. |
|
IIUC, the flag is entirely useless without build-std, so there would be no point in stabilizing it alone. |
|
I think it's still useful to indicate that the design work has been done and it's a flag that can be relied on. But it could also be made effectively stabilization-ready even if stabilization itself is blocked on build-std, 🤷♂️. |
|
I wouldn't be against stabilizing that flag (given the usual well-testedness and no-blocking-bugs) even if its effectively useless without build-std, as long as we are clear about that limitation on stabilization with intent to change the prebuilt dist artifacts to flip the default to w/ protection in the future. That would be a step that I would like to see so we have some stronger confidence in this flag's impl and behavior. Basically, IMO we should not and cannot be building and distributing artifacts built with this flag while its unstable. Feel free to discuss this further in https://rust-lang.zulipchat.com/#narrow/channel/131828-t-compiler. @rustbot blocked (should not be building stable dist artifacts while this flag is still unstable) |
|
Reminder, once the PR becomes ready for a review, use |
|
FWIW rustc is already an artifact that is compiled with unstable flags that gets shipped. To be clear, the current version of this change doesn't affect artifacts that are shipped in releases, only nightly. I guess the purpose of nightly is to gather experience with flags that were not yet stabilized? Without this change it's not really possible to get experience with the flag, since the libs poison any executable you link and disable the feature. |
|
|
Yes -- there are some unstable flags for various reasons, but e.g. for more "feature"-like ones we do not want to enable more unstable features lightly for stable artifacts.
Sorry I missed this, this makes sense to me. I'd at least be up for experimenting on nightly (just that we should not be building stable artifacts with the unstable flag; the current set of changes in this PR is for building nightly artifacts). |
|
@rustbot review |
|
Hm, this should probably get a libs reviewer since this primarily affects pre-build std. I'm at least on-board with experimenting on nightly-only (for now, before flag is stabilized). r? libs |
|
FWIW, I'm not sure this is really a libs issue (if libs wants to weigh in though, by all means!). The main questions I would be interested in are:
|
|
Answer to question 1 from @wesleywiser : Total sysroot libs: +74,574 bytes (+0.56%) (13.3 MB -> 13.4 MB) More detailed look:
|
|
Answer to question 2 from @wesleywiser: I ran the coretests and the alloctests and it was basically in the noise, as you would expect with NOP-like instructions. 35% of the tests were faster, 34% within +/-1%, 30% slower. Overall performance was -0.11% My machine is a recent Xeon that has support for the instruction. It's not decoding it like a NOP, but like an ENDBR64 instruction. However, the benchmarking executable did not have the bit set, so the kernel didn't set the CPU to the mode where the rules would be enforced. |
This isn't really something that is for me to have an opinion on. For now this allows people on the nightly builds to start enabling it so they can gain experience. Perhaps at some point it becomes standard or there is a way to use a different set of standard library artifacts when enabling it. I think that's a separate decision. |
The CET control-flow enforcement technology feature on x64 prevents control-flow hijacking via corrupted vtables and similar. It requires a special NOP at all entry points that are targeted by indirect branches/calls.
For an executable to be protected, all object files linked into it have to have support. Until now, any rust object file would disable support for the entire executable. This adds the flag (if available) so that rust system libraries do not poison the protection when linked in.
Note: This doesn't do anything for shadow stack support. The two features can be enabled separately, so it's valuable in itself.
tracking issue: #93754