diff --git a/clang/docs/DataFlowSanitizer.rst b/clang/docs/DataFlowSanitizer.rst --- a/clang/docs/DataFlowSanitizer.rst +++ b/clang/docs/DataFlowSanitizer.rst @@ -137,6 +137,20 @@ fun:memcpy=uninstrumented fun:memcpy=custom +For instrumented functions, the ABI list supports a ``force_zero_labels`` +category, which will make all stores and return values set zero labels. +Functions should never be labelled with both ``force_zero_labels`` +and ``uninstrumented`` or any of the unistrumented wrapper kinds. + +For example: + +.. code-block:: none + + # e.g. void writes_data(char* out_buf, int out_buf_len) {...} + # Applying force_zero_labels will force out_buf shadow to zero. + fun:writes_data=force_zero_labels + + Compilation Flags ----------------- diff --git a/compiler-rt/test/dfsan/Inputs/flags_abilist.txt b/compiler-rt/test/dfsan/Inputs/flags_abilist.txt --- a/compiler-rt/test/dfsan/Inputs/flags_abilist.txt +++ b/compiler-rt/test/dfsan/Inputs/flags_abilist.txt @@ -1,10 +1,9 @@ fun:f=uninstrumented +fun:function_to_force_zero=force_zero_labels + fun:main=uninstrumented fun:main=discard -fun:dfsan_create_label=uninstrumented -fun:dfsan_create_label=discard - fun:dfsan_set_label=uninstrumented fun:dfsan_set_label=discard diff --git a/compiler-rt/test/dfsan/force_zero.c b/compiler-rt/test/dfsan/force_zero.c new file mode 100644 --- /dev/null +++ b/compiler-rt/test/dfsan/force_zero.c @@ -0,0 +1,32 @@ +// RUN: %clang_dfsan %s -fsanitize-ignorelist=%S/Inputs/flags_abilist.txt -DFORCE_ZERO_LABELS -o %t && %run %t +// RUN: %clang_dfsan %s -o %t && %run %t +// +// REQUIRES: x86_64-target-arch + +#include + +#include + +int function_to_force_zero(int i, int* out) { + *out = i; + return i; +} + +int main(void) { + int i = 1; + dfsan_label i_label = 2; + dfsan_set_label(i_label, &i, sizeof(i)); + + int out = 0; + int ret = function_to_force_zero(i, &out); + +#ifdef FORCE_ZERO_LABELS + assert(dfsan_get_label(out) == 0); + assert(dfsan_get_label(ret) == 0); +#else + assert(dfsan_get_label(out) == i_label); + assert(dfsan_get_label(ret) == i_label); +#endif + + return 0; +} diff --git a/llvm/lib/Transforms/Instrumentation/DataFlowSanitizer.cpp b/llvm/lib/Transforms/Instrumentation/DataFlowSanitizer.cpp --- a/llvm/lib/Transforms/Instrumentation/DataFlowSanitizer.cpp +++ b/llvm/lib/Transforms/Instrumentation/DataFlowSanitizer.cpp @@ -144,8 +144,17 @@ // to the "native" (i.e. unsanitized) ABI. Unless the ABI list contains // additional annotations for those functions, a call to one of those functions // will produce a warning message, as the labelling behaviour of the function is -// unknown. The other supported annotations are "functional" and "discard", -// which are described below under DataFlowSanitizer::WrapperKind. +// unknown. The other supported annotations for uninstrumented functions are +// "functional" and "discard", which are described below under +// DataFlowSanitizer::WrapperKind. +// Functions will often be labelled with both "uninstrumented" and one of +// "functional" or "discard". This will leave the function unchanged by this +// pass, and create a wrapper function that will call the original. +// +// Instrumented functions can also be annotated as "force_zero_labels", which +// will make all shadow and return values set zero labels. +// Functions should never be labelled with both "force_zero_labels" and +// "uninstrumented" or any of the unistrumented wrapper kinds. static cl::list ClABIListFiles( "dfsan-abilist", cl::desc("File listing native ABI functions and how the pass treats them"), @@ -469,6 +478,7 @@ getShadowOriginAddress(Value *Addr, Align InstAlignment, Instruction *Pos); bool isInstrumented(const Function *F); bool isInstrumented(const GlobalAlias *GA); + bool isForceZeroLabels(const Function *F); FunctionType *getArgsFunctionType(FunctionType *T); FunctionType *getTrampolineFunctionType(FunctionType *T); TransformedFunction getCustomFunctionType(FunctionType *T); @@ -541,6 +551,7 @@ DominatorTree DT; DataFlowSanitizer::InstrumentedABI IA; bool IsNativeABI; + bool IsForceZeroLabels; AllocaInst *LabelReturnAlloca = nullptr; AllocaInst *OriginReturnAlloca = nullptr; DenseMap ValShadowMap; @@ -571,8 +582,10 @@ DenseMap CachedCollapsedShadows; DenseMap> ShadowElements; - DFSanFunction(DataFlowSanitizer &DFS, Function *F, bool IsNativeABI) - : DFS(DFS), F(F), IA(DFS.getInstrumentedABI()), IsNativeABI(IsNativeABI) { + DFSanFunction(DataFlowSanitizer &DFS, Function *F, bool IsNativeABI, + bool IsForceZeroLabels) + : DFS(DFS), F(F), IA(DFS.getInstrumentedABI()), IsNativeABI(IsNativeABI), + IsForceZeroLabels(IsForceZeroLabels) { DT.recalculate(*F); } @@ -1107,6 +1120,10 @@ return !ABIList.isIn(*GA, "uninstrumented"); } +bool DataFlowSanitizer::isForceZeroLabels(const Function *F) { + return ABIList.isIn(*F, "force_zero_labels"); +} + DataFlowSanitizer::InstrumentedABI DataFlowSanitizer::getInstrumentedABI() { return ClArgsABI ? IA_Args : IA_TLS; } @@ -1197,7 +1214,8 @@ // F is called by a wrapped custom function with primitive shadows. So // its arguments and return value need conversion. - DFSanFunction DFSF(*this, F, /*IsNativeABI=*/true); + DFSanFunction DFSF(*this, F, /*IsNativeABI=*/true, + /*ForceZeroLabels=*/false); Function::arg_iterator ValAI = F->arg_begin(), ShadowAI = AI; ++ValAI; for (unsigned N = FT->getNumParams(); N != 0; ++ValAI, ++ShadowAI, --N) { @@ -1399,6 +1417,7 @@ std::vector FnsToInstrument; SmallPtrSet FnsWithNativeABI; + SmallPtrSet FnsWithForceZeroLabel; for (Function &F : M) if (!F.isIntrinsic() && !DFSanRuntimeFunctions.contains(&F)) FnsToInstrument.push_back(&F); @@ -1446,6 +1465,9 @@ FT->getReturnType()->isVoidTy()); if (isInstrumented(&F)) { + if (isForceZeroLabels(&F)) + FnsWithForceZeroLabel.insert(&F); + // Instrumented functions get a '.dfsan' suffix. This allows us to more // easily identify cases of mismatching ABIs. This naming scheme is // mangling-compatible (see Itanium ABI), using a vendor-specific suffix. @@ -1541,7 +1563,8 @@ removeUnreachableBlocks(*F); - DFSanFunction DFSF(*this, F, FnsWithNativeABI.count(F)); + DFSanFunction DFSF(*this, F, FnsWithNativeABI.count(F), + FnsWithForceZeroLabel.count(F)); // DFSanVisitor may create new basic blocks, which confuses df_iterator. // Build a copy of the list before iterating over it. @@ -1705,6 +1728,8 @@ Value *DFSanFunction::getShadow(Value *V) { if (!isa(V) && !isa(V)) return DFS.getZeroShadow(V); + if (IsForceZeroLabels) + return DFS.getZeroShadow(V); Value *&Shadow = ValShadowMap[V]; if (!Shadow) { if (Argument *A = dyn_cast(V)) { diff --git a/llvm/test/Instrumentation/DataFlowSanitizer/Inputs/abilist.txt b/llvm/test/Instrumentation/DataFlowSanitizer/Inputs/abilist.txt --- a/llvm/test/Instrumentation/DataFlowSanitizer/Inputs/abilist.txt +++ b/llvm/test/Instrumentation/DataFlowSanitizer/Inputs/abilist.txt @@ -8,3 +8,5 @@ fun:custom*=custom fun:uninstrumented*=uninstrumented + +fun:function_to_force_zero=force_zero_labels diff --git a/llvm/test/Instrumentation/DataFlowSanitizer/force_zero.ll b/llvm/test/Instrumentation/DataFlowSanitizer/force_zero.ll new file mode 100644 --- /dev/null +++ b/llvm/test/Instrumentation/DataFlowSanitizer/force_zero.ll @@ -0,0 +1,16 @@ +; RUN: opt < %s -dfsan -dfsan-abilist=%S/Inputs/abilist.txt -S | FileCheck %s -DSHADOW_XOR_MASK=87960930222080 +target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128" +target triple = "x86_64-pc-linux-gnu" + +define i32 @function_to_force_zero(i32 %0, i32* %1) { + ; CHECK-LABEL: define i32 @function_to_force_zero.dfsan + ; CHECK: %[[#SHADOW_XOR:]] = xor i64 {{.*}}, [[SHADOW_XOR_MASK]] + ; CHECK: %[[#SHADOW_PTR:]] = inttoptr i64 %[[#SHADOW_XOR]] to i8* + ; CHECK: %[[#SHADOW_BITCAST:]] = bitcast i8* %[[#SHADOW_PTR]] to i32* + ; CHECK: store i32 0, i32* %[[#SHADOW_BITCAST]] + ; CHECK: store i32 %{{.*}} + store i32 %0, i32* %1, align 4 + ; CHECK: store i8 0, {{.*}}@__dfsan_retval_tls + ; CHECK: ret i32 + ret i32 %0 +}