Skip to content

Commit

Permalink
[Clang] Introduce -fexperimental-sanitize-metadata=
Browse files Browse the repository at this point in the history
Introduces the frontend flag -fexperimental-sanitize-metadata=, which
enables SanitizerBinaryMetadata instrumentation.

The first intended user of the binary metadata emitted will be a variant
of GWP-TSan [1]. The plan is to open source a stable and production
quality version of GWP-TSan. The development of which, however, requires
upstream compiler support.

[1] https://llvm.org/devmtg/2020-09/slides/Morehouse-GWP-Tsan.pdf

Until the tool has been open sourced, we mark this kind of
instrumentation as "experimental", and reserve the option to change
binary format, remove features, and similar.

Reviewed By: vitalybuka, MaskRay

Differential Revision: https://reviews.llvm.org/D130888
  • Loading branch information
melver committed Sep 7, 2022
1 parent 97c2220 commit c4842bb
Show file tree
Hide file tree
Showing 8 changed files with 149 additions and 0 deletions.
2 changes: 2 additions & 0 deletions clang/include/clang/Basic/CodeGenOptions.def
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,8 @@ CODEGENOPT(SanitizeCoverageNoPrune, 1, 0) ///< Disable coverage pruning.
CODEGENOPT(SanitizeCoverageStackDepth, 1, 0) ///< Enable max stack depth tracing
CODEGENOPT(SanitizeCoverageTraceLoads, 1, 0) ///< Enable tracing of loads.
CODEGENOPT(SanitizeCoverageTraceStores, 1, 0) ///< Enable tracing of stores.
CODEGENOPT(SanitizeBinaryMetadataCovered, 1, 0) ///< Emit PCs for covered functions.
CODEGENOPT(SanitizeBinaryMetadataAtomics, 1, 0) ///< Emit PCs for atomic operations.
CODEGENOPT(SanitizeStats , 1, 0) ///< Collect statistics for sanitizers.
CODEGENOPT(SimplifyLibCalls , 1, 1) ///< Set when -fbuiltin is enabled.
CODEGENOPT(SoftFloat , 1, 0) ///< -soft-float.
Expand Down
5 changes: 5 additions & 0 deletions clang/include/clang/Basic/CodeGenOptions.h
Original file line number Diff line number Diff line change
Expand Up @@ -485,6 +485,11 @@ class CodeGenOptions : public CodeGenOptionsBase {
SanitizeCoverageTraceCmp || SanitizeCoverageTraceLoads ||
SanitizeCoverageTraceStores;
}

