xref: /freebsd-src/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/SetgidSetuidOrderChecker.cpp (revision 0fca6ea1d4eea4c934cfff25ac9ee8ad6fe95583)
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