-
Notifications
You must be signed in to change notification settings - Fork 13.8k
Stabilize stack-protector #146369
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Stabilize stack-protector #146369
Conversation
This PR modifies If appropriate, please update These commits modify compiler targets. Some changes occurred in tests/codegen-llvm/stack-protector.rs cc @rust-lang/project-exploit-mitigations, @rcvalle Some changes occurred in src/doc/rustc/src/exploit-mitigations.md cc @rust-lang/project-exploit-mitigations, @rcvalle Some changes occurred in tests/ui/stack-protector cc @rust-lang/project-exploit-mitigations, @rcvalle |
|
This comment has been minimized.
This comment has been minimized.
c37026a
to
7d5bc08
Compare
The exact rules are unstable and subject to change, but | ||
currently, it generates stack protectors for functions that, | ||
*post-optimization*, contain either arrays (of any size | ||
or type) or address-taken locals. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The exact rules are unstable and subject to change, but | |
currently, it generates stack protectors for functions that, | |
*post-optimization*, contain either arrays (of any size | |
or type) or address-taken locals. | |
The exact rules are unstable and subject to change, but | |
currently, it generates stack protectors for functions that, | |
*post-optimization*, contain stack allocations. |
As in Rust all allocas are arrays. (Making this "address-taken stack allocations" would be the potential future improvement.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suppose that "spill" stack allocations don't count? So stack allocations isn't exactly precise.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will say "contain stack allocations that are accessed in a way that's not precisely tracked by the compiler".
@rfcbot fcp merge See PR description for stabilisation report |
Team member @davidtwco has proposed to merge this. The next step is review by the rest of the tagged team members:
No concerns currently listed. Once a majority of reviewers approve (and at most 2 approvals are outstanding), this will enter its final comment period. If you spot a major issue that hasn't been raised at any point in this process, please speak up! See this document for info about what commands tagged team members can give me. |
dcfd0bd
to
0539523
Compare
This comment has been minimized.
This comment has been minimized.
determined by static control flow). | ||
none | ||
Do not generate stack canaries. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know it's not widely used by Clang/Gcc, but should we add why the basic mode/strategy is not applicable to Rust and thus not available in case people look for it for matching build configurations?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that outside of rustc it's always called -fstack-protector
(rather than "basic stack protection", which is not a name I've ever heard outside of a Rust context), but I agree that it makes sense to have documentation for why it does not exist. I think only in the markdown file, rather than in this file (tho I have no problem putting it in both places).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, agreed. I actually meant mainly in the markdown docs, but left the comment in the wrong place. Something like: -fstack-protector
or -fstack-protector=basic
is not available/applicable to Rust because... (or equivalent to it). What do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, will do that soon-ish.
Should this be a target modifier? (See #136966.) This exploit mitigation has mostly local properties and provide benefits when applied partially (but my recommendation is always to use/apply it thoroughly). It also doesn't cause any ABI changes or incompatibilities when used/applied partially so I'm unsure whether to make it a target modifier or not. Any thoughts? |
As far as I can tell, target modifier should only be used for cases where differences in the target modifier can lead to unsoundness. With And it's pretty common for code to be compiled together with different stack-protector settings - for example, for C code to be compiled with I don't think that anyone expects to need to use On the other hand, it is the case that in more modern mitigations, you generally expect them to be implemented for the entire program (theoretically, you could make Actually, thinking about this more, what we probably want is to have a separate I believe |
Another use is when it may inadvertently lead to considerably reducing the effectiveness or make it considerably difficult to reason about the security guarantees/threat model of an exploit mitigation, the latter is which is not the case here. This exploit mitigation has local security guarantees/threat model that are relatively easy to reason about. However, inadvertently linking in unprotected compiled code could reduce the effectiveness of the exploit mitigation and security of the overall program, and thus why I asked about your thoughts about it.
I mean, this is exactly the feature target modifiers provide. The user, upon their choosing, can override it with a flag (e.g., |
Nit: I thought that there is a widespread agreement to only use "unsafe" to indicate actual unsoundness as opposed to merely "undesirable consequences" - which is why I want the The more significant issue, is that the way Basically, as far as I can tell, today, there is a fairly small number of security-critical projects (for example, the Linux kernel, web browsers) that expect to control everything that goes into their address space - these are also the people that tend to be using the "modern" sanitizer mitigations. The other projects basically live with whatever they end up with. These normally don't use the "modern" mitigations. Trying to make stack-protector force them into a "whole program" mitigation world will just make them not use stack-protector. Also, I think that even with the "modern" mitigations, if you want to have a chance to push them within an open world, you'll need to have a way for people to enable them opportunistically, because otherwise it will just be very hard to incrementally adopt them - anyone that has a build system would need to make a binary choice of whether to enable them or not, and in both cases they will be wrong. This would mean that you might want to have separate The other direction is that |
Actually not. There are several examples in the approved RFC about exploit mitigations--I'd argue that the rationale for the RFC was mostly based on exploit mitigations. E.g.: "For sanitizers used in production (such as shadow call stack or kcfi) this is particularly problematic, as a vulnerability in sanitized code may allow you to jump into unsanitized code."
I'm not sure about that. Is it? Most major Linux distributions build their packages with stack protector strong, including the C standard library (which would be the equivalent of
We wouldn't be forcing the user to apply the mitigation throughout we would just have more secure defaults and a better security posture (which is the culture behind the Rust compiler and community). We can be informative, but also give the option to the user to do otherwise if needed/wanted. This is not forcing the user, but helping the user to do the right thing. I don't think we would also want to have an additional
It's actually enabled by default in Ubuntu for quite some time already1. As I mentioned above, not only for all packages built and shipped with the distribution, including the C standard library, but also implicitly enabled in the provided toolchain. Footnotes |
It is indeed the case that it can make sense to use target modifiers for exploit mitigations even if it strictly speaking isn't UB to mix them. See for example rust-lang/compiler-team#868, which says:
However, I want to clarify that shadow call stack and kcfi are not examples of this. These two sanitizers can be part of an actual ABI issue. For example, last year I spent over a week debugging an ABI issue related to shadow-call-stack being mismatched between C and Rust CUs. On the topic of |
tl;dr I think the right "default for defaults" is
But it doesn't error if you try to use it with code that has different stack-protector flags. For example, if you have code that suffers performance impact from stack protectors, you can set For example, today, if you have Rust code that links against C code that is compiled on Ubuntu, the C code is likely to have stack protectors and the Rust code is likely to not, and AFAICT there are no plans for changing that - once we stabilize Of course, the current definition of target modifiers only applies to Rust code compiled within a single Rust "world", which means that, since (other than However, for the ABI target modifiers, if it was possible, it would make sense to extend them to deal with everything within the same address space (ABI mismatches between C and Rust are just as bad as ABI mismatches between Rust and Rust). That probably also makes sense for e.g. Spectre mitigations - it would be useful to have a programmatic way of making sure that all object files linked within a kernel have Spectre mitigations enabled - but for stack protectors, it is going to be continue to be the case that many times, code within an address space probably does not have a single stack-protector setting - which is more secure than what happens with the "modern" sanitizers, which is that in these "open world" cases, all code within the same address space has them in the same setting - disabled.
I agree that
For mitigations that make sense to enable partially (not e.g. Spectre mitigations - if you have a single reachable function in your code that is vulnerable to Spectre, attackers can exploit it even if it doesn't have a bug, which makes having Spectre mitigations in only a part of the code basically useless), having a separate In some sense, this is bikeshedding, but I think that the right "default for defaults" would be |
Looking more at things - the I don't think this is the right affordance for stack protector. I think that for stack protector, disabling it is more of a case of "I care more about performance than about security". In which case, I think the right affordance is that the application compiler - the leaf caller of I don't want people that are trying to fix their compile errors to end up using the "I really know what I am doing, it's actually fine" flag. |
Maybe it's better if we just make Or we could have rustc as that tool. In any case that can be further work since nobody wants it now. |
Thanks, @Darksonn! I was quoting the part of where the risk of reducing the effectiveness of the exploit mitigation and security of the overall program are also mentioned as rationale. I know you had some ABI issues as well for these. For example, for CFI, even though there are no ABI issues, I'm intending to make it a target modifier entirely because of the risk of reducing the effectiveness of the exploit mitigation and security of the overall program.
The more I think about it, I think we should either:
For both (1) and (2), adding the For (2), we could perform a comprehensive set of tests for binary size, build time, and run time performance and make a decision based upon the ROI. |
|
I would suggest to split out the discussion around the behavior of stack protector enforcement and figure out what to best do there on a separate thread instead of the stabilization here. We generally want to avoid such larger discussions on stabilizations, because stabilizations are supposed to just be the final sign-off. It was definitely good that the issue came up, but I think now it's time to move it out. From reading the comments, @arielb1's suggestion around not making it a target modifier and having some sort of From a stabilization POV, I see two options really:
As mentioned, I'd suggest having a more detailed discussion about the advantages and use cases of mitigation enforcement in another place than the stabilization PR here (for example a separate issue or a Zulip thread). |
There is a zulip thread https://rust-lang.zulipchat.com/#narrow/channel/233931-t-compiler.2Fmajor-changes/topic/Proposal.20for.20Adapt.20Stack.20Protector.20for.20Ru.E2.80.A6.20compiler-team.23841/with/538305719. We can talk on it It's linked from the stabilization comments, but not loudly enough. |
@rfcbot concern mitigation-enforcement |
Let's move mitigation enforcement discussion to https://rust-lang.zulipchat.com/#narrow/channel/233931-t-compiler.2Fmajor-changes/topic/Mitigation.20enforcement.20.28.60-C.20allow-partial-mitigations.60.29/with/539144305 |
☔ The latest upstream changes (presumably #146499) made this pull request unmergeable. Please resolve the merge conflicts. |
I propose stabilizing `-Cstack-protector` as `-Zstack-protector`. This PR adds a new `-Cstack-protector` flag, leaving the unstable `-Z` flag as is to ease the transition period. The `-Z` flag will be removed in the future. No RFC/MCP, this flag was added in 84197 and was not deemed large enough to require additional process. The tracking issue for this feature is 114903. The `-Cstack-protector=strong` mode uses the same underlying heuristics as Clang's `-fstack-protector-strong`. These heuristics weren't designed for Rust, and may be over-conservative in some cases - for example, if Rust stores a field's data in an alloca using an LLVM array type, LLVM regard the alloca as meaning that the function has a C array, and enable stack overflow canaries even if the function accesses the alloca in a safe way. Some people thought we should wait on stabilization until there are better heuristics, but I didn't hear about any concrete case where this unduly harms performance, and I think that when a need comes, we can improve the heuristics in LLVM after stabilization. The heuristics do seem to not be under-conservative, so this should not be a security risk. The `-Cstack-protector=basic` mode (`-fstack-protector`) uses heuristics that are specifically designed to catch old-C-style string manipulation. This is not a good fit to Rust, which does not perform much unsafe C-style string manipulation. As far as I can tell, nobody has been asking for it, and few people are using it even in today's C - modern distros (e.g. [Debian]) tend to use `-fstack-protector-strong`. Therefore, `-Cstack-protector=basic` has been **removed**. If anyone is interested in it, they are welcome to add it back as an unstable option. [Debian]: https://wiki.debian.org/Hardening#DEB_BUILD_HARDENING_STACKPROTECTOR_.28gcc.2Fg.2B-.2B-_-fstack-protector-strong.29 Most implementation was done in <rust-lang#84197>. The command-line attribute enables the relevant LLVM attribute on all functions in <https://github.com/rust-lang/rust/blob/68baa87ba6f03f8b6af2a368690161f1601e4040/compiler/rustc_codegen_llvm/src/attributes.rs#L267-L276>. Each target can indicate that it does not support stack canaries - currently, the GPU platforms `nvptx64-nvidia-cuda` and `amdgcn-amd-amdhsa`. On these platforms, use of `-Cstack-protector` causes an error. The feature has tests that make sure that the LLVM heuristic gives reasonable results for several functions, by checking for `__security_check_cookie` (on Windows) or `__stack_chk_fail` (on Linux). See <https://github.com/rust-lang/rust/tree/68baa87ba6f03f8b6af2a368690161f1601e4040/tests/assembly-llvm/stack-protector> No call-for-testing has been conducted, but the feature seems to be in use. No reported bugs seem to exist. - bbjornse was the original implementor at 84197 - mrcnski documented it at 111722 - wesleywiser added tests for Windows at 116037 - davidtwco worked on the feature at 121742 - nikic provided support from the LLVM side (on Zulip on <https://rust-lang.zulipchat.com/#narrow/channel/233931-t-compiler.2Fmajor-changes/topic/Proposal.20for.20Adapt.20Stack.20Protector.20for.20Ru.E2.80.A6.20compiler-team.23841> and elsewhere), thanks nikic! No FIXMEs related to this feature. This feature cannot cause undefined behavior. No changes to reference/spec, docs added to the codegen docs as part of the stabilization PR. No. None. No support needed for rustdoc, clippy, rust-analyzer, rustfmt or rustup. Cargo could expose this as an option in build profiles but I would expect the decision as to what version should be used would be made for the entire crate graph at build time rather than by individual package authors. `-C stack-protector` is propagated to C compilers using cc-rs via rust-lang/cc-rs issue 1550
stack-protector=basic is a heuristic designed for C putting this in its own commit to allow for easy reversion if someone wants it
0539523
to
29eea09
Compare
This PR was rebased onto a different master commit. Here's a range-diff highlighting what actually changed. Rebasing is a normal part of keeping PRs up to date, so no action is needed—this note is just to help reviewers. |
Added the comment about stack-protector=basic. I expect to need these even after we stabilize the mitigaiton enforcement RFC. |
I believe the discussion on mitigation enforcement is highly meaningful. On the other hand, I hope we can reach a conclusion on the stabilization PR for |
My reading is that the consensus between the interested people is that enforcement should be the default, therefore this is now blocked on that. If you disagree and would like to see this stabilized without enforcement by not making it the default, you are welcome to make your case on the Zulip thread and the enforcement RFC. |
Stabilize
-Zstack-protector
as-Cstack-protector
I propose stabilizing
-Zstack-protector
as-Cstack-protector
. This PR adds a new-Cstack-protector
flag, leaving the unstable-Z
flag as is to ease the transition period. The-Z
flag will be removed in the future.Please hold technical discussion in the zulip thread at https://rust-lang.zulipchat.com/#narrow/channel/233931-t-compiler.2Fmajor-changes/topic/Proposal.20for.20Adapt.20Stack.20Protector.20for.20Ru.E2.80.A6.20compiler-team.23841
Let's move mitigation enforcement discussion to https://rust-lang.zulipchat.com/#narrow/channel/233931-t-compiler.2Fmajor-changes/topic/Mitigation.20enforcement.20.28.60-C.20allow-partial-sanitizer.60.29/with/539144305
-Zstack-protector
stabilization reportWhat is the RFC for this feature and what changes have occurred to the user-facing design since the RFC was finalized?
No RFC/MCP, this flag was added in #84197 and was not deemed large enough to require additional process.
The tracking issue for this feature is #114903.
What behavior are we committing to that has been controversial? Summarize the major arguments pro/con.
The
-Cstack-protector=strong
mode uses the same underlying heuristics as Clang's-fstack-protector-strong
.These heuristics weren't designed for Rust, and may be over-conservative in some cases - for example, if
Rust stores a field's data in an alloca using an LLVM array type, LLVM regard the alloca as meaning
that the function has a C array, and enable stack overflow canaries even if the function accesses
the alloca in a safe way. Some people thought we should wait on stabilization until there are better
heuristics, but I didn't hear about any concrete case where this unduly harms performance, and I think
that when a need comes, we can improve the heuristics in LLVM after stabilization.
The heuristics do seem to not be under-conservative, so this should not be a security risk.
The
-Cstack-protector=basic
mode (-fstack-protector
) uses heuristics that are specifically designedto catch old-C-style string manipulation. This is not a good fit to Rust, which does not perform much
unsafe C-style string manipulation. As far as I can tell, nobody has been asking for it,
and few people are using it even in today's C - modern distros (e.g. Debian) tend to use
-fstack-protector-strong
.Therefore,
-Cstack-protector=basic
has been removed. If anyone is interested in it, theyare welcome to add it back as an unstable option.
Summarize the major parts of the implementation and provide links into the code (or to PRs)
Most implementation was done in #84197. The command-line
attribute enables the relevant LLVM attribute on all functions in
rust/compiler/rustc_codegen_llvm/src/attributes.rs
Lines 267 to 276 in 68baa87
Each target can indicate that it does not support stack canaries - currently,
the GPU platforms
nvptx64-nvidia-cuda
andamdgcn-amd-amdhsa
. On theseplatforms, use of
-Cstack-protector
causes an error.Summarize existing test coverage of this feature
The feature has tests that make sure that the LLVM heuristic gives reasonable
results for several functions, by checking for
__security_check_cookie
(on Windows)or
__stack_chk_fail
(on Linux). Seehttps://github.com/rust-lang/rust/tree/68baa87ba6f03f8b6af2a368690161f1601e4040/tests/assembly-llvm/stack-protector
Has a call-for-testing period been conducted? If so, what feedback was received?
No call-for-testing has been conducted, but the feature seems to be in use.
What outstanding bugs in the issue tracker involve this feature? Are they stabilization-blocking?
No reported bugs seem to exist.
Summarize contributors to the feature by name for recognition and assuredness that people involved in the feature agree with stabilization
-Zstack-protector
test for Windows targets #116037-C stack-protector=all
#121742thanks @nikic!
What FIXMEs are still in the code for that feature and why is it ok to leave them there?
No FIXMEs related to this feature.
What static checks are done that are needed to prevent undefined behavior?
This feature cannot cause undefined behavior.
In what way does this feature interact with the reference/specification, and are those edits prepared?
No changes to reference/spec, docs added to the codegen docs as part of the stabilization PR.
Does this feature introduce new expressions and can they produce temporaries? What are the lifetimes of those temporaries?
No.
What other unstable features may be exposed by this feature?
None.
What is tooling support like for this feature, w.r.t rustdoc, clippy, rust-analzyer, rustfmt, etc.?
No support needed for rustdoc, clippy, rust-analyzer, rustfmt or rustup.
Cargo could expose this as an option in build profiles but I would expect the decision as to what version should be used would
be made for the entire crate graph at build time rather than by individual package authors.
-C stack-protector
is propagated to C compilers using cc-rs via rust-lang/cc-rs#1550Fixes #114903
r? @wesleywiser (feel free to reassign)