[analyzer] Add a syntactic security check for ObjC NSCoder API.
authorArtem Dergachev <artem.dergachev@gmail.com>
Thu, 19 Dec 2019 22:21:02 +0000 (14:21 -0800)
committerArtem Dergachev <artem.dergachev@gmail.com>
Thu, 19 Dec 2019 22:54:29 +0000 (14:54 -0800)
Method '-[NSCoder decodeValueOfObjCType:at:]' is not only deprecated
but also a security hazard, hence a loud check.

Differential Revision: https://reviews.llvm.org/D71728

clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
clang/lib/Driver/ToolChains/Clang.cpp
clang/lib/StaticAnalyzer/Checkers/CheckSecuritySyntaxOnly.cpp
clang/test/Analysis/security-syntax-checks-nscoder.m [new file with mode: 0644]
clang/www/analyzer/available_checks.html

index 43632e8..5b23de4 100644 (file)
@@ -773,6 +773,11 @@ def DeprecatedOrUnsafeBufferHandling :
   Dependencies<[SecuritySyntaxChecker]>,
   Documentation<HasDocumentation>;
 
+def decodeValueOfObjCType : Checker<"decodeValueOfObjCType">,
+  HelpText<"Warn on uses of the '-decodeValueOfObjCType:at:' method">,
+  Dependencies<[SecuritySyntaxChecker]>,
+  Documentation<HasDocumentation>;
+
 } // end "security.insecureAPI"
 
 let ParentPackage = Security in {
index 6b93dc2..1347c3d 100644 (file)
@@ -2789,8 +2789,11 @@ static void RenderAnalyzerOptions(const ArgList &Args, ArgStringList &CmdArgs,
       CmdArgs.push_back("-analyzer-disable-checker=unix.Vfork");
     }
 
-    if (Triple.isOSDarwin())
+    if (Triple.isOSDarwin()) {
       CmdArgs.push_back("-analyzer-checker=osx");
+      CmdArgs.push_back(
+          "-analyzer-checker=security.insecureAPI.decodeValueOfObjCType");
+    }
 
     CmdArgs.push_back("-analyzer-checker=deadcode");
 
index 260a289..d9ffa56 100644 (file)
@@ -49,6 +49,7 @@ struct ChecksFilter {
   DefaultBool check_vfork;
   DefaultBool check_FloatLoopCounter;
   DefaultBool check_UncheckedReturn;
+  DefaultBool check_decodeValueOfObjCType;
 
   CheckerNameRef checkName_bcmp;
   CheckerNameRef checkName_bcopy;
@@ -63,6 +64,7 @@ struct ChecksFilter {
   CheckerNameRef checkName_vfork;
   CheckerNameRef checkName_FloatLoopCounter;
   CheckerNameRef checkName_UncheckedReturn;
+  CheckerNameRef checkName_decodeValueOfObjCType;
 };
 
 class WalkAST : public StmtVisitor<WalkAST> {
@@ -83,6 +85,7 @@ public:
 
   // Statement visitor methods.
   void VisitCallExpr(CallExpr *CE);
+  void VisitObjCMessageExpr(ObjCMessageExpr *CE);
   void VisitForStmt(ForStmt *S);
   void VisitCompoundStmt (CompoundStmt *S);
   void VisitStmt(Stmt *S) { VisitChildren(S); }
@@ -93,6 +96,7 @@ public:
   bool checkCall_strCommon(const CallExpr *CE, const FunctionDecl *FD);
 
   typedef void (WalkAST::*FnCheck)(const CallExpr *, const FunctionDecl *);
+  typedef void (WalkAST::*MsgCheck)(const ObjCMessageExpr *);
 
   // Checker-specific methods.
   void checkLoopConditionForFloat(const ForStmt *FS);
@@ -110,6 +114,7 @@ public:
   void checkCall_rand(const CallExpr *CE, const FunctionDecl *FD);
   void checkCall_random(const CallExpr *CE, const FunctionDecl *FD);
   void checkCall_vfork(const CallExpr *CE, const FunctionDecl *FD);
+  void checkMsg_decodeValueOfObjCType(const ObjCMessageExpr *ME);
   void checkUncheckedReturnValue(CallExpr *CE);
 };
 } // end anonymous namespace
@@ -182,6 +187,20 @@ void WalkAST::VisitCallExpr(CallExpr *CE) {
   VisitChildren(CE);
 }
 
+void WalkAST::VisitObjCMessageExpr(ObjCMessageExpr *ME) {
+  MsgCheck evalFunction =
+      llvm::StringSwitch<MsgCheck>(ME->getSelector().getAsString())
+          .Case("decodeValueOfObjCType:at:",
+                &WalkAST::checkMsg_decodeValueOfObjCType)
+          .Default(nullptr);
+
+  if (evalFunction)
+    (this->*evalFunction)(ME);
+
+  // Recurse and check children.
+  VisitChildren(ME);
+}
+
 void WalkAST::VisitCompoundStmt(CompoundStmt *S) {
   for (Stmt *Child : S->children())
     if (Child) {
@@ -924,6 +943,54 @@ void WalkAST::checkCall_vfork(const CallExpr *CE, const FunctionDecl *FD) {
 }
 
 //===----------------------------------------------------------------------===//
+// Check: '-decodeValueOfObjCType:at:' should not be used.
+// It is deprecated in favor of '-decodeValueOfObjCType:at:size:' due to
+// likelihood of buffer overflows.
+//===----------------------------------------------------------------------===//
+
+void WalkAST::checkMsg_decodeValueOfObjCType(const ObjCMessageExpr *ME) {
+  if (!filter.check_decodeValueOfObjCType)
+    return;
+
+  // Check availability of the secure alternative:
+  // iOS 11+, macOS 10.13+, tvOS 11+, and watchOS 4.0+
+  // FIXME: We probably shouldn't register the check if it's not available.
+  const TargetInfo &TI = AC->getASTContext().getTargetInfo();
+  const llvm::Triple &T = TI.getTriple();
+  const VersionTuple &VT = TI.getPlatformMinVersion();
+  switch (T.getOS()) {
+  case llvm::Triple::IOS:
+    if (VT < VersionTuple(11, 0))
+      return;
+    break;
+  case llvm::Triple::MacOSX:
+    if (VT < VersionTuple(10, 13))
+      return;
+    break;
+  case llvm::Triple::WatchOS:
+    if (VT < VersionTuple(4, 0))
+      return;
+    break;
+  case llvm::Triple::TvOS:
+    if (VT < VersionTuple(11, 0))
+      return;
+    break;
+  default:
+    return;
+  }
+
+  PathDiagnosticLocation MELoc =
+      PathDiagnosticLocation::createBegin(ME, BR.getSourceManager(), AC);
+  BR.EmitBasicReport(
+      AC->getDecl(), filter.checkName_decodeValueOfObjCType,
+      "Potential buffer overflow in '-decodeValueOfObjCType:at:'", "Security",
+      "Deprecated method '-decodeValueOfObjCType:at:' is insecure "
+      "as it can lead to potential buffer overflows. Use the safer "
+      "'-decodeValueOfObjCType:at:size:' method.",
+      MELoc, ME->getSourceRange());
+}
+
+//===----------------------------------------------------------------------===//
 // Check: Should check whether privileges are dropped successfully.
 // Originally: <rdar://problem/6337132>
 //===----------------------------------------------------------------------===//
@@ -1035,3 +1102,4 @@ REGISTER_CHECKER(vfork)
 REGISTER_CHECKER(FloatLoopCounter)
 REGISTER_CHECKER(UncheckedReturn)
 REGISTER_CHECKER(DeprecatedOrUnsafeBufferHandling)
+REGISTER_CHECKER(decodeValueOfObjCType)
diff --git a/clang/test/Analysis/security-syntax-checks-nscoder.m b/clang/test/Analysis/security-syntax-checks-nscoder.m
new file mode 100644 (file)
index 0000000..23aa95b
--- /dev/null
@@ -0,0 +1,36 @@
+// RUN: %clang_analyze_cc1 -triple thumbv7-apple-ios11.0 -verify=available \
+// RUN:     -analyzer-checker=security.insecureAPI.decodeValueOfObjCType %s
+// RUN: %clang_analyze_cc1 -triple thumbv7-apple-ios10.0 -verify=notavailable \
+// RUN:     -analyzer-checker=security.insecureAPI.decodeValueOfObjCType %s
+// RUN: %clang_analyze_cc1 -triple x86_64-apple-macos10.13 -verify=available \
+// RUN:     -analyzer-checker=security.insecureAPI.decodeValueOfObjCType %s
+// RUN: %clang_analyze_cc1 -triple x86_64-apple-macos10.12 -verify=notavailable \
+// RUN:     -analyzer-checker=security.insecureAPI.decodeValueOfObjCType %s
+// RUN: %clang_analyze_cc1 -triple thumbv7-apple-watchos4.0 -verify=available \
+// RUN:     -analyzer-checker=security.insecureAPI.decodeValueOfObjCType %s
+// RUN: %clang_analyze_cc1 -triple thumbv7-apple-watchos3.0 -verify=notavailable \
+// RUN:     -analyzer-checker=security.insecureAPI.decodeValueOfObjCType %s
+// RUN: %clang_analyze_cc1 -triple thumbv7-apple-tvos11.0 -verify=available \
+// RUN:     -analyzer-checker=security.insecureAPI.decodeValueOfObjCType %s
+// RUN: %clang_analyze_cc1 -triple thumbv7-apple-tvos10.0 -verify=notavailable \
+// RUN:     -analyzer-checker=security.insecureAPI.decodeValueOfObjCType %s
+
+// notavailable-no-diagnostics
+
+typedef unsigned long NSUInteger;
+
+@interface NSCoder
+- (void)decodeValueOfObjCType:(const char *)type
+                           at:(void *)data;
+- (void)decodeValueOfObjCType:(const char *)type
+                           at:(void *)data
+                         size:(NSUInteger)size;
+@end
+
+void test(NSCoder *decoder) {
+  // This would be a vulnerability on 64-bit platforms
+  // but not on 32-bit platforms.
+  NSUInteger x;
+  [decoder decodeValueOfObjCType:"I" at:&x]; // available-warning{{Deprecated method '-decodeValueOfObjCType:at:' is insecure as it can lead to potential buffer overflows. Use the safer '-decodeValueOfObjCType:at:size:' method}}
+  [decoder decodeValueOfObjCType:"I" at:&x size:sizeof(x)]; // no-warning
+}
index 12e66f1..501a2b3 100644 (file)
@@ -1446,6 +1446,22 @@ void test() {
 }
 </pre></div></div></td></tr>
 
+
+<tr><td><a id="security.insecureAPI.decodeValueOfObjCType"><div class="namedescr expandable"><span class="name">
+security.insecureAPI.decodeValueOfObjCType</span><span class="lang">
+(ObjC)</span><div class="descr">
+Warn on uses of the <code>-[NSCoder decodeValueOfObjCType:at:]</code> method.
+The safe alternative is <code>-[NSCoder decodeValueOfObjCType:at:size:]</code>.</div></div></a></td>
+<td><div class="exampleContainer expandable">
+<div class="example"><pre>
+void test(NSCoder *decoder) {
+  // This would be a vulnerability on 64-bit platforms
+  // but not on 32-bit platforms.
+  NSUInteger x;
+  [decoder decodeValueOfObjCType:"I" at:&x]; // warn
+}
+</pre></div></div></td></tr>
+
 </tbody></table>
 
 <!-- =========================== unix =========================== -->