Skip to content

Conversation

arielb1
Copy link
Contributor

@arielb1 arielb1 commented Sep 13, 2025

@arielb1 arielb1 changed the title mitigation enforcement Mitigation enforcement Sep 13, 2025
@clarfonthey
Copy link

FYI: Your rendered link doesn't work because you updated the filename in the URL to account for the PR number, but didn't actually update the filename in the code itself.

@arielb1
Copy link
Contributor Author

arielb1 commented Sep 13, 2025

FYI: Your rendered link doesn't work because you updated the filename in the URL to account for the PR number, but didn't actually update the filename in the code itself.

Fixed

@ehuss ehuss added the T-compiler Relevant to the compiler team, which will review and decide on the RFC. label Sep 13, 2025
For example, with `-C stack-protector`, the compatibility table will be
as follows:

| Base\Child | none | none-noenforce | strong | strong-noenforce | all | all-noenforce |
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you use the words "dependency" instead of "child" here and above? Child is not a word we generally use for crate relationships which makes it a bit confusing

and vulnerabilities.

Mitigations are generally enabled by passing a flag to the compiler (for
example, [`-Z harden-sls`] or [`-Z stack-protector`]). If the compilation
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You always talk about stack-protector here, since that's your primary motivation of course, but I think it would be good to more explicitly list out all kinds of mitigations that Rust has it that people would like Rust to have in the future, to ensure that this makes sense for all of them (for example, the ones from https://doc.rust-lang.org/nightly/rustc/exploit-mitigations.html#exploit-mitigations-1).

I would especially be interested in whether there are existing stable mitigations that would like to make use of this, especially if it has a flag to toggle it.

Copy link
Member

@rcvalle rcvalle Sep 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some exploit mitigations that would benefit from it (e.g., CFI, and including most in the https://doc.rust-lang.org/nightly/rustc/exploit-mitigations.html#exploit-mitigations-1) precedes the Target Modifiers feature (which was intended to also solve this), but I don't think there are any stable exploit mitigations except maybe -C control-flow-guard.

@ojeda
Copy link

ojeda commented Sep 14, 2025

Some projects may already have tooling to check certain things are as expected, e.g. objtool in the Linux kernel -- perhaps worth mentioning in "Motivation" or "External tools" sections. Of course, having a higher level layer checking things look OK is good, and likely could cover different aspects and would work for more projects.

@arielb1
Copy link
Contributor Author

arielb1 commented Sep 14, 2025

Some projects may already have tooling to check certain things are as expected, e.g. objtool in the Linux kernel -- perhaps worth mentioning in "Motivation" or "External tools" sections

I mentioned hardening-check. If you have experience with objtool, you can add that as well.

I also couldn't find any documentation for objtool used as a hardening check tool, so if you could provide me some I would try to include it.

@ojeda
Copy link

ojeda commented Sep 14, 2025

The docs are here: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/tools/objtool/Documentation/objtool.txt

I mentioned objtool because it is lower level, e.g. it inspects the instruction stream and so on, while hardening-check, AFAIK, is more about probing declared information using other tools (mainly, at least, from a quick look -- Cc @kees).

@arielb1
Copy link
Contributor Author

arielb1 commented Sep 14, 2025

So it looks like it works on a per-.o file basis rather than on a per-executable/shared-library basis, so it requires controlling the linker, basically.

Is that right?

hardening-check, AFAIK, is more about probing declared information using other tools

hardening-check is a lot less sophisticated, but it does perform some checks on the instruction stream - e.g. looking for checks to stack_chk_fail. It also works on executables rather than object files.

I do think that the big difference is "tool that works on .o files vs. tool that works on executables", since if the tool needs .o files you have to use it as a part of the compilation process.

@ojeda
Copy link

ojeda commented Sep 14, 2025

it does perform some checks on the instruction stream - e.g. looking for checks to stack_chk_fail

I don't think the stack_chk_fail check counts as "checks on the instruction stream". The one that comes close to that is the stack clash one, which is essentially checking regexes against the disassembly.

since if the tool needs .o files you have to use it as a part of the compilation process.

objtool has checks on linked files too, and more generally one could make a tool that works on final executables (there are very advanced analyzers out there, after all). In any case, projects that care about these bits (i.e. those with custom tools like the kernel) can likely deal with a custom build process that inspects object files. So I don't think that is a big difference -- I would say the big differences are the use case and the level at which the checks are performed.

Anyway, none of the above really matters -- I mentioned objtool because it is the kind of tool that the "Why not an external tool?" section (or the motivation) should probably mention, given it is a good example of a tool that is able to perform certain non-trivial low level checks for a quite complex project (and is open source). Having more layers checks things from different angles is good! :)

Thanks for working on this!

@arielb1
Copy link
Contributor Author

arielb1 commented Sep 14, 2025

If there was a "magic" analyzer that would reliably do the sanitizer enforcement, there would be much less need for it as a compiler flag.

As far as I can tell, the existing analyzers either have large holes or require significant project-specific intervention.

That of course does not mean they are not useful, only that they don't automatically solve the problem for everyone.

@ojeda
Copy link

ojeda commented Sep 14, 2025

Not sure if there is a disagreement here, but just in case: I didn't claim there is a "magic" analyzer out there solving this. Quite the contrary -- I said that even if such a tool existed that covered everything, having an independent check at another layer like this RFC proposes would still be useful. The objtool relevance is not because it solves the issue completely or for every project, but rather because it is an important example of such tooling, especially since the Linux kernel is mentioned.

library only comes with a single set of enabled mitigations per target.

Mitigation enforcement should be disableable by the end-user via a compiler
flag.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's be cool if this was somehow per dependency, like maybe you'd add some disable-mitigation-enforcement = {std} into the binary's Cargo.toml.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's be cool if this was somehow per dependency, like maybe you'd add some disable-mitigation-enforcement = {std} into the binary's Cargo.toml.

You probably want something in the style of -C allow-partial-mitigations=stack-protector=std+alloc+core, so you know which mitigations you are allowing.

(Of course, with also a syntax in Cargo, which should come with a separate RFC I think).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added that to alternatives

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Specifying crate names from the sysroot is very problematic since std has about 10 different dependencies that would need to be specified and that are not stable.

What would we desired for this use case of allowing the sysroot and nothing else would be special syntax to allow it only in the sysroot. A list of crate names would not help.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What would we desired for this use case of allowing the sysroot and nothing else would be special syntax to allow it only in the sysroot. A list of crate names would not help.

Do you think that special casing core (or @core or something) to apply to the entire sysroot would work?

@rcvalle
Copy link
Member

rcvalle commented Sep 15, 2025

A summary of the reasoning behind explicitly stating that it is allowed for the Rust compiler to accept a mitigation might be applied partially:

  • Allowing the user to inadvertently enable an exploit mitigation partially, or worse having this behavior by default, may lead to considerably reducing the effectiveness of the mitigation without the user knowing about it and thinking the program is otherwise protected or have security guarantees it actually doesn't have.

For solving this, I'm in favor of the simpler -C allow-partial-mitigations[=<mitigation1>,...,<mitigationN>]. It's simple, effective, and cover all cases where we wouldn't want the user to inadvertently enable an exploit mitigation partially (e.g., CFI, Stack Protector, etc.). It also aligns with the Rust philosophy of having the user to explicitly opt for the less safe approach.

For stack smashing protection specifically (which is beyond the scope of this RFC, but relevant to choosing the right approach):

  • For stack protector, the most common behavior the user gets for quite some time already is actually globally enabled and enforced, not partially enabled. Most major Linux distributions build their packages with stack protector strong, including the C standard library (which would be the equivalent of always using -Zbuild-std) and have the option implicitly enabled in their toolchains so the behavior the user gets is much closer to always using -Zbuild-std than not. For example, it's enabled by default in Ubuntu for quite some time already (see https://wiki.ubuntu.com/ToolChain/CompilerFlags).

For this, I propose either:

  1. That the user has to use -C allow-partial-mitigations=stack-protector to allow it to be applied partially as described above. (Otherwise, the user might inadvertently think they are getting the most common behavior as described above.)
  2. Do (1) and enable it with the strong mode/strategy by default both for the Rust standard library and the Rust compiler--this is the default for most major Linux distributions for quite some time already.

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 (considering the returns are different from a program written in C or C++ compared to a program written in Rust).

@rcvalle rcvalle self-requested a review September 15, 2025 20:00
@kees
Copy link

kees commented Sep 15, 2025

The docs are here: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/tools/objtool/Documentation/objtool.txt

I mentioned objtool because it is lower level, e.g. it inspects the instruction stream and so on, while hardening-check, AFAIK, is more about probing declared information using other tools (mainly, at least, from a quick look -- Cc @kees).

Objtool is mainly designed to validate the expected construction of the (x86) kernel's functions and related metadata (e.g. "can we always unwind the stack correctly?" or "what things are reachable for indirect calls?")

As far as the mitigation enforcement idea as presented, I like the idea of having this be a declared compatibility thing to check. In C it is trivial to mix and match different mitigations and the resulting binary is very hard to analyze after the fact (see hardening-check which is best-effort). Rejecting mismatches up front would be nice! :)

@arielb1
Copy link
Contributor Author

arielb1 commented Sep 16, 2025

Do (1) and enable it with the strong mode/strategy by default both for the Rust standard library and the Rust compiler--this is the default for most major Linux distributions for quite some time already.

I do think there is an interesting middle point where we ship a libstd that has stack protector=strong enabled, but don't enable it for default in the toolchain. This means that people can easily enable complete stack protector for their application without -Z build-std, but there is less annoying performance impact.

In any case, it should not be relevant to this RFC.

@davidtwco
Copy link
Member

For solving this, I'm in favor of the simpler -C allow-partial-mitigations[=<mitigation1>,...,<mitigationN>]. It's simple, effective, and cover all cases where we wouldn't want the user to inadvertently enable an exploit mitigation partially (e.g., CFI, Stack Protector, etc.). It also aligns with the Rust philosophy of having the user to explicitly opt for the less safe approach.

I also much prefer this approach to having many -noenforce variants of flag values. When a distribution of Rust ships a standard library with a mitigation enabled, then I think there are two ways of doing this that we should encourage:

  1. Using -Zbuild-std or an equivalent mechanism. Our draft for build-std rust-project-goals#274 aims eventually to make it so that if a target modifier (or exploit mitigation, I guess) is enabled, then the mismatch with std is detected and std is rebuilt automatically. This seems like a reasonable user experience and addresses this for the Rust project's distribution of Rust.
  2. Shipping rustc w/ the default changed to match the flags used with the standard library, so the partial mitigations error doesn't trigger by default when rustc is used without any flags. I think I'd be happy for this to be what a Linux distribution/internal company distribution/etc does - as long as the flags/defaults changed are all otherwise stable.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
T-compiler Relevant to the compiler team, which will review and decide on the RFC.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

9 participants