//===-- SetgidSetuidOrderChecker.cpp - check privilege revocation calls ---===// // // 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 a checker to detect possible reversed order of privilege // revocations when 'setgid' and 'setuid' is used. // //===----------------------------------------------------------------------===// #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/CallDescription.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h" using namespace clang; using namespace ento; namespace { enum SetPrivilegeFunctionKind { Irrelevant, Setuid, Setgid }; class SetgidSetuidOrderChecker : public Checker { const BugType BT{this, "Possible wrong order of privilege revocation"}; const CallDescription SetuidDesc{CDM::CLibrary, {"setuid"}, 1}; const CallDescription SetgidDesc{CDM::CLibrary, {"setgid"}, 1}; const CallDescription GetuidDesc{CDM::CLibrary, {"getuid"}, 0}; const CallDescription GetgidDesc{CDM::CLibrary, {"getgid"}, 0}; const CallDescriptionSet OtherSetPrivilegeDesc{ {CDM::CLibrary, {"seteuid"}, 1}, {CDM::CLibrary, {"setegid"}, 1}, {CDM::CLibrary, {"setreuid"}, 2}, {CDM::CLibrary, {"setregid"}, 2}, {CDM::CLibrary, {"setresuid"}, 3}, {CDM::CLibrary, {"setresgid"}, 3}}; public: void checkPostCall(const CallEvent &Call, CheckerContext &C) const; ProgramStateRef evalAssume(ProgramStateRef State, SVal Cond, bool Assumption) const; private: void processSetuid(ProgramStateRef State, const CallEvent &Call, CheckerContext &C) const; void processSetgid(ProgramStateRef State, const CallEvent &Call, CheckerContext &C) const; void processOther(ProgramStateRef State, const CallEvent &Call, CheckerContext &C) const; /// Check if a function like \c getuid or \c getgid is called directly from /// the first argument of function called from \a Call. bool isFunctionCalledInArg(const CallDescription &Desc, const CallEvent &Call) const; void emitReport(ProgramStateRef State, CheckerContext &C) const; }; } // end anonymous namespace /// Store if there was a call to 'setuid(getuid())' or 'setgid(getgid())' not /// followed by other different privilege-change functions. /// If the value \c Setuid is stored and a 'setgid(getgid())' call is found we /// have found the bug to be reported. Value \c Setgid is used too to prevent /// warnings at a setgid-setuid-setgid sequence. REGISTER_TRAIT_WITH_PROGRAMSTATE(LastSetPrivilegeCall, SetPrivilegeFunctionKind) /// Store the symbol value of the last 'setuid(getuid())' call. This is used to /// detect if the result is compared to -1 and avoid warnings on that branch /// (which is the failure branch of the call), and for identification of note /// tags. REGISTER_TRAIT_WITH_PROGRAMSTATE(LastSetuidCallSVal, SymbolRef) void SetgidSetuidOrderChecker::checkPostCall(const CallEvent &Call, CheckerContext &C) const { ProgramStateRef State = C.getState(); if (SetuidDesc.matches(Call)) { processSetuid(State, Call, C); } else if (SetgidDesc.matches(Call)) { processSetgid(State, Call, C); } else if (OtherSetPrivilegeDesc.contains(Call)) { processOther(State, Call, C); } } ProgramStateRef SetgidSetuidOrderChecker::evalAssume(ProgramStateRef State, SVal Cond, bool Assumption) const { SValBuilder &SVB = State->getStateManager().getSValBuilder(); SymbolRef LastSetuidSym = State->get(); if (!LastSetuidSym) return State; // Check if the most recent call to 'setuid(getuid())' is assumed to be != 0. // It should be only -1 at failure, but we want to accept a "!= 0" check too. // (But now an invalid failure check like "!= 1" will be recognized as correct // too. The "invalid failure check" is a different bug that is not the scope // of this checker.) auto FailComparison = SVB.evalBinOpNN(State, BO_NE, nonloc::SymbolVal(LastSetuidSym), SVB.makeIntVal(0, /*isUnsigned=*/false), SVB.getConditionType()) .getAs(); if (!FailComparison) return State; if (auto IsFailBranch = State->assume(*FailComparison); IsFailBranch.first && !IsFailBranch.second) { // This is the 'setuid(getuid())' != 0 case. // On this branch we do not want to emit warning. State = State->set(Irrelevant); State = State->set(SymbolRef{}); } return State; } void SetgidSetuidOrderChecker::processSetuid(ProgramStateRef State, const CallEvent &Call, CheckerContext &C) const { bool IsSetuidWithGetuid = isFunctionCalledInArg(GetuidDesc, Call); if (State->get() != Setgid && IsSetuidWithGetuid) { SymbolRef RetSym = Call.getReturnValue().getAsSymbol(); State = State->set(Setuid); State = State->set(RetSym); const NoteTag *Note = C.getNoteTag([this, RetSym](PathSensitiveBugReport &BR) { if (!BR.isInteresting(RetSym) || &BR.getBugType() != &this->BT) return ""; return "Call to 'setuid' found here that removes superuser privileges"; }); C.addTransition(State, Note); return; } State = State->set(Irrelevant); State = State->set(SymbolRef{}); C.addTransition(State); } void SetgidSetuidOrderChecker::processSetgid(ProgramStateRef State, const CallEvent &Call, CheckerContext &C) const { bool IsSetgidWithGetgid = isFunctionCalledInArg(GetgidDesc, Call); if (State->get() == Setuid) { if (IsSetgidWithGetgid) { State = State->set(Irrelevant); emitReport(State, C); return; } State = State->set(Irrelevant); } else { State = State->set(IsSetgidWithGetgid ? Setgid : Irrelevant); } State = State->set(SymbolRef{}); C.addTransition(State); } void SetgidSetuidOrderChecker::processOther(ProgramStateRef State, const CallEvent &Call, CheckerContext &C) const { State = State->set(SymbolRef{}); State = State->set(Irrelevant); C.addTransition(State); } bool SetgidSetuidOrderChecker::isFunctionCalledInArg( const CallDescription &Desc, const CallEvent &Call) const { if (const auto *CallInArg0 = dyn_cast(Call.getArgExpr(0)->IgnoreParenImpCasts())) return Desc.matchesAsWritten(*CallInArg0); return false; } void SetgidSetuidOrderChecker::emitReport(ProgramStateRef State, CheckerContext &C) const { if (ExplodedNode *N = C.generateNonFatalErrorNode(State)) { llvm::StringLiteral Msg = "A 'setgid(getgid())' call following a 'setuid(getuid())' " "call is likely to fail; probably the order of these " "statements is wrong"; auto Report = std::make_unique(BT, Msg, N); Report->markInteresting(State->get()); C.emitReport(std::move(Report)); } } void ento::registerSetgidSetuidOrderChecker(CheckerManager &mgr) { mgr.registerChecker(); } bool ento::shouldRegisterSetgidSetuidOrderChecker(const CheckerManager &mgr) { return true; }