Add SafeStack support to rustc

Adds support for LLVM [SafeStack] which provides backward edge control
flow protection by separating the stack into two parts: data which is
only accessed in provable safe ways is allocated on the normal stack
(the "safe stack") and all other data is placed in a separate allocation
(the "unsafe stack").

SafeStack support is enabled by passing `-Zsanitizer=safestack`.

[SafeStack]: https://clang.llvm.org/docs/SafeStack.html
This commit is contained in:
Wesley Wiser 2023-05-19 19:30:15 -04:00
parent d22314e0f5
commit 019d75b44e
14 changed files with 71 additions and 10 deletions

View File

@ -88,6 +88,9 @@ pub fn sanitize_attrs<'ll>(
attrs.push(llvm::AttributeKind::SanitizeMemTag.create_attr(cx.llcx));
}
if enabled.contains(SanitizerSet::SAFESTACK) {
attrs.push(llvm::AttributeKind::SanitizeSafeStack.create_attr(cx.llcx));
}
attrs
}

View File

@ -196,6 +196,7 @@ pub enum AttributeKind {
AllocSize = 37,
AllocatedPointer = 38,
AllocAlign = 39,
SanitizeSafeStack = 40,
}
/// LLVMIntPredicate

View File

@ -1188,6 +1188,9 @@ fn add_sanitizer_libraries(sess: &Session, crate_type: CrateType, linker: &mut d
if sanitizer.contains(SanitizerSet::HWADDRESS) {
link_sanitizer_runtime(sess, linker, "hwasan");
}
if sanitizer.contains(SanitizerSet::SAFESTACK) {
link_sanitizer_runtime(sess, linker, "safestack");
}
}
fn link_sanitizer_runtime(sess: &Session, linker: &mut dyn Linker, name: &str) {

View File

@ -96,6 +96,7 @@ enum LLVMRustAttribute {
AllocatedPointer = 38,
AllocAlign = 39,
#endif
SanitizeSafeStack = 40,
};
typedef struct OpaqueRustString *RustStringRef;

View File

@ -234,6 +234,8 @@ static Attribute::AttrKind fromRust(LLVMRustAttribute Kind) {
case AllocAlign:
return Attribute::AllocAlign;
#endif
case SanitizeSafeStack:
return Attribute::SafeStack;
}
report_fatal_error("bad AttributeKind");
}

View File

@ -372,7 +372,7 @@ mod desc {
pub const parse_opt_panic_strategy: &str = parse_panic_strategy;
pub const parse_oom_strategy: &str = "either `panic` or `abort`";
pub const parse_relro_level: &str = "one of: `full`, `partial`, or `off`";
pub const parse_sanitizers: &str = "comma separated list of sanitizers: `address`, `cfi`, `hwaddress`, `kcfi`, `kernel-address`, `leak`, `memory`, `memtag`, `shadow-call-stack`, or `thread`";
pub const parse_sanitizers: &str = "comma separated list of sanitizers: `address`, `cfi`, `hwaddress`, `kcfi`, `kernel-address`, `leak`, `memory`, `memtag`, `safestack`, `shadow-call-stack`, or `thread`";
pub const parse_sanitizer_memory_track_origins: &str = "0, 1, or 2";
pub const parse_cfguard: &str =
"either a boolean (`yes`, `no`, `on`, `off`, etc), `checks`, or `nochecks`";
@ -694,6 +694,7 @@ mod parse {
"shadow-call-stack" => SanitizerSet::SHADOWCALLSTACK,
"thread" => SanitizerSet::THREAD,
"hwaddress" => SanitizerSet::HWADDRESS,
"safestack" => SanitizerSet::SAFESTACK,
_ => return false,
}
}

View File

