File: SetgidSetuidOrderChecker.cpp

package info (click to toggle)
llvm-toolchain-20 1%3A20.1.6-1~exp1
  • links: PTS, VCS
  • area: main
  • in suites: experimental
  • size: 2,111,304 kB
  • sloc: cpp: 7,438,677; ansic: 1,393,822; asm: 1,012,926; python: 241,650; f90: 86,635; objc: 75,479; lisp: 42,144; pascal: 17,286; sh: 10,027; ml: 5,082; perl: 4,730; awk: 3,523; makefile: 3,349; javascript: 2,251; xml: 892; fortran: 672
file content (196 lines) | stat: -rw-r--r-- 8,597 bytes parent folder | download | duplicates (7)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
//===-- 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<check::PostCall, eval::Assume> {
  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<LastSetuidCallSVal>();
  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<DefinedOrUnknownSVal>();
  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<LastSetPrivilegeCall>(Irrelevant);
    State = State->set<LastSetuidCallSVal>(SymbolRef{});
  }
  return State;
}

void SetgidSetuidOrderChecker::processSetuid(ProgramStateRef State,
                                             const CallEvent &Call,
                                             CheckerContext &C) const {
  bool IsSetuidWithGetuid = isFunctionCalledInArg(GetuidDesc, Call);
  if (State->get<LastSetPrivilegeCall>() != Setgid && IsSetuidWithGetuid) {
    SymbolRef RetSym = Call.getReturnValue().getAsSymbol();
    State = State->set<LastSetPrivilegeCall>(Setuid);
    State = State->set<LastSetuidCallSVal>(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<LastSetPrivilegeCall>(Irrelevant);
  State = State->set<LastSetuidCallSVal>(SymbolRef{});
  C.addTransition(State);
}

void SetgidSetuidOrderChecker::processSetgid(ProgramStateRef State,
                                             const CallEvent &Call,
                                             CheckerContext &C) const {
  bool IsSetgidWithGetgid = isFunctionCalledInArg(GetgidDesc, Call);
  if (State->get<LastSetPrivilegeCall>() == Setuid) {
    if (IsSetgidWithGetgid) {
      State = State->set<LastSetPrivilegeCall>(Irrelevant);
      emitReport(State, C);
      return;
    }
    State = State->set<LastSetPrivilegeCall>(Irrelevant);
  } else {
    State = State->set<LastSetPrivilegeCall>(IsSetgidWithGetgid ? Setgid
                                                                : Irrelevant);
  }
  State = State->set<LastSetuidCallSVal>(SymbolRef{});
  C.addTransition(State);
}

void SetgidSetuidOrderChecker::processOther(ProgramStateRef State,
                                            const CallEvent &Call,
                                            CheckerContext &C) const {
  State = State->set<LastSetuidCallSVal>(SymbolRef{});
  State = State->set<LastSetPrivilegeCall>(Irrelevant);
  C.addTransition(State);
}

bool SetgidSetuidOrderChecker::isFunctionCalledInArg(
    const CallDescription &Desc, const CallEvent &Call) const {
  if (const auto *CallInArg0 =
          dyn_cast<CallExpr>(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<PathSensitiveBugReport>(BT, Msg, N);
    Report->markInteresting(State->get<LastSetuidCallSVal>());
    C.emitReport(std::move(Report));
  }
}

void ento::registerSetgidSetuidOrderChecker(CheckerManager &mgr) {
  mgr.registerChecker<SetgidSetuidOrderChecker>();
}

bool ento::shouldRegisterSetgidSetuidOrderChecker(const CheckerManager &mgr) {
  return true;
}