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