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