summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorArtem Dergachev <artem.dergachev@gmail.com>2019-02-21 23:55:28 +0000
committerArtem Dergachev <artem.dergachev@gmail.com>2019-02-21 23:55:28 +0000
commit6ce648a1c1e92094fbff01c0677c43e6eece43e9 (patch)
tree2f1c87c95b7dbc70c5eac3e31246a415d8bebaf5
parent5a56ebca03d01135d39a47d6a4b4c06ae67ec9b7 (diff)
downloadclang-6ce648a1c1e92094fbff01c0677c43e6eece43e9.tar.gz
[analyzer] MIGChecker: A checker for Mach Interface Generator conventions.
This checker detects use-after-free bugs in (various forks of) the Mach kernel that are caused by errors in MIG server routines - functions called remotely by MIG clients. The MIG convention forces the server to only deallocate objects it receives from the client when the routine is executed successfully. Otherwise, if the server routine exits with an error, the client assumes that it needs to deallocate the out-of-line data it passed to the server manually. This means that deallocating such data within the MIG routine and then returning a non-zero error code is always a dangerous use-after-free bug. rdar://problem/35380337 Differential Revision: https://reviews.llvm.org/D57558 git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@354635 91177308-0d34-0410-b5e6-96231b3b80d8
-rw-r--r--include/clang/StaticAnalyzer/Checkers/Checkers.td9
-rw-r--r--lib/StaticAnalyzer/Checkers/CMakeLists.txt1
-rw-r--r--lib/StaticAnalyzer/Checkers/MIGChecker.cpp144
-rw-r--r--test/Analysis/mig.mm36
4 files changed, 190 insertions, 0 deletions
diff --git a/include/clang/StaticAnalyzer/Checkers/Checkers.td b/include/clang/StaticAnalyzer/Checkers/Checkers.td
index 0f7ed610df..bf015c5b45 100644
--- a/include/clang/StaticAnalyzer/Checkers/Checkers.td
+++ b/include/clang/StaticAnalyzer/Checkers/Checkers.td
@@ -828,6 +828,15 @@ def OSObjectCStyleCast : Checker<"OSObjectCStyleCast">,
} // 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">,
diff --git a/lib/StaticAnalyzer/Checkers/CMakeLists.txt b/lib/StaticAnalyzer/Checkers/CMakeLists.txt
index da26abe376..db2d0e76e4 100644
--- a/lib/StaticAnalyzer/Checkers/CMakeLists.txt
+++ b/lib/StaticAnalyzer/Checkers/CMakeLists.txt
@@ -51,6 +51,7 @@ add_clang_library(clangStaticAnalyzerCheckers
MallocOverflowSecurityChecker.cpp
MallocSizeofChecker.cpp
MmapWriteExecChecker.cpp
+ MIGChecker.cpp
MoveChecker.cpp
MPI-Checker/MPIBugReporter.cpp
MPI-Checker/MPIChecker.cpp
diff --git a/lib/StaticAnalyzer/Checkers/MIGChecker.cpp b/lib/StaticAnalyzer/Checkers/MIGChecker.cpp
new file mode 100644
index 0000000000..8586d95a87
--- /dev/null
+++ b/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;
+}
diff --git a/test/Analysis/mig.mm b/test/Analysis/mig.mm
new file mode 100644
index 0000000000..1def03c825
--- /dev/null
+++ b/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);
+}