Index: include/clang/StaticAnalyzer/Checkers/Checkers.td =================================================================== --- include/clang/StaticAnalyzer/Checkers/Checkers.td +++ include/clang/StaticAnalyzer/Checkers/Checkers.td @@ -377,6 +377,10 @@ HelpText<"Check for overflows in the arguments to malloc()">, DescFile<"MallocOverflowSecurityChecker.cpp">; +def DirtyScalarChecker : Checker<"DirtyScalar">, + HelpText<"Warn on using tainted integers without proper bound check">, + DescFile<"DirtyScalarChecker.cpp">; + } // end "alpha.security" //===----------------------------------------------------------------------===// Index: lib/StaticAnalyzer/Checkers/CMakeLists.txt =================================================================== --- lib/StaticAnalyzer/Checkers/CMakeLists.txt +++ lib/StaticAnalyzer/Checkers/CMakeLists.txt @@ -31,6 +31,7 @@ DebugCheckers.cpp DereferenceChecker.cpp DirectIvarAssignment.cpp + DirtyScalarChecker.cpp DivZeroChecker.cpp DynamicTypePropagation.cpp DynamicTypeChecker.cpp Index: lib/StaticAnalyzer/Checkers/DirtyScalarChecker.cpp =================================================================== --- /dev/null +++ lib/StaticAnalyzer/Checkers/DirtyScalarChecker.cpp @@ -0,0 +1,221 @@ +//===-- DirtyScalarChecker.cpp ------------------------------------*- C++ -*--// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Checks for dirty/tainted integers which are used without bound checking. +// By default (criticalOnly == true) reports dirty usage in +// - memcpy, malloc, calloc, strcpy, strncpy, memmove functions +// - array indexing +// - memory allocation with new +// - pointer arithmetic +// otherwise (criticalOnly == false) it also reports usage as +// - function argument +// - loop variable +// +//===----------------------------------------------------------------------===// + +#include "ClangSACheckers.h" +#include "clang/AST/ExprCXX.h" +#include "clang/AST/ParentMap.h" +#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" +#include "clang/StaticAnalyzer/Core/Checker.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" + +using namespace clang; +using namespace ento; + +namespace { + +class DirtyScalarChecker + : public Checker, + check::PostStmt, + check::PreStmt, check::BranchCondition> { +public: + DefaultBool IsCriticalOnly; + mutable std::unique_ptr UnboundedBugType; + + void checkPreCall(const CallEvent &Call, CheckerContext &C) const; + void checkPreStmt(const ArraySubscriptExpr *ASE, CheckerContext &C) const; + void checkPostStmt(const CXXNewExpr *NE, CheckerContext &C) const; + void checkPreStmt(const BinaryOperator *BO, CheckerContext &C) const; + void checkBranchCondition(const Stmt *Cond, CheckerContext &Ctx) const; + +private: + void checkLoopCond(const Stmt *Cond, CheckerContext &C, + int RecurseLimit = 3) const; + void checkUnbounded(CheckerContext &C, ProgramStateRef PS, + const Stmt *ST) const; + bool isUnbounded(CheckerContext &C, ProgramStateRef PS, SVal &V) const; + void reportBug(CheckerContext &C, ProgramStateRef PS, SVal &V) const; +}; + +} // end of anonymous namespace + +void DirtyScalarChecker::checkPreCall(const CallEvent &Call, + CheckerContext &C) const { + const CallExpr *CE = dyn_cast(Call.getOriginExpr()); + const FunctionDecl *FDecl = C.getCalleeDecl(CE); + if (!FDecl || FDecl->getKind() != Decl::Function) + return; + StringRef FName = C.getCalleeName(FDecl); + if (FName.empty()) + return; + if (IsCriticalOnly) { + bool AllowedFunc = llvm::StringSwitch(FName) + .Case("memcpy", true) + .Case("malloc", true) + .Case("calloc", true) + .Case("strcpy", true) + .Case("strncpy", true) + .Case("memmove", true) + .Default(false); + if (!AllowedFunc) + return; + } + ProgramStateRef PS = C.getState(); + for (unsigned int i = 0; i < CE->getNumArgs(); ++i) { + const Expr *Arg = CE->getArg(i); + checkUnbounded(C, PS, Arg); + } +} + +void DirtyScalarChecker::checkPreStmt(const ArraySubscriptExpr *ASE, + CheckerContext &C) const { + checkUnbounded(C, C.getState(), ASE->getIdx()); +} + +void DirtyScalarChecker::checkPostStmt(const CXXNewExpr *NE, + CheckerContext &C) const { + if (!NE->isArray()) + return; + checkUnbounded(C, C.getState(), NE->getArraySize()); +} + +void DirtyScalarChecker::checkPreStmt(const BinaryOperator *BO, + CheckerContext &C) const { + if (!BO->isAdditiveOp()) + return; + Expr *LHS = BO->getLHS(); + Expr *RHS = BO->getRHS(); + if (RHS->getType()->isPointerType()) { + std::swap(LHS, RHS); + } + if (!LHS->getType()->isPointerType() || !RHS->getType()->isIntegerType()) + return; + checkUnbounded(C, C.getState(), RHS); +} + +// We want to check the whole loop condition so we catch the direct descendant +// statement of the loop only. +void DirtyScalarChecker::checkBranchCondition(const Stmt *Cond, + CheckerContext &C) const { + if (IsCriticalOnly) + return; + ParentMap &PM = C.getLocationContext()->getParentMap(); + const Stmt *P = PM.getParentIgnoreParenCasts(Cond); + if (!P || (!isa(P) && !isa(P) && !isa(P))) + return; + checkLoopCond(Cond, C); +} + +// The heuristic implemented here tries to get conditions where the +// loop variable will be run in relation to an unbounded and tainted value. +void DirtyScalarChecker::checkLoopCond(const Stmt *Cond, CheckerContext &C, + int RecurseLimit) const { + if (RecurseLimit == 0) + return; + const BinaryOperator *BO = dyn_cast(Cond); + if (!BO) + return; + if (BO->isLogicalOp()) { + Expr *LHS = BO->getLHS(); + Expr *RHS = BO->getRHS(); + checkLoopCond(LHS, C, RecurseLimit - 1); + checkLoopCond(RHS, C, RecurseLimit - 1); + return; + } + if (!BO->isComparisonOp()) + return; + Expr *LHS = BO->getLHS(); + Expr *RHS = BO->getRHS(); + checkUnbounded(C, C.getState(), LHS); + checkUnbounded(C, C.getState(), RHS); +} + +void DirtyScalarChecker::checkUnbounded(CheckerContext &C, ProgramStateRef PS, + const Stmt *ST) const { + SVal Val = C.getSVal(ST); + if (Val.isUndef() || !PS->isTainted(Val)) + return; + if (isUnbounded(C, PS, Val)) + reportBug(C, PS, Val); +} + +// We make here an indirect query on in-place constraints of V. +// If it can be assumed that a value cannot be the highest value of that type +// it surely has an upper bound. The same is true for lower bounds in case of +// signed types. +bool DirtyScalarChecker::isUnbounded(CheckerContext &C, ProgramStateRef PS, + SVal &V) const { + const int TooNarrowForBoundCheck = 8; + + SValBuilder &SVB = C.getSValBuilder(); + ASTContext &Ctx = SVB.getContext(); + const SymExpr *SE = V.getAsSymExpr(); + if (!SE) + return false; + QualType Ty = SE->getType(); + if (Ty.isNull()) + Ty = Ctx.IntTy; + if (!Ty->isIntegerType() || Ctx.getIntWidth(Ty) <= TooNarrowForBoundCheck) + return false; + + BasicValueFactory &BVF = SVB.getBasicValueFactory(); + nonloc::ConcreteInt Max(BVF.getMaxValue(Ty)); + SVal LTCond = SVB.evalBinOpNN(PS, BO_LT, V.castAs(), Max, Ctx.IntTy); + if (LTCond.isUnknownOrUndef()) + return false; + ProgramStateRef PSNotLess = PS->assume(LTCond.castAs(), false); + if (PSNotLess) + return true; + + if (Ty->isSignedIntegerType()) { + nonloc::ConcreteInt Min(BVF.getMinValue(Ty)); + SVal GTCond = + SVB.evalBinOpNN(PS, BO_GT, V.castAs(), Min, Ctx.IntTy); + if (GTCond.isUnknownOrUndef()) + return false; + ProgramStateRef PSNotGreater = + PS->assume(GTCond.castAs(), false); + if (PSNotGreater) + return true; + } + + return false; +} + +void DirtyScalarChecker::reportBug(CheckerContext &C, ProgramStateRef PS, + SVal &V) const { + ExplodedNode *EN = C.generateNonFatalErrorNode(PS); + if (!UnboundedBugType) + UnboundedBugType.reset(new BugType(this, "Unchecked tainted variable usage", + "Insecure usage")); + auto BR = llvm::make_unique( + *UnboundedBugType, + "Tainted variable is used without proper bound checking", EN); + BR->markInteresting(C.getLocationContext()); + BR->markInteresting(V); + C.emitReport(std::move(BR)); +} + +void ento::registerDirtyScalarChecker(CheckerManager &mgr) { + DirtyScalarChecker *checker = mgr.registerChecker(); + checker->IsCriticalOnly = + mgr.getAnalyzerOptions().getBooleanOption("criticalOnly", true, checker); +} Index: lib/StaticAnalyzer/Checkers/GenericTaintChecker.cpp =================================================================== --- lib/StaticAnalyzer/Checkers/GenericTaintChecker.cpp +++ lib/StaticAnalyzer/Checkers/GenericTaintChecker.cpp @@ -222,6 +222,7 @@ .Case("getline", TaintPropagationRule(2, 0)) .Case("getdelim", TaintPropagationRule(3, 0)) .Case("fgetln", TaintPropagationRule(0, ReturnValueIndex)) + .Case("recv", TaintPropagationRule(InvalidArgIndex, 1, true)) .Default(TaintPropagationRule()); if (!Rule.isNull()) Index: test/Analysis/dirty-scalar-unbounded.cpp =================================================================== --- /dev/null +++ test/Analysis/dirty-scalar-unbounded.cpp @@ -0,0 +1,79 @@ +// RUN: %clang_cc1 -analyze -analyzer-checker=alpha.security.taint,alpha.security.DirtyScalar -verify -analyzer-config alpha.security.DirtyScalar:criticalOnly=false %s + +#include "Inputs/system-header-simulator.h" + +typedef long ssize_t; + +ssize_t recv(int s, void *buf, size_t len, int flags); + +void gets_tainted_ival(int val) { + (void)val; +} + +void gets_tainted_uval(unsigned int val) { + (void)val; +} + +int tainted_usage() { + int size; + scanf("%d", &size); + char *buff = new char[size]; // expected-warning{{Tainted variable is used without proper bound checking}} + for (int i = 0; i < size; ++i) { // expected-warning{{Tainted variable is used without proper bound checking}} + scanf("%d", &buff[i]); + } + buff[size - 1] = 0; // expected-warning{{Tainted variable is used without proper bound checking}} + *(buff + size - 2) = 0; // expected-warning{{Tainted variable is used without proper bound checking}} + gets_tainted_ival(size); // expected-warning{{Tainted variable is used without proper bound checking}} + + return 0; +} + +int tainted_usage_checked() { + int size; + scanf("%d", &size); + if (size < 0 || size > 255) + return -1; + char *buff = new char[size]; // no warning + for (int i = 0; i < size; ++i) { // no warning + scanf("%d", &buff[i]); // no warning + } + buff[size - 1] = 0; // no warning + *(buff + size - 2) = 0; // no warning + gets_tainted_ival(size); // no warning + + unsigned int idx; + scanf("%d", &idx); + if (idx > 255) + return -1; + gets_tainted_uval(idx); // no warning + + return 0; +} + +int detect_tainted(char const **messages) { + int sock, index; + scanf("%d", &sock); + if (recv(sock, &index, sizeof(index), 0) != sizeof(index)) { // expected-warning{{Tainted variable is used without proper bound checking}} + return -1; + } + int index2 = index; + printf("%s\n", messages[index]); // expected-warning{{Tainted variable is used without proper bound checking}} + printf("%s\n", messages[index2]); // expected-warning{{Tainted variable is used without proper bound checking}} + + return 0; +} + +int skip_sizes_likely_used_for_table_access(char const **messages) { + int sock; + char byte; + + scanf("%d", &sock); + if (recv(sock, &byte, sizeof(byte), 0) != sizeof(byte)) { // expected-warning{{Tainted variable is used without proper bound checking}} + return -1; + } + char byte2 = byte; + printf("%s\n", messages[byte]); // no warning + printf("%s\n", messages[byte2]); // no warning + + return 0; +}