1118 lines · cpp
1//==- CheckSecuritySyntaxOnly.cpp - Basic security checks --------*- C++ -*-==//2//3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.4// See https://llvm.org/LICENSE.txt for license information.5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception6//7//===----------------------------------------------------------------------===//8//9// This file defines a set of flow-insensitive security checks.10//11//===----------------------------------------------------------------------===//12 13#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"14#include "clang/AST/StmtVisitor.h"15#include "clang/Analysis/AnalysisDeclContext.h"16#include "clang/Basic/TargetInfo.h"17#include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h"18#include "clang/StaticAnalyzer/Core/Checker.h"19#include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h"20#include "llvm/ADT/SmallString.h"21#include "llvm/ADT/StringSwitch.h"22#include "llvm/Support/raw_ostream.h"23 24using namespace clang;25using namespace ento;26 27static bool isArc4RandomAvailable(const ASTContext &Ctx) {28 const llvm::Triple &T = Ctx.getTargetInfo().getTriple();29 return T.getVendor() == llvm::Triple::Apple ||30 T.isOSFreeBSD() ||31 T.isOSNetBSD() ||32 T.isOSOpenBSD() ||33 T.isOSDragonFly();34}35 36namespace {37struct ChecksFilter {38 bool check_bcmp = false;39 bool check_bcopy = false;40 bool check_bzero = false;41 bool check_gets = false;42 bool check_getpw = false;43 bool check_mktemp = false;44 bool check_mkstemp = false;45 bool check_strcpy = false;46 bool check_DeprecatedOrUnsafeBufferHandling = false;47 bool check_rand = false;48 bool check_vfork = false;49 bool check_FloatLoopCounter = false;50 bool check_UncheckedReturn = false;51 bool check_decodeValueOfObjCType = false;52 53 CheckerNameRef checkName_bcmp;54 CheckerNameRef checkName_bcopy;55 CheckerNameRef checkName_bzero;56 CheckerNameRef checkName_gets;57 CheckerNameRef checkName_getpw;58 CheckerNameRef checkName_mktemp;59 CheckerNameRef checkName_mkstemp;60 CheckerNameRef checkName_strcpy;61 CheckerNameRef checkName_DeprecatedOrUnsafeBufferHandling;62 CheckerNameRef checkName_rand;63 CheckerNameRef checkName_vfork;64 CheckerNameRef checkName_FloatLoopCounter;65 CheckerNameRef checkName_UncheckedReturn;66 CheckerNameRef checkName_decodeValueOfObjCType;67};68 69class WalkAST : public StmtVisitor<WalkAST> {70 BugReporter &BR;71 AnalysisDeclContext* AC;72 enum { num_setids = 6 };73 IdentifierInfo *II_setid[num_setids];74 75 const bool CheckRand;76 const ChecksFilter &filter;77 78public:79 WalkAST(BugReporter &br, AnalysisDeclContext* ac,80 const ChecksFilter &f)81 : BR(br), AC(ac), II_setid(),82 CheckRand(isArc4RandomAvailable(BR.getContext())),83 filter(f) {}84 85 // Statement visitor methods.86 void VisitCallExpr(CallExpr *CE);87 void VisitObjCMessageExpr(ObjCMessageExpr *CE);88 void VisitForStmt(ForStmt *S);89 void VisitCompoundStmt (CompoundStmt *S);90 void VisitStmt(Stmt *S) { VisitChildren(S); }91 92 void VisitChildren(Stmt *S);93 94 // Helpers.95 bool checkCall_strCommon(const CallExpr *CE, const FunctionDecl *FD);96 97 typedef void (WalkAST::*FnCheck)(const CallExpr *, const FunctionDecl *);98 typedef void (WalkAST::*MsgCheck)(const ObjCMessageExpr *);99 100 // Checker-specific methods.101 void checkLoopConditionForFloat(const ForStmt *FS);102 void checkCall_bcmp(const CallExpr *CE, const FunctionDecl *FD);103 void checkCall_bcopy(const CallExpr *CE, const FunctionDecl *FD);104 void checkCall_bzero(const CallExpr *CE, const FunctionDecl *FD);105 void checkCall_gets(const CallExpr *CE, const FunctionDecl *FD);106 void checkCall_getpw(const CallExpr *CE, const FunctionDecl *FD);107 void checkCall_mktemp(const CallExpr *CE, const FunctionDecl *FD);108 void checkCall_mkstemp(const CallExpr *CE, const FunctionDecl *FD);109 void checkCall_strcpy(const CallExpr *CE, const FunctionDecl *FD);110 void checkCall_strcat(const CallExpr *CE, const FunctionDecl *FD);111 void checkDeprecatedOrUnsafeBufferHandling(const CallExpr *CE,112 const FunctionDecl *FD);113 void checkCall_rand(const CallExpr *CE, const FunctionDecl *FD);114 void checkCall_random(const CallExpr *CE, const FunctionDecl *FD);115 void checkCall_vfork(const CallExpr *CE, const FunctionDecl *FD);116 void checkMsg_decodeValueOfObjCType(const ObjCMessageExpr *ME);117 void checkUncheckedReturnValue(CallExpr *CE);118};119} // end anonymous namespace120 121//===----------------------------------------------------------------------===//122// AST walking.123//===----------------------------------------------------------------------===//124 125void WalkAST::VisitChildren(Stmt *S) {126 for (Stmt *Child : S->children())127 if (Child)128 Visit(Child);129}130 131void WalkAST::VisitCallExpr(CallExpr *CE) {132 // Get the callee.133 const FunctionDecl *FD = CE->getDirectCallee();134 135 if (!FD)136 return;137 138 // Get the name of the callee. If it's a builtin, strip off the prefix.139 IdentifierInfo *II = FD->getIdentifier();140 if (!II) // if no identifier, not a simple C function141 return;142 StringRef Name = II->getName();143 Name.consume_front("__builtin_");144 145 // Set the evaluation function by switching on the callee name.146 FnCheck evalFunction =147 llvm::StringSwitch<FnCheck>(Name)148 .Case("bcmp", &WalkAST::checkCall_bcmp)149 .Case("bcopy", &WalkAST::checkCall_bcopy)150 .Case("bzero", &WalkAST::checkCall_bzero)151 .Case("gets", &WalkAST::checkCall_gets)152 .Case("getpw", &WalkAST::checkCall_getpw)153 .Case("mktemp", &WalkAST::checkCall_mktemp)154 .Case("mkstemp", &WalkAST::checkCall_mkstemp)155 .Case("mkdtemp", &WalkAST::checkCall_mkstemp)156 .Case("mkstemps", &WalkAST::checkCall_mkstemp)157 .Cases({"strcpy", "__strcpy_chk"}, &WalkAST::checkCall_strcpy)158 .Cases({"strcat", "__strcat_chk"}, &WalkAST::checkCall_strcat)159 .Cases({"sprintf", "vsprintf", "scanf", "wscanf", "fscanf", "fwscanf",160 "vscanf", "vwscanf", "vfscanf", "vfwscanf"},161 &WalkAST::checkDeprecatedOrUnsafeBufferHandling)162 .Cases({"sscanf", "swscanf", "vsscanf", "vswscanf", "swprintf",163 "snprintf", "vswprintf", "vsnprintf", "memcpy", "memmove"},164 &WalkAST::checkDeprecatedOrUnsafeBufferHandling)165 .Cases({"strncpy", "strncat", "memset", "fprintf"},166 &WalkAST::checkDeprecatedOrUnsafeBufferHandling)167 .Case("drand48", &WalkAST::checkCall_rand)168 .Case("erand48", &WalkAST::checkCall_rand)169 .Case("jrand48", &WalkAST::checkCall_rand)170 .Case("lrand48", &WalkAST::checkCall_rand)171 .Case("mrand48", &WalkAST::checkCall_rand)172 .Case("nrand48", &WalkAST::checkCall_rand)173 .Case("lcong48", &WalkAST::checkCall_rand)174 .Case("rand", &WalkAST::checkCall_rand)175 .Case("rand_r", &WalkAST::checkCall_rand)176 .Case("random", &WalkAST::checkCall_random)177 .Case("vfork", &WalkAST::checkCall_vfork)178 .Default(nullptr);179 180 // If the callee isn't defined, it is not of security concern.181 // Check and evaluate the call.182 if (evalFunction)183 (this->*evalFunction)(CE, FD);184 185 // Recurse and check children.186 VisitChildren(CE);187}188 189void WalkAST::VisitObjCMessageExpr(ObjCMessageExpr *ME) {190 MsgCheck evalFunction =191 llvm::StringSwitch<MsgCheck>(ME->getSelector().getAsString())192 .Case("decodeValueOfObjCType:at:",193 &WalkAST::checkMsg_decodeValueOfObjCType)194 .Default(nullptr);195 196 if (evalFunction)197 (this->*evalFunction)(ME);198 199 // Recurse and check children.200 VisitChildren(ME);201}202 203void WalkAST::VisitCompoundStmt(CompoundStmt *S) {204 for (Stmt *Child : S->children())205 if (Child) {206 if (CallExpr *CE = dyn_cast<CallExpr>(Child))207 checkUncheckedReturnValue(CE);208 Visit(Child);209 }210}211 212void WalkAST::VisitForStmt(ForStmt *FS) {213 checkLoopConditionForFloat(FS);214 215 // Recurse and check children.216 VisitChildren(FS);217}218 219//===----------------------------------------------------------------------===//220// Check: floating point variable used as loop counter.221// Implements: CERT security coding advisory FLP-30.222//===----------------------------------------------------------------------===//223 224// Returns either 'x' or 'y', depending on which one of them is incremented225// in 'expr', or nullptr if none of them is incremented.226static const DeclRefExpr*227getIncrementedVar(const Expr *expr, const VarDecl *x, const VarDecl *y) {228 expr = expr->IgnoreParenCasts();229 230 if (const BinaryOperator *B = dyn_cast<BinaryOperator>(expr)) {231 if (!(B->isAssignmentOp() || B->isCompoundAssignmentOp() ||232 B->getOpcode() == BO_Comma))233 return nullptr;234 235 if (const DeclRefExpr *lhs = getIncrementedVar(B->getLHS(), x, y))236 return lhs;237 238 if (const DeclRefExpr *rhs = getIncrementedVar(B->getRHS(), x, y))239 return rhs;240 241 return nullptr;242 }243 244 if (const DeclRefExpr *DR = dyn_cast<DeclRefExpr>(expr)) {245 const NamedDecl *ND = DR->getDecl();246 return ND == x || ND == y ? DR : nullptr;247 }248 249 if (const UnaryOperator *U = dyn_cast<UnaryOperator>(expr))250 return U->isIncrementDecrementOp()251 ? getIncrementedVar(U->getSubExpr(), x, y) : nullptr;252 253 return nullptr;254}255 256/// CheckLoopConditionForFloat - This check looks for 'for' statements that257/// use a floating point variable as a loop counter.258/// CERT: FLP30-C, FLP30-CPP.259///260void WalkAST::checkLoopConditionForFloat(const ForStmt *FS) {261 if (!filter.check_FloatLoopCounter)262 return;263 264 // Does the loop have a condition?265 const Expr *condition = FS->getCond();266 267 if (!condition)268 return;269 270 // Does the loop have an increment?271 const Expr *increment = FS->getInc();272 273 if (!increment)274 return;275 276 // Strip away '()' and casts.277 condition = condition->IgnoreParenCasts();278 increment = increment->IgnoreParenCasts();279 280 // Is the loop condition a comparison?281 const BinaryOperator *B = dyn_cast<BinaryOperator>(condition);282 283 if (!B)284 return;285 286 // Is this a comparison?287 if (!(B->isRelationalOp() || B->isEqualityOp()))288 return;289 290 // Are we comparing variables?291 const DeclRefExpr *drLHS =292 dyn_cast<DeclRefExpr>(B->getLHS()->IgnoreParenLValueCasts());293 const DeclRefExpr *drRHS =294 dyn_cast<DeclRefExpr>(B->getRHS()->IgnoreParenLValueCasts());295 296 // Does at least one of the variables have a floating point type?297 drLHS = drLHS && drLHS->getType()->isRealFloatingType() ? drLHS : nullptr;298 drRHS = drRHS && drRHS->getType()->isRealFloatingType() ? drRHS : nullptr;299 300 if (!drLHS && !drRHS)301 return;302 303 const VarDecl *vdLHS = drLHS ? dyn_cast<VarDecl>(drLHS->getDecl()) : nullptr;304 const VarDecl *vdRHS = drRHS ? dyn_cast<VarDecl>(drRHS->getDecl()) : nullptr;305 306 if (!vdLHS && !vdRHS)307 return;308 309 // Does either variable appear in increment?310 const DeclRefExpr *drInc = getIncrementedVar(increment, vdLHS, vdRHS);311 if (!drInc)312 return;313 314 const VarDecl *vdInc = cast<VarDecl>(drInc->getDecl());315 assert(vdInc && (vdInc == vdLHS || vdInc == vdRHS));316 317 // Emit the error. First figure out which DeclRefExpr in the condition318 // referenced the compared variable.319 const DeclRefExpr *drCond = vdLHS == vdInc ? drLHS : drRHS;320 321 SmallVector<SourceRange, 2> ranges;322 SmallString<256> sbuf;323 llvm::raw_svector_ostream os(sbuf);324 325 os << "Variable '" << drCond->getDecl()->getName()326 << "' with floating point type '" << drCond->getType()327 << "' should not be used as a loop counter";328 329 ranges.push_back(drCond->getSourceRange());330 ranges.push_back(drInc->getSourceRange());331 332 const char *bugType = "Floating point variable used as loop counter";333 334 PathDiagnosticLocation FSLoc =335 PathDiagnosticLocation::createBegin(FS, BR.getSourceManager(), AC);336 BR.EmitBasicReport(AC->getDecl(), filter.checkName_FloatLoopCounter,337 bugType, "Security", os.str(),338 FSLoc, ranges);339}340 341//===----------------------------------------------------------------------===//342// Check: Any use of bcmp.343// CWE-477: Use of Obsolete Functions344// bcmp was deprecated in POSIX.1-2008345//===----------------------------------------------------------------------===//346 347void WalkAST::checkCall_bcmp(const CallExpr *CE, const FunctionDecl *FD) {348 if (!filter.check_bcmp)349 return;350 351 const FunctionProtoType *FPT = FD->getType()->getAs<FunctionProtoType>();352 if (!FPT)353 return;354 355 // Verify that the function takes three arguments.356 if (FPT->getNumParams() != 3)357 return;358 359 for (int i = 0; i < 2; i++) {360 // Verify the first and second argument type is void*.361 const PointerType *PT = FPT->getParamType(i)->getAs<PointerType>();362 if (!PT)363 return;364 365 if (PT->getPointeeType().getUnqualifiedType() != BR.getContext().VoidTy)366 return;367 }368 369 // Verify the third argument type is integer.370 if (!FPT->getParamType(2)->isIntegralOrUnscopedEnumerationType())371 return;372 373 // Issue a warning.374 PathDiagnosticLocation CELoc =375 PathDiagnosticLocation::createBegin(CE, BR.getSourceManager(), AC);376 BR.EmitBasicReport(AC->getDecl(), filter.checkName_bcmp,377 "Use of deprecated function in call to 'bcmp()'",378 "Security",379 "The bcmp() function is obsoleted by memcmp().",380 CELoc, CE->getCallee()->getSourceRange());381}382 383//===----------------------------------------------------------------------===//384// Check: Any use of bcopy.385// CWE-477: Use of Obsolete Functions386// bcopy was deprecated in POSIX.1-2008387//===----------------------------------------------------------------------===//388 389void WalkAST::checkCall_bcopy(const CallExpr *CE, const FunctionDecl *FD) {390 if (!filter.check_bcopy)391 return;392 393 const FunctionProtoType *FPT = FD->getType()->getAs<FunctionProtoType>();394 if (!FPT)395 return;396 397 // Verify that the function takes three arguments.398 if (FPT->getNumParams() != 3)399 return;400 401 for (int i = 0; i < 2; i++) {402 // Verify the first and second argument type is void*.403 const PointerType *PT = FPT->getParamType(i)->getAs<PointerType>();404 if (!PT)405 return;406 407 if (PT->getPointeeType().getUnqualifiedType() != BR.getContext().VoidTy)408 return;409 }410 411 // Verify the third argument type is integer.412 if (!FPT->getParamType(2)->isIntegralOrUnscopedEnumerationType())413 return;414 415 // Issue a warning.416 PathDiagnosticLocation CELoc =417 PathDiagnosticLocation::createBegin(CE, BR.getSourceManager(), AC);418 BR.EmitBasicReport(AC->getDecl(), filter.checkName_bcopy,419 "Use of deprecated function in call to 'bcopy()'",420 "Security",421 "The bcopy() function is obsoleted by memcpy() "422 "or memmove().",423 CELoc, CE->getCallee()->getSourceRange());424}425 426//===----------------------------------------------------------------------===//427// Check: Any use of bzero.428// CWE-477: Use of Obsolete Functions429// bzero was deprecated in POSIX.1-2008430//===----------------------------------------------------------------------===//431 432void WalkAST::checkCall_bzero(const CallExpr *CE, const FunctionDecl *FD) {433 if (!filter.check_bzero)434 return;435 436 const FunctionProtoType *FPT = FD->getType()->getAs<FunctionProtoType>();437 if (!FPT)438 return;439 440 // Verify that the function takes two arguments.441 if (FPT->getNumParams() != 2)442 return;443 444 // Verify the first argument type is void*.445 const PointerType *PT = FPT->getParamType(0)->getAs<PointerType>();446 if (!PT)447 return;448 449 if (PT->getPointeeType().getUnqualifiedType() != BR.getContext().VoidTy)450 return;451 452 // Verify the second argument type is integer.453 if (!FPT->getParamType(1)->isIntegralOrUnscopedEnumerationType())454 return;455 456 // Issue a warning.457 PathDiagnosticLocation CELoc =458 PathDiagnosticLocation::createBegin(CE, BR.getSourceManager(), AC);459 BR.EmitBasicReport(AC->getDecl(), filter.checkName_bzero,460 "Use of deprecated function in call to 'bzero()'",461 "Security",462 "The bzero() function is obsoleted by memset().",463 CELoc, CE->getCallee()->getSourceRange());464}465 466 467//===----------------------------------------------------------------------===//468// Check: Any use of 'gets' is insecure. Most man pages literally says this.469//470// Implements (part of): 300-BSI (buildsecurityin.us-cert.gov)471// CWE-242: Use of Inherently Dangerous Function472//===----------------------------------------------------------------------===//473 474void WalkAST::checkCall_gets(const CallExpr *CE, const FunctionDecl *FD) {475 if (!filter.check_gets)476 return;477 478 const FunctionProtoType *FPT = FD->getType()->getAs<FunctionProtoType>();479 if (!FPT)480 return;481 482 // Verify that the function takes a single argument.483 if (FPT->getNumParams() != 1)484 return;485 486 // Is the argument a 'char*'?487 const PointerType *PT = FPT->getParamType(0)->getAs<PointerType>();488 if (!PT)489 return;490 491 if (PT->getPointeeType().getUnqualifiedType() != BR.getContext().CharTy)492 return;493 494 // Issue a warning.495 PathDiagnosticLocation CELoc =496 PathDiagnosticLocation::createBegin(CE, BR.getSourceManager(), AC);497 BR.EmitBasicReport(AC->getDecl(), filter.checkName_gets,498 "Potential buffer overflow in call to 'gets'",499 "Security",500 "Call to function 'gets' is extremely insecure as it can "501 "always result in a buffer overflow",502 CELoc, CE->getCallee()->getSourceRange());503}504 505//===----------------------------------------------------------------------===//506// Check: Any use of 'getpwd' is insecure.507// CWE-477: Use of Obsolete Functions508//===----------------------------------------------------------------------===//509 510void WalkAST::checkCall_getpw(const CallExpr *CE, const FunctionDecl *FD) {511 if (!filter.check_getpw)512 return;513 514 const FunctionProtoType *FPT = FD->getType()->getAs<FunctionProtoType>();515 if (!FPT)516 return;517 518 // Verify that the function takes two arguments.519 if (FPT->getNumParams() != 2)520 return;521 522 // Verify the first argument type is integer.523 if (!FPT->getParamType(0)->isIntegralOrUnscopedEnumerationType())524 return;525 526 // Verify the second argument type is char*.527 const PointerType *PT = FPT->getParamType(1)->getAs<PointerType>();528 if (!PT)529 return;530 531 if (PT->getPointeeType().getUnqualifiedType() != BR.getContext().CharTy)532 return;533 534 // Issue a warning.535 PathDiagnosticLocation CELoc =536 PathDiagnosticLocation::createBegin(CE, BR.getSourceManager(), AC);537 BR.EmitBasicReport(AC->getDecl(), filter.checkName_getpw,538 "Potential buffer overflow in call to 'getpw'",539 "Security",540 "The getpw() function is dangerous as it may overflow the "541 "provided buffer. It is obsoleted by getpwuid().",542 CELoc, CE->getCallee()->getSourceRange());543}544 545//===----------------------------------------------------------------------===//546// Check: Any use of 'mktemp' is insecure. It is obsoleted by mkstemp().547// CWE-377: Insecure Temporary File548//===----------------------------------------------------------------------===//549 550void WalkAST::checkCall_mktemp(const CallExpr *CE, const FunctionDecl *FD) {551 if (!filter.check_mktemp) {552 // Fall back to the security check of looking for enough 'X's in the553 // format string, since that is a less severe warning.554 checkCall_mkstemp(CE, FD);555 return;556 }557 558 const FunctionProtoType *FPT = FD->getType()->getAs<FunctionProtoType>();559 if(!FPT)560 return;561 562 // Verify that the function takes a single argument.563 if (FPT->getNumParams() != 1)564 return;565 566 // Verify that the argument is Pointer Type.567 const PointerType *PT = FPT->getParamType(0)->getAs<PointerType>();568 if (!PT)569 return;570 571 // Verify that the argument is a 'char*'.572 if (PT->getPointeeType().getUnqualifiedType() != BR.getContext().CharTy)573 return;574 575 // Issue a warning.576 PathDiagnosticLocation CELoc =577 PathDiagnosticLocation::createBegin(CE, BR.getSourceManager(), AC);578 BR.EmitBasicReport(AC->getDecl(), filter.checkName_mktemp,579 "Potential insecure temporary file in call 'mktemp'",580 "Security",581 "Call to function 'mktemp' is insecure as it always "582 "creates or uses insecure temporary file. Use 'mkstemp' "583 "instead",584 CELoc, CE->getCallee()->getSourceRange());585}586 587//===----------------------------------------------------------------------===//588// Check: Use of 'mkstemp', 'mktemp', 'mkdtemp' should contain at least 6 X's.589//===----------------------------------------------------------------------===//590 591void WalkAST::checkCall_mkstemp(const CallExpr *CE, const FunctionDecl *FD) {592 if (!filter.check_mkstemp)593 return;594 595 StringRef Name = FD->getIdentifier()->getName();596 std::pair<signed, signed> ArgSuffix =597 llvm::StringSwitch<std::pair<signed, signed> >(Name)598 .Case("mktemp", std::make_pair(0,-1))599 .Case("mkstemp", std::make_pair(0,-1))600 .Case("mkdtemp", std::make_pair(0,-1))601 .Case("mkstemps", std::make_pair(0,1))602 .Default(std::make_pair(-1, -1));603 604 assert(ArgSuffix.first >= 0 && "Unsupported function");605 606 // Check if the number of arguments is consistent with out expectations.607 unsigned numArgs = CE->getNumArgs();608 if ((signed) numArgs <= ArgSuffix.first)609 return;610 611 const StringLiteral *strArg =612 dyn_cast<StringLiteral>(CE->getArg((unsigned)ArgSuffix.first)613 ->IgnoreParenImpCasts());614 615 // Currently we only handle string literals. It is possible to do better,616 // either by looking at references to const variables, or by doing real617 // flow analysis.618 if (!strArg || strArg->getCharByteWidth() != 1)619 return;620 621 // Count the number of X's, taking into account a possible cutoff suffix.622 StringRef str = strArg->getString();623 unsigned numX = 0;624 unsigned n = str.size();625 626 // Take into account the suffix.627 unsigned suffix = 0;628 if (ArgSuffix.second >= 0) {629 const Expr *suffixEx = CE->getArg((unsigned)ArgSuffix.second);630 Expr::EvalResult EVResult;631 if (!suffixEx->EvaluateAsInt(EVResult, BR.getContext()))632 return;633 llvm::APSInt Result = EVResult.Val.getInt();634 // FIXME: Issue a warning.635 if (Result.isNegative())636 return;637 suffix = (unsigned) Result.getZExtValue();638 n = (n > suffix) ? n - suffix : 0;639 }640 641 for (unsigned i = 0; i < n; ++i)642 if (str[i] == 'X') ++numX;643 644 if (numX >= 6)645 return;646 647 // Issue a warning.648 PathDiagnosticLocation CELoc =649 PathDiagnosticLocation::createBegin(CE, BR.getSourceManager(), AC);650 SmallString<512> buf;651 llvm::raw_svector_ostream out(buf);652 out << "Call to '" << Name << "' should have at least 6 'X's in the"653 " format string to be secure (" << numX << " 'X'";654 if (numX != 1)655 out << 's';656 out << " seen";657 if (suffix) {658 out << ", " << suffix << " character";659 if (suffix > 1)660 out << 's';661 out << " used as a suffix";662 }663 out << ')';664 BR.EmitBasicReport(AC->getDecl(), filter.checkName_mkstemp,665 "Insecure temporary file creation", "Security",666 out.str(), CELoc, strArg->getSourceRange());667}668 669//===----------------------------------------------------------------------===//670// Check: Any use of 'strcpy' is insecure.671//672// CWE-119: Improper Restriction of Operations within673// the Bounds of a Memory Buffer674//===----------------------------------------------------------------------===//675 676void WalkAST::checkCall_strcpy(const CallExpr *CE, const FunctionDecl *FD) {677 if (!filter.check_strcpy)678 return;679 680 if (!checkCall_strCommon(CE, FD))681 return;682 683 const auto *Target = CE->getArg(0)->IgnoreImpCasts(),684 *Source = CE->getArg(1)->IgnoreImpCasts();685 686 if (const auto *Array = dyn_cast<ConstantArrayType>(Target->getType())) {687 uint64_t ArraySize = BR.getContext().getTypeSize(Array) / 8;688 if (const auto *String = dyn_cast<StringLiteral>(Source)) {689 if (ArraySize >= String->getLength() + 1)690 return;691 }692 }693 694 // Issue a warning.695 PathDiagnosticLocation CELoc =696 PathDiagnosticLocation::createBegin(CE, BR.getSourceManager(), AC);697 BR.EmitBasicReport(AC->getDecl(), filter.checkName_strcpy,698 "Potential insecure memory buffer bounds restriction in "699 "call 'strcpy'",700 "Security",701 "Call to function 'strcpy' is insecure as it does not "702 "provide bounding of the memory buffer. Replace "703 "unbounded copy functions with analogous functions that "704 "support length arguments such as 'strlcpy'. CWE-119.",705 CELoc, CE->getCallee()->getSourceRange());706}707 708//===----------------------------------------------------------------------===//709// Check: Any use of 'strcat' is insecure.710//711// CWE-119: Improper Restriction of Operations within712// the Bounds of a Memory Buffer713//===----------------------------------------------------------------------===//714 715void WalkAST::checkCall_strcat(const CallExpr *CE, const FunctionDecl *FD) {716 if (!filter.check_strcpy)717 return;718 719 if (!checkCall_strCommon(CE, FD))720 return;721 722 // Issue a warning.723 PathDiagnosticLocation CELoc =724 PathDiagnosticLocation::createBegin(CE, BR.getSourceManager(), AC);725 BR.EmitBasicReport(AC->getDecl(), filter.checkName_strcpy,726 "Potential insecure memory buffer bounds restriction in "727 "call 'strcat'",728 "Security",729 "Call to function 'strcat' is insecure as it does not "730 "provide bounding of the memory buffer. Replace "731 "unbounded copy functions with analogous functions that "732 "support length arguments such as 'strlcat'. CWE-119.",733 CELoc, CE->getCallee()->getSourceRange());734}735 736//===----------------------------------------------------------------------===//737// Check: Any use of 'sprintf', 'vsprintf', 'scanf', 'wscanf', 'fscanf',738// 'fwscanf', 'vscanf', 'vwscanf', 'vfscanf', 'vfwscanf', 'sscanf',739// 'swscanf', 'vsscanf', 'vswscanf', 'swprintf', 'snprintf', 'vswprintf',740// 'vsnprintf', 'memcpy', 'memmove', 'strncpy', 'strncat', 'memset',741// 'fprintf' is deprecated since C11.742//743// Use of 'sprintf', 'fprintf', 'vsprintf', 'scanf', 'wscanf', 'fscanf',744// 'fwscanf', 'vscanf', 'vwscanf', 'vfscanf', 'vfwscanf', 'sscanf',745// 'swscanf', 'vsscanf', 'vswscanf' without buffer limitations746// is insecure.747//748// CWE-119: Improper Restriction of Operations within749// the Bounds of a Memory Buffer750//===----------------------------------------------------------------------===//751 752void WalkAST::checkDeprecatedOrUnsafeBufferHandling(const CallExpr *CE,753 const FunctionDecl *FD) {754 if (!filter.check_DeprecatedOrUnsafeBufferHandling)755 return;756 757 if (!BR.getContext().getLangOpts().C11)758 return;759 760 // Issue a warning. ArgIndex == -1: Deprecated but not unsafe (has size761 // restrictions).762 enum { DEPR_ONLY = -1, UNKNOWN_CALL = -2 };763 764 StringRef Name = FD->getIdentifier()->getName();765 Name.consume_front("__builtin_");766 767 int ArgIndex =768 llvm::StringSwitch<int>(Name)769 .Cases({"scanf", "wscanf", "vscanf", "vwscanf"}, 0)770 .Cases({"fscanf", "fwscanf", "vfscanf", "vfwscanf", "sscanf",771 "swscanf", "vsscanf", "vswscanf"},772 1)773 .Cases({"sprintf", "vsprintf", "fprintf"}, 1)774 .Cases({"swprintf", "snprintf", "vswprintf", "vsnprintf", "memcpy",775 "memmove", "memset", "strncpy", "strncat"},776 DEPR_ONLY)777 .Default(UNKNOWN_CALL);778 779 assert(ArgIndex != UNKNOWN_CALL && "Unsupported function");780 bool BoundsProvided = ArgIndex == DEPR_ONLY;781 782 if (!BoundsProvided) {783 // Currently we only handle (not wide) string literals. It is possible to do784 // better, either by looking at references to const variables, or by doing785 // real flow analysis.786 auto FormatString =787 dyn_cast<StringLiteral>(CE->getArg(ArgIndex)->IgnoreParenImpCasts());788 if (FormatString && !FormatString->getString().contains("%s") &&789 !FormatString->getString().contains("%["))790 BoundsProvided = true;791 }792 793 SmallString<128> Buf1;794 SmallString<512> Buf2;795 llvm::raw_svector_ostream Out1(Buf1);796 llvm::raw_svector_ostream Out2(Buf2);797 798 Out1 << "Potential insecure memory buffer bounds restriction in call '"799 << Name << "'";800 Out2 << "Call to function '" << Name801 << "' is insecure as it does not provide ";802 803 if (!BoundsProvided) {804 Out2 << "bounding of the memory buffer or ";805 }806 807 Out2 << "security checks introduced "808 "in the C11 standard. Replace with analogous functions that "809 "support length arguments or provides boundary checks such as '"810 << Name << "_s' in case of C11";811 812 PathDiagnosticLocation CELoc =813 PathDiagnosticLocation::createBegin(CE, BR.getSourceManager(), AC);814 BR.EmitBasicReport(AC->getDecl(),815 filter.checkName_DeprecatedOrUnsafeBufferHandling,816 Out1.str(), "Security", Out2.str(), CELoc,817 CE->getCallee()->getSourceRange());818}819 820//===----------------------------------------------------------------------===//821// Common check for str* functions with no bounds parameters.822//===----------------------------------------------------------------------===//823 824bool WalkAST::checkCall_strCommon(const CallExpr *CE, const FunctionDecl *FD) {825 const FunctionProtoType *FPT = FD->getType()->getAs<FunctionProtoType>();826 if (!FPT)827 return false;828 829 // Verify the function takes two arguments, three in the _chk version.830 int numArgs = FPT->getNumParams();831 if (numArgs != 2 && numArgs != 3)832 return false;833 834 // Verify the type for both arguments.835 for (int i = 0; i < 2; i++) {836 // Verify that the arguments are pointers.837 const PointerType *PT = FPT->getParamType(i)->getAs<PointerType>();838 if (!PT)839 return false;840 841 // Verify that the argument is a 'char*'.842 if (PT->getPointeeType().getUnqualifiedType() != BR.getContext().CharTy)843 return false;844 }845 846 return true;847}848 849//===----------------------------------------------------------------------===//850// Check: Linear congruent random number generators should not be used,851// i.e. rand(), random().852//853// E. Bach, "Efficient prediction of Marsaglia-Zaman random number generators,"854// in IEEE Transactions on Information Theory, vol. 44, no. 3, pp. 1253-1257,855// May 1998, https://doi.org/10.1109/18.669305856//857// CWE-338: Use of cryptographically weak prng858//===----------------------------------------------------------------------===//859 860void WalkAST::checkCall_rand(const CallExpr *CE, const FunctionDecl *FD) {861 if (!filter.check_rand || !CheckRand)862 return;863 864 const FunctionProtoType *FTP = FD->getType()->getAs<FunctionProtoType>();865 if (!FTP)866 return;867 868 if (FTP->getNumParams() == 1) {869 // Is the argument an 'unsigned short *'?870 // (Actually any integer type is allowed.)871 const PointerType *PT = FTP->getParamType(0)->getAs<PointerType>();872 if (!PT)873 return;874 875 if (! PT->getPointeeType()->isIntegralOrUnscopedEnumerationType())876 return;877 } else if (FTP->getNumParams() != 0)878 return;879 880 // Issue a warning.881 SmallString<256> buf1;882 llvm::raw_svector_ostream os1(buf1);883 os1 << '\'' << *FD << "' is a poor random number generator";884 885 SmallString<256> buf2;886 llvm::raw_svector_ostream os2(buf2);887 os2 << "Function '" << *FD888 << "' is obsolete because it implements a poor random number generator."889 << " Use 'arc4random' instead";890 891 PathDiagnosticLocation CELoc =892 PathDiagnosticLocation::createBegin(CE, BR.getSourceManager(), AC);893 BR.EmitBasicReport(AC->getDecl(), filter.checkName_rand, os1.str(),894 "Security", os2.str(), CELoc,895 CE->getCallee()->getSourceRange());896}897 898// See justification for rand().899void WalkAST::checkCall_random(const CallExpr *CE, const FunctionDecl *FD) {900 if (!CheckRand || !filter.check_rand)901 return;902 903 const FunctionProtoType *FTP = FD->getType()->getAs<FunctionProtoType>();904 if (!FTP)905 return;906 907 // Verify that the function takes no argument.908 if (FTP->getNumParams() != 0)909 return;910 911 // Issue a warning.912 PathDiagnosticLocation CELoc =913 PathDiagnosticLocation::createBegin(CE, BR.getSourceManager(), AC);914 BR.EmitBasicReport(AC->getDecl(), filter.checkName_rand,915 "'random' is not a secure random number generator",916 "Security",917 "The 'random' function produces a sequence of values that "918 "an adversary may be able to predict. Use 'arc4random' "919 "instead", CELoc, CE->getCallee()->getSourceRange());920}921 922//===----------------------------------------------------------------------===//923// Check: 'vfork' should not be used.924// POS33-C: Do not use vfork().925//===----------------------------------------------------------------------===//926 927void WalkAST::checkCall_vfork(const CallExpr *CE, const FunctionDecl *FD) {928 if (!filter.check_vfork)929 return;930 931 // All calls to vfork() are insecure, issue a warning.932 PathDiagnosticLocation CELoc =933 PathDiagnosticLocation::createBegin(CE, BR.getSourceManager(), AC);934 BR.EmitBasicReport(AC->getDecl(), filter.checkName_vfork,935 "Potential insecure implementation-specific behavior in "936 "call 'vfork'",937 "Security",938 "Call to function 'vfork' is insecure as it can lead to "939 "denial of service situations in the parent process. "940 "Replace calls to vfork with calls to the safer "941 "'posix_spawn' function",942 CELoc, CE->getCallee()->getSourceRange());943}944 945//===----------------------------------------------------------------------===//946// Check: '-decodeValueOfObjCType:at:' should not be used.947// It is deprecated in favor of '-decodeValueOfObjCType:at:size:' due to948// likelihood of buffer overflows.949//===----------------------------------------------------------------------===//950 951void WalkAST::checkMsg_decodeValueOfObjCType(const ObjCMessageExpr *ME) {952 if (!filter.check_decodeValueOfObjCType)953 return;954 955 // Check availability of the secure alternative:956 // iOS 11+, macOS 10.13+, tvOS 11+, and watchOS 4.0+957 // FIXME: We probably shouldn't register the check if it's not available.958 const TargetInfo &TI = AC->getASTContext().getTargetInfo();959 const llvm::Triple &T = TI.getTriple();960 const VersionTuple &VT = TI.getPlatformMinVersion();961 switch (T.getOS()) {962 case llvm::Triple::IOS:963 if (VT < VersionTuple(11, 0))964 return;965 break;966 case llvm::Triple::MacOSX:967 if (VT < VersionTuple(10, 13))968 return;969 break;970 case llvm::Triple::WatchOS:971 if (VT < VersionTuple(4, 0))972 return;973 break;974 case llvm::Triple::TvOS:975 if (VT < VersionTuple(11, 0))976 return;977 break;978 case llvm::Triple::XROS:979 break;980 default:981 return;982 }983 984 PathDiagnosticLocation MELoc =985 PathDiagnosticLocation::createBegin(ME, BR.getSourceManager(), AC);986 BR.EmitBasicReport(987 AC->getDecl(), filter.checkName_decodeValueOfObjCType,988 "Potential buffer overflow in '-decodeValueOfObjCType:at:'", "Security",989 "Deprecated method '-decodeValueOfObjCType:at:' is insecure "990 "as it can lead to potential buffer overflows. Use the safer "991 "'-decodeValueOfObjCType:at:size:' method.",992 MELoc, ME->getSourceRange());993}994 995//===----------------------------------------------------------------------===//996// Check: The caller should always verify that the privileges997// were dropped successfully.998//999// Some library functions, like setuid() and setgid(), should always be used1000// with a check of the return value to verify that the function completed1001// successfully. If the drop fails, the software will continue to run1002// with the raised privileges, which might provide additional access1003// to unprivileged users.1004//1005// (Note that this check predates __attribute__((warn_unused_result)).1006// Do we still need it now that we have a compiler warning for this?1007// Are these standard functions already annotated this way?)1008//===----------------------------------------------------------------------===//1009 1010void WalkAST::checkUncheckedReturnValue(CallExpr *CE) {1011 if (!filter.check_UncheckedReturn)1012 return;1013 1014 const FunctionDecl *FD = CE->getDirectCallee();1015 if (!FD)1016 return;1017 1018 if (II_setid[0] == nullptr) {1019 static const char * const identifiers[num_setids] = {1020 "setuid", "setgid", "seteuid", "setegid",1021 "setreuid", "setregid"1022 };1023 1024 for (size_t i = 0; i < num_setids; i++)1025 II_setid[i] = &BR.getContext().Idents.get(identifiers[i]);1026 }1027 1028 const IdentifierInfo *id = FD->getIdentifier();1029 size_t identifierid;1030 1031 for (identifierid = 0; identifierid < num_setids; identifierid++)1032 if (id == II_setid[identifierid])1033 break;1034 1035 if (identifierid >= num_setids)1036 return;1037 1038 const FunctionProtoType *FTP = FD->getType()->getAs<FunctionProtoType>();1039 if (!FTP)1040 return;1041 1042 // Verify that the function takes one or two arguments (depending on1043 // the function).1044 if (FTP->getNumParams() != (identifierid < 4 ? 1 : 2))1045 return;1046 1047 // The arguments must be integers.1048 for (unsigned i = 0; i < FTP->getNumParams(); i++)1049 if (!FTP->getParamType(i)->isIntegralOrUnscopedEnumerationType())1050 return;1051 1052 // Issue a warning.1053 SmallString<256> buf1;1054 llvm::raw_svector_ostream os1(buf1);1055 os1 << "Return value is not checked in call to '" << *FD << '\'';1056 1057 SmallString<256> buf2;1058 llvm::raw_svector_ostream os2(buf2);1059 os2 << "The return value from the call to '" << *FD1060 << "' is not checked. If an error occurs in '" << *FD1061 << "', the following code may execute with unexpected privileges";1062 1063 PathDiagnosticLocation CELoc =1064 PathDiagnosticLocation::createBegin(CE, BR.getSourceManager(), AC);1065 BR.EmitBasicReport(AC->getDecl(), filter.checkName_UncheckedReturn, os1.str(),1066 "Security", os2.str(), CELoc,1067 CE->getCallee()->getSourceRange());1068}1069 1070//===----------------------------------------------------------------------===//1071// SecuritySyntaxChecker1072//===----------------------------------------------------------------------===//1073 1074namespace {1075class SecuritySyntaxChecker : public Checker<check::ASTCodeBody> {1076public:1077 ChecksFilter filter;1078 1079 void checkASTCodeBody(const Decl *D, AnalysisManager& mgr,1080 BugReporter &BR) const {1081 WalkAST walker(BR, mgr.getAnalysisDeclContext(D), filter);1082 walker.Visit(D->getBody());1083 }1084};1085}1086 1087void ento::registerSecuritySyntaxChecker(CheckerManager &mgr) {1088 mgr.registerChecker<SecuritySyntaxChecker>();1089}1090 1091bool ento::shouldRegisterSecuritySyntaxChecker(const CheckerManager &mgr) {1092 return true;1093}1094 1095#define REGISTER_CHECKER(name) \1096 void ento::register##name(CheckerManager &mgr) { \1097 SecuritySyntaxChecker *checker = mgr.getChecker<SecuritySyntaxChecker>(); \1098 checker->filter.check_##name = true; \1099 checker->filter.checkName_##name = mgr.getCurrentCheckerName(); \1100 } \1101 \1102 bool ento::shouldRegister##name(const CheckerManager &mgr) { return true; }1103 1104REGISTER_CHECKER(bcmp)1105REGISTER_CHECKER(bcopy)1106REGISTER_CHECKER(bzero)1107REGISTER_CHECKER(gets)1108REGISTER_CHECKER(getpw)1109REGISTER_CHECKER(mkstemp)1110REGISTER_CHECKER(mktemp)1111REGISTER_CHECKER(strcpy)1112REGISTER_CHECKER(rand)1113REGISTER_CHECKER(vfork)1114REGISTER_CHECKER(FloatLoopCounter)1115REGISTER_CHECKER(UncheckedReturn)1116REGISTER_CHECKER(DeprecatedOrUnsafeBufferHandling)1117REGISTER_CHECKER(decodeValueOfObjCType)1118