--- /dev/null
+//== 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;
+}
--- /dev/null
+// 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);
+}