// Check if any one of SanitizeBinaryMetadata* is enabled.
bool hasSanitizeBinaryMetadata() const {
return SanitizeBinaryMetadataCovered || SanitizeBinaryMetadataAtomics;
}
};

} // end namespace clang
Expand Down
14 changes: 14 additions & 0 deletions clang/include/clang/Driver/Options.td
Original file line number Diff line number Diff line change
Expand Up @@ -1673,6 +1673,12 @@ def fsanitize_coverage_ignorelist : Joined<["-"], "fsanitize-coverage-ignorelist
HelpText<"Disable sanitizer coverage instrumentation for modules and functions "
"that match the provided special case list, even the allowed ones">,
MarshallingInfoStringVector<CodeGenOpts<"SanitizeCoverageIgnorelistFiles">>;
def fexperimental_sanitize_metadata_EQ : CommaJoined<["-"], "fexperimental-sanitize-metadata=">,
Group<f_Group>,
HelpText<"Specify the type of metadata to emit for binary analysis sanitizers">;
def fno_experimental_sanitize_metadata_EQ : CommaJoined<["-"], "fno-experimental-sanitize-metadata=">,
Group<f_Group>, Flags<[CoreOption]>,
HelpText<"Disable emitting metadata for binary analysis sanitizers">;
def fsanitize_memory_track_origins_EQ : Joined<["-"], "fsanitize-memory-track-origins=">,
Group<f_clang_Group>,
HelpText<"Enable origins tracking in MemorySanitizer">,
Expand Down Expand Up @@ -5497,6 +5503,14 @@ def fsanitize_coverage_trace_stores
: Flag<["-"], "fsanitize-coverage-trace-stores">,
HelpText<"Enable tracing of stores">,
MarshallingInfoFlag<CodeGenOpts<"SanitizeCoverageTraceStores">>;
def fexperimental_sanitize_metadata_EQ_covered
: Flag<["-"], "fexperimental-sanitize-metadata=covered">,
HelpText<"Emit PCs for code covered with binary analysis sanitizers">,
MarshallingInfoFlag<CodeGenOpts<"SanitizeBinaryMetadataCovered">>;
def fexperimental_sanitize_metadata_EQ_atomics
: Flag<["-"], "fexperimental-sanitize-metadata=atomics">,
HelpText<"Emit PCs for atomic operations used by binary analysis sanitizers">,
MarshallingInfoFlag<CodeGenOpts<"SanitizeBinaryMetadataAtomics">>;
def fpatchable_function_entry_offset_EQ
: Joined<["-"], "fpatchable-function-entry-offset=">, MetaVarName<"<M>">,
HelpText<"Generate M NOPs before function entry">,
Expand Down
1 change: 1 addition & 0 deletions clang/include/clang/Driver/SanitizerArgs.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ class SanitizerArgs {
std::vector<std::string> CoverageAllowlistFiles;
std::vector<std::string> CoverageIgnorelistFiles;
int CoverageFeatures = 0;
int BinaryMetadataFeatures = 0;
int MsanTrackOrigins = 0;
bool MsanUseAfterDtor = true;
bool MsanParamRetval = false;
Expand Down
14 changes: 14 additions & 0 deletions clang/lib/CodeGen/BackendUtil.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@
#include "llvm/Transforms/Instrumentation/MemProfiler.h"
#include "llvm/Transforms/Instrumentation/MemorySanitizer.h"
#include "llvm/Transforms/Instrumentation/SanitizerCoverage.h"
#include "llvm/Transforms/Instrumentation/SanitizerBinaryMetadata.h"
#include "llvm/Transforms/Instrumentation/ThreadSanitizer.h"
#include "llvm/Transforms/ObjCARC.h"
#include "llvm/Transforms/Scalar.h"
Expand Down Expand Up @@ -218,6 +219,14 @@ getSancovOptsFromCGOpts(const CodeGenOptions &CGOpts) {
return Opts;
}

static SanitizerBinaryMetadataOptions
getSanitizerBinaryMetadataOptions(const CodeGenOptions &CGOpts) {
SanitizerBinaryMetadataOptions Opts;
Opts.Covered = CGOpts.SanitizeBinaryMetadataCovered;
Opts.Atomics = CGOpts.SanitizeBinaryMetadataAtomics;
return Opts;
}

// Check if ASan should use GC-friendly instrumentation for globals.
// First of all, there is no point if -fdata-sections is off (expect for MachO,
// where this is not a factor). Also, on ELF this feature requires an assembler
Expand Down Expand Up @@ -632,6 +641,11 @@ static void addSanitizers(const Triple &TargetTriple,
CodeGenOpts.SanitizeCoverageIgnorelistFiles));
}

if (CodeGenOpts.hasSanitizeBinaryMetadata()) {
MPM.addPass(SanitizerBinaryMetadataPass(
getSanitizerBinaryMetadataOptions(CodeGenOpts)));
}

