[DFSan] Add force_zero_label abilist option to DFSan. This can be used as a work-around for overtainting.

Reviewed By: morehouse

Differential Revision: https://reviews.llvm.org/D109847
This commit is contained in:
Andrew Browne 2021-09-15 11:50:07 -07:00
parent e93baded39
commit c533b88a6d
6 changed files with 97 additions and 9 deletions

View File

@ -137,6 +137,20 @@ For example:
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
-----------------

View File

@ -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

View File

@ -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 <sanitizer/dfsan_interface.h>
#include <assert.h>
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;
}

View File

@ -144,8 +144,17 @@ static cl::opt<bool> ClPreserveAlignment(
// 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<std::string> ClABIListFiles(
"dfsan-abilist",
cl::desc("File listing native ABI functions and how the pass treats them"),
@ -469,6 +478,7 @@ class DataFlowSanitizer {
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 @@ struct DFSanFunction {
DominatorTree DT;
DataFlowSanitizer::InstrumentedABI IA;
bool IsNativeABI;
bool IsForceZeroLabels;
AllocaInst *LabelReturnAlloca = nullptr;
AllocaInst *OriginReturnAlloca = nullptr;
DenseMap<Value *, Value *> ValShadowMap;
@ -571,8 +582,10 @@ struct DFSanFunction {
DenseMap<Value *, Value *> CachedCollapsedShadows;
DenseMap<Value *, std::set<Value *>> 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 @@ bool DataFlowSanitizer::isInstrumented(const GlobalAlias *GA) {
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 @@ Constant *DataFlowSanitizer::getOrBuildTrampolineFunction(FunctionType *FT,
// 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 @@ bool DataFlowSanitizer::runImpl(Module &M) {
std::vector<Function *> FnsToInstrument;
SmallPtrSet<Function *, 2> FnsWithNativeABI;
SmallPtrSet<Function *, 2> FnsWithForceZeroLabel;
for (Function &F : M)
if (!F.isIntrinsic() && !DFSanRuntimeFunctions.contains(&F))
FnsToInstrument.push_back(&F);
@ -1446,6 +1465,9 @@ bool DataFlowSanitizer::runImpl(Module &M) {
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 @@ bool DataFlowSanitizer::runImpl(Module &M) {
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::getShadowForTLSArgument(Argument *A) {
Value *DFSanFunction::getShadow(Value *V) {
if (!isa<Argument>(V) && !isa<Instruction>(V))
return DFS.getZeroShadow(V);
if (IsForceZeroLabels)
return DFS.getZeroShadow(V);
Value *&Shadow = ValShadowMap[V];
if (!Shadow) {
if (Argument *A = dyn_cast<Argument>(V)) {

View File

@ -8,3 +8,5 @@ fun:custom*=uninstrumented
fun:custom*=custom
fun:uninstrumented*=uninstrumented
fun:function_to_force_zero=force_zero_labels

View File

@ -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
}