Add support for generating the EHCont section

In the future Windows will enable Control-flow Enforcement Technology
(CET aka Shadow Stacks). To protect the path where the context is
updated during exception handling, the binary is required to enumerate
valid unwind entrypoints in a dedicated section which is validated when
the context is being set during exception handling.

The required support for EHCONT has already been merged into LLVM,
long ago. This change adds the Rust codegen option to enable it.

Reference:

* https://reviews.llvm.org/D40223

This also adds a new `ehcont-guard` option to the bootstrap config which
enables EHCont Guard when building std.
This commit is contained in:
Arlie Davis 2023-11-17 10:05:38 -08:00
parent 2f8d81f9db
commit e11d8d147b
10 changed files with 76 additions and 2 deletions

View File

@ -350,6 +350,15 @@ pub unsafe fn create_module<'ll>(
1, 1,
); );
} }
// Set module flag to enable Windows EHCont Guard (/guard:ehcont).
if sess.opts.cg.ehcont_guard {
llvm::LLVMRustAddModuleFlag(
llmod,
llvm::LLVMModFlagBehavior::Warning,
"ehcontguard\0".as_ptr() as *const _,
1,
)
}
// Insert `llvm.ident` metadata. // Insert `llvm.ident` metadata.
// //

View File

@ -2378,6 +2378,11 @@ fn add_order_independent_options(
cmd.control_flow_guard(); cmd.control_flow_guard();
} }
// OBJECT-FILES-NO, AUDIT-ORDER
if sess.opts.cg.ehcont_guard {
cmd.ehcont_guard();
}
add_rpath_args(cmd, sess, codegen_results, out_filename); add_rpath_args(cmd, sess, codegen_results, out_filename);
} }

View File

