clang 20.0.0git
SetgidSetuidOrderChecker.cpp
Go to the documentation of this file.
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
23
24using namespace clang;
25using namespace ento;
26
27namespace {
28
29enum SetPrivilegeFunctionKind { Irrelevant, Setuid, Setgid };
30
31class 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
45public:
46 void checkPostCall(const CallEvent &Call, CheckerContext &C) const;
47 ProgramStateRef evalAssume(ProgramStateRef State, SVal Cond,
48 bool Assumption) const;
49
50private:
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.
71REGISTER_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.
77
78void 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
90ProgramStateRef 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
120void 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
142void 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
161void 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
169bool 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
177void 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
190void ento::registerSetgidSetuidOrderChecker(CheckerManager &mgr) {
191 mgr.registerChecker<SetgidSetuidOrderChecker>();
192}
193
194bool ento::shouldRegisterSetgidSetuidOrderChecker(const CheckerManager &mgr) {
195 return true;
196}
#define REGISTER_TRAIT_WITH_PROGRAMSTATE(Name, Type)
Declares a program state trait for type Type called Name, and introduce a type named NameTy.
const BugType & getBugType() const
Definition: BugReporter.h:149
An immutable set of CallDescriptions.
A CallDescription is a pattern that can be used to match calls based on the qualified name and the ar...
bool matchesAsWritten(const CallExpr &CE) const
Returns true if the CallExpr is a call to a function that matches the CallDescription.
Represents an abstract call to a function or method along a particular path.
Definition: CallEvent.h:153
CHECKER * registerChecker(AT &&... Args)
Used to register checkers.
The tag upon which the TagVisitor reacts.
Definition: BugReporter.h:779
bool isInteresting(SymbolRef sym) const
nonloc::ConcreteInt makeIntVal(const IntegerLiteral *integer)
Definition: SValBuilder.h:290
virtual SVal evalBinOpNN(ProgramStateRef state, BinaryOperator::Opcode op, NonLoc lhs, NonLoc rhs, QualType resultTy)=0
Create a new value which represents a binary expression with two non- location operands.
QualType getConditionType() const
Definition: SValBuilder.h:153
SVal - This represents a symbolic expression, which can be either an L-value or an R-value.
Definition: SVals.h:55
Symbolic value.
Definition: SymExpr.h:30
Represents symbolic expression that isn't a location.
Definition: SVals.h:276
@ Irrelevant
We do not know anything about 'errno'.
Definition: ErrnoModeling.h:29
The JSON file list parser is used to communicate input to InstallAPI.