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