auto MSanPass = [&](SanitizerMask Mask, bool CompileKernel) {
if (LangOpts.Sanitize.has(Mask)) {
int TrackOrigins = CodeGenOpts.SanitizeMemoryTrackOrigins;
Expand Down
59 changes: 59 additions & 0 deletions clang/lib/Driver/SanitizerArgs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,11 @@ enum CoverageFeature {
CoverageTraceStores = 1 << 17,
};

enum BinaryMetadataFeature {
BinaryMetadataCovered = 1 << 0,
BinaryMetadataAtomics = 1 << 1,
};

/// Parse a -fsanitize= or -fno-sanitize= argument's values, diagnosing any
/// invalid components. Returns a SanitizerMask.
static SanitizerMask parseArgValues(const Driver &D, const llvm::opt::Arg *A,
Expand All @@ -110,6 +115,11 @@ static SanitizerMask parseArgValues(const Driver &D, const llvm::opt::Arg *A,
static int parseCoverageFeatures(const Driver &D, const llvm::opt::Arg *A,
bool DiagnoseErrors);

/// Parse -f(no-)?sanitize-metadata= flag values, diagnosing any invalid
/// components. Returns OR of members of \c BinaryMetadataFeature enumeration.
static int parseBinaryMetadataFeatures(const Driver &D, const llvm::opt::Arg *A,
bool DiagnoseErrors);

/// Produce an argument string from ArgList \p Args, which shows how it
/// provides some sanitizer kind from \p Mask. For example, the argument list
/// "-fsanitize=thread,vptr -fsanitize=address" with mask \c NeedsUbsanRt
Expand Down Expand Up @@ -834,6 +844,22 @@ SanitizerArgs::SanitizerArgs(const ToolChain &TC,
DiagnoseErrors);
}

// Parse -f(no-)?sanitize-metadata.
for (const auto *Arg :
Args.filtered(options::OPT_fexperimental_sanitize_metadata_EQ,
options::OPT_fno_experimental_sanitize_metadata_EQ)) {
if (Arg->getOption().matches(
options::OPT_fexperimental_sanitize_metadata_EQ)) {
Arg->claim();
BinaryMetadataFeatures |=
parseBinaryMetadataFeatures(D, Arg, DiagnoseErrors);
} else {
Arg->claim();
BinaryMetadataFeatures &=
~parseBinaryMetadataFeatures(D, Arg, DiagnoseErrors);
}
}

SharedRuntime =
Args.hasFlag(options::OPT_shared_libsan, options::OPT_static_libsan,
TC.getTriple().isAndroid() || TC.getTriple().isOSFuchsia() ||
Expand Down Expand Up @@ -1095,6 +1121,17 @@ void SanitizerArgs::addArgs(const ToolChain &TC, const llvm::opt::ArgList &Args,
addSpecialCaseListOpt(Args, CmdArgs, "-fsanitize-coverage-ignorelist=",
CoverageIgnorelistFiles);

// Translate available BinaryMetadataFeatures to corresponding clang-cc1
// flags. Does not depend on any other sanitizers.
const std::pair<int, std::string> BinaryMetadataFlags[] = {
std::make_pair(BinaryMetadataCovered, "covered"),
std::make_pair(BinaryMetadataAtomics, "atomics")};
for (const auto &F : BinaryMetadataFlags) {
if (BinaryMetadataFeatures & F.first)
CmdArgs.push_back(
Args.MakeArgString("-fexperimental-sanitize-metadata=" + F.second));
}

if (TC.getTriple().isOSWindows() && needsUbsanRt()) {
// Instruct the code generator to embed linker directives in the object file
// that cause the required runtime libraries to be linked.
Expand Down Expand Up @@ -1341,6 +1378,28 @@ int parseCoverageFeatures(const Driver &D, const llvm::opt::Arg *A,
return Features;
}

int parseBinaryMetadataFeatures(const Driver &D, const llvm::opt::Arg *A,
bool DiagnoseErrors) {
assert(
A->getOption().matches(options::OPT_fexperimental_sanitize_metadata_EQ) ||
A->getOption().matches(
options::OPT_fno_experimental_sanitize_metadata_EQ));
int Features = 0;
for (int i = 0, n = A->getNumValues(); i != n; ++i) {
const char *Value = A->getValue(i);
int F = llvm::StringSwitch<int>(Value)
.Case("covered", BinaryMetadataCovered)
.Case("atomics", BinaryMetadataAtomics)
.Case("all", ~0)
.Default(0);
if (F == 0 && DiagnoseErrors)
D.Diag(clang::diag::err_drv_unsupported_option_argument)
<< A->getOption().getName() << Value;
Features |= F;
}
return Features;
}

std::string lastArgumentForMask(const Driver &D, const llvm::opt::ArgList &Args,
SanitizerMask Mask) {
for (llvm::opt::ArgList::const_reverse_iterator I = Args.rbegin(),
Expand Down
31 changes: 31 additions & 0 deletions clang/test/CodeGen/sanitize-metadata.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// RUN: %clang_cc1 -O -fexperimental-sanitize-metadata=atomics -triple x86_64-gnu-linux -x c -S -emit-llvm %s -o - | FileCheck %s --check-prefixes=CHECK,ATOMICS
// RUN: %clang_cc1 -O -fexperimental-sanitize-metadata=atomics -triple aarch64-gnu-linux -x c -S -emit-llvm %s -o - | FileCheck %s --check-prefixes=CHECK,ATOMICS

int x, y;

void empty() {
// CHECK-NOT: define dso_local void @empty() {{.*}} !pcsections
}

int atomics() {
// ATOMICS-LABEL: define dso_local i32 @atomics()
// ATOMICS-SAME: !pcsections ![[ATOMICS_COVERED:[0-9]+]]
// ATOMICS-NEXT: entry:
// ATOMICS-NEXT: atomicrmw add {{.*}} !pcsections ![[ATOMIC_OP:[0-9]+]]
// ATOMICS-NOT: load {{.*}} !pcsections
__atomic_fetch_add(&x, 1, __ATOMIC_RELAXED);
return y;
}
// ATOMICS-LABEL: __sanitizer_metadata_atomics.module_ctor
// ATOMICS: call void @__sanitizer_metadata_atomics_add(i32 1, ptr @__start_sanmd_atomics, ptr @__stop_sanmd_atomics)
// ATOMICS-LABEL: __sanitizer_metadata_atomics.module_dtor
// ATOMICS: call void @__sanitizer_metadata_atomics_del(i32 1, ptr @__start_sanmd_atomics, ptr @__stop_sanmd_atomics)

// CHECK-LABEL: __sanitizer_metadata_covered.module_ctor
// CHECK: call void @__sanitizer_metadata_covered_add(i32 1, ptr @__start_sanmd_covered, ptr @__stop_sanmd_covered)
// CHECK-LABEL: __sanitizer_metadata_covered.module_dtor
// CHECK: call void @__sanitizer_metadata_covered_del(i32 1, ptr @__start_sanmd_covered, ptr @__stop_sanmd_covered)

// ATOMICS: ![[ATOMICS_COVERED]] = !{!"sanmd_covered", ![[ATOMICS_COVERED_AUX:[0-9]+]]}
// ATOMICS: ![[ATOMICS_COVERED_AUX]] = !{i32 1}
// ATOMICS: ![[ATOMIC_OP]] = !{!"sanmd_atomics"}
23 changes: 23 additions & 0 deletions clang/test/Driver/fsanitize-metadata.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// RUN: %clang --target=x86_64-linux-gnu -fexperimental-sanitize-metadata=all -fno-experimental-sanitize-metadata=all %s -### 2>&1 | FileCheck %s
// CHECK-NOT: -fexperimental-sanitize-metadata

// RUN: %clang --target=x86_64-linux-gnu -fexperimental-sanitize-metadata=bad_arg %s -### 2>&1 | FileCheck -check-prefix=CHECK-INVALID %s
// CHECK-INVALID: error: unsupported argument 'bad_arg' to option '-fexperimental-sanitize-metadata='

// RUN: %clang --target=x86_64-linux-gnu -fexperimental-sanitize-metadata=covered %s -### 2>&1 | FileCheck -check-prefix=CHECK-COVERED %s
// RUN: %clang --target=x86_64-linux-gnu -fexperimental-sanitize-metadata=atomics -fno-experimental-sanitize-metadata=atomics -fexperimental-sanitize-metadata=covered %s -### 2>&1 | FileCheck -check-prefix=CHECK-COVERED %s
// RUN: %clang --target=x86_64-linux-gnu -fexperimental-sanitize-metadata=all -fno-experimental-sanitize-metadata=atomics %s -### 2>&1 | FileCheck -check-prefix=CHECK-COVERED %s
// CHECK-COVERED: "-fexperimental-sanitize-metadata=covered"
// CHECK-COVERED-NOT: "-fexperimental-sanitize-metadata=atomics"

// RUN: %clang --target=x86_64-linux-gnu -fexperimental-sanitize-metadata=atomics %s -### 2>&1 | FileCheck -check-prefix=CHECK-ATOMICS %s
// RUN: %clang --target=x86_64-linux-gnu -fexperimental-sanitize-metadata=covered -fno-experimental-sanitize-metadata=covered -fexperimental-sanitize-metadata=atomics %s -### 2>&1 | FileCheck -check-prefix=CHECK-ATOMICS %s
// RUN: %clang --target=x86_64-linux-gnu -fexperimental-sanitize-metadata=all -fno-experimental-sanitize-metadata=covered %s -### 2>&1 | FileCheck -check-prefix=CHECK-ATOMICS %s
// CHECK-ATOMICS: "-fexperimental-sanitize-metadata=atomics"
// CHECK-ATOMICS-NOT: "-fexperimental-sanitize-metadata=covered"

// RUN: %clang --target=x86_64-linux-gnu -fexperimental-sanitize-metadata=covered,atomics %s -### 2>&1 | FileCheck -check-prefix=CHECK-ALL %s
// RUN: %clang --target=x86_64-linux-gnu -fexperimental-sanitize-metadata=covered -fexperimental-sanitize-metadata=atomics %s -### 2>&1 | FileCheck -check-prefix=CHECK-ALL %s
// RUN: %clang --target=x86_64-linux-gnu -fexperimental-sanitize-metadata=all %s -### 2>&1 | FileCheck -check-prefix=CHECK-ALL %s
// CHECK-ALL: "-fexperimental-sanitize-metadata=covered"
// CHECK-ALL: "-fexperimental-sanitize-metadata=atomics"

0 comments on commit c4842bb

Please sign in to comment.