Index: clang/include/clang/StaticAnalyzer/Checkers/Checkers.td =================================================================== --- clang/include/clang/StaticAnalyzer/Checkers/Checkers.td +++ clang/include/clang/StaticAnalyzer/Checkers/Checkers.td @@ -811,6 +811,14 @@ Documentation; } // end "optin.performance" +let ParentPackage = OSXAlpha in { + +def MIGChecker : Checker<"MIG">, + HelpText<"Find violations of Mach Interface Generator calling convention">, + Documentation; + +} // end "alpha.osx" + let ParentPackage = CocoaAlpha in { def IvarInvalidationModeling : Checker<"IvarInvalidationModeling">, Index: clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt =================================================================== --- clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt +++ clang/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: clang/lib/StaticAnalyzer/Checkers/MIGChecker.cpp =================================================================== --- /dev/null +++ clang/lib/StaticAnalyzer/Checkers/MIGChecker.cpp @@ -0,0 +1,139 @@ +//== 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 { + 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 checkEndFunction(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(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(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()->isUnsignedIntegerType() && + 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(true)); +} + +void MIGChecker::checkEndFunction(const ReturnStmt *RS, + CheckerContext &C) const { + // It is very unlikely that a MIG callback will be called from anywhere + // within the project under analysis. Therefore it's always in top frame. + 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()) + return; + + SVal V = C.getSVal(RS); + if (!State->isNonNull(V).isConstrainedTrue()) + return; + + ExplodedNode *N = C.generateErrorNode(); + if (!N) + return; + + auto R = llvm::make_unique( + BT, + "MIG callback fails with error after deallocating argument value. " + "This is use-after-free vulnerability because caller will try to " + "deallocate it again", + N); + + C.emitReport(std::move(R)); +} + +void ento::registerMIGChecker(CheckerManager &Mgr) { + Mgr.registerChecker(); +} + +bool ento::shouldRegisterMIGChecker(const LangOptions &LO) { + return true; +} Index: clang/test/Analysis/mig.cpp =================================================================== --- /dev/null +++ clang/test/Analysis/mig.cpp @@ -0,0 +1,26 @@ +// RUN: %clang_analyze_cc1 -w -analyzer-checker=core,alpha.osx.MIG -verify %s + +// XNU APIs. + +typedef unsigned 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); + +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 use-after-free vulnerability because caller will try to deallocate it again}} + } + return KERN_SUCCESS; +} + +// 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); +}