1 1.1 joerg //== MIGChecker.cpp - MIG calling convention checker ------------*- C++ -*--==// 2 1.1 joerg // 3 1.1 joerg // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 1.1 joerg // See https://llvm.org/LICENSE.txt for license information. 5 1.1 joerg // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 1.1 joerg // 7 1.1 joerg //===----------------------------------------------------------------------===// 8 1.1 joerg // 9 1.1 joerg // This file defines MIGChecker, a Mach Interface Generator calling convention 10 1.1 joerg // checker. Namely, in MIG callback implementation the following rules apply: 11 1.1 joerg // - When a server routine returns an error code that represents success, it 12 1.1 joerg // must take ownership of resources passed to it (and eventually release 13 1.1 joerg // them). 14 1.1 joerg // - Additionally, when returning success, all out-parameters must be 15 1.1 joerg // initialized. 16 1.1 joerg // - When it returns any other error code, it must not take ownership, 17 1.1 joerg // because the message and its out-of-line parameters will be destroyed 18 1.1 joerg // by the client that called the function. 19 1.1 joerg // For now we only check the last rule, as its violations lead to dangerous 20 1.1 joerg // use-after-free exploits. 21 1.1 joerg // 22 1.1 joerg //===----------------------------------------------------------------------===// 23 1.1 joerg 24 1.1.1.2 joerg #include "clang/AST/Attr.h" 25 1.1 joerg #include "clang/Analysis/AnyCall.h" 26 1.1 joerg #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" 27 1.1 joerg #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" 28 1.1 joerg #include "clang/StaticAnalyzer/Core/Checker.h" 29 1.1 joerg #include "clang/StaticAnalyzer/Core/CheckerManager.h" 30 1.1 joerg #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" 31 1.1 joerg #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" 32 1.1 joerg 33 1.1 joerg using namespace clang; 34 1.1 joerg using namespace ento; 35 1.1 joerg 36 1.1 joerg namespace { 37 1.1 joerg class MIGChecker : public Checker<check::PostCall, check::PreStmt<ReturnStmt>, 38 1.1 joerg check::EndFunction> { 39 1.1 joerg BugType BT{this, "Use-after-free (MIG calling convention violation)", 40 1.1 joerg categories::MemoryError}; 41 1.1 joerg 42 1.1 joerg // The checker knows that an out-of-line object is deallocated if it is 43 1.1 joerg // passed as an argument to one of these functions. If this object is 44 1.1 joerg // additionally an argument of a MIG routine, the checker keeps track of that 45 1.1 joerg // information and issues a warning when an error is returned from the 46 1.1 joerg // respective routine. 47 1.1 joerg std::vector<std::pair<CallDescription, unsigned>> Deallocators = { 48 1.1 joerg #define CALL(required_args, deallocated_arg, ...) \ 49 1.1 joerg {{{__VA_ARGS__}, required_args}, deallocated_arg} 50 1.1 joerg // E.g., if the checker sees a C function 'vm_deallocate' that is 51 1.1 joerg // defined on class 'IOUserClient' that has exactly 3 parameters, it knows 52 1.1 joerg // that argument #1 (starting from 0, i.e. the second argument) is going 53 1.1 joerg // to be consumed in the sense of the MIG consume-on-success convention. 54 1.1 joerg CALL(3, 1, "vm_deallocate"), 55 1.1 joerg CALL(3, 1, "mach_vm_deallocate"), 56 1.1 joerg CALL(2, 0, "mig_deallocate"), 57 1.1 joerg CALL(2, 1, "mach_port_deallocate"), 58 1.1 joerg CALL(1, 0, "device_deallocate"), 59 1.1 joerg CALL(1, 0, "iokit_remove_connect_reference"), 60 1.1 joerg CALL(1, 0, "iokit_remove_reference"), 61 1.1 joerg CALL(1, 0, "iokit_release_port"), 62 1.1 joerg CALL(1, 0, "ipc_port_release"), 63 1.1 joerg CALL(1, 0, "ipc_port_release_sonce"), 64 1.1 joerg CALL(1, 0, "ipc_voucher_attr_control_release"), 65 1.1 joerg CALL(1, 0, "ipc_voucher_release"), 66 1.1 joerg CALL(1, 0, "lock_set_dereference"), 67 1.1 joerg CALL(1, 0, "memory_object_control_deallocate"), 68 1.1 joerg CALL(1, 0, "pset_deallocate"), 69 1.1 joerg CALL(1, 0, "semaphore_dereference"), 70 1.1 joerg CALL(1, 0, "space_deallocate"), 71 1.1 joerg CALL(1, 0, "space_inspect_deallocate"), 72 1.1 joerg CALL(1, 0, "task_deallocate"), 73 1.1 joerg CALL(1, 0, "task_inspect_deallocate"), 74 1.1 joerg CALL(1, 0, "task_name_deallocate"), 75 1.1 joerg CALL(1, 0, "thread_deallocate"), 76 1.1 joerg CALL(1, 0, "thread_inspect_deallocate"), 77 1.1 joerg CALL(1, 0, "upl_deallocate"), 78 1.1 joerg CALL(1, 0, "vm_map_deallocate"), 79 1.1 joerg // E.g., if the checker sees a method 'releaseAsyncReference64()' that is 80 1.1 joerg // defined on class 'IOUserClient' that takes exactly 1 argument, it knows 81 1.1 joerg // that the argument is going to be consumed in the sense of the MIG 82 1.1 joerg // consume-on-success convention. 83 1.1 joerg CALL(1, 0, "IOUserClient", "releaseAsyncReference64"), 84 1.1 joerg CALL(1, 0, "IOUserClient", "releaseNotificationPort"), 85 1.1 joerg #undef CALL 86 1.1 joerg }; 87 1.1 joerg 88 1.1 joerg CallDescription OsRefRetain{"os_ref_retain", 1}; 89 1.1 joerg 90 1.1 joerg void checkReturnAux(const ReturnStmt *RS, CheckerContext &C) const; 91 1.1 joerg 92 1.1 joerg public: 93 1.1 joerg void checkPostCall(const CallEvent &Call, CheckerContext &C) const; 94 1.1 joerg 95 1.1 joerg // HACK: We're making two attempts to find the bug: checkEndFunction 96 1.1 joerg // should normally be enough but it fails when the return value is a literal 97 1.1 joerg // that never gets put into the Environment and ends of function with multiple 98 1.1 joerg // returns get agglutinated across returns, preventing us from obtaining 99 1.1 joerg // the return value. The problem is similar to https://reviews.llvm.org/D25326 100 1.1 joerg // but now we step into it in the top-level function. 101 1.1 joerg void checkPreStmt(const ReturnStmt *RS, CheckerContext &C) const { 102 1.1 joerg checkReturnAux(RS, C); 103 1.1 joerg } 104 1.1 joerg void checkEndFunction(const ReturnStmt *RS, CheckerContext &C) const { 105 1.1 joerg checkReturnAux(RS, C); 106 1.1 joerg } 107 1.1 joerg 108 1.1 joerg }; 109 1.1 joerg } // end anonymous namespace 110 1.1 joerg 111 1.1 joerg // A flag that says that the programmer has called a MIG destructor 112 1.1 joerg // for at least one parameter. 113 1.1 joerg REGISTER_TRAIT_WITH_PROGRAMSTATE(ReleasedParameter, bool) 114 1.1 joerg // A set of parameters for which the check is suppressed because 115 1.1 joerg // reference counting is being performed. 116 1.1 joerg REGISTER_SET_WITH_PROGRAMSTATE(RefCountedParameters, const ParmVarDecl *) 117 1.1 joerg 118 1.1 joerg static const ParmVarDecl *getOriginParam(SVal V, CheckerContext &C, 119 1.1 joerg bool IncludeBaseRegions = false) { 120 1.1 joerg // TODO: We should most likely always include base regions here. 121 1.1 joerg SymbolRef Sym = V.getAsSymbol(IncludeBaseRegions); 122 1.1 joerg if (!Sym) 123 1.1 joerg return nullptr; 124 1.1 joerg 125 1.1 joerg // If we optimistically assume that the MIG routine never re-uses the storage 126 1.1 joerg // that was passed to it as arguments when it invalidates it (but at most when 127 1.1 joerg // it assigns to parameter variables directly), this procedure correctly 128 1.1 joerg // determines if the value was loaded from the transitive closure of MIG 129 1.1 joerg // routine arguments in the heap. 130 1.1 joerg while (const MemRegion *MR = Sym->getOriginRegion()) { 131 1.1 joerg const auto *VR = dyn_cast<VarRegion>(MR); 132 1.1 joerg if (VR && VR->hasStackParametersStorage() && 133 1.1 joerg VR->getStackFrame()->inTopFrame()) 134 1.1 joerg return cast<ParmVarDecl>(VR->getDecl()); 135 1.1 joerg 136 1.1 joerg const SymbolicRegion *SR = MR->getSymbolicBase(); 137 1.1 joerg if (!SR) 138 1.1 joerg return nullptr; 139 1.1 joerg 140 1.1 joerg Sym = SR->getSymbol(); 141 1.1 joerg } 142 1.1 joerg 143 1.1 joerg return nullptr; 144 1.1 joerg } 145 1.1 joerg 146 1.1 joerg static bool isInMIGCall(CheckerContext &C) { 147 1.1 joerg const LocationContext *LC = C.getLocationContext(); 148 1.1 joerg assert(LC && "Unknown location context"); 149 1.1 joerg 150 1.1 joerg const StackFrameContext *SFC; 151 1.1 joerg // Find the top frame. 152 1.1 joerg while (LC) { 153 1.1 joerg SFC = LC->getStackFrame(); 154 1.1 joerg LC = SFC->getParent(); 155 1.1 joerg } 156 1.1 joerg 157 1.1 joerg const Decl *D = SFC->getDecl(); 158 1.1 joerg 159 1.1 joerg if (Optional<AnyCall> AC = AnyCall::forDecl(D)) { 160 1.1 joerg // Even though there's a Sema warning when the return type of an annotated 161 1.1 joerg // function is not a kern_return_t, this warning isn't an error, so we need 162 1.1 joerg // an extra sanity check here. 163 1.1 joerg // FIXME: AnyCall doesn't support blocks yet, so they remain unchecked 164 1.1 joerg // for now. 165 1.1 joerg if (!AC->getReturnType(C.getASTContext()) 166 1.1 joerg .getCanonicalType()->isSignedIntegerType()) 167 1.1 joerg return false; 168 1.1 joerg } 169 1.1 joerg 170 1.1 joerg if (D->hasAttr<MIGServerRoutineAttr>()) 171 1.1 joerg return true; 172 1.1 joerg 173 1.1 joerg // See if there's an annotated method in the superclass. 174 1.1 joerg if (const auto *MD = dyn_cast<CXXMethodDecl>(D)) 175 1.1 joerg for (const auto *OMD: MD->overridden_methods()) 176 1.1 joerg if (OMD->hasAttr<MIGServerRoutineAttr>()) 177 1.1 joerg return true; 178 1.1 joerg 179 1.1 joerg return false; 180 1.1 joerg } 181 1.1 joerg 182 1.1 joerg void MIGChecker::checkPostCall(const CallEvent &Call, CheckerContext &C) const { 183 1.1 joerg if (Call.isCalled(OsRefRetain)) { 184 1.1 joerg // If the code is doing reference counting over the parameter, 185 1.1 joerg // it opens up an opportunity for safely calling a destructor function. 186 1.1 joerg // TODO: We should still check for over-releases. 187 1.1 joerg if (const ParmVarDecl *PVD = 188 1.1 joerg getOriginParam(Call.getArgSVal(0), C, /*IncludeBaseRegions=*/true)) { 189 1.1 joerg // We never need to clean up the program state because these are 190 1.1 joerg // top-level parameters anyway, so they're always live. 191 1.1 joerg C.addTransition(C.getState()->add<RefCountedParameters>(PVD)); 192 1.1 joerg } 193 1.1 joerg return; 194 1.1 joerg } 195 1.1 joerg 196 1.1 joerg if (!isInMIGCall(C)) 197 1.1 joerg return; 198 1.1 joerg 199 1.1 joerg auto I = llvm::find_if(Deallocators, 200 1.1 joerg [&](const std::pair<CallDescription, unsigned> &Item) { 201 1.1 joerg return Call.isCalled(Item.first); 202 1.1 joerg }); 203 1.1 joerg if (I == Deallocators.end()) 204 1.1 joerg return; 205 1.1 joerg 206 1.1 joerg ProgramStateRef State = C.getState(); 207 1.1 joerg unsigned ArgIdx = I->second; 208 1.1 joerg SVal Arg = Call.getArgSVal(ArgIdx); 209 1.1 joerg const ParmVarDecl *PVD = getOriginParam(Arg, C); 210 1.1 joerg if (!PVD || State->contains<RefCountedParameters>(PVD)) 211 1.1 joerg return; 212 1.1 joerg 213 1.1.1.2 joerg const NoteTag *T = 214 1.1.1.2 joerg C.getNoteTag([this, PVD](PathSensitiveBugReport &BR) -> std::string { 215 1.1.1.2 joerg if (&BR.getBugType() != &BT) 216 1.1.1.2 joerg return ""; 217 1.1.1.2 joerg SmallString<64> Str; 218 1.1.1.2 joerg llvm::raw_svector_ostream OS(Str); 219 1.1.1.2 joerg OS << "Value passed through parameter '" << PVD->getName() 220 1.1.1.2 joerg << "\' is deallocated"; 221 1.1.1.2 joerg return std::string(OS.str()); 222 1.1.1.2 joerg }); 223 1.1 joerg C.addTransition(State->set<ReleasedParameter>(true), T); 224 1.1 joerg } 225 1.1 joerg 226 1.1 joerg // Returns true if V can potentially represent a "successful" kern_return_t. 227 1.1 joerg static bool mayBeSuccess(SVal V, CheckerContext &C) { 228 1.1 joerg ProgramStateRef State = C.getState(); 229 1.1 joerg 230 1.1 joerg // Can V represent KERN_SUCCESS? 231 1.1 joerg if (!State->isNull(V).isConstrainedFalse()) 232 1.1 joerg return true; 233 1.1 joerg 234 1.1 joerg SValBuilder &SVB = C.getSValBuilder(); 235 1.1 joerg ASTContext &ACtx = C.getASTContext(); 236 1.1 joerg 237 1.1 joerg // Can V represent MIG_NO_REPLY? 238 1.1 joerg static const int MigNoReply = -305; 239 1.1 joerg V = SVB.evalEQ(C.getState(), V, SVB.makeIntVal(MigNoReply, ACtx.IntTy)); 240 1.1 joerg if (!State->isNull(V).isConstrainedTrue()) 241 1.1 joerg return true; 242 1.1 joerg 243 1.1 joerg // If none of the above, it's definitely an error. 244 1.1 joerg return false; 245 1.1 joerg } 246 1.1 joerg 247 1.1 joerg void MIGChecker::checkReturnAux(const ReturnStmt *RS, CheckerContext &C) const { 248 1.1 joerg // It is very unlikely that a MIG callback will be called from anywhere 249 1.1 joerg // within the project under analysis and the caller isn't itself a routine 250 1.1 joerg // that follows the MIG calling convention. Therefore we're safe to believe 251 1.1 joerg // that it's always the top frame that is of interest. There's a slight chance 252 1.1 joerg // that the user would want to enforce the MIG calling convention upon 253 1.1 joerg // a random routine in the middle of nowhere, but given that the convention is 254 1.1 joerg // fairly weird and hard to follow in the first place, there's relatively 255 1.1 joerg // little motivation to spread it this way. 256 1.1 joerg if (!C.inTopFrame()) 257 1.1 joerg return; 258 1.1 joerg 259 1.1 joerg if (!isInMIGCall(C)) 260 1.1 joerg return; 261 1.1 joerg 262 1.1 joerg // We know that the function is non-void, but what if the return statement 263 1.1 joerg // is not there in the code? It's not a compile error, we should not crash. 264 1.1 joerg if (!RS) 265 1.1 joerg return; 266 1.1 joerg 267 1.1 joerg ProgramStateRef State = C.getState(); 268 1.1 joerg if (!State->get<ReleasedParameter>()) 269 1.1 joerg return; 270 1.1 joerg 271 1.1 joerg SVal V = C.getSVal(RS); 272 1.1 joerg if (mayBeSuccess(V, C)) 273 1.1 joerg return; 274 1.1 joerg 275 1.1 joerg ExplodedNode *N = C.generateErrorNode(); 276 1.1 joerg if (!N) 277 1.1 joerg return; 278 1.1 joerg 279 1.1 joerg auto R = std::make_unique<PathSensitiveBugReport>( 280 1.1 joerg BT, 281 1.1 joerg "MIG callback fails with error after deallocating argument value. " 282 1.1 joerg "This is a use-after-free vulnerability because the caller will try to " 283 1.1 joerg "deallocate it again", 284 1.1 joerg N); 285 1.1 joerg 286 1.1 joerg R->addRange(RS->getSourceRange()); 287 1.1 joerg bugreporter::trackExpressionValue(N, RS->getRetValue(), *R, 288 1.1 joerg bugreporter::TrackingKind::Thorough, false); 289 1.1 joerg C.emitReport(std::move(R)); 290 1.1 joerg } 291 1.1 joerg 292 1.1 joerg void ento::registerMIGChecker(CheckerManager &Mgr) { 293 1.1 joerg Mgr.registerChecker<MIGChecker>(); 294 1.1 joerg } 295 1.1 joerg 296 1.1.1.2 joerg bool ento::shouldRegisterMIGChecker(const CheckerManager &mgr) { 297 1.1 joerg return true; 298 1.1 joerg } 299