xref: /llvm-project/clang/lib/StaticAnalyzer/Checkers/CStringSyntaxChecker.cpp (revision 54fc3767fcfd29eadd58ed5c3fad5e18d7724c89)
1 //== CStringSyntaxChecker.cpp - CoreFoundation containers API *- 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 // An AST checker that looks for common pitfalls when using C string APIs.
11 //  - Identifies erroneous patterns in the last argument to strncat - the number
12 //    of bytes to copy.
13 //
14 //===----------------------------------------------------------------------===//
15 #include "ClangSACheckers.h"
16 #include "clang/AST/Expr.h"
17 #include "clang/AST/OperationKinds.h"
18 #include "clang/AST/StmtVisitor.h"
19 #include "clang/Analysis/AnalysisDeclContext.h"
20 #include "clang/Basic/TargetInfo.h"
21 #include "clang/Basic/TypeTraits.h"
22 #include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h"
23 #include "clang/StaticAnalyzer/Core/Checker.h"
24 #include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h"
25 #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
26 #include "llvm/ADT/SmallString.h"
27 #include "llvm/Support/raw_ostream.h"
28 
29 using namespace clang;
30 using namespace ento;
31 
32 namespace {
33 class WalkAST: public StmtVisitor<WalkAST> {
34   const CheckerBase *Checker;
35   BugReporter &BR;
36   AnalysisDeclContext* AC;
37 
38   /// Check if two expressions refer to the same declaration.
39   bool sameDecl(const Expr *A1, const Expr *A2) {
40     if (const auto *D1 = dyn_cast<DeclRefExpr>(A1->IgnoreParenCasts()))
41       if (const auto *D2 = dyn_cast<DeclRefExpr>(A2->IgnoreParenCasts()))
42         return D1->getDecl() == D2->getDecl();
43     return false;
44   }
45 
46   /// Check if the expression E is a sizeof(WithArg).
47   bool isSizeof(const Expr *E, const Expr *WithArg) {
48     if (const auto *UE = dyn_cast<UnaryExprOrTypeTraitExpr>(E))
49       if (UE->getKind() == UETT_SizeOf && !UE->isArgumentType())
50         return sameDecl(UE->getArgumentExpr(), WithArg);
51     return false;
52   }
53 
54   /// Check if the expression E is a strlen(WithArg).
55   bool isStrlen(const Expr *E, const Expr *WithArg) {
56     if (const auto *CE = dyn_cast<CallExpr>(E)) {
57       const FunctionDecl *FD = CE->getDirectCallee();
58       if (!FD)
59         return false;
60       return (CheckerContext::isCLibraryFunction(FD, "strlen") &&
61               sameDecl(CE->getArg(0), WithArg));
62     }
63     return false;
64   }
65 
66   /// Check if the expression is an integer literal with value 1.
67   bool isOne(const Expr *E) {
68     if (const auto *IL = dyn_cast<IntegerLiteral>(E))
69       return (IL->getValue().isIntN(1));
70     return false;
71   }
72 
73   StringRef getPrintableName(const Expr *E) {
74     if (const auto *D = dyn_cast<DeclRefExpr>(E->IgnoreParenCasts()))
75       return D->getDecl()->getName();
76     return StringRef();
77   }
78 
79   /// Identify erroneous patterns in the last argument to strncat - the number
80   /// of bytes to copy.
81   bool containsBadStrncatPattern(const CallExpr *CE);
82 
83   /// Identify erroneous patterns in the last argument to strlcpy - the number
84   /// of bytes to copy.
85   /// The bad pattern checked is when the size is known
86   /// to be larger than the destination can handle.
87   ///   char dst[2];
88   ///   size_t cpy = 4;
89   ///   strlcpy(dst, "abcd", sizeof("abcd") - 1);
90   ///   strlcpy(dst, "abcd", 4);
91   ///   strlcpy(dst + 3, "abcd", 2);
92   ///   strlcpy(dst, "abcd", cpy);
93   /// Identify erroneous patterns in the last argument to strlcat - the number
94   /// of bytes to copy.
95   /// The bad pattern checked is when the last argument is basically
96   /// pointing to the destination buffer size or argument larger or
97   /// equal to.
98   ///   char dst[2];
99   ///   strlcat(dst, src2, sizeof(dst));
100   ///   strlcat(dst, src2, 2);
101   ///   strlcat(dst, src2, 10);
102   bool containsBadStrlcpyStrlcatPattern(const CallExpr *CE);
103 
104 public:
105   WalkAST(const CheckerBase *Checker, BugReporter &BR, AnalysisDeclContext *AC)
106       : Checker(Checker), BR(BR), AC(AC) {}
107 
108   // Statement visitor methods.
109   void VisitChildren(Stmt *S);
110   void VisitStmt(Stmt *S) {
111     VisitChildren(S);
112   }
113   void VisitCallExpr(CallExpr *CE);
114 };
115 } // end anonymous namespace
116 
117 // The correct size argument should look like following:
118 //   strncat(dst, src, sizeof(dst) - strlen(dest) - 1);
119 // We look for the following anti-patterns:
120 //   - strncat(dst, src, sizeof(dst) - strlen(dst));
121 //   - strncat(dst, src, sizeof(dst) - 1);
122 //   - strncat(dst, src, sizeof(dst));
123 bool WalkAST::containsBadStrncatPattern(const CallExpr *CE) {
124   if (CE->getNumArgs() != 3)
125     return false;
126   const Expr *DstArg = CE->getArg(0);
127   const Expr *SrcArg = CE->getArg(1);
128   const Expr *LenArg = CE->getArg(2);
129 
130   // Identify wrong size expressions, which are commonly used instead.
131   if (const auto *BE = dyn_cast<BinaryOperator>(LenArg->IgnoreParenCasts())) {
132     // - sizeof(dst) - strlen(dst)
133     if (BE->getOpcode() == BO_Sub) {
134       const Expr *L = BE->getLHS();
135       const Expr *R = BE->getRHS();
136       if (isSizeof(L, DstArg) && isStrlen(R, DstArg))
137         return true;
138 
139       // - sizeof(dst) - 1
140       if (isSizeof(L, DstArg) && isOne(R->IgnoreParenCasts()))
141         return true;
142     }
143   }
144   // - sizeof(dst)
145   if (isSizeof(LenArg, DstArg))
146     return true;
147 
148   // - sizeof(src)
149   if (isSizeof(LenArg, SrcArg))
150     return true;
151   return false;
152 }
153 
154 bool WalkAST::containsBadStrlcpyStrlcatPattern(const CallExpr *CE) {
155   if (CE->getNumArgs() != 3)
156     return false;
157   const FunctionDecl *FD = CE->getDirectCallee();
158   bool Append = CheckerContext::isCLibraryFunction(FD, "strlcat");
159   const Expr *DstArg = CE->getArg(0);
160   const Expr *LenArg = CE->getArg(2);
161 
162   const auto *DstArgDecl = dyn_cast<DeclRefExpr>(DstArg->IgnoreParenImpCasts());
163   const auto *LenArgDecl = dyn_cast<DeclRefExpr>(LenArg->IgnoreParenLValueCasts());
164   uint64_t DstOff = 0;
165   // - sizeof(dst)
166   // strlcat appends at most size - strlen(dst) - 1
167   if (Append && isSizeof(LenArg, DstArg))
168     return true;
169   // - size_t dstlen = sizeof(dst)
170   if (LenArgDecl) {
171     const auto *LenArgVal = dyn_cast<VarDecl>(LenArgDecl->getDecl());
172     if (LenArgVal->getInit())
173       LenArg = LenArgVal->getInit();
174   }
175 
176   // - integral value
177   // We try to figure out if the last argument is possibly longer
178   // than the destination can possibly handle if its size can be defined.
179   if (const auto *IL = dyn_cast<IntegerLiteral>(LenArg->IgnoreParenImpCasts())) {
180     uint64_t ILRawVal = IL->getValue().getZExtValue();
181 
182     // Case when there is pointer arithmetic on the destination buffer
183     // especially when we offset from the base decreasing the
184     // buffer length accordingly.
185     if (!DstArgDecl) {
186       if (const auto *BE = dyn_cast<BinaryOperator>(DstArg->IgnoreParenImpCasts())) {
187         DstArgDecl = dyn_cast<DeclRefExpr>(BE->getLHS()->IgnoreParenImpCasts());
188         if (BE->getOpcode() == BO_Add) {
189           if ((IL = dyn_cast<IntegerLiteral>(BE->getRHS()->IgnoreParenImpCasts()))) {
190             DstOff = IL->getValue().getZExtValue();
191           }
192         }
193       }
194     }
195     if (DstArgDecl) {
196       if (const auto *Buffer = dyn_cast<ConstantArrayType>(DstArgDecl->getType())) {
197         ASTContext &C = BR.getContext();
198         uint64_t BufferLen = C.getTypeSize(Buffer) / 8;
199         auto RemainingBufferLen = BufferLen - DstOff;
200         if (Append)
201           RemainingBufferLen -= 1;
202         if (RemainingBufferLen < ILRawVal)
203           return true;
204       }
205     }
206   }
207 
208   return false;
209 }
210 
211 void WalkAST::VisitCallExpr(CallExpr *CE) {
212   const FunctionDecl *FD = CE->getDirectCallee();
213   if (!FD)
214     return;
215 
216   if (CheckerContext::isCLibraryFunction(FD, "strncat")) {
217     if (containsBadStrncatPattern(CE)) {
218       const Expr *DstArg = CE->getArg(0);
219       const Expr *LenArg = CE->getArg(2);
220       PathDiagnosticLocation Loc =
221         PathDiagnosticLocation::createBegin(LenArg, BR.getSourceManager(), AC);
222 
223       StringRef DstName = getPrintableName(DstArg);
224 
225       SmallString<256> S;
226       llvm::raw_svector_ostream os(S);
227       os << "Potential buffer overflow. ";
228       if (!DstName.empty()) {
229         os << "Replace with 'sizeof(" << DstName << ") "
230               "- strlen(" << DstName <<") - 1'";
231         os << " or u";
232       } else
233         os << "U";
234       os << "se a safer 'strlcat' API";
235 
236       BR.EmitBasicReport(FD, Checker, "Anti-pattern in the argument",
237                          "C String API", os.str(), Loc,
238                          LenArg->getSourceRange());
239     }
240   } else if (CheckerContext::isCLibraryFunction(FD, "strlcpy")) {
241     if (containsBadStrlcpyStrlcatPattern(CE)) {
242       const Expr *DstArg = CE->getArg(0);
243       const Expr *LenArg = CE->getArg(2);
244       PathDiagnosticLocation Loc =
245         PathDiagnosticLocation::createBegin(LenArg, BR.getSourceManager(), AC);
246 
247       StringRef DstName = getPrintableName(DstArg);
248 
249       SmallString<256> S;
250       llvm::raw_svector_ostream os(S);
251       os << "The third argument is larger than the size of the input buffer. ";
252       if (!DstName.empty())
253         os << "Replace with the value 'sizeof(" << DstName << ")` or lower";
254 
255       BR.EmitBasicReport(FD, Checker, "Anti-pattern in the argument",
256                          "C String API", os.str(), Loc,
257                          LenArg->getSourceRange());
258     }
259   } else if (CheckerContext::isCLibraryFunction(FD, "strlcat")) {
260     if (containsBadStrlcpyStrlcatPattern(CE)) {
261       const Expr *DstArg = CE->getArg(0);
262       const Expr *LenArg = CE->getArg(2);
263       PathDiagnosticLocation Loc =
264         PathDiagnosticLocation::createBegin(LenArg, BR.getSourceManager(), AC);
265 
266       StringRef DstName = getPrintableName(DstArg);
267       StringRef LenName = getPrintableName(LenArg);
268 
269       SmallString<256> S;
270       llvm::raw_svector_ostream os(S);
271       os << "The third argument allows to potentially copy more bytes than it should. ";
272       os << "Replace with the value ";
273       if (!LenName.empty())
274         os << "'" << LenName << "'";
275       else
276         os << " <size> ";
277       if (!DstName.empty())
278         os << " - strlen(" << DstName << ")";
279       else
280         os << " - strlen(<destination buffer>)";
281       os << " - 1 or lower";
282 
283       BR.EmitBasicReport(FD, Checker, "Anti-pattern in the argument",
284                          "C String API", os.str(), Loc,
285                          LenArg->getSourceRange());
286     }
287   }
288 
289   // Recurse and check children.
290   VisitChildren(CE);
291 }
292 
293 void WalkAST::VisitChildren(Stmt *S) {
294   for (Stmt *Child : S->children())
295     if (Child)
296       Visit(Child);
297 }
298 
299 namespace {
300 class CStringSyntaxChecker: public Checker<check::ASTCodeBody> {
301 public:
302 
303   void checkASTCodeBody(const Decl *D, AnalysisManager& Mgr,
304       BugReporter &BR) const {
305     WalkAST walker(this, BR, Mgr.getAnalysisDeclContext(D));
306     walker.Visit(D->getBody());
307   }
308 };
309 }
310 
311 void ento::registerCStringSyntaxChecker(CheckerManager &mgr) {
312   mgr.registerChecker<CStringSyntaxChecker>();
313 }
314 
315