This revision was automatically updated to reflect the committed changes.
Closed by commit rC354635: [analyzer] MIGChecker: A checker for Mach Interface 
Generator conventions. (authored by dergachev, committed by ).
Herald added a subscriber: cfe-commits.

Changed prior to commit:
  https://reviews.llvm.org/D57558?vs=187870&id=187881#toc

Repository:
  rC Clang

CHANGES SINCE LAST ACTION
  https://reviews.llvm.org/D57558/new/

https://reviews.llvm.org/D57558

Files:
  include/clang/StaticAnalyzer/Checkers/Checkers.td
  lib/StaticAnalyzer/Checkers/CMakeLists.txt
  lib/StaticAnalyzer/Checkers/MIGChecker.cpp
  test/Analysis/mig.mm

Index: test/Analysis/mig.mm
===================================================================
--- test/Analysis/mig.mm
+++ test/Analysis/mig.mm
@@ -0,0 +1,36 @@
+// RUN: %clang_analyze_cc1 -w -analyzer-checker=core,alpha.osx.MIG -verify %s
+
+// XNU APIs.
+
+typedef int kern_return_t;
+#define KERN_SUCCESS 0
+#define KERN_ERROR 1
+
+typedef unsigned mach_port_name_t;
+typedef unsigned vm_address_t;
+typedef unsigned vm_size_t;
+
+kern_return_t vm_deallocate(mach_port_name_t, vm_address_t, vm_size_t);
+
+// Tests.
+
+kern_return_t basic_test(mach_port_name_t port, vm_address_t address, vm_size_t size) {
+  vm_deallocate(port, address, size);
+  if (size > 10) {
+    return KERN_ERROR; // expected-warning{{MIG callback fails with error after deallocating argument value. This is a use-after-free vulnerability because the caller will try to deallocate it again}}
+  }
+  return KERN_SUCCESS;
+}
+
+kern_return_t test_unknown_return_value(mach_port_name_t port, vm_address_t address, vm_size_t size) {
+  extern kern_return_t foo();
+
+  vm_deallocate(port, address, size);
+  // We don't know if it's a success or a failure.
+  return foo(); // no-warning
+}
+
+// Make sure we don't crash when they forgot to write the return statement.
+kern_return_t no_crash(mach_port_name_t port, vm_address_t address, vm_size_t size) {
+  vm_deallocate(port, address, size);
+}
Index: lib/StaticAnalyzer/Checkers/MIGChecker.cpp
===================================================================
--- lib/StaticAnalyzer/Checkers/MIGChecker.cpp
+++ lib/StaticAnalyzer/Checkers/MIGChecker.cpp
@@ -0,0 +1,144 @@
+//== MIGChecker.cpp - MIG calling convention checker ------------*- C++ -*--==//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// This file defines MIGChecker, a Mach Interface Generator calling convention
+// checker. Namely, in MIG callback implementation the following rules apply:
+// - When a server routine returns KERN_SUCCESS, it must take ownership of
+//   resources (and eventually release them).
+// - Additionally, when returning KERN_SUCCESS, all out-parameters must be
+//   initialized.
+// - When it returns anything except KERN_SUCCESS it must not take ownership,
+//   because the message and its descriptors will be destroyed by the server
+//   function.
+// For now we only check the last rule, as its violations lead to dangerous
+// use-after-free exploits.
+//
+//===----------------------------------------------------------------------===//
+
+#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
+#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
+#include "clang/StaticAnalyzer/Core/Checker.h"
+#include "clang/StaticAnalyzer/Core/CheckerManager.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
+
+using namespace clang;
+using namespace ento;
+
+namespace {
+class MIGChecker : public Checker<check::PostCall, check::PreStmt<ReturnStmt>> {
+  BugType BT{this, "Use-after-free (MIG calling convention violation)",
+             categories::MemoryError};
+
+  CallDescription vm_deallocate { "vm_deallocate", 3 };
+
+public:
+  void checkPostCall(const CallEvent &Call, CheckerContext &C) const;
+  void checkPreStmt(const ReturnStmt *RS, CheckerContext &C) const;
+};
+} // end anonymous namespace
+
+REGISTER_TRAIT_WITH_PROGRAMSTATE(ReleasedParameter, bool)
+
+static bool isCurrentArgSVal(SVal V, CheckerContext &C) {
+  SymbolRef Sym = V.getAsSymbol();
+  if (!Sym)
+    return false;
+
+  const auto *VR = dyn_cast_or_null<VarRegion>(Sym->getOriginRegion());
+  return VR && VR->hasStackParametersStorage() &&
+         VR->getStackFrame()->inTopFrame();
+}
+
+// This function will probably be replaced with looking up annotations.
+static bool isInMIGCall(const LocationContext *LC) {
+  const StackFrameContext *SFC;
+  // Find the top frame.
+  while (LC) {
+    SFC = LC->getStackFrame();
+    LC = SFC->getParent();
+  }
+
+  const auto *FD = dyn_cast<FunctionDecl>(SFC->getDecl());
+  if (!FD)
+    return false;
+
+  // FIXME: This is an unreliable (even if surprisingly reliable) heuristic.
+  // The real solution here is to make MIG annotate its callbacks in
+  // autogenerated headers so that we didn't need to think hard if it's
+  // actually a MIG callback.
+  QualType T = FD->getReturnType();
+  return T.getCanonicalType()->isIntegerType() &&
+         T.getAsString() == "kern_return_t";
+}
+
+void MIGChecker::checkPostCall(const CallEvent &Call, CheckerContext &C) const {
+  if (!isInMIGCall(C.getStackFrame()))
+    return;
+
+  if (!Call.isGlobalCFunction())
+    return;
+
+  if (!Call.isCalled(vm_deallocate))
+    return;
+
+  // TODO: Unhardcode "1".
+  SVal Arg = Call.getArgSVal(1);
+  if (isCurrentArgSVal(Arg, C))
+    C.addTransition(C.getState()->set<ReleasedParameter>(true));
+}
+
+void MIGChecker::checkPreStmt(const ReturnStmt *RS, CheckerContext &C) const {
+  // It is very unlikely that a MIG callback will be called from anywhere
+  // within the project under analysis and the caller isn't itself a routine
+  // that follows the MIG calling convention. Therefore we're safe to believe
+  // that it's always the top frame that is of interest. There's a slight chance
+  // that the user would want to enforce the MIG calling convention upon
+  // a random routine in the middle of nowhere, but given that the convention is
+  // fairly weird and hard to follow in the first place, there's relatively
+  // little motivation to spread it this way.
+  if (!C.inTopFrame())
+    return;
+
+  if (!isInMIGCall(C.getStackFrame()))
+    return;
+
+  // We know that the function is non-void, but what if the return statement
+  // is not there in the code? It's not a compile error, we should not crash.
+  if (!RS)
+    return;
+
+  ProgramStateRef State = C.getState();
+  if (!State->get<ReleasedParameter>())
+    return;
+
+  SVal V = C.getSVal(RS);
+  if (!State->isNonNull(V).isConstrainedTrue())
+    return;
+
+  ExplodedNode *N = C.generateErrorNode();
+  if (!N)
+    return;
+
+  auto R = llvm::make_unique<BugReport>(
+      BT,
+      "MIG callback fails with error after deallocating argument value. "
+      "This is a use-after-free vulnerability because the caller will try to "
+      "deallocate it again",
+      N);
+
+  C.emitReport(std::move(R));
+}
+
+void ento::registerMIGChecker(CheckerManager &Mgr) {
+  Mgr.registerChecker<MIGChecker>();
+}
+
+bool ento::shouldRegisterMIGChecker(const LangOptions &LO) {
+  return true;
+}
Index: lib/StaticAnalyzer/Checkers/CMakeLists.txt
===================================================================
--- lib/StaticAnalyzer/Checkers/CMakeLists.txt
+++ lib/StaticAnalyzer/Checkers/CMakeLists.txt
@@ -51,6 +51,7 @@
   MallocOverflowSecurityChecker.cpp
   MallocSizeofChecker.cpp
   MmapWriteExecChecker.cpp
+  MIGChecker.cpp
   MoveChecker.cpp
   MPI-Checker/MPIBugReporter.cpp
   MPI-Checker/MPIChecker.cpp
Index: include/clang/StaticAnalyzer/Checkers/Checkers.td
===================================================================
--- include/clang/StaticAnalyzer/Checkers/Checkers.td
+++ include/clang/StaticAnalyzer/Checkers/Checkers.td
@@ -828,6 +828,15 @@
 
 } // end "optin.osx"
 
+let ParentPackage = OSXAlpha in {
+
+def MIGChecker : Checker<"MIG">,
+  HelpText<"Find violations of the Mach Interface Generator "
+           "calling convention">,
+  Documentation<NotDocumented>;
+
+} // end "alpha.osx"
+
 let ParentPackage = CocoaAlpha in {
 
 def IvarInvalidationModeling : Checker<"IvarInvalidationModeling">,
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to