@ -815,6 +815,7 @@ bitflags::bitflags! {
const SHADOWCALLSTACK = 1 << 7;
const KCFI = 1 << 8;
const KERNELADDRESS = 1 << 9;
const SAFESTACK = 1 << 10;
}
}
@ -831,6 +832,7 @@ impl SanitizerSet {
SanitizerSet::LEAK => "leak",
SanitizerSet::MEMORY => "memory",
SanitizerSet::MEMTAG => "memtag",
SanitizerSet::SAFESTACK => "safestack",
SanitizerSet::SHADOWCALLSTACK => "shadow-call-stack",
SanitizerSet::THREAD => "thread",
SanitizerSet::HWADDRESS => "hwaddress",
@ -871,6 +873,7 @@ impl IntoIterator for SanitizerSet {
SanitizerSet::THREAD,
SanitizerSet::HWADDRESS,
SanitizerSet::KERNELADDRESS,
SanitizerSet::SAFESTACK,
]
.iter()
.copied()
@ -2364,6 +2367,7 @@ impl Target {
Some("leak") => SanitizerSet::LEAK,
Some("memory") => SanitizerSet::MEMORY,
Some("memtag") => SanitizerSet::MEMTAG,
Some("safestack") => SanitizerSet::SAFESTACK,
Some("shadow-call-stack") => SanitizerSet::SHADOWCALLSTACK,
Some("thread") => SanitizerSet::THREAD,
Some("hwaddress") => SanitizerSet::HWADDRESS,

View File

@ -11,6 +11,7 @@ pub fn target() -> Target {
| SanitizerSet::CFI
| SanitizerSet::LEAK
| SanitizerSet::MEMORY
| SanitizerSet::SAFESTACK
| SanitizerSet::THREAD;
base.supports_xray = true;

View File

@ -1017,7 +1017,7 @@ fn supported_sanitizers(
"x86_64-unknown-illumos" => common_libs("illumos", "x86_64", &["asan"]),
"x86_64-pc-solaris" => common_libs("solaris", "x86_64", &["asan"]),
"x86_64-unknown-linux-gnu" => {
common_libs("linux", "x86_64", &["asan", "lsan", "msan", "tsan"])
common_libs("linux", "x86_64", &["asan", "lsan", "msan", "safestack", "tsan"])
}
"x86_64-unknown-linux-musl" => {
common_libs("linux", "x86_64", &["asan", "lsan", "msan", "tsan"])

View File

@ -66,7 +66,7 @@ equivalent.
| Heap corruption protection | Yes | 1.32.0 (2019-01-17) (via operating system default or specified allocator) |
| Stack smashing protection | Yes | Nightly |
| Forward-edge control flow protection | Yes | Nightly |
| Backward-edge control flow protection (e.g., shadow and safe stack) | No | |
| Backward-edge control flow protection (e.g., shadow and safe stack) | Yes | Nightly |
<small id="fn:1">1\. See
<https://github.com/rust-lang/rust/tree/master/compiler/rustc_target/src/spec>
@ -443,20 +443,21 @@ Newer processors provide hardware assistance for backward-edge control flow
protection, such as ARM Pointer Authentication, and Intel Shadow Stack as
part of Intel CET.
The Rust compiler does not support shadow or safe stack. There is work
currently ongoing to add support for the sanitizers[40], which may or may
not include support for safe stack<sup id="fnref:7" role="doc-noteref"><a
href="#fn:7" class="footnote">7</a></sup>.
The Rust compiler supports shadow stack for aarch64 only
<sup id="fnref:7" role="doc-noteref"><a href="#fn:7" class="footnote">7</a></sup>
on nightly Rust compilers [43]-[44]. Safe stack is available on nightly
Rust compilers [45]-[46].
```text
$ readelf -s target/release/hello-rust | grep __safestack_init
1177: 00000000000057b0 444 FUNC GLOBAL DEFAULT 9 __safestack_init
```
Fig. 16.Checking if LLVM SafeStack is enabled for a given binary.
The presence of the `__safestack_init` symbol indicates that LLVM SafeStack
is enabled for a given binary. Conversely, the absence of the
is enabled for a given binary (see Fig. 16). Conversely, the absence of the
`__safestack_init` symbol indicates that LLVM SafeStack is not enabled for a
given binary (see Fig. 16).
given binary.
<small id="fn:7">7\. The shadow stack implementation for the AMD64
architecture and equivalent in LLVM was removed due to performance and
@ -628,3 +629,15 @@ defaults (unrelated to `READ_IMPLIES_EXEC`).
42. bbjornse. “add codegen option for using LLVM stack smash protection #84197.”
GitHub. <https://github.com/rust-lang/rust/pull/84197>
43. ivanloz. “Add support for LLVM ShadowCallStack. #98208.” GitHub.
<https://github.com/rust-lang/rust/pull/98208>.
44. “ShadowCallStack.” The Rust Unstable Book.
[https://doc.rust-lang.org/unstable-book/compiler-flags/sanitizer.html#shadowcallstack](../unstable-book/compiler-flags/sanitizer.html#shadowcallstack).
45. W. Wiser. “Add support for LLVM SafeStack #112000” GitHub.
<https://github.com/rust-lang/rust/pull/112000>
46. “SafeStack.” The Rust Unstable Book.
[https://doc.rust-lang/org/unstable-book/compiler-flags/sanitizer.html#safestack](../unstable-book/compiler-flags/sanitizer.html#safestack).

View File

@ -21,7 +21,8 @@ This feature allows for use of one of following sanitizers:
* [MemorySanitizer](#memorysanitizer) a detector of uninitialized reads.
* [MemTagSanitizer](#memtagsanitizer) fast memory error detector based on
Armv8.5-A Memory Tagging Extension.
* [ShadowCallStack](#shadowcallstack) provides backward-edge control flow protection.
* [SafeStack](#safestack) provides backward-edge control flow protection by separating the stack into safe and unsafe regions.
* [ShadowCallStack](#shadowcallstack) provides backward-edge control flow protection (aarch64 only).
* [ThreadSanitizer](#threadsanitizer) a fast data race detector.
To enable a sanitizer compile with `-Zsanitizer=address`,`-Zsanitizer=cfi`,
@ -712,6 +713,16 @@ To enable this target feature compile with `-C target-feature="+mte"`.
See the [LLVM MemTagSanitizer documentation][llvm-memtag] for more details.
# SafeStack
SafeStack provides backward edge control flow protection by separating the stack into data which is only accessed safely (the safe stack) and all other data (the unsafe stack).
SafeStack can be enabled with the `-Zsanitizer=safestack` option and is supported on the following targets:
* `x86_64-unknown-linux-gnu`
See the [Clang SafeStack documentation][clang-safestack] for more details.
# ShadowCallStack
ShadowCallStack provides backward edge control flow protection by storing a function's return address in a separately allocated 'shadow call stack' and loading the return address from that shadow call stack.
@ -828,6 +839,7 @@ Sanitizers produce symbolized stacktraces when llvm-symbolizer binary is in `PAT
[clang-kcfi]: https://clang.llvm.org/docs/ControlFlowIntegrity.html#fsanitize-kcfi
[clang-lsan]: https://clang.llvm.org/docs/LeakSanitizer.html
[clang-msan]: https://clang.llvm.org/docs/MemorySanitizer.html
[clang-safestack]: https://clang.llvm.org/docs/SafeStack.html
[clang-scs]: https://clang.llvm.org/docs/ShadowCallStack.html
[clang-tsan]: https://clang.llvm.org/docs/ThreadSanitizer.html
[linux-kasan]: https://www.kernel.org/doc/html/latest/dev-tools/kasan.html

View File

@ -70,6 +70,11 @@ pub(super) fn handle_needs(
condition: cache.sanitizer_shadow_call_stack,
ignore_reason: "ignored on targets without shadow call stacks",
},
Need {
name: "needs-sanitizer-safestack",
condition: cache.sanitizer_safestack,
ignore_reason: "ignored on targets without SafeStack support",
},
Need {
name: "needs-run-enabled",
condition: config.run_enabled(),
@ -184,6 +189,7 @@ pub(super) struct CachedNeedsConditions {
sanitizer_hwaddress: bool,
sanitizer_memtag: bool,
sanitizer_shadow_call_stack: bool,
sanitizer_safestack: bool,
xray: bool,
rust_lld: bool,
i686_dlltool: bool,
@ -220,6 +226,7 @@ impl CachedNeedsConditions {
sanitizer_hwaddress: util::HWASAN_SUPPORTED_TARGETS.contains(target),
sanitizer_memtag: util::MEMTAG_SUPPORTED_TARGETS.contains(target),
sanitizer_shadow_call_stack: util::SHADOWCALLSTACK_SUPPORTED_TARGETS.contains(target),
sanitizer_safestack: util::SAFESTACK_SUPPORTED_TARGETS.contains(target),
xray: util::XRAY_SUPPORTED_TARGETS.contains(target),
// For tests using the `needs-rust-lld` directive (e.g. for `-Zgcc-ld=lld`), we need to find

View File

@ -104,6 +104,8 @@ pub const XRAY_SUPPORTED_TARGETS: &[&str] = &[
"x86_64-unknown-openbsd",
];
pub const SAFESTACK_SUPPORTED_TARGETS: &[&str] = &["x86_64-unknown-linux-gnu"];
pub fn make_new_path(path: &str) -> String {
assert!(cfg!(windows));
// Windows just uses PATH as the library search path, so we have to

View File

@ -0,0 +1,11 @@
// This tests that the safestack attribute is applied when enabling the safe-stack sanitizer.
//
// needs-sanitizer-safestack
// compile-flags: -Zsanitizer=safestack
#![crate_type = "lib"]
// CHECK: ; Function Attrs:{{.*}}safestack
pub fn tagged() {}
// CHECK: attributes #0 = {{.*}}safestack