diff --git a/CMakeLists.txt b/CMakeLists.txt index a239c84a..52cf8dff 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.5) +cmake_minimum_required(VERSION 3.13) project(symsan VERSION 1.2.2 LANGUAGES C CXX ASM) diff --git a/driver/aflpp/symsan.cpp b/driver/aflpp/symsan.cpp index 37f6c5cb..51a1e1c3 100644 --- a/driver/aflpp/symsan.cpp +++ b/driver/aflpp/symsan.cpp @@ -62,6 +62,7 @@ using namespace __dfsan; static bool NestedSolving = false; static int TraceBounds = 0; +static int ExitOnMemError = 1; // default is exit on memory error static int SolveUB = 0; static int ForceStdin = 0; static bool SaveSolved = false; @@ -171,14 +172,19 @@ static void handle_cond(pipe_msg &msg, my_mutator_t *my_mutator) { lc += 1; } + // prase flags + bool always_solve = (msg.flags & F_ADD_CONS) == 0; + bool loop_latch = (msg.flags & F_LOOP_LATCH) != 0; + bool loop_exit = (msg.flags & F_LOOP_EXIT) != 0; + const branch_ctx_t ctx = my_mutator->cov_mgr->add_branch((void*)msg.addr, - msg.id, msg.result != 0, msg.context, false, false); + msg.id, msg.result != 0, msg.context, loop_latch, loop_exit); branch_ctx_t neg_ctx = std::make_shared(); *neg_ctx = *ctx; neg_ctx->direction = !ctx->direction; - if (my_mutator->cov_mgr->is_branch_interesting(neg_ctx)) { + if (my_mutator->cov_mgr->is_branch_interesting(neg_ctx) || always_solve) { // parse the uniont table AST to solving tasks std::vector tasks; if (my_mutator->parser->parse_cond(msg.label, ctx->direction, msg.flags & F_ADD_CONS, tasks) != 0) { @@ -274,6 +280,10 @@ extern "C" my_mutator_t *afl_custom_init(afl_state *afl, unsigned int seed) { if (getenv("SYMSAN_TRACE_BOUNDS")) { TraceBounds = 1; } + // disable exit on memory error + if (getenv("SYMSAN_DONT_EXIT_ON_MEMERROR")) { + ExitOnMemError = 0; + } if (getenv("SYMSAN_SOLVE_UB")) { TraceBounds = 1; // solve undefined depends on trace bounds SolveUB = 1; @@ -415,6 +425,7 @@ extern "C" u32 afl_custom_fuzz_count(my_mutator_t *data, const u8 *buf, symsan_set_args(argc, data->argv); symsan_set_debug(DEBUG); symsan_set_bounds_check(TraceBounds); + symsan_set_exit_on_memerror(ExitOnMemError); symsan_set_solve_ub(SolveUB); symsan_set_force_stdin(ForceStdin); } diff --git a/driver/harness-proxy.c b/driver/harness-proxy.c index 43fb85f3..9d368191 100644 --- a/driver/harness-proxy.c +++ b/driver/harness-proxy.c @@ -20,8 +20,16 @@ #include extern int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size); +__attribute__((weak)) int LLVMFuzzerInitialize(int *argc, char ***argv); +__attribute__((weak)) void LLVMFuzzerCleanup(void); int main(int argc, char* argv[]) { + + // Initialize the fuzzer if the function is available + if (LLVMFuzzerInitialize) { + LLVMFuzzerInitialize(&argc, &argv); + } + // open file int fd = open(argv[1], O_RDONLY); if (fd < 0) { @@ -50,5 +58,11 @@ int main(int argc, char* argv[]) { int retval = LLVMFuzzerTestOneInput((const uint8_t *)string, fsize); free(string); + + // Cleanup if the function is available + if (LLVMFuzzerCleanup) { + LLVMFuzzerCleanup(); + } + return retval; } diff --git a/instrumentation/TaintPass.cpp b/instrumentation/TaintPass.cpp index c7fa698a..3b0306a1 100644 --- a/instrumentation/TaintPass.cpp +++ b/instrumentation/TaintPass.cpp @@ -180,6 +180,12 @@ static cl::opt ClSolveUB( cl::desc("Solve undefined behaviours."), cl::Hidden, cl::init(false)); +// SYMSAN specific flags, only send events for annotated basic blocks +static cl::opt ClTraceAnnotatedBB( + "taint-trace-annotated-bb", + cl::desc("Only trace annotated basic blocks."), + cl::Hidden, cl::init(false)); + static StringRef getGlobalTypeString(const GlobalValue &G) { // Types of GlobalVariables are always pointer types. Type *GType = G.getValueType(); @@ -448,6 +454,7 @@ class Taint { void addContextRecording(Function &F); void addFrameTracing(Function &F); uint32_t getInstructionId(Instruction *Inst); + const uint32_t InvalidInstructionId = -1; void initializeRuntimeFunctions(Module &M); void initializeCallbackFunctions(Module &M); @@ -480,6 +487,9 @@ class Taint { /// Returns the shadow type of of V's type. Type *getShadowTy(Value *V); + /// Returns an uninitialized shadow value with the shadow type of OrigTy. + Constant *getUninitializedShadow(Type *OrigTy); + public: Taint(const std::vector &ABIListFiles); @@ -756,6 +766,23 @@ bool Taint::isZeroShadow(Value *V) { return isa(V); } +Constant *Taint::getUninitializedShadow(Type *OrigTy) { + if (!isa(OrigTy) && !isa(OrigTy)) + return UninitializedPrimitiveShadow; + Type *ShadowTy = getShadowTy(OrigTy); + if (ArrayType *AT = dyn_cast(ShadowTy)) { + SmallVector Elements(AT->getNumElements(), + getUninitializedShadow(AT->getElementType())); + return ConstantArray::get(AT, Elements); + } else if (StructType *ST = dyn_cast(ShadowTy)) { + SmallVector Elements(ST->getNumElements()); + for (unsigned I = 0, N = ST->getNumElements(); I < N; ++I) + Elements[I] = getUninitializedShadow(ST->getElementType(I)); + return ConstantStruct::get(ST, Elements); + } + llvm_unreachable("Unexpected type for uninitialized shadow"); +} + Constant *Taint::getZeroShadow(Type *OrigTy) { if (!isa(OrigTy) && !isa(OrigTy)) return ZeroPrimitiveShadow; @@ -791,6 +818,20 @@ Type *Taint::getShadowTy(Value *V) { } uint32_t Taint::getInstructionId(Instruction *Inst) { + // check if there is a bbid annotation + if (MDNode *BBID = Inst->getMetadata("bbid")) { + auto C = dyn_cast(BBID->getOperand(0)); + if (ConstantInt *CI = dyn_cast(C->getValue())) { + uint64_t BBIDValue = CI->getZExtValue(); + assert(BBIDValue < UINT32_MAX && + "bbid value is too large for 32-bit hash"); + return static_cast(BBIDValue); + } + } + if (ClTraceAnnotatedBB && Inst->isTerminator()) + return InvalidInstructionId; + + // otherwise, fallback to hash static uint32_t unamed = 0; auto SourceInfo = Mod->getSourceFileName(); DILocation *Loc = Inst->getDebugLoc(); @@ -1376,11 +1417,28 @@ bool Taint::runImpl(Module &M) { initializeRuntimeFunctions(M); std::vector FnsToInstrument; + SmallPtrSet IFuncs; SmallPtrSet FnsWithNativeABI; SmallPtrSet FnsWithForceZeroLabel; SmallPtrSet PersonalityFns; + + // find ifunc resolvers and their dependencies, we can't instrument them + // as dfsan initialization is not done yet + for (auto &ifunc : M.ifuncs()) { + auto *resolver = ifunc.getResolverFunction(); + IFuncs.insert(resolver); + for (auto &I : instructions(resolver)) { + if (CallBase *CB = dyn_cast(&I)) { + if (Function *Callee = CB->getCalledFunction()) { + IFuncs.insert(Callee); + } + } + } + } + for (Function &F : M) { - if (!F.isIntrinsic() && !TaintRuntimeFunctions.count(&F)) { + if (!F.isIntrinsic() && !TaintRuntimeFunctions.count(&F) && + !IFuncs.count(&F)) { FnsToInstrument.push_back(&F); if (F.hasPersonalityFn()) PersonalityFns.insert(F.getPersonalityFn()); @@ -1919,7 +1977,7 @@ Value *TaintFunction::loadShadow(Type *T, Value *Addr, uint64_t Size, if (AllocaInst *AI = dyn_cast(Addr)) { const auto i = AllocaShadowMap.find(AI); if (i != AllocaShadowMap.end()) { - return IRB.CreateLoad(TT.PrimitiveShadowTy, i->second); + return IRB.CreateLoad(TT.getShadowTy(T), i->second); } } @@ -2302,10 +2360,13 @@ void TaintFunction::visitSwitchInst(SwitchInst *I) { Value *CondShadow = getShadow(Cond); if (TT.isZeroShadow(CondShadow)) return; + uint32_t cid = TT.getInstructionId(I); + if (cid == TT.InvalidInstructionId) + return; unsigned size = DL.getTypeSizeInBits(Cond->getType()); ConstantInt *Size = ConstantInt::get(TT.Int32Ty, size); ConstantInt *Predicate = ConstantInt::get(TT.Int32Ty, 32); // EQ, == - ConstantInt *CID = ConstantInt::get(TT.Int32Ty, TT.getInstructionId(I)); + ConstantInt *CID = ConstantInt::get(TT.Int32Ty, cid); IRBuilder<> IRB(I); for (auto C : I->cases()) { @@ -2508,11 +2569,12 @@ void TaintVisitor::visitAllocaInst(AllocaInst &I) { } if (AllLoadsStores) { IRBuilder<> IRB(&I); - AllocaInst *AI = IRB.CreateAlloca(TF.TT.PrimitiveShadowTy); + AllocaInst *AI = IRB.CreateAlloca(TF.TT.getShadowTy(I.getAllocatedType()), + I.getArraySize(), I.getName() + ".taint"); TF.AllocaShadowMap[&I] = AI; if (ClTraceBound) { // set shadow to uninit - IRB.CreateStore(TF.TT.UninitializedPrimitiveShadow, AI); + IRB.CreateStore(TF.TT.getUninitializedShadow(I.getAllocatedType()), AI); } } if (!ClTraceBound) { @@ -2530,6 +2592,16 @@ void TaintVisitor::visitAllocaInst(AllocaInst &I) { } // set uninit shadow for allocation with constant size if (!AllLoadsStores && isa(ArraySize)) { + Value *Init = TF.TT.UninitializedPrimitiveShadow; + // XXX: skip __va_list_tag, as we don't trace llvm.va_start + if (ArrayType *AT = dyn_cast(T)) { + T = AT->getElementType(); + } + if (T->isStructTy() && + T->getStructName().find("__va_list_tag") != StringRef::npos) { + // FIXME: don't set uninit, assuming llvm.va_start will be called + Init = TF.TT.ZeroPrimitiveShadow; + } // handle not all loads and stores cases here IRBuilder<> IRB(I.getNextNode()); auto DL = I.getModule()->getDataLayout(); @@ -2538,9 +2610,9 @@ void TaintVisitor::visitAllocaInst(AllocaInst &I) { Value *Size = ConstantInt::get(TF.TT.IntptrTy, (size->getFixedValue() + 7) >> 3); IRB.CreateCall(TF.TT.TaintSetLabelFn, - {TF.TT.UninitializedPrimitiveShadow, - IRB.CreateBitCast(&I, Type::getInt8PtrTy(*TF.TT.Ctx)), - Size}); + {Init, + IRB.CreateBitCast(&I, Type::getInt8PtrTy(*TF.TT.Ctx)), + Size}); } } } @@ -3015,8 +3087,11 @@ void TaintFunction::visitCondition(Value *Condition, Instruction *I) { // except for loop exit if (TT.isZeroShadow(Shadow) && (flag & LoopExitBranch) == 0) return; + uint32_t cid = TT.getInstructionId(I); + if (cid == TT.InvalidInstructionId) + return; // XXX: forget about loop? ConstantInt *LF = ConstantInt::get(TT.Int8Ty, flag); - ConstantInt *CID = ConstantInt::get(TT.Int32Ty, TT.getInstructionId(I)); + ConstantInt *CID = ConstantInt::get(TT.Int32Ty, cid); IRB.CreateCall(TT.TaintTraceCondFn, {Shadow, Condition, LF, CID}); } diff --git a/runtime/dfsan/dfsan.cpp b/runtime/dfsan/dfsan.cpp index a8552dbc..2f41b2f6 100644 --- a/runtime/dfsan/dfsan.cpp +++ b/runtime/dfsan/dfsan.cpp @@ -355,26 +355,158 @@ dfsan_label __taint_union(dfsan_label l1, dfsan_label l2, uint16_t op, internal_memcpy(&__dfsan_label_info[label], &label_info, sizeof(dfsan_label_info)); __union_table.insert(&__dfsan_label_info[label], label); - if (l1 && op == __dfsan::Trunc && flags().solve_ub) { - // check for data loss, after the new label is created - // -fsanitize=implicit-unsigned-integer-truncation - // old_vale >= (1 << new_size) - if (orig_op1 < (1UL << size)) { - // if current value does not have loss - dfsan_label loss = __taint_union(l1, 0, (bvuge << 8) | __dfsan::ICmp, - get_label_info(l1)->size, orig_op1, - 1UL << size); - __taint_trace_cond(loss, 0, UndefinedCheck, ub_unsigned_integer_truncation); - } - // -fsanitize=implicit-signed-integer-truncation - // old_value < signed(1 << (size - 1)) - int64_t target = (int64_t)((0xFFFFFFFFFFFFFFFFUL >> (size-1)) << (size-1)); - if ((int64_t)orig_op1 >= target) { - uint16_t old_size = get_label_info(l1)->size; - if (old_size < 64) target &= ~(1UL << old_size); - dfsan_label loss = __taint_union(l1, 0, (bvslt << 8) | __dfsan::ICmp, - old_size, orig_op1, target); - __taint_trace_cond(loss, 0, UndefinedCheck, ub_signed_integer_truncation); + if (flags().solve_ub) { + if (op == __dfsan::Trunc && l1) { + // check for data loss, after the new label is created + // -fsanitize=implicit-unsigned-integer-truncation + // old_vale >= (1 << new_size) + if (orig_op1 < (1UL << size)) { + // if current value does not have loss + dfsan_label loss = __taint_union(l1, 0, (bvuge << 8) | __dfsan::ICmp, + get_label_info(l1)->size, orig_op1, + 1UL << size); + __taint_trace_cond(loss, 0, UndefinedCheck, ub_unsigned_integer_truncation); + } + // -fsanitize=implicit-signed-integer-truncation + // old_value < signed(1 << (size - 1)) + int64_t target = (int64_t)((0xFFFFFFFFFFFFFFFFUL >> (size-1)) << (size-1)); + if ((int64_t)orig_op1 >= target) { + uint16_t old_size = get_label_info(l1)->size; + if (old_size < 64) target &= ~(1UL << old_size); + dfsan_label loss = __taint_union(l1, 0, (bvslt << 8) | __dfsan::ICmp, + old_size, orig_op1, target); + __taint_trace_cond(loss, 0, UndefinedCheck, ub_signed_integer_truncation); + } + + // -fsanitize=implicit-integer-sign-change + // Check if sign bit changed during truncation + { + uint16_t src_size = get_label_info(l1)->size; + const uint64_t new_mask = size == 64 ? 0xFFFFFFFFFFFFFFFFUL : (1UL << size) - 1; + uint64_t src_sign_bit = 1ULL << (src_size - 1); + uint64_t dst_sign_bit = 1ULL << (size - 1); + bool src_sign = (orig_op1 & src_sign_bit) != 0; + bool dst_sign = ((orig_op1 & new_mask) & dst_sign_bit) != 0; + if (src_sign == dst_sign) { + // Currently no sign change, check if it can happen + // Sign changes when: sign_bit(l1) != sign_bit(label) + // We check: (l1 < 0) XOR (label < 0) + dfsan_label src_neg = __taint_union(l1, 0, (bvslt << 8) | __dfsan::ICmp, + src_size, orig_op1, 0); + dfsan_label dst_neg = __taint_union(label, 0, (bvslt << 8) | __dfsan::ICmp, + size, orig_op1 & new_mask, 0); + dfsan_label sign_diff = __taint_union(src_neg, dst_neg, __dfsan::Xor, 1, + src_sign ? 1 : 0, dst_sign ? 1 : 0); + __taint_trace_cond(sign_diff, 0, UndefinedCheck, ub_integer_sign_change); + } + } + } else if (op == __dfsan::Add) { + // check for integer overflow + // -fsanitize=signed-integer-overflow, unsigned-integer-overflow + // + // we only care about l2, which is always symbolic + const uint64_t mask = size == 64 ? 0xFFFFFFFFFFFFFFFFUL : (1UL << size) - 1; + uint64_t result = (orig_op1 + orig_op2) & mask; + + // Signed overflow detection: + // Overflow occurs when ((op1 ^ result) & (op2 ^ result)) has sign bit set + // This means both operands had same sign, but result has different sign + uint64_t xor1 = (orig_op1 ^ result) & mask; + uint64_t xor2 = (orig_op2 ^ result) & mask; + uint64_t overflow_check = xor1 & xor2; + uint64_t sign_bit = 1ULL << (size - 1); + bool has_signed_overflow = (overflow_check & sign_bit) != 0; + + if (!has_signed_overflow) { + // Build symbolic expression: ((l1 ^ label) & (l2 ^ label)) < 0 + dfsan_label xor_l1 = __taint_union(l1, label, __dfsan::Xor, size, orig_op1, result); + dfsan_label xor_l2 = __taint_union(l2, label, __dfsan::Xor, size, orig_op2, result); + dfsan_label and_xors = __taint_union(xor_l1, xor_l2, __dfsan::And, size, xor1, xor2); + dfsan_label cond = __taint_union(and_xors, 0, (bvslt << 8) | __dfsan::ICmp, + size, overflow_check, 0); + __taint_trace_cond(cond, 0, UndefinedCheck, ub_integer_overflow); + } + + // Unsigned overflow: result < op1 (for any non-zero op2) + // When adding two unsigned numbers, overflow means result wrapped around + if (result >= orig_op1 && orig_op2 != 0) { + dfsan_label cond = __taint_union(label, l1, (bvult << 8) | __dfsan::ICmp, + size, result, orig_op1); + __taint_trace_cond(cond, 0, UndefinedCheck, ub_integer_overflow); + } + } else if (op == __dfsan::Mul) { + // check for integer overflow + // we only care about l2, which is always symbolic + const uint64_t mask = size == 64 ? 0xFFFFFFFFFFFFFFFFUL : (1UL << size) - 1; + uint64_t result = (orig_op1 * orig_op2) & mask; + + // For multiplication, overflow is harder to detect symbolically + // Use the approach: if a != 0, then overflow iff result / a != b + // But we approximate with sign-based check similar to addition + uint64_t xor1 = (orig_op1 ^ result) & mask; + uint64_t xor2 = (orig_op2 ^ result) & mask; + uint64_t overflow_check = xor1 & xor2; + uint64_t sign_bit = 1ULL << (size - 1); + + // For signed multiplication: check if signs are inconsistent + // Product of same signs should be positive, different signs should be negative + // This is an approximation - full check would need wider multiplication + bool has_signed_overflow = (overflow_check & sign_bit) != 0; + + if (!has_signed_overflow && orig_op1 != 0 && orig_op2 != 0) { + dfsan_label xor_l1 = __taint_union(l1, label, __dfsan::Xor, size, orig_op1, result); + dfsan_label xor_l2 = __taint_union(l2, label, __dfsan::Xor, size, orig_op2, result); + dfsan_label and_xors = __taint_union(xor_l1, xor_l2, __dfsan::And, size, xor1, xor2); + dfsan_label cond = __taint_union(and_xors, 0, (bvslt << 8) | __dfsan::ICmp, + size, overflow_check, 0); + __taint_trace_cond(cond, 0, UndefinedCheck, ub_integer_overflow); + } + + // Unsigned overflow: for multiplication, check if result / op1 != op2 (when op1 != 0) + if (orig_op1 != 0 && result / orig_op1 == orig_op2) { + // No overflow currently, check if overflow can happen + // Approximate: result < op1 || result < op2 when both > 1 + if (orig_op1 > 1 && orig_op2 > 1) { + dfsan_label cond = __taint_union(label, l1, (bvult << 8) | __dfsan::ICmp, + size, result, orig_op1); + __taint_trace_cond(cond, 0, UndefinedCheck, ub_integer_overflow); + } + } + } else if (op == __dfsan::Sub) { + // check for integer overflow (underflow for subtraction) + // -fsanitize=signed-integer-overflow, unsigned-integer-overflow + const uint64_t mask = size == 64 ? 0xFFFFFFFFFFFFFFFFUL : (1UL << size) - 1; + uint64_t result = (orig_op1 - orig_op2) & mask; + + // Signed overflow detection for subtraction: + // Overflow occurs when sign(a) != sign(b) and sign(result) != sign(a) + // Formula: (a ^ b) & (a ^ result) has sign bit set + // Examples: + // INT_MAX - (-1) = overflow (positive - negative, result should be more positive but wraps) + // INT_MIN - 1 = overflow (negative - positive, result should be more negative but wraps) + uint64_t xor_ab = (orig_op1 ^ orig_op2) & mask; + uint64_t xor_ar = (orig_op1 ^ result) & mask; + uint64_t overflow_check = xor_ab & xor_ar; + uint64_t sign_bit = 1ULL << (size - 1); + bool has_signed_overflow = (overflow_check & sign_bit) != 0; + + if (!has_signed_overflow) { + // Build symbolic expression: ((l1 ^ l2) & (l1 ^ label)) < 0 + dfsan_label xor_l1l2 = __taint_union(l1, l2, __dfsan::Xor, size, orig_op1, orig_op2); + dfsan_label xor_l1r = __taint_union(l1, label, __dfsan::Xor, size, orig_op1, result); + dfsan_label and_xors = __taint_union(xor_l1l2, xor_l1r, __dfsan::And, size, xor_ab, xor_ar); + dfsan_label cond = __taint_union(and_xors, 0, (bvslt << 8) | __dfsan::ICmp, + size, overflow_check, 0); + __taint_trace_cond(cond, 0, UndefinedCheck, ub_integer_overflow); + } + + // Unsigned underflow: result > op1 when op2 > 0 + // When subtracting, if a < b, result wraps around to large value (result > a) + if (result <= orig_op1 && orig_op2 != 0) { + dfsan_label cond = __taint_union(label, l1, (bvugt << 8) | __dfsan::ICmp, + size, result, orig_op1); + __taint_trace_cond(cond, 0, UndefinedCheck, ub_integer_overflow); + } } } return label; @@ -494,7 +626,11 @@ void __taint_union_store(dfsan_label l, dfsan_label *ls, uptr n, uint64_t align) if (l != kInitializingLabel) { // for debugging dfsan_label h = atomic_load(&__dfsan_last_label, memory_order_relaxed); - assert(l <= h || l >= __alloca_stack_top); + assert(l <= __alloca_stack_bottom); + if (l > h && l < __alloca_stack_top) { + AOUT("WARNING: unallocated label %d > %d, and < %d\n", + l, h, __alloca_stack_top); + } } else { for (uptr i = 0; i < n; ++i) ls[i] = l; @@ -1296,5 +1432,6 @@ SANITIZER_INTERFACE_WEAK_DEF(void, __taint_trace_gep, dfsan_label, uint64_t, SANITIZER_INTERFACE_WEAK_DEF(void, __taint_trace_offset, dfsan_label, int64_t, unsigned) {} SANITIZER_INTERFACE_WEAK_DEF(void, __taint_trace_memcmp, dfsan_label) {} +SANITIZER_INTERFACE_WEAK_DEF(void, __taint_trace_distance, uint64_t, uint64_t) {} SANITIZER_WEAK_ATTRIBUTE THREADLOCAL uint32_t __taint_trace_callstack; } // extern "C" diff --git a/runtime/dfsan/dfsan.h b/runtime/dfsan/dfsan.h index 74fe562b..5847f43d 100644 --- a/runtime/dfsan/dfsan.h +++ b/runtime/dfsan/dfsan.h @@ -254,6 +254,7 @@ enum undefined_check_ids { ub_null_pointer, ub_unsigned_integer_truncation, ub_signed_integer_truncation, + ub_integer_sign_change, }; #define F_ADD_CONS 0x1 diff --git a/runtime/dfsan/dfsan_custom.cpp b/runtime/dfsan/dfsan_custom.cpp index 211b33f2..11cd91ec 100644 --- a/runtime/dfsan/dfsan_custom.cpp +++ b/runtime/dfsan/dfsan_custom.cpp @@ -547,6 +547,29 @@ __dfsw_pread(int fd, void *buf, size_t count, off_t offset, return ret; } +SANITIZER_INTERFACE_ATTRIBUTE ssize_t +__dfsw_pread64(int fd, void *buf, size_t count, off_t offset, + dfsan_label fd_label, dfsan_label buf_label, + dfsan_label count_label, dfsan_label offset_label, + dfsan_label *ret_label) { + __taint_check_bounds(buf_label, (uptr)buf, count_label, count); + if (count_label) + __taint_solve_bounds(buf_label, (uint64_t)buf, count_label, count, 0, 1, 0, 0); + ssize_t ret = pread64(fd, buf, count, offset); + *ret_label = 0; + if (ret >= 0) { + if (taint_get_file(fd)) { + for (ssize_t i = 0; i < ret; i++) { + dfsan_set_label(get_label_for(fd, offset + i), (char *)buf + i, 1); + } + // *ret_label = dfsan_union(0, 0, fsize, sizeof(ret) * 8, offset, 0); + } else { + dfsan_set_label(0, buf, ret); + } + } + return ret; +} + SANITIZER_INTERFACE_ATTRIBUTE ssize_t __dfsw_read(int fd, void *buf, size_t count, dfsan_label fd_label, dfsan_label buf_label, @@ -889,11 +912,29 @@ static dfsan_label taint_strtol(const char *nptr, uptr len, size_t ret_size, int SANITIZER_INTERFACE_ATTRIBUTE int __dfsw_atoi(const char *nptr, dfsan_label nptr_label, dfsan_label *ret_label) { + char *tmp_endptr; + int ret = (int)strtol(nptr, &tmp_endptr, 10); + uptr len = (uptr)tmp_endptr - (uptr)nptr; + *ret_label = taint_strtol(nptr, len, sizeof(ret), 10); + return ret; +} + +SANITIZER_INTERFACE_ATTRIBUTE +long __dfsw_atol(const char *nptr, dfsan_label nptr_label, dfsan_label *ret_label) { char *tmp_endptr; long ret = strtol(nptr, &tmp_endptr, 10); uptr len = (uptr)tmp_endptr - (uptr)nptr; *ret_label = taint_strtol(nptr, len, sizeof(ret), 10); - return (int)ret; + return ret; +} + +SANITIZER_INTERFACE_ATTRIBUTE +long long __dfsw_atoll(const char *nptr, dfsan_label nptr_label, dfsan_label *ret_label) { + char *tmp_endptr; + long long ret = strtoll(nptr, &tmp_endptr, 10); + uptr len = (uptr)tmp_endptr - (uptr)nptr; + *ret_label = taint_strtol(nptr, len, sizeof(ret), 10); + return ret; } SANITIZER_INTERFACE_ATTRIBUTE @@ -1981,7 +2022,7 @@ char *__dfsw_fgets_unlocked(char *s, int size, FILE *stream, dfsan_label s_label if (ret) { if (taint_get_file(fd)) { // including terminating \0 - for(size_t i = 0; i < strlen(ret); i++) { + for (size_t i = 0; i < strlen(ret); i++) { char *buf = s + i; dfsan_set_label(get_label_for(fd, offset + i), buf, 1); } @@ -2005,7 +2046,7 @@ static inline void __taint_check_malloc_size(size_t size, dfsan_label size_label AOUT("*alloc size: %lu = %d\n", size, size_label); // -fsanitize=unsigned-integer-overflow dfsan_label os = dfsan_union(0, size_label, (bveq << 8) | ICmp, 64, 0, size); - __taint_trace_cond(os, 0, 0, 0); + __taint_trace_cond(os, 0, UndefinedCheck, ub_integer_overflow); } } @@ -2594,6 +2635,23 @@ __dfsw_lseek(int fd, off_t offset, int whence, dfsan_label fd_label, return ret; } +SANITIZER_INTERFACE_ATTRIBUTE off64_t +__dfsw_lseek64(int fd, off64_t offset, int whence, dfsan_label fd_label, + dfsan_label offset_label, dfsan_label whence_label, + dfsan_label *ret_label) { + off64_t ret = lseek64(fd, offset, whence); + if (ret != (off64_t)-1) { + if (taint_get_file(fd)) { + taint_set_offset_label(offset_label); + if (offset_label) { + __taint_trace_offset(offset_label, offset, sizeof(offset) * 8); + } + } + *ret_label = offset_label; + } else *ret_label = 0; + return ret; +} + SANITIZER_INTERFACE_ATTRIBUTE int __dfsw_fseek(FILE *stream, long offset, int whence, dfsan_label stream_label, dfsan_label offset_label, dfsan_label whence_label, diff --git a/runtime/dfsan/done_abilist.txt b/runtime/dfsan/done_abilist.txt index e484a382..a2538bff 100644 --- a/runtime/dfsan/done_abilist.txt +++ b/runtime/dfsan/done_abilist.txt @@ -99,6 +99,7 @@ fun:__isinff=functional fun:__signbit=functional fun:__signbitf=functional fun:__signbitl=functional +fun:abs=functional fun:btowc=functional fun:exp=functional fun:exp2=functional @@ -110,6 +111,9 @@ fun:finitef=functional fun:finitel=functional fun:floor=functional fun:fmod=functional +fun:frexp=functional +fun:frexpf=functional +fun:frexpl=functional fun:isinf=functional fun:isinff=functional fun:isinfl=functional @@ -255,6 +259,7 @@ fun:getsockname=custom fun:getsockopt=custom fun:nanosleep=custom fun:pread=custom +fun:pread64=custom fun:read=custom fun:recv=custom fun:recvfrom=custom @@ -286,6 +291,8 @@ fun:strtoll=custom fun:strtoul=custom fun:strtoull=custom fun:atoi=custom +fun:atol=custom +fun:atoll=custom fun:tolower=custom fun:toupper=custom @@ -358,7 +365,7 @@ fun:sched_getaffinity=custom fun:select=custom fun:sigemptyset=custom fun:sigaction=custom -fun:signal=custom +#fun:signal=custom fun:gettimeofday=custom # sprintf-like @@ -400,6 +407,7 @@ fun:open=custom fun:openat=custom fun:openat2=custom fun:lseek=custom +fun:lseek64=custom fun:fseek=custom fun:fseeko=custom fun:fseeko64=custom diff --git a/tests/ubsan_intovfl.c b/tests/ubsan_intovfl.c index ff970ce0..5d1391f4 100644 --- a/tests/ubsan_intovfl.c +++ b/tests/ubsan_intovfl.c @@ -4,7 +4,7 @@ // RUN: clang -O0 -fsanitize=unsigned-integer-overflow -o %t.ubsan %s // RUN: env KO_DONT_OPTIMIZE=1 KO_USE_FASTGEN=1 KO_SOLVE_UB=1 %ko-clang -o %t.fg %s // RUN: env TAINT_OPTIONS="taint_file=%t.bin output_dir=%t.out solve_ub=1" %fgtest %t.fg %t.bin -// RUN: not env UBSAN_OPTIONS="halt_on_error=1" %t.ubsan %t.out/id-0-0-0 2>&1 | FileCheck %s +// RUN: not env UBSAN_OPTIONS="halt_on_error=1" %t.ubsan %t.out/id-0-0-1 2>&1 | FileCheck %s // CHECK: runtime error: unsigned integer overflow #include diff --git a/tests/ubsan_mulovfl.c b/tests/ubsan_mulovfl.c new file mode 100644 index 00000000..b848fbbb --- /dev/null +++ b/tests/ubsan_mulovfl.c @@ -0,0 +1,40 @@ +// RUN: rm -rf %t.out +// RUN: mkdir -p %t.out +// RUN: python -c"import sys; sys.stdout.buffer.write(b'\x02\x00\x00\x00\x03\x00\x00\x00')" > %t.bin +// RUN: clang -O0 -fsanitize=signed-integer-overflow,unsigned-integer-overflow -o %t.ubsan %s +// RUN: env KO_DONT_OPTIMIZE=1 KO_USE_FASTGEN=1 KO_SOLVE_UB=1 %ko-clang -o %t.fg %s +// RUN: env TAINT_OPTIONS="taint_file=%t.bin output_dir=%t.out solve_ub=1" %fgtest %t.fg %t.bin +// RUN: not env UBSAN_OPTIONS="halt_on_error=1" %t.ubsan %t.out/id-0-0-0 2>&1 | FileCheck %s --check-prefix=MULOVFL +// MULOVFL: runtime error: {{signed|unsigned}} integer overflow +// MULOVFL: SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior + +// Test multiplication overflow detection +// Multiplication can overflow even with small-looking numbers +// e.g., 65536 * 65536 overflows 32-bit + +#include +#include +#include +#include +#include "lib.h" + +int main (int argc, char** argv) { + if (argc < 2) { + fprintf(stderr, "Usage: %s [file]\n", argv[0]); + return 0; + } + + FILE* fp = chk_fopen(argv[1], "rb"); + int32_t a = 0, b = 0; + + chk_fread(&a, sizeof(a), 1, fp); + chk_fread(&b, sizeof(b), 1, fp); + fclose(fp); + + // Start with a=2, b=3 (result=6, no overflow) + // Solver should find values that cause overflow + // e.g., a=0x10000, b=0x10000 -> 0x100000000 (overflows 32-bit) + int32_t result = a * b; + printf("%d\n", result); + return 0; +} diff --git a/tests/ubsan_negateovfl.c b/tests/ubsan_negateovfl.c new file mode 100644 index 00000000..fcbba649 --- /dev/null +++ b/tests/ubsan_negateovfl.c @@ -0,0 +1,37 @@ +// RUN: rm -rf %t.out +// RUN: mkdir -p %t.out +// RUN: python -c"import sys; sys.stdout.buffer.write(b'\x00\x00\x00\x00')" > %t.bin +// RUN: clang -O0 -fsanitize=signed-integer-overflow -o %t.ubsan %s +// RUN: env KO_DONT_OPTIMIZE=1 KO_USE_FASTGEN=1 KO_SOLVE_UB=1 %ko-clang -o %t.fg %s +// RUN: env TAINT_OPTIONS="taint_file=%t.bin output_dir=%t.out solve_ub=1" %fgtest %t.fg %t.bin +// RUN: not env UBSAN_OPTIONS="halt_on_error=1" %t.ubsan %t.out/id-0-0-0 2>&1 | FileCheck %s --check-prefix=NEGOVFL +// NEGOVFL: runtime error: negation of +// NEGOVFL: cannot be represented + +// Test negate overflow detection +// -INT_MIN overflows because INT_MIN = -2147483648 and +2147483648 doesn't fit in int32_t + +#include +#include +#include +#include +#include "lib.h" + +int main (int argc, char** argv) { + if (argc < 2) { + fprintf(stderr, "Usage: %s [file]\n", argv[0]); + return 0; + } + + FILE* fp = chk_fopen(argv[1], "rb"); + int32_t x = 0; + + chk_fread(&x, sizeof(x), 1, fp); + fclose(fp); + + // Start with x=0 (no overflow when negating) + // Solver should find x=INT_MIN (-2147483648) which overflows when negated + int32_t y = -x; + printf("%d -> %d\n", x, y); + return 0; +} diff --git a/tests/ubsan_signchange.c b/tests/ubsan_signchange.c new file mode 100644 index 00000000..d3693365 --- /dev/null +++ b/tests/ubsan_signchange.c @@ -0,0 +1,40 @@ +// RUN: rm -rf %t.out +// RUN: mkdir -p %t.out +// RUN: python -c"import sys; sys.stdout.buffer.write(b'\x00\x00\x00\x00')" > %t.bin +// RUN: clang -O0 -fsanitize=implicit-integer-sign-change -o %t.ubsan %s +// RUN: env KO_DONT_OPTIMIZE=1 KO_USE_FASTGEN=1 KO_SOLVE_UB=1 %ko-clang -o %t.fg %s +// RUN: env TAINT_OPTIONS="taint_file=%t.bin output_dir=%t.out solve_ub=1" %fgtest %t.fg %t.bin +// RUN: not env UBSAN_OPTIONS="halt_on_error=1" %t.ubsan %t.out/id-0-0-1 2>&1 | FileCheck %s --check-prefix=SIGNCHANGE +// SIGNCHANGE: runtime error: implicit conversion from type +// SIGNCHANGE: changed the value to + +// Test implicit integer sign change detection +// Sign change occurs when truncating a positive value to negative or vice versa +// Example: int32_t 128 -> int8_t -128 (sign changes from positive to negative) + +#include +#include +#include +#include +#include "lib.h" + +int main (int argc, char** argv) { + if (argc < 2) { + fprintf(stderr, "Usage: %s [file]\n", argv[0]); + return 0; + } + + FILE* fp = chk_fopen(argv[1], "rb"); + int32_t x = 0; + + chk_fread(&x, sizeof(x), 1, fp); + fclose(fp); + + // Start with x=0 (no sign change when truncating to int8_t) + // Solver should find e.g., x=128 which becomes -128 as int8_t (sign change) + // or x=-129 which becomes 127 as int8_t (sign change) + // Use implicit conversion (no explicit cast) to trigger ubsan + int8_t y = x; + printf("%d -> %d\n", x, y); + return 0; +} diff --git a/tests/ubsan_signed_intovfl.c b/tests/ubsan_signed_intovfl.c new file mode 100644 index 00000000..70dc071b --- /dev/null +++ b/tests/ubsan_signed_intovfl.c @@ -0,0 +1,41 @@ +// RUN: rm -rf %t.out +// RUN: mkdir -p %t.out +// RUN: python -c"import sys; sys.stdout.buffer.write(b'\x00\x00\x00\x00\x01\x00\x00\x00')" > %t.bin +// RUN: clang -O0 -fsanitize=signed-integer-overflow -o %t.ubsan %s +// RUN: env KO_DONT_OPTIMIZE=1 KO_USE_FASTGEN=1 KO_SOLVE_UB=1 %ko-clang -o %t.fg %s +// RUN: env TAINT_OPTIONS="taint_file=%t.bin output_dir=%t.out solve_ub=1" %fgtest %t.fg %t.bin +// RUN: not env UBSAN_OPTIONS="halt_on_error=1" %t.ubsan %t.out/id-0-0-0 2>&1 | FileCheck %s --check-prefix=ADDOVFL +// ADDOVFL: runtime error: signed integer overflow +// ADDOVFL: SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior + +// Test signed integer overflow detection +// The runtime should detect when addition can overflow in both directions: +// - Positive overflow: INT_MAX + positive -> negative +// - Negative overflow: INT_MIN + negative -> positive + +#include +#include +#include +#include +#include "lib.h" + +int main (int argc, char** argv) { + if (argc < 2) { + fprintf(stderr, "Usage: %s [file]\n", argv[0]); + return 0; + } + + FILE* fp = chk_fopen(argv[1], "rb"); + int32_t a = 0, b = 0; + + chk_fread(&a, sizeof(a), 1, fp); + chk_fread(&b, sizeof(b), 1, fp); + fclose(fp); + + // Start with a=0, b=1 (no overflow) + // Solver should find a=INT_MAX, b=1 (overflow) + // or a=INT_MIN, b=-1 (underflow) + int32_t result = a + b; + printf("%d\n", result); + return 0; +} diff --git a/tests/ubsan_subovfl.c b/tests/ubsan_subovfl.c new file mode 100644 index 00000000..91ce4d0e --- /dev/null +++ b/tests/ubsan_subovfl.c @@ -0,0 +1,41 @@ +// RUN: rm -rf %t.out +// RUN: mkdir -p %t.out +// RUN: python -c"import sys; sys.stdout.buffer.write(b'\x00\x00\x00\x00\x01\x00\x00\x00')" > %t.bin +// RUN: clang -O0 -fsanitize=signed-integer-overflow -o %t.ubsan %s +// RUN: env KO_DONT_OPTIMIZE=1 KO_USE_FASTGEN=1 KO_SOLVE_UB=1 %ko-clang -o %t.fg %s +// RUN: env TAINT_OPTIONS="taint_file=%t.bin output_dir=%t.out solve_ub=1" %fgtest %t.fg %t.bin +// RUN: not env UBSAN_OPTIONS="halt_on_error=1" %t.ubsan %t.out/id-0-0-0 2>&1 | FileCheck %s --check-prefix=SUBOVFL +// SUBOVFL: runtime error: signed integer overflow +// SUBOVFL: SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior + +// Test signed subtraction overflow detection +// Overflow occurs when: +// - INT_MAX - (-1) wraps to negative (positive overflow) +// - INT_MIN - 1 wraps to positive (negative overflow) + +#include +#include +#include +#include +#include "lib.h" + +int main (int argc, char** argv) { + if (argc < 2) { + fprintf(stderr, "Usage: %s [file]\n", argv[0]); + return 0; + } + + FILE* fp = chk_fopen(argv[1], "rb"); + int32_t a = 0, b = 0; + + chk_fread(&a, sizeof(a), 1, fp); + chk_fread(&b, sizeof(b), 1, fp); + fclose(fp); + + // Start with a=0, b=1 (no overflow) + // Solver should find e.g., a=INT_MIN, b=1 (underflow) + // or a=INT_MAX, b=-1 (overflow) + int32_t result = a - b; + printf("%d\n", result); + return 0; +}