xref: /llvm-project/clang/lib/StaticAnalyzer/Checkers/CheckSecuritySyntaxOnly.cpp (revision 417d566775d923a55fbe9451533dc17ebfad9e7e)
1 //==- CheckSecuritySyntaxOnly.cpp - Basic security checks --------*- C++ -*-==//
2 //
3 //                     The LLVM Compiler Infrastructure
4 //
5 // This file is distributed under the University of Illinois Open Source
6 // License. See LICENSE.TXT for details.
7 //
8 //===----------------------------------------------------------------------===//
9 //
10 //  This file defines a set of flow-insensitive security checks.
11 //
12 //===----------------------------------------------------------------------===//
13 
14 #include "ClangSACheckers.h"
15 #include "clang/StaticAnalyzer/Core/Checker.h"
16 #include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h"
17 #include "clang/Basic/TargetInfo.h"
18 #include "clang/AST/StmtVisitor.h"
19 #include "llvm/Support/raw_ostream.h"
20 #include "llvm/ADT/StringSwitch.h"
21 
22 using namespace clang;
23 using namespace ento;
24 
25 static bool isArc4RandomAvailable(const ASTContext &Ctx) {
26   const llvm::Triple &T = Ctx.Target.getTriple();
27   return T.getVendor() == llvm::Triple::Apple ||
28          T.getOS() == llvm::Triple::FreeBSD ||
29          T.getOS() == llvm::Triple::NetBSD ||
30          T.getOS() == llvm::Triple::OpenBSD ||
31          T.getOS() == llvm::Triple::DragonFly;
32 }
33 
34 namespace {
35 class WalkAST : public StmtVisitor<WalkAST> {
36   BugReporter &BR;
37   enum { num_setids = 6 };
38   IdentifierInfo *II_setid[num_setids];
39 
40   const bool CheckRand;
41 
42 public:
43   WalkAST(BugReporter &br) : BR(br), II_setid(),
44                  CheckRand(isArc4RandomAvailable(BR.getContext())) {}
45 
46   // Statement visitor methods.
47   void VisitCallExpr(CallExpr *CE);
48   void VisitForStmt(ForStmt *S);
49   void VisitCompoundStmt (CompoundStmt *S);
50   void VisitStmt(Stmt *S) { VisitChildren(S); }
51 
52   void VisitChildren(Stmt *S);
53 
54   // Helpers.
55   bool checkCall_strCommon(const CallExpr *CE, const FunctionDecl *FD);
56 
57   typedef void (WalkAST::*FnCheck)(const CallExpr *,
58 				   const FunctionDecl *);
59 
60   // Checker-specific methods.
61   void checkLoopConditionForFloat(const ForStmt *FS);
62   void checkCall_gets(const CallExpr *CE, const FunctionDecl *FD);
63   void checkCall_getpw(const CallExpr *CE, const FunctionDecl *FD);
64   void checkCall_mktemp(const CallExpr *CE, const FunctionDecl *FD);
65   void checkCall_strcpy(const CallExpr *CE, const FunctionDecl *FD);
66   void checkCall_strcat(const CallExpr *CE, const FunctionDecl *FD);
67   void checkCall_rand(const CallExpr *CE, const FunctionDecl *FD);
68   void checkCall_random(const CallExpr *CE, const FunctionDecl *FD);
69   void checkUncheckedReturnValue(CallExpr *CE);
70 };
71 } // end anonymous namespace
72 
73 //===----------------------------------------------------------------------===//
74 // AST walking.
75 //===----------------------------------------------------------------------===//
76 
77 void WalkAST::VisitChildren(Stmt *S) {
78   for (Stmt::child_iterator I = S->child_begin(), E = S->child_end(); I!=E; ++I)
79     if (Stmt *child = *I)
80       Visit(child);
81 }
82 
83 void WalkAST::VisitCallExpr(CallExpr *CE) {
84   // Get the callee.
85   const FunctionDecl *FD = CE->getDirectCallee();
86 
87   if (!FD)
88     return;
89 
90   // Get the name of the callee. If it's a builtin, strip off the prefix.
91   IdentifierInfo *II = FD->getIdentifier();
92   if (!II)   // if no identifier, not a simple C function
93     return;
94   StringRef Name = II->getName();
95   if (Name.startswith("__builtin_"))
96     Name = Name.substr(10);
97 
98   // Set the evaluation function by switching on the callee name.
99   FnCheck evalFunction = llvm::StringSwitch<FnCheck>(Name)
100     .Case("gets", &WalkAST::checkCall_gets)
101     .Case("getpw", &WalkAST::checkCall_getpw)
102     .Case("mktemp", &WalkAST::checkCall_mktemp)
103     .Cases("strcpy", "__strcpy_chk", &WalkAST::checkCall_strcpy)
104     .Cases("strcat", "__strcat_chk", &WalkAST::checkCall_strcat)
105     .Case("drand48", &WalkAST::checkCall_rand)
106     .Case("erand48", &WalkAST::checkCall_rand)
107     .Case("jrand48", &WalkAST::checkCall_rand)
108     .Case("lrand48", &WalkAST::checkCall_rand)
109     .Case("mrand48", &WalkAST::checkCall_rand)
110     .Case("nrand48", &WalkAST::checkCall_rand)
111     .Case("lcong48", &WalkAST::checkCall_rand)
112     .Case("rand", &WalkAST::checkCall_rand)
113     .Case("rand_r", &WalkAST::checkCall_rand)
114     .Case("random", &WalkAST::checkCall_random)
115     .Default(NULL);
116 
117   // If the callee isn't defined, it is not of security concern.
118   // Check and evaluate the call.
119   if (evalFunction)
120     (this->*evalFunction)(CE, FD);
121 
122   // Recurse and check children.
123   VisitChildren(CE);
124 }
125 
126 void WalkAST::VisitCompoundStmt(CompoundStmt *S) {
127   for (Stmt::child_iterator I = S->child_begin(), E = S->child_end(); I!=E; ++I)
128     if (Stmt *child = *I) {
129       if (CallExpr *CE = dyn_cast<CallExpr>(child))
130         checkUncheckedReturnValue(CE);
131       Visit(child);
132     }
133 }
134 
135 void WalkAST::VisitForStmt(ForStmt *FS) {
136   checkLoopConditionForFloat(FS);
137 
138   // Recurse and check children.
139   VisitChildren(FS);
140 }
141 
142 //===----------------------------------------------------------------------===//
143 // Check: floating poing variable used as loop counter.
144 // Originally: <rdar://problem/6336718>
145 // Implements: CERT security coding advisory FLP-30.
146 //===----------------------------------------------------------------------===//
147 
148 static const DeclRefExpr*
149 getIncrementedVar(const Expr *expr, const VarDecl *x, const VarDecl *y) {
150   expr = expr->IgnoreParenCasts();
151 
152   if (const BinaryOperator *B = dyn_cast<BinaryOperator>(expr)) {
153     if (!(B->isAssignmentOp() || B->isCompoundAssignmentOp() ||
154           B->getOpcode() == BO_Comma))
155       return NULL;
156 
157     if (const DeclRefExpr *lhs = getIncrementedVar(B->getLHS(), x, y))
158       return lhs;
159 
160     if (const DeclRefExpr *rhs = getIncrementedVar(B->getRHS(), x, y))
161       return rhs;
162 
163     return NULL;
164   }
165 
166   if (const DeclRefExpr *DR = dyn_cast<DeclRefExpr>(expr)) {
167     const NamedDecl *ND = DR->getDecl();
168     return ND == x || ND == y ? DR : NULL;
169   }
170 
171   if (const UnaryOperator *U = dyn_cast<UnaryOperator>(expr))
172     return U->isIncrementDecrementOp()
173       ? getIncrementedVar(U->getSubExpr(), x, y) : NULL;
174 
175   return NULL;
176 }
177 
178 /// CheckLoopConditionForFloat - This check looks for 'for' statements that
179 ///  use a floating point variable as a loop counter.
180 ///  CERT: FLP30-C, FLP30-CPP.
181 ///
182 void WalkAST::checkLoopConditionForFloat(const ForStmt *FS) {
183   // Does the loop have a condition?
184   const Expr *condition = FS->getCond();
185 
186   if (!condition)
187     return;
188 
189   // Does the loop have an increment?
190   const Expr *increment = FS->getInc();
191 
192   if (!increment)
193     return;
194 
195   // Strip away '()' and casts.
196   condition = condition->IgnoreParenCasts();
197   increment = increment->IgnoreParenCasts();
198 
199   // Is the loop condition a comparison?
200   const BinaryOperator *B = dyn_cast<BinaryOperator>(condition);
201 
202   if (!B)
203     return;
204 
205   // Is this a comparison?
206   if (!(B->isRelationalOp() || B->isEqualityOp()))
207     return;
208 
209   // Are we comparing variables?
210   const DeclRefExpr *drLHS =
211     dyn_cast<DeclRefExpr>(B->getLHS()->IgnoreParenLValueCasts());
212   const DeclRefExpr *drRHS =
213     dyn_cast<DeclRefExpr>(B->getRHS()->IgnoreParenLValueCasts());
214 
215   // Does at least one of the variables have a floating point type?
216   drLHS = drLHS && drLHS->getType()->isRealFloatingType() ? drLHS : NULL;
217   drRHS = drRHS && drRHS->getType()->isRealFloatingType() ? drRHS : NULL;
218 
219   if (!drLHS && !drRHS)
220     return;
221 
222   const VarDecl *vdLHS = drLHS ? dyn_cast<VarDecl>(drLHS->getDecl()) : NULL;
223   const VarDecl *vdRHS = drRHS ? dyn_cast<VarDecl>(drRHS->getDecl()) : NULL;
224 
225   if (!vdLHS && !vdRHS)
226     return;
227 
228   // Does either variable appear in increment?
229   const DeclRefExpr *drInc = getIncrementedVar(increment, vdLHS, vdRHS);
230 
231   if (!drInc)
232     return;
233 
234   // Emit the error.  First figure out which DeclRefExpr in the condition
235   // referenced the compared variable.
236   const DeclRefExpr *drCond = vdLHS == drInc->getDecl() ? drLHS : drRHS;
237 
238   SmallVector<SourceRange, 2> ranges;
239   llvm::SmallString<256> sbuf;
240   llvm::raw_svector_ostream os(sbuf);
241 
242   os << "Variable '" << drCond->getDecl()->getName()
243      << "' with floating point type '" << drCond->getType().getAsString()
244      << "' should not be used as a loop counter";
245 
246   ranges.push_back(drCond->getSourceRange());
247   ranges.push_back(drInc->getSourceRange());
248 
249   const char *bugType = "Floating point variable used as loop counter";
250   BR.EmitBasicReport(bugType, "Security", os.str(),
251                      FS->getLocStart(), ranges.data(), ranges.size());
252 }
253 
254 //===----------------------------------------------------------------------===//
255 // Check: Any use of 'gets' is insecure.
256 // Originally: <rdar://problem/6335715>
257 // Implements (part of): 300-BSI (buildsecurityin.us-cert.gov)
258 // CWE-242: Use of Inherently Dangerous Function
259 //===----------------------------------------------------------------------===//
260 
261 void WalkAST::checkCall_gets(const CallExpr *CE, const FunctionDecl *FD) {
262   const FunctionProtoType *FPT
263     = dyn_cast<FunctionProtoType>(FD->getType().IgnoreParens());
264   if (!FPT)
265     return;
266 
267   // Verify that the function takes a single argument.
268   if (FPT->getNumArgs() != 1)
269     return;
270 
271   // Is the argument a 'char*'?
272   const PointerType *PT = dyn_cast<PointerType>(FPT->getArgType(0));
273   if (!PT)
274     return;
275 
276   if (PT->getPointeeType().getUnqualifiedType() != BR.getContext().CharTy)
277     return;
278 
279   // Issue a warning.
280   SourceRange R = CE->getCallee()->getSourceRange();
281   BR.EmitBasicReport("Potential buffer overflow in call to 'gets'",
282                      "Security",
283                      "Call to function 'gets' is extremely insecure as it can "
284                      "always result in a buffer overflow",
285                      CE->getLocStart(), &R, 1);
286 }
287 
288 //===----------------------------------------------------------------------===//
289 // Check: Any use of 'getpwd' is insecure.
290 // CWE-477: Use of Obsolete Functions
291 //===----------------------------------------------------------------------===//
292 
293 void WalkAST::checkCall_getpw(const CallExpr *CE, const FunctionDecl *FD) {
294   const FunctionProtoType *FPT
295     = dyn_cast<FunctionProtoType>(FD->getType().IgnoreParens());
296   if (!FPT)
297     return;
298 
299   // Verify that the function takes two arguments.
300   if (FPT->getNumArgs() != 2)
301     return;
302 
303   // Verify the first argument type is integer.
304   if (!FPT->getArgType(0)->isIntegerType())
305     return;
306 
307   // Verify the second argument type is char*.
308   const PointerType *PT = dyn_cast<PointerType>(FPT->getArgType(1));
309   if (!PT)
310     return;
311 
312   if (PT->getPointeeType().getUnqualifiedType() != BR.getContext().CharTy)
313     return;
314 
315   // Issue a warning.
316   SourceRange R = CE->getCallee()->getSourceRange();
317   BR.EmitBasicReport("Potential buffer overflow in call to 'getpw'",
318                      "Security",
319                      "The getpw() function is dangerous as it may overflow the "
320                      "provided buffer. It is obsoleted by getpwuid().",
321                      CE->getLocStart(), &R, 1);
322 }
323 
324 //===----------------------------------------------------------------------===//
325 // Check: Any use of 'mktemp' is insecure.It is obsoleted by mkstemp().
326 // CWE-377: Insecure Temporary File
327 //===----------------------------------------------------------------------===//
328 
329 void WalkAST::checkCall_mktemp(const CallExpr *CE, const FunctionDecl *FD) {
330   const FunctionProtoType *FPT
331     = dyn_cast<FunctionProtoType>(FD->getType().IgnoreParens());
332   if(!FPT)
333     return;
334 
335   // Verify that the function takes a single argument.
336   if (FPT->getNumArgs() != 1)
337     return;
338 
339   // Verify that the argument is Pointer Type.
340   const PointerType *PT = dyn_cast<PointerType>(FPT->getArgType(0));
341   if (!PT)
342     return;
343 
344   // Verify that the argument is a 'char*'.
345   if (PT->getPointeeType().getUnqualifiedType() != BR.getContext().CharTy)
346     return;
347 
348   // Issue a waring.
349   SourceRange R = CE->getCallee()->getSourceRange();
350   BR.EmitBasicReport("Potential insecure temporary file in call 'mktemp'",
351                      "Security",
352                      "Call to function 'mktemp' is insecure as it always "
353                      "creates or uses insecure temporary file.  Use 'mkstemp' instead",
354                      CE->getLocStart(), &R, 1);
355 }
356 
357 //===----------------------------------------------------------------------===//
358 // Check: Any use of 'strcpy' is insecure.
359 //
360 // CWE-119: Improper Restriction of Operations within
361 // the Bounds of a Memory Buffer
362 //===----------------------------------------------------------------------===//
363 void WalkAST::checkCall_strcpy(const CallExpr *CE, const FunctionDecl *FD) {
364   if (!checkCall_strCommon(CE, FD))
365     return;
366 
367   // Issue a warning.
368   SourceRange R = CE->getCallee()->getSourceRange();
369   BR.EmitBasicReport("Potential insecure memory buffer bounds restriction in "
370                      "call 'strcpy'",
371                      "Security",
372                      "Call to function 'strcpy' is insecure as it does not "
373 		     "provide bounding of the memory buffer. Replace "
374 		     "unbounded copy functions with analogous functions that "
375 		     "support length arguments such as 'strncpy'. CWE-119.",
376                      CE->getLocStart(), &R, 1);
377 }
378 
379 //===----------------------------------------------------------------------===//
380 // Check: Any use of 'strcat' is insecure.
381 //
382 // CWE-119: Improper Restriction of Operations within
383 // the Bounds of a Memory Buffer
384 //===----------------------------------------------------------------------===//
385 void WalkAST::checkCall_strcat(const CallExpr *CE, const FunctionDecl *FD) {
386   if (!checkCall_strCommon(CE, FD))
387     return;
388 
389   // Issue a warning.
390   SourceRange R = CE->getCallee()->getSourceRange();
391   BR.EmitBasicReport("Potential insecure memory buffer bounds restriction in "
392 		     "call 'strcat'",
393 		     "Security",
394 		     "Call to function 'strcat' is insecure as it does not "
395 		     "provide bounding of the memory buffer. Replace "
396 		     "unbounded copy functions with analogous functions that "
397 		     "support length arguments such as 'strncat'. CWE-119.",
398                      CE->getLocStart(), &R, 1);
399 }
400 
401 //===----------------------------------------------------------------------===//
402 // Common check for str* functions with no bounds parameters.
403 //===----------------------------------------------------------------------===//
404 bool WalkAST::checkCall_strCommon(const CallExpr *CE, const FunctionDecl *FD) {
405   const FunctionProtoType *FPT
406     = dyn_cast<FunctionProtoType>(FD->getType().IgnoreParens());
407   if (!FPT)
408     return false;
409 
410   // Verify the function takes two arguments, three in the _chk version.
411   int numArgs = FPT->getNumArgs();
412   if (numArgs != 2 && numArgs != 3)
413     return false;
414 
415   // Verify the type for both arguments.
416   for (int i = 0; i < 2; i++) {
417     // Verify that the arguments are pointers.
418     const PointerType *PT = dyn_cast<PointerType>(FPT->getArgType(i));
419     if (!PT)
420       return false;
421 
422     // Verify that the argument is a 'char*'.
423     if (PT->getPointeeType().getUnqualifiedType() != BR.getContext().CharTy)
424       return false;
425   }
426 
427   return true;
428 }
429 
430 //===----------------------------------------------------------------------===//
431 // Check: Linear congruent random number generators should not be used
432 // Originally: <rdar://problem/63371000>
433 // CWE-338: Use of cryptographically weak prng
434 //===----------------------------------------------------------------------===//
435 
436 void WalkAST::checkCall_rand(const CallExpr *CE, const FunctionDecl *FD) {
437   if (!CheckRand)
438     return;
439 
440   const FunctionProtoType *FTP
441     = dyn_cast<FunctionProtoType>(FD->getType().IgnoreParens());
442   if (!FTP)
443     return;
444 
445   if (FTP->getNumArgs() == 1) {
446     // Is the argument an 'unsigned short *'?
447     // (Actually any integer type is allowed.)
448     const PointerType *PT = dyn_cast<PointerType>(FTP->getArgType(0));
449     if (!PT)
450       return;
451 
452     if (! PT->getPointeeType()->isIntegerType())
453       return;
454   }
455   else if (FTP->getNumArgs() != 0)
456     return;
457 
458   // Issue a warning.
459   llvm::SmallString<256> buf1;
460   llvm::raw_svector_ostream os1(buf1);
461   os1 << '\'' << FD << "' is a poor random number generator";
462 
463   llvm::SmallString<256> buf2;
464   llvm::raw_svector_ostream os2(buf2);
465   os2 << "Function '" << FD
466       << "' is obsolete because it implements a poor random number generator."
467       << "  Use 'arc4random' instead";
468 
469   SourceRange R = CE->getCallee()->getSourceRange();
470   BR.EmitBasicReport(os1.str(), "Security", os2.str(),CE->getLocStart(), &R, 1);
471 }
472 
473 //===----------------------------------------------------------------------===//
474 // Check: 'random' should not be used
475 // Originally: <rdar://problem/63371000>
476 //===----------------------------------------------------------------------===//
477 
478 void WalkAST::checkCall_random(const CallExpr *CE, const FunctionDecl *FD) {
479   if (!CheckRand)
480     return;
481 
482   const FunctionProtoType *FTP
483     = dyn_cast<FunctionProtoType>(FD->getType().IgnoreParens());
484   if (!FTP)
485     return;
486 
487   // Verify that the function takes no argument.
488   if (FTP->getNumArgs() != 0)
489     return;
490 
491   // Issue a warning.
492   SourceRange R = CE->getCallee()->getSourceRange();
493   BR.EmitBasicReport("'random' is not a secure random number generator",
494                      "Security",
495                      "The 'random' function produces a sequence of values that "
496                      "an adversary may be able to predict.  Use 'arc4random' "
497                      "instead", CE->getLocStart(), &R, 1);
498 }
499 
500 //===----------------------------------------------------------------------===//
501 // Check: Should check whether privileges are dropped successfully.
502 // Originally: <rdar://problem/6337132>
503 //===----------------------------------------------------------------------===//
504 
505 void WalkAST::checkUncheckedReturnValue(CallExpr *CE) {
506   const FunctionDecl *FD = CE->getDirectCallee();
507   if (!FD)
508     return;
509 
510   if (II_setid[0] == NULL) {
511     static const char * const identifiers[num_setids] = {
512       "setuid", "setgid", "seteuid", "setegid",
513       "setreuid", "setregid"
514     };
515 
516     for (size_t i = 0; i < num_setids; i++)
517       II_setid[i] = &BR.getContext().Idents.get(identifiers[i]);
518   }
519 
520   const IdentifierInfo *id = FD->getIdentifier();
521   size_t identifierid;
522 
523   for (identifierid = 0; identifierid < num_setids; identifierid++)
524     if (id == II_setid[identifierid])
525       break;
526 
527   if (identifierid >= num_setids)
528     return;
529 
530   const FunctionProtoType *FTP
531     = dyn_cast<FunctionProtoType>(FD->getType().IgnoreParens());
532   if (!FTP)
533     return;
534 
535   // Verify that the function takes one or two arguments (depending on
536   //   the function).
537   if (FTP->getNumArgs() != (identifierid < 4 ? 1 : 2))
538     return;
539 
540   // The arguments must be integers.
541   for (unsigned i = 0; i < FTP->getNumArgs(); i++)
542     if (! FTP->getArgType(i)->isIntegerType())
543       return;
544 
545   // Issue a warning.
546   llvm::SmallString<256> buf1;
547   llvm::raw_svector_ostream os1(buf1);
548   os1 << "Return value is not checked in call to '" << FD << '\'';
549 
550   llvm::SmallString<256> buf2;
551   llvm::raw_svector_ostream os2(buf2);
552   os2 << "The return value from the call to '" << FD
553       << "' is not checked.  If an error occurs in '" << FD
554       << "', the following code may execute with unexpected privileges";
555 
556   SourceRange R = CE->getCallee()->getSourceRange();
557   BR.EmitBasicReport(os1.str(), "Security", os2.str(),CE->getLocStart(), &R, 1);
558 }
559 
560 //===----------------------------------------------------------------------===//
561 // SecuritySyntaxChecker
562 //===----------------------------------------------------------------------===//
563 
564 namespace {
565 class SecuritySyntaxChecker : public Checker<check::ASTCodeBody> {
566 public:
567   void checkASTCodeBody(const Decl *D, AnalysisManager& mgr,
568                         BugReporter &BR) const {
569     WalkAST walker(BR);
570     walker.Visit(D->getBody());
571   }
572 };
573 }
574 
575 void ento::registerSecuritySyntaxChecker(CheckerManager &mgr) {
576   mgr.registerChecker<SecuritySyntaxChecker>();
577 }
578