1*0fca6ea1SDimitry Andric //===-- SetgidSetuidOrderChecker.cpp - check privilege revocation calls ---===// 2*0fca6ea1SDimitry Andric // 3*0fca6ea1SDimitry Andric // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4*0fca6ea1SDimitry Andric // See https://llvm.org/LICENSE.txt for license information. 5*0fca6ea1SDimitry Andric // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6*0fca6ea1SDimitry Andric // 7*0fca6ea1SDimitry Andric //===----------------------------------------------------------------------===// 8*0fca6ea1SDimitry Andric // 9*0fca6ea1SDimitry Andric // This file defines a checker to detect possible reversed order of privilege 10*0fca6ea1SDimitry Andric // revocations when 'setgid' and 'setuid' is used. 11*0fca6ea1SDimitry Andric // 12*0fca6ea1SDimitry Andric //===----------------------------------------------------------------------===// 13*0fca6ea1SDimitry Andric 14*0fca6ea1SDimitry Andric #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" 15*0fca6ea1SDimitry Andric #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" 16*0fca6ea1SDimitry Andric #include "clang/StaticAnalyzer/Core/Checker.h" 17*0fca6ea1SDimitry Andric #include "clang/StaticAnalyzer/Core/CheckerManager.h" 18*0fca6ea1SDimitry Andric #include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h" 19*0fca6ea1SDimitry Andric #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" 20*0fca6ea1SDimitry Andric #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" 21*0fca6ea1SDimitry Andric #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h" 22*0fca6ea1SDimitry Andric #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h" 23*0fca6ea1SDimitry Andric 24*0fca6ea1SDimitry Andric using namespace clang; 25*0fca6ea1SDimitry Andric using namespace ento; 26*0fca6ea1SDimitry Andric 27*0fca6ea1SDimitry Andric namespace { 28*0fca6ea1SDimitry Andric 29*0fca6ea1SDimitry Andric enum SetPrivilegeFunctionKind { Irrelevant, Setuid, Setgid }; 30*0fca6ea1SDimitry Andric 31*0fca6ea1SDimitry Andric class SetgidSetuidOrderChecker : public Checker<check::PostCall, eval::Assume> { 32*0fca6ea1SDimitry Andric const BugType BT{this, "Possible wrong order of privilege revocation"}; 33*0fca6ea1SDimitry Andric 34*0fca6ea1SDimitry Andric const CallDescription SetuidDesc{CDM::CLibrary, {"setuid"}, 1}; 35*0fca6ea1SDimitry Andric const CallDescription SetgidDesc{CDM::CLibrary, {"setgid"}, 1}; 36*0fca6ea1SDimitry Andric 37*0fca6ea1SDimitry Andric const CallDescription GetuidDesc{CDM::CLibrary, {"getuid"}, 0}; 38*0fca6ea1SDimitry Andric const CallDescription GetgidDesc{CDM::CLibrary, {"getgid"}, 0}; 39*0fca6ea1SDimitry Andric 40*0fca6ea1SDimitry Andric const CallDescriptionSet OtherSetPrivilegeDesc{ 41*0fca6ea1SDimitry Andric {CDM::CLibrary, {"seteuid"}, 1}, {CDM::CLibrary, {"setegid"}, 1}, 42*0fca6ea1SDimitry Andric {CDM::CLibrary, {"setreuid"}, 2}, {CDM::CLibrary, {"setregid"}, 2}, 43*0fca6ea1SDimitry Andric {CDM::CLibrary, {"setresuid"}, 3}, {CDM::CLibrary, {"setresgid"}, 3}}; 44*0fca6ea1SDimitry Andric 45*0fca6ea1SDimitry Andric public: 46*0fca6ea1SDimitry Andric void checkPostCall(const CallEvent &Call, CheckerContext &C) const; 47*0fca6ea1SDimitry Andric ProgramStateRef evalAssume(ProgramStateRef State, SVal Cond, 48*0fca6ea1SDimitry Andric bool Assumption) const; 49*0fca6ea1SDimitry Andric 50*0fca6ea1SDimitry Andric private: 51*0fca6ea1SDimitry Andric void processSetuid(ProgramStateRef State, const CallEvent &Call, 52*0fca6ea1SDimitry Andric CheckerContext &C) const; 53*0fca6ea1SDimitry Andric void processSetgid(ProgramStateRef State, const CallEvent &Call, 54*0fca6ea1SDimitry Andric CheckerContext &C) const; 55*0fca6ea1SDimitry Andric void processOther(ProgramStateRef State, const CallEvent &Call, 56*0fca6ea1SDimitry Andric CheckerContext &C) const; 57*0fca6ea1SDimitry Andric /// Check if a function like \c getuid or \c getgid is called directly from 58*0fca6ea1SDimitry Andric /// the first argument of function called from \a Call. 59*0fca6ea1SDimitry Andric bool isFunctionCalledInArg(const CallDescription &Desc, 60*0fca6ea1SDimitry Andric const CallEvent &Call) const; 61*0fca6ea1SDimitry Andric void emitReport(ProgramStateRef State, CheckerContext &C) const; 62*0fca6ea1SDimitry Andric }; 63*0fca6ea1SDimitry Andric 64*0fca6ea1SDimitry Andric } // end anonymous namespace 65*0fca6ea1SDimitry Andric 66*0fca6ea1SDimitry Andric /// Store if there was a call to 'setuid(getuid())' or 'setgid(getgid())' not 67*0fca6ea1SDimitry Andric /// followed by other different privilege-change functions. 68*0fca6ea1SDimitry Andric /// If the value \c Setuid is stored and a 'setgid(getgid())' call is found we 69*0fca6ea1SDimitry Andric /// have found the bug to be reported. Value \c Setgid is used too to prevent 70*0fca6ea1SDimitry Andric /// warnings at a setgid-setuid-setgid sequence. 71*0fca6ea1SDimitry Andric REGISTER_TRAIT_WITH_PROGRAMSTATE(LastSetPrivilegeCall, SetPrivilegeFunctionKind) 72*0fca6ea1SDimitry Andric /// Store the symbol value of the last 'setuid(getuid())' call. This is used to 73*0fca6ea1SDimitry Andric /// detect if the result is compared to -1 and avoid warnings on that branch 74*0fca6ea1SDimitry Andric /// (which is the failure branch of the call), and for identification of note 75*0fca6ea1SDimitry Andric /// tags. 76*0fca6ea1SDimitry Andric REGISTER_TRAIT_WITH_PROGRAMSTATE(LastSetuidCallSVal, SymbolRef) 77*0fca6ea1SDimitry Andric 78*0fca6ea1SDimitry Andric void SetgidSetuidOrderChecker::checkPostCall(const CallEvent &Call, 79*0fca6ea1SDimitry Andric CheckerContext &C) const { 80*0fca6ea1SDimitry Andric ProgramStateRef State = C.getState(); 81*0fca6ea1SDimitry Andric if (SetuidDesc.matches(Call)) { 82*0fca6ea1SDimitry Andric processSetuid(State, Call, C); 83*0fca6ea1SDimitry Andric } else if (SetgidDesc.matches(Call)) { 84*0fca6ea1SDimitry Andric processSetgid(State, Call, C); 85*0fca6ea1SDimitry Andric } else if (OtherSetPrivilegeDesc.contains(Call)) { 86*0fca6ea1SDimitry Andric processOther(State, Call, C); 87*0fca6ea1SDimitry Andric } 88*0fca6ea1SDimitry Andric } 89*0fca6ea1SDimitry Andric 90*0fca6ea1SDimitry Andric ProgramStateRef SetgidSetuidOrderChecker::evalAssume(ProgramStateRef State, 91*0fca6ea1SDimitry Andric SVal Cond, 92*0fca6ea1SDimitry Andric bool Assumption) const { 93*0fca6ea1SDimitry Andric SValBuilder &SVB = State->getStateManager().getSValBuilder(); 94*0fca6ea1SDimitry Andric SymbolRef LastSetuidSym = State->get<LastSetuidCallSVal>(); 95*0fca6ea1SDimitry Andric if (!LastSetuidSym) 96*0fca6ea1SDimitry Andric return State; 97*0fca6ea1SDimitry Andric 98*0fca6ea1SDimitry Andric // Check if the most recent call to 'setuid(getuid())' is assumed to be != 0. 99*0fca6ea1SDimitry Andric // It should be only -1 at failure, but we want to accept a "!= 0" check too. 100*0fca6ea1SDimitry Andric // (But now an invalid failure check like "!= 1" will be recognized as correct 101*0fca6ea1SDimitry Andric // too. The "invalid failure check" is a different bug that is not the scope 102*0fca6ea1SDimitry Andric // of this checker.) 103*0fca6ea1SDimitry Andric auto FailComparison = 104*0fca6ea1SDimitry Andric SVB.evalBinOpNN(State, BO_NE, nonloc::SymbolVal(LastSetuidSym), 105*0fca6ea1SDimitry Andric SVB.makeIntVal(0, /*isUnsigned=*/false), 106*0fca6ea1SDimitry Andric SVB.getConditionType()) 107*0fca6ea1SDimitry Andric .getAs<DefinedOrUnknownSVal>(); 108*0fca6ea1SDimitry Andric if (!FailComparison) 109*0fca6ea1SDimitry Andric return State; 110*0fca6ea1SDimitry Andric if (auto IsFailBranch = State->assume(*FailComparison); 111*0fca6ea1SDimitry Andric IsFailBranch.first && !IsFailBranch.second) { 112*0fca6ea1SDimitry Andric // This is the 'setuid(getuid())' != 0 case. 113*0fca6ea1SDimitry Andric // On this branch we do not want to emit warning. 114*0fca6ea1SDimitry Andric State = State->set<LastSetPrivilegeCall>(Irrelevant); 115*0fca6ea1SDimitry Andric State = State->set<LastSetuidCallSVal>(SymbolRef{}); 116*0fca6ea1SDimitry Andric } 117*0fca6ea1SDimitry Andric return State; 118*0fca6ea1SDimitry Andric } 119*0fca6ea1SDimitry Andric 120*0fca6ea1SDimitry Andric void SetgidSetuidOrderChecker::processSetuid(ProgramStateRef State, 121*0fca6ea1SDimitry Andric const CallEvent &Call, 122*0fca6ea1SDimitry Andric CheckerContext &C) const { 123*0fca6ea1SDimitry Andric bool IsSetuidWithGetuid = isFunctionCalledInArg(GetuidDesc, Call); 124*0fca6ea1SDimitry Andric if (State->get<LastSetPrivilegeCall>() != Setgid && IsSetuidWithGetuid) { 125*0fca6ea1SDimitry Andric SymbolRef RetSym = Call.getReturnValue().getAsSymbol(); 126*0fca6ea1SDimitry Andric State = State->set<LastSetPrivilegeCall>(Setuid); 127*0fca6ea1SDimitry Andric State = State->set<LastSetuidCallSVal>(RetSym); 128*0fca6ea1SDimitry Andric const NoteTag *Note = C.getNoteTag([this, 129*0fca6ea1SDimitry Andric RetSym](PathSensitiveBugReport &BR) { 130*0fca6ea1SDimitry Andric if (!BR.isInteresting(RetSym) || &BR.getBugType() != &this->BT) 131*0fca6ea1SDimitry Andric return ""; 132*0fca6ea1SDimitry Andric return "Call to 'setuid' found here that removes superuser privileges"; 133*0fca6ea1SDimitry Andric }); 134*0fca6ea1SDimitry Andric C.addTransition(State, Note); 135*0fca6ea1SDimitry Andric return; 136*0fca6ea1SDimitry Andric } 137*0fca6ea1SDimitry Andric State = State->set<LastSetPrivilegeCall>(Irrelevant); 138*0fca6ea1SDimitry Andric State = State->set<LastSetuidCallSVal>(SymbolRef{}); 139*0fca6ea1SDimitry Andric C.addTransition(State); 140*0fca6ea1SDimitry Andric } 141*0fca6ea1SDimitry Andric 142*0fca6ea1SDimitry Andric void SetgidSetuidOrderChecker::processSetgid(ProgramStateRef State, 143*0fca6ea1SDimitry Andric const CallEvent &Call, 144*0fca6ea1SDimitry Andric CheckerContext &C) const { 145*0fca6ea1SDimitry Andric bool IsSetgidWithGetgid = isFunctionCalledInArg(GetgidDesc, Call); 146*0fca6ea1SDimitry Andric if (State->get<LastSetPrivilegeCall>() == Setuid) { 147*0fca6ea1SDimitry Andric if (IsSetgidWithGetgid) { 148*0fca6ea1SDimitry Andric State = State->set<LastSetPrivilegeCall>(Irrelevant); 149*0fca6ea1SDimitry Andric emitReport(State, C); 150*0fca6ea1SDimitry Andric return; 151*0fca6ea1SDimitry Andric } 152*0fca6ea1SDimitry Andric State = State->set<LastSetPrivilegeCall>(Irrelevant); 153*0fca6ea1SDimitry Andric } else { 154*0fca6ea1SDimitry Andric State = State->set<LastSetPrivilegeCall>(IsSetgidWithGetgid ? Setgid 155*0fca6ea1SDimitry Andric : Irrelevant); 156*0fca6ea1SDimitry Andric } 157*0fca6ea1SDimitry Andric State = State->set<LastSetuidCallSVal>(SymbolRef{}); 158*0fca6ea1SDimitry Andric C.addTransition(State); 159*0fca6ea1SDimitry Andric } 160*0fca6ea1SDimitry Andric 161*0fca6ea1SDimitry Andric void SetgidSetuidOrderChecker::processOther(ProgramStateRef State, 162*0fca6ea1SDimitry Andric const CallEvent &Call, 163*0fca6ea1SDimitry Andric CheckerContext &C) const { 164*0fca6ea1SDimitry Andric State = State->set<LastSetuidCallSVal>(SymbolRef{}); 165*0fca6ea1SDimitry Andric State = State->set<LastSetPrivilegeCall>(Irrelevant); 166*0fca6ea1SDimitry Andric C.addTransition(State); 167*0fca6ea1SDimitry Andric } 168*0fca6ea1SDimitry Andric 169*0fca6ea1SDimitry Andric bool SetgidSetuidOrderChecker::isFunctionCalledInArg( 170*0fca6ea1SDimitry Andric const CallDescription &Desc, const CallEvent &Call) const { 171*0fca6ea1SDimitry Andric if (const auto *CallInArg0 = 172*0fca6ea1SDimitry Andric dyn_cast<CallExpr>(Call.getArgExpr(0)->IgnoreParenImpCasts())) 173*0fca6ea1SDimitry Andric return Desc.matchesAsWritten(*CallInArg0); 174*0fca6ea1SDimitry Andric return false; 175*0fca6ea1SDimitry Andric } 176*0fca6ea1SDimitry Andric 177*0fca6ea1SDimitry Andric void SetgidSetuidOrderChecker::emitReport(ProgramStateRef State, 178*0fca6ea1SDimitry Andric CheckerContext &C) const { 179*0fca6ea1SDimitry Andric if (ExplodedNode *N = C.generateNonFatalErrorNode(State)) { 180*0fca6ea1SDimitry Andric llvm::StringLiteral Msg = 181*0fca6ea1SDimitry Andric "A 'setgid(getgid())' call following a 'setuid(getuid())' " 182*0fca6ea1SDimitry Andric "call is likely to fail; probably the order of these " 183*0fca6ea1SDimitry Andric "statements is wrong"; 184*0fca6ea1SDimitry Andric auto Report = std::make_unique<PathSensitiveBugReport>(BT, Msg, N); 185*0fca6ea1SDimitry Andric Report->markInteresting(State->get<LastSetuidCallSVal>()); 186*0fca6ea1SDimitry Andric C.emitReport(std::move(Report)); 187*0fca6ea1SDimitry Andric } 188*0fca6ea1SDimitry Andric } 189*0fca6ea1SDimitry Andric 190*0fca6ea1SDimitry Andric void ento::registerSetgidSetuidOrderChecker(CheckerManager &mgr) { 191*0fca6ea1SDimitry Andric mgr.registerChecker<SetgidSetuidOrderChecker>(); 192*0fca6ea1SDimitry Andric } 193*0fca6ea1SDimitry Andric 194*0fca6ea1SDimitry Andric bool ento::shouldRegisterSetgidSetuidOrderChecker(const CheckerManager &mgr) { 195*0fca6ea1SDimitry Andric return true; 196*0fca6ea1SDimitry Andric } 197