diff --git a/src/doc/unstable-book/src/language-features/no-sanitize.md b/src/doc/unstable-book/src/language-features/no-sanitize.md new file mode 100644 index 00000000000..28c683934d4 --- /dev/null +++ b/src/doc/unstable-book/src/language-features/no-sanitize.md @@ -0,0 +1,29 @@ +# `no_sanitize` + +The tracking issue for this feature is: [#39699] + +[#39699]: https://github.com/rust-lang/rust/issues/39699 + +------------------------ + +The `no_sanitize` attribute can be used to selectively disable sanitizer +instrumentation in an annotated function. This might be useful to: avoid +instrumentation overhead in a performance critical function, or avoid +instrumenting code that contains constructs unsupported by given sanitizer. + +The precise effect of this annotation depends on particular sanitizer in use. +For example, with `no_sanitize(thread)`, the thread sanitizer will no longer +instrument non-atomic store / load operations, but it will instrument atomic +operations to avoid reporting false positives and provide meaning full stack +traces. + +## Examples + +``` rust +#![feature(no_sanitize)] + +#[no_sanitize(address)] +fn foo() { + // ... +} +``` diff --git a/src/librustc/middle/codegen_fn_attrs.rs b/src/librustc/middle/codegen_fn_attrs.rs index 9f8c2020861..82adcfddc28 100644 --- a/src/librustc/middle/codegen_fn_attrs.rs +++ b/src/librustc/middle/codegen_fn_attrs.rs @@ -72,6 +72,14 @@ bitflags! { const FFI_RETURNS_TWICE = 1 << 10; /// `#[track_caller]`: allow access to the caller location const TRACK_CALLER = 1 << 11; + /// `#[no_sanitize(address)]`: disables address sanitizer instrumentation + const NO_SANITIZE_ADDRESS = 1 << 12; + /// `#[no_sanitize(memory)]`: disables memory sanitizer instrumentation + const NO_SANITIZE_MEMORY = 1 << 13; + /// `#[no_sanitize(thread)]`: disables thread sanitizer instrumentation + const NO_SANITIZE_THREAD = 1 << 14; + /// All `#[no_sanitize(...)]` attributes. + const NO_SANITIZE_ANY = Self::NO_SANITIZE_ADDRESS.bits | Self::NO_SANITIZE_MEMORY.bits | Self::NO_SANITIZE_THREAD.bits; } } diff --git a/src/librustc_codegen_llvm/attributes.rs b/src/librustc_codegen_llvm/attributes.rs index e3920d99c90..a9e4fdba030 100644 --- a/src/librustc_codegen_llvm/attributes.rs +++ b/src/librustc_codegen_llvm/attributes.rs @@ -46,6 +46,31 @@ fn inline(cx: &CodegenCx<'ll, '_>, val: &'ll Value, inline: InlineAttr) { }; } +/// Apply LLVM sanitize attributes. +#[inline] +pub fn sanitize(cx: &CodegenCx<'ll, '_>, codegen_fn_flags: CodegenFnAttrFlags, llfn: &'ll Value) { + if let Some(ref sanitizer) = cx.tcx.sess.opts.debugging_opts.sanitizer { + match *sanitizer { + Sanitizer::Address => { + if !codegen_fn_flags.contains(CodegenFnAttrFlags::NO_SANITIZE_ADDRESS) { + llvm::Attribute::SanitizeAddress.apply_llfn(Function, llfn); + } + } + Sanitizer::Memory => { + if !codegen_fn_flags.contains(CodegenFnAttrFlags::NO_SANITIZE_MEMORY) { + llvm::Attribute::SanitizeMemory.apply_llfn(Function, llfn); + } + } + Sanitizer::Thread => { + if !codegen_fn_flags.contains(CodegenFnAttrFlags::NO_SANITIZE_THREAD) { + llvm::Attribute::SanitizeThread.apply_llfn(Function, llfn); + } + } + Sanitizer::Leak => {} + } + } +} + /// Tell LLVM to emit or not emit the information necessary to unwind the stack for the function. #[inline] pub fn emit_uwtable(val: &'ll Value, emit: bool) { @@ -288,6 +313,7 @@ pub fn from_fn_attrs( if codegen_fn_attrs.flags.contains(CodegenFnAttrFlags::ALLOCATOR) { Attribute::NoAlias.apply_llfn(llvm::AttributePlace::ReturnValue, llfn); } + sanitize(cx, codegen_fn_attrs.flags, llfn); unwind( llfn, diff --git a/src/librustc_codegen_llvm/base.rs b/src/librustc_codegen_llvm/base.rs index d3b524c1a1e..04c084e459e 100644 --- a/src/librustc_codegen_llvm/base.rs +++ b/src/librustc_codegen_llvm/base.rs @@ -15,6 +15,7 @@ use super::ModuleLlvm; +use crate::attributes; use crate::builder::Builder; use crate::common; use crate::context::CodegenCx; @@ -23,7 +24,7 @@ use crate::metadata; use crate::value::Value; use rustc::dep_graph; -use rustc::middle::codegen_fn_attrs::CodegenFnAttrs; +use rustc::middle::codegen_fn_attrs::{CodegenFnAttrFlags, CodegenFnAttrs}; use rustc::middle::cstore::EncodedMetadata; use rustc::middle::exported_symbols; use rustc::mir::mono::{Linkage, Visibility}; @@ -131,7 +132,9 @@ pub fn compile_codegen_unit( // If this codegen unit contains the main function, also create the // wrapper here - maybe_create_entry_wrapper::>(&cx); + if let Some(entry) = maybe_create_entry_wrapper::>(&cx) { + attributes::sanitize(&cx, CodegenFnAttrFlags::empty(), entry); + } // Run replace-all-uses-with for statics that need it for &(old_g, new_g) in cx.statics_to_rauw().borrow().iter() { diff --git a/src/librustc_codegen_llvm/declare.rs b/src/librustc_codegen_llvm/declare.rs index bb06b521621..691f32dd85a 100644 --- a/src/librustc_codegen_llvm/declare.rs +++ b/src/librustc_codegen_llvm/declare.rs @@ -19,7 +19,6 @@ use crate::llvm::AttributePlace::Function; use crate::type_::Type; use crate::value::Value; use log::debug; -use rustc::session::config::Sanitizer; use rustc::ty::Ty; use rustc_codegen_ssa::traits::*; use rustc_data_structures::small_c_str::SmallCStr; @@ -47,21 +46,6 @@ fn declare_raw_fn( llvm::Attribute::NoRedZone.apply_llfn(Function, llfn); } - if let Some(ref sanitizer) = cx.tcx.sess.opts.debugging_opts.sanitizer { - match *sanitizer { - Sanitizer::Address => { - llvm::Attribute::SanitizeAddress.apply_llfn(Function, llfn); - } - Sanitizer::Memory => { - llvm::Attribute::SanitizeMemory.apply_llfn(Function, llfn); - } - Sanitizer::Thread => { - llvm::Attribute::SanitizeThread.apply_llfn(Function, llfn); - } - _ => {} - } - } - attributes::default_optimisation_attrs(cx.tcx.sess, llfn); attributes::non_lazy_bind(cx.sess(), llfn); llfn diff --git a/src/librustc_codegen_ssa/base.rs b/src/librustc_codegen_ssa/base.rs index 1f43a4027c5..90015091384 100644 --- a/src/librustc_codegen_ssa/base.rs +++ b/src/librustc_codegen_ssa/base.rs @@ -391,10 +391,12 @@ pub fn codegen_instance<'a, 'tcx: 'a, Bx: BuilderMethods<'a, 'tcx>>( /// Creates the `main` function which will initialize the rust runtime and call /// users main function. -pub fn maybe_create_entry_wrapper<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(cx: &'a Bx::CodegenCx) { +pub fn maybe_create_entry_wrapper<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>( + cx: &'a Bx::CodegenCx, +) -> Option { let (main_def_id, span) = match cx.tcx().entry_fn(LOCAL_CRATE) { Some((def_id, _)) => (def_id, cx.tcx().def_span(def_id)), - None => return, + None => return None, }; let instance = Instance::mono(cx.tcx(), main_def_id); @@ -402,17 +404,15 @@ pub fn maybe_create_entry_wrapper<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(cx: &' if !cx.codegen_unit().contains_item(&MonoItem::Fn(instance)) { // We want to create the wrapper in the same codegen unit as Rust's main // function. - return; + return None; } let main_llfn = cx.get_fn_addr(instance); - let et = cx.tcx().entry_fn(LOCAL_CRATE).map(|e| e.1); - match et { - Some(EntryFnType::Main) => create_entry_fn::(cx, span, main_llfn, main_def_id, true), - Some(EntryFnType::Start) => create_entry_fn::(cx, span, main_llfn, main_def_id, false), - None => {} // Do nothing. - } + return cx.tcx().entry_fn(LOCAL_CRATE).map(|(_, et)| { + let use_start_lang_item = EntryFnType::Start != et; + create_entry_fn::(cx, span, main_llfn, main_def_id, use_start_lang_item) + }); fn create_entry_fn<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>( cx: &'a Bx::CodegenCx, @@ -420,7 +420,7 @@ pub fn maybe_create_entry_wrapper<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(cx: &' rust_main: Bx::Value, rust_main_def_id: DefId, use_start_lang_item: bool, - ) { + ) -> Bx::Function { // The entry function is either `int main(void)` or `int main(int argc, char **argv)`, // depending on whether the target needs `argc` and `argv` to be passed in. let llfty = if cx.sess().target.target.options.main_needs_argc_argv { @@ -481,6 +481,8 @@ pub fn maybe_create_entry_wrapper<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(cx: &' let result = bx.call(start_fn, &args, None); let cast = bx.intcast(result, cx.type_int(), true); bx.ret(cast); + + llfn } } diff --git a/src/librustc_feature/active.rs b/src/librustc_feature/active.rs index 4ae79f9ccaa..d7fd15a8a7b 100644 --- a/src/librustc_feature/active.rs +++ b/src/librustc_feature/active.rs @@ -541,6 +541,9 @@ declare_features! ( /// Allows `T: ?const Trait` syntax in bounds. (active, const_trait_bound_opt_out, "1.42.0", Some(67794), None), + /// Allows the use of `no_sanitize` attribute. + (active, no_sanitize, "1.42.0", Some(39699), None), + // ------------------------------------------------------------------------- // feature-group-end: actual feature gates // ------------------------------------------------------------------------- diff --git a/src/librustc_feature/builtin_attrs.rs b/src/librustc_feature/builtin_attrs.rs index a38726e3de8..e2e061c185c 100644 --- a/src/librustc_feature/builtin_attrs.rs +++ b/src/librustc_feature/builtin_attrs.rs @@ -261,6 +261,11 @@ pub const BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[ ungated!(cold, Whitelisted, template!(Word)), ungated!(no_builtins, Whitelisted, template!(Word)), ungated!(target_feature, Whitelisted, template!(List: r#"enable = "name""#)), + gated!( + no_sanitize, Whitelisted, + template!(List: "address, memory, thread"), + experimental!(no_sanitize) + ), // FIXME: #14408 whitelist docs since rustdoc looks at them ungated!(doc, Whitelisted, template!(List: "hidden|inline|...", NameValueStr: "string")), diff --git a/src/librustc_mir/transform/inline.rs b/src/librustc_mir/transform/inline.rs index a3cafcb5763..b6802505df7 100644 --- a/src/librustc_mir/transform/inline.rs +++ b/src/librustc_mir/transform/inline.rs @@ -8,6 +8,7 @@ use rustc_index::vec::{Idx, IndexVec}; use rustc::middle::codegen_fn_attrs::CodegenFnAttrFlags; use rustc::mir::visit::*; use rustc::mir::*; +use rustc::session::config::Sanitizer; use rustc::ty::subst::{InternalSubsts, Subst, SubstsRef}; use rustc::ty::{self, Instance, InstanceDef, ParamEnv, Ty, TyCtxt, TypeFoldable}; @@ -228,6 +229,28 @@ impl Inliner<'tcx> { return false; } + // Avoid inlining functions marked as no_sanitize if sanitizer is enabled, + // since instrumentation might be enabled and performed on the caller. + match self.tcx.sess.opts.debugging_opts.sanitizer { + Some(Sanitizer::Address) => { + if codegen_fn_attrs.flags.contains(CodegenFnAttrFlags::NO_SANITIZE_ADDRESS) { + return false; + } + } + Some(Sanitizer::Memory) => { + if codegen_fn_attrs.flags.contains(CodegenFnAttrFlags::NO_SANITIZE_MEMORY) { + return false; + } + } + Some(Sanitizer::Thread) => { + if codegen_fn_attrs.flags.contains(CodegenFnAttrFlags::NO_SANITIZE_THREAD) { + return false; + } + } + Some(Sanitizer::Leak) => {} + None => {} + } + let hinted = match codegen_fn_attrs.inline { // Just treat inline(always) as a hint for now, // there are cases that prevent inlining that we diff --git a/src/librustc_session/lint/builtin.rs b/src/librustc_session/lint/builtin.rs index c326061100b..a61ab5b5e17 100644 --- a/src/librustc_session/lint/builtin.rs +++ b/src/librustc_session/lint/builtin.rs @@ -474,6 +474,12 @@ declare_lint! { }; } +declare_lint! { + pub INLINE_NO_SANITIZE, + Warn, + "detects incompatible use of `#[inline(always)]` and `#[no_sanitize(...)]`", +} + declare_lint_pass! { /// Does nothing as a lint pass, but registers some `Lint`s /// that are used by other parts of the compiler. @@ -537,5 +543,6 @@ declare_lint_pass! { MUTABLE_BORROW_RESERVATION_CONFLICT, INDIRECT_STRUCTURAL_MATCH, SOFT_UNSTABLE, + INLINE_NO_SANITIZE, ] } diff --git a/src/librustc_span/symbol.rs b/src/librustc_span/symbol.rs index c060e8948e3..931a3c15cf0 100644 --- a/src/librustc_span/symbol.rs +++ b/src/librustc_span/symbol.rs @@ -120,6 +120,7 @@ symbols! { abi_vectorcall, abi_x86_interrupt, aborts, + address, add_with_overflow, advanced_slice_patterns, adx_target_feature, @@ -445,6 +446,7 @@ symbols! { mem_uninitialized, mem_zeroed, member_constraints, + memory, message, meta, min_align_of, @@ -487,6 +489,7 @@ symbols! { None, non_exhaustive, non_modrs_mods, + no_sanitize, no_stack_check, no_start, no_std, @@ -721,6 +724,7 @@ symbols! { test_removed_feature, test_runner, then_with, + thread, thread_local, tool_attributes, tool_lints, diff --git a/src/librustc_typeck/collect.rs b/src/librustc_typeck/collect.rs index dc089c90456..040b85b98ed 100644 --- a/src/librustc_typeck/collect.rs +++ b/src/librustc_typeck/collect.rs @@ -1,3 +1,5 @@ +// ignore-tidy-filelength + //! "Collection" is the process of determining the type and other external //! details of each item in Rust. Collection is specifically concerned //! with *inter-procedural* things -- for example, for a function @@ -2743,6 +2745,7 @@ fn codegen_fn_attrs(tcx: TyCtxt<'_>, id: DefId) -> CodegenFnAttrs { let mut inline_span = None; let mut link_ordinal_span = None; + let mut no_sanitize_span = None; for attr in attrs.iter() { if attr.check_name(sym::cold) { codegen_fn_attrs.flags |= CodegenFnAttrFlags::COLD; @@ -2832,6 +2835,24 @@ fn codegen_fn_attrs(tcx: TyCtxt<'_>, id: DefId) -> CodegenFnAttrs { if let ordinal @ Some(_) = check_link_ordinal(tcx, attr) { codegen_fn_attrs.link_ordinal = ordinal; } + } else if attr.check_name(sym::no_sanitize) { + no_sanitize_span = Some(attr.span); + if let Some(list) = attr.meta_item_list() { + for item in list.iter() { + if item.check_name(sym::address) { + codegen_fn_attrs.flags |= CodegenFnAttrFlags::NO_SANITIZE_ADDRESS; + } else if item.check_name(sym::memory) { + codegen_fn_attrs.flags |= CodegenFnAttrFlags::NO_SANITIZE_MEMORY; + } else if item.check_name(sym::thread) { + codegen_fn_attrs.flags |= CodegenFnAttrFlags::NO_SANITIZE_THREAD; + } else { + tcx.sess + .struct_span_err(item.span(), "invalid argument for `no_sanitize`") + .note("expected one of: `address`, `memory` or `thread`") + .emit(); + } + } + } } } @@ -2911,7 +2932,6 @@ fn codegen_fn_attrs(tcx: TyCtxt<'_>, id: DefId) -> CodegenFnAttrs { // purpose functions as they wouldn't have the right target features // enabled. For that reason we also forbid #[inline(always)] as it can't be // respected. - if codegen_fn_attrs.target_features.len() > 0 { if codegen_fn_attrs.inline == InlineAttr::Always { if let Some(span) = inline_span { @@ -2924,6 +2944,22 @@ fn codegen_fn_attrs(tcx: TyCtxt<'_>, id: DefId) -> CodegenFnAttrs { } } + if codegen_fn_attrs.flags.intersects(CodegenFnAttrFlags::NO_SANITIZE_ANY) { + if codegen_fn_attrs.inline == InlineAttr::Always { + if let (Some(no_sanitize_span), Some(inline_span)) = (no_sanitize_span, inline_span) { + let hir_id = tcx.hir().as_local_hir_id(id).unwrap(); + tcx.struct_span_lint_hir( + lint::builtin::INLINE_NO_SANITIZE, + hir_id, + no_sanitize_span, + "`no_sanitize` will have no effect after inlining", + ) + .span_note(inline_span, "inlining requested here") + .emit(); + } + } + } + // Weak lang items have the same semantics as "std internal" symbols in the // sense that they're preserved through all our LTO passes and only // strippable by the linker. diff --git a/src/test/codegen/sanitizer-no-sanitize-inlining.rs b/src/test/codegen/sanitizer-no-sanitize-inlining.rs new file mode 100644 index 00000000000..d96e76618d3 --- /dev/null +++ b/src/test/codegen/sanitizer-no-sanitize-inlining.rs @@ -0,0 +1,32 @@ +// Verifies that no_sanitize attribute prevents inlining when +// given sanitizer is enabled, but has no effect on inlining otherwise. +// +// needs-sanitizer-support +// only-x86_64 +// +// revisions: ASAN LSAN +// +//[ASAN] compile-flags: -Zsanitizer=address -C opt-level=3 -Z mir-opt-level=3 +//[LSAN] compile-flags: -Zsanitizer=leak -C opt-level=3 -Z mir-opt-level=3 + +#![crate_type="lib"] +#![feature(no_sanitize)] + +// ASAN-LABEL: define void @test +// ASAN: tail call fastcc void @random_inline +// ASAN: } +// +// LSAN-LABEL: define void @test +// LSAN-NO: call +// LSAN: } +#[no_mangle] +pub fn test(n: &mut u32) { + random_inline(n); +} + +#[no_sanitize(address)] +#[inline] +#[no_mangle] +pub fn random_inline(n: &mut u32) { + *n = 42; +} diff --git a/src/test/codegen/sanitizer-no-sanitize.rs b/src/test/codegen/sanitizer-no-sanitize.rs new file mode 100644 index 00000000000..dfceb28c8dd --- /dev/null +++ b/src/test/codegen/sanitizer-no-sanitize.rs @@ -0,0 +1,29 @@ +// Verifies that no_sanitze attribute can be used to +// selectively disable sanitizer instrumentation. +// +// needs-sanitizer-support +// compile-flags: -Zsanitizer=address + +#![crate_type="lib"] +#![feature(no_sanitize)] + +// CHECK-LABEL: ; sanitizer_no_sanitize::unsanitized +// CHECK-NEXT: ; Function Attrs: +// CHECK-NOT: sanitize_address +// CHECK: start: +// CHECK-NOT: call void @__asan_report_load +// CHECK: } +#[no_sanitize(address)] +pub fn unsanitized(b: &mut u8) -> u8 { + *b +} + +// CHECK-LABEL: ; sanitizer_no_sanitize::sanitized +// CHECK-NEXT: ; Function Attrs: +// CHECK: sanitize_address +// CHECK: start: +// CHECK: call void @__asan_report_load +// CHECK: } +pub fn sanitized(b: &mut u8) -> u8 { + *b +} diff --git a/src/test/ui/feature-gates/feature-gate-no_sanitize.rs b/src/test/ui/feature-gates/feature-gate-no_sanitize.rs new file mode 100644 index 00000000000..66a9263e13a --- /dev/null +++ b/src/test/ui/feature-gates/feature-gate-no_sanitize.rs @@ -0,0 +1,4 @@ +#[no_sanitize(address)] +//~^ the `#[no_sanitize]` attribute is an experimental feature +fn main() { +} diff --git a/src/test/ui/feature-gates/feature-gate-no_sanitize.stderr b/src/test/ui/feature-gates/feature-gate-no_sanitize.stderr new file mode 100644 index 00000000000..7359cf03652 --- /dev/null +++ b/src/test/ui/feature-gates/feature-gate-no_sanitize.stderr @@ -0,0 +1,12 @@ +error[E0658]: the `#[no_sanitize]` attribute is an experimental feature + --> $DIR/feature-gate-no_sanitize.rs:1:1 + | +LL | #[no_sanitize(address)] + | ^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: for more information, see https://github.com/rust-lang/rust/issues/39699 + = help: add `#![feature(no_sanitize)]` to the crate attributes to enable + +error: aborting due to previous error + +For more information about this error, try `rustc --explain E0658`. diff --git a/src/test/ui/invalid/invalid-no-sanitize.rs b/src/test/ui/invalid/invalid-no-sanitize.rs new file mode 100644 index 00000000000..b52e3cc83fa --- /dev/null +++ b/src/test/ui/invalid/invalid-no-sanitize.rs @@ -0,0 +1,5 @@ +#![feature(no_sanitize)] + +#[no_sanitize(brontosaurus)] //~ ERROR invalid argument +fn main() { +} diff --git a/src/test/ui/invalid/invalid-no-sanitize.stderr b/src/test/ui/invalid/invalid-no-sanitize.stderr new file mode 100644 index 00000000000..e9983e5fbd2 --- /dev/null +++ b/src/test/ui/invalid/invalid-no-sanitize.stderr @@ -0,0 +1,10 @@ +error: invalid argument for `no_sanitize` + --> $DIR/invalid-no-sanitize.rs:3:15 + | +LL | #[no_sanitize(brontosaurus)] + | ^^^^^^^^^^^^ + | + = note: expected one of: `address`, `memory` or `thread` + +error: aborting due to previous error + diff --git a/src/test/ui/sanitize-inline-always.rs b/src/test/ui/sanitize-inline-always.rs new file mode 100644 index 00000000000..52dc5578180 --- /dev/null +++ b/src/test/ui/sanitize-inline-always.rs @@ -0,0 +1,15 @@ +// check-pass + +#![feature(no_sanitize)] + +#[inline(always)] +//~^ NOTE inlining requested here +#[no_sanitize(address)] +//~^ WARN will have no effect after inlining +//~| NOTE on by default +fn x() { +} + +fn main() { + x() +} diff --git a/src/test/ui/sanitize-inline-always.stderr b/src/test/ui/sanitize-inline-always.stderr new file mode 100644 index 00000000000..50b9474baa2 --- /dev/null +++ b/src/test/ui/sanitize-inline-always.stderr @@ -0,0 +1,13 @@ +warning: `no_sanitize` will have no effect after inlining + --> $DIR/sanitize-inline-always.rs:7:1 + | +LL | #[no_sanitize(address)] + | ^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `#[warn(inline_no_sanitize)]` on by default +note: inlining requested here + --> $DIR/sanitize-inline-always.rs:5:1 + | +LL | #[inline(always)] + | ^^^^^^^^^^^^^^^^^ +