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