@ -185,6 +185,7 @@ pub trait Linker {
fn optimize(&mut self); fn optimize(&mut self);
fn pgo_gen(&mut self); fn pgo_gen(&mut self);
fn control_flow_guard(&mut self); fn control_flow_guard(&mut self);
fn ehcont_guard(&mut self);
fn debuginfo(&mut self, strip: Strip, natvis_debugger_visualizers: &[PathBuf]); fn debuginfo(&mut self, strip: Strip, natvis_debugger_visualizers: &[PathBuf]);
fn no_crt_objects(&mut self); fn no_crt_objects(&mut self);
fn no_default_libraries(&mut self); fn no_default_libraries(&mut self);
@ -605,6 +606,8 @@ impl<'a> Linker for GccLinker<'a> {
fn control_flow_guard(&mut self) {} fn control_flow_guard(&mut self) {}
fn ehcont_guard(&mut self) {}
fn debuginfo(&mut self, strip: Strip, _: &[PathBuf]) { fn debuginfo(&mut self, strip: Strip, _: &[PathBuf]) {
// MacOS linker doesn't support stripping symbols directly anymore. // MacOS linker doesn't support stripping symbols directly anymore.
if self.sess.target.is_like_osx { if self.sess.target.is_like_osx {
@ -914,6 +917,12 @@ impl<'a> Linker for MsvcLinker<'a> {
self.cmd.arg("/guard:cf"); self.cmd.arg("/guard:cf");
} }
fn ehcont_guard(&mut self) {
if self.sess.target.pointer_width == 64 {
self.cmd.arg("/guard:ehcont");
}
}
fn debuginfo(&mut self, strip: Strip, natvis_debugger_visualizers: &[PathBuf]) { fn debuginfo(&mut self, strip: Strip, natvis_debugger_visualizers: &[PathBuf]) {
match strip { match strip {
Strip::None => { Strip::None => {
@ -1127,6 +1136,8 @@ impl<'a> Linker for EmLinker<'a> {
fn control_flow_guard(&mut self) {} fn control_flow_guard(&mut self) {}
fn ehcont_guard(&mut self) {}
fn debuginfo(&mut self, _strip: Strip, _: &[PathBuf]) { fn debuginfo(&mut self, _strip: Strip, _: &[PathBuf]) {
// Preserve names or generate source maps depending on debug info // Preserve names or generate source maps depending on debug info
// For more information see https://emscripten.org/docs/tools_reference/emcc.html#emcc-g // For more information see https://emscripten.org/docs/tools_reference/emcc.html#emcc-g
@ -1319,6 +1330,8 @@ impl<'a> Linker for WasmLd<'a> {
fn control_flow_guard(&mut self) {} fn control_flow_guard(&mut self) {}
fn ehcont_guard(&mut self) {}
fn no_crt_objects(&mut self) {} fn no_crt_objects(&mut self) {}
fn no_default_libraries(&mut self) {} fn no_default_libraries(&mut self) {}
@ -1472,6 +1485,8 @@ impl<'a> Linker for L4Bender<'a> {
fn control_flow_guard(&mut self) {} fn control_flow_guard(&mut self) {}
fn ehcont_guard(&mut self) {}
fn no_crt_objects(&mut self) {} fn no_crt_objects(&mut self) {}
} }
@ -1613,6 +1628,8 @@ impl<'a> Linker for AixLinker<'a> {
fn control_flow_guard(&mut self) {} fn control_flow_guard(&mut self) {}
fn ehcont_guard(&mut self) {}
fn debuginfo(&mut self, strip: Strip, _: &[PathBuf]) { fn debuginfo(&mut self, strip: Strip, _: &[PathBuf]) {
match strip { match strip {
Strip::None => {} Strip::None => {}
@ -1835,6 +1852,8 @@ impl<'a> Linker for PtxLinker<'a> {
fn control_flow_guard(&mut self) {} fn control_flow_guard(&mut self) {}
fn ehcont_guard(&mut self) {}
fn export_symbols(&mut self, _tmpdir: &Path, _crate_type: CrateType, _symbols: &[String]) {} fn export_symbols(&mut self, _tmpdir: &Path, _crate_type: CrateType, _symbols: &[String]) {}
fn subsystem(&mut self, _subsystem: &str) {} fn subsystem(&mut self, _subsystem: &str) {}
@ -1931,6 +1950,8 @@ impl<'a> Linker for BpfLinker<'a> {
fn control_flow_guard(&mut self) {} fn control_flow_guard(&mut self) {}
fn ehcont_guard(&mut self) {}
fn export_symbols(&mut self, tmpdir: &Path, _crate_type: CrateType, symbols: &[String]) { fn export_symbols(&mut self, tmpdir: &Path, _crate_type: CrateType, symbols: &[String]) {
let path = tmpdir.join("symbols"); let path = tmpdir.join("symbols");
let res: io::Result<()> = try { let res: io::Result<()> = try {

View File

@ -1387,6 +1387,8 @@ options! {
"allow the linker to link its default libraries (default: no)"), "allow the linker to link its default libraries (default: no)"),
dlltool: Option<PathBuf> = (None, parse_opt_pathbuf, [UNTRACKED], dlltool: Option<PathBuf> = (None, parse_opt_pathbuf, [UNTRACKED],
"import library generation tool (ignored except when targeting windows-gnu)"), "import library generation tool (ignored except when targeting windows-gnu)"),
ehcont_guard: bool = (false, parse_bool, [TRACKED],
"generate Windows EHCont Guard tables"),
embed_bitcode: bool = (true, parse_bool, [TRACKED], embed_bitcode: bool = (true, parse_bool, [TRACKED],
"emit bitcode in rlibs (default: yes)"), "emit bitcode in rlibs (default: yes)"),
extra_filename: String = (String::new(), parse_string, [UNTRACKED], extra_filename: String = (String::new(), parse_string, [UNTRACKED],

View File

@ -686,6 +686,10 @@ change-id = 116881
# This only applies from stage 1 onwards, and only for Windows targets. # This only applies from stage 1 onwards, and only for Windows targets.
#control-flow-guard = false #control-flow-guard = false
# Enable Windows EHCont Guard checks in the standard library.
# This only applies from stage 1 onwards, and only for Windows targets.
#ehcont-guard = false
# Enable symbol-mangling-version v0. This can be helpful when profiling rustc, # Enable symbol-mangling-version v0. This can be helpful when profiling rustc,
# as generics will be preserved in symbols (rather than erased into opaque T). # as generics will be preserved in symbols (rather than erased into opaque T).
# When no setting is given, the new scheme will be used when compiling the # When no setting is given, the new scheme will be used when compiling the

View File

@ -1964,6 +1964,12 @@ impl<'a> Builder<'a> {
rustflags.arg("-Ccontrol-flow-guard"); rustflags.arg("-Ccontrol-flow-guard");
} }
// Same for EHCont Guard (this is not combined with the previous if-statement to make
// merges with upstream easier).
if cfg!(windows) && mode == Mode::Std && self.config.ehcont_guard && compiler.stage >= 1 {
rustflags.arg("-Cehcont-guard");
}
// For `cargo doc` invocations, make rustdoc print the Rust version into the docs // For `cargo doc` invocations, make rustdoc print the Rust version into the docs
// This replaces spaces with tabs because RUSTDOCFLAGS does not // This replaces spaces with tabs because RUSTDOCFLAGS does not
// support arguments with regular spaces. Hopefully someday Cargo will // support arguments with regular spaces. Hopefully someday Cargo will
@ -2172,7 +2178,11 @@ impl<'a> Builder<'a> {
} }
// Only execute if it's supposed to run as default // Only execute if it's supposed to run as default
if desc.default && should_run.is_really_default() { self.ensure(step) } else { None } if desc.default && should_run.is_really_default() {
self.ensure(step)
} else {
None
}
} }
/// Checks if any of the "should_run" paths is in the `Builder` paths. /// Checks if any of the "should_run" paths is in the `Builder` paths.

View File

@ -248,6 +248,7 @@ pub struct Config {
pub local_rebuild: bool, pub local_rebuild: bool,
pub jemalloc: bool, pub jemalloc: bool,
pub control_flow_guard: bool, pub control_flow_guard: bool,
pub ehcont_guard: bool,
// dist misc // dist misc
pub dist_sign_folder: Option<PathBuf>, pub dist_sign_folder: Option<PathBuf>,
@ -1019,6 +1020,7 @@ define_config! {
test_compare_mode: Option<bool> = "test-compare-mode", test_compare_mode: Option<bool> = "test-compare-mode",
llvm_libunwind: Option<String> = "llvm-libunwind", llvm_libunwind: Option<String> = "llvm-libunwind",
control_flow_guard: Option<bool> = "control-flow-guard", control_flow_guard: Option<bool> = "control-flow-guard",
ehcont_guard: Option<bool> = "ehcont-guard",
new_symbol_mangling: Option<bool> = "new-symbol-mangling", new_symbol_mangling: Option<bool> = "new-symbol-mangling",
profile_generate: Option<String> = "profile-generate", profile_generate: Option<String> = "profile-generate",
profile_use: Option<String> = "profile-use", profile_use: Option<String> = "profile-use",
@ -1452,6 +1454,7 @@ impl Config {
config.rust_thin_lto_import_instr_limit = rust.thin_lto_import_instr_limit; config.rust_thin_lto_import_instr_limit = rust.thin_lto_import_instr_limit;
set(&mut config.rust_remap_debuginfo, rust.remap_debuginfo); set(&mut config.rust_remap_debuginfo, rust.remap_debuginfo);
set(&mut config.control_flow_guard, rust.control_flow_guard); set(&mut config.control_flow_guard, rust.control_flow_guard);
set(&mut config.ehcont_guard, rust.ehcont_guard);
config.llvm_libunwind_default = rust config.llvm_libunwind_default = rust
.llvm_libunwind .llvm_libunwind
.map(|v| v.parse().expect("failed to parse rust.llvm-libunwind")); .map(|v| v.parse().expect("failed to parse rust.llvm-libunwind"));

View File

@ -1,6 +1,6 @@
use super::*; use super::*;
use crate::core::config::{Config, DryRun, TargetSelection};
use crate::core::build_steps::doc::DocumentationFormat; use crate::core::build_steps::doc::DocumentationFormat;
use crate::core::config::{Config, DryRun, TargetSelection};
use std::thread; use std::thread;
fn configure(cmd: &str, host: &[&str], target: &[&str]) -> Config { fn configure(cmd: &str, host: &[&str], target: &[&str]) -> Config {

View File

@ -0,0 +1,10 @@
// compile-flags:
#![crate_type = "lib"]
// A basic test function.
pub fn test() {
}
// Ensure the module flag ehcontguard is not present
// CHECK-NOT: !"ehcontguard"

View File

@ -0,0 +1,10 @@
// compile-flags: -C ehcont_guard
#![crate_type = "lib"]
// A basic test function.
pub fn test() {
}
// Ensure the module flag ehcontguard=1 is present
// CHECK: !"ehcontguard", i32 1