brintos

brintos / llvm-project-archived public Read only

0
0
Text · 8.4 KiB · dbe3fd3 Raw
197 lines · cpp
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-exception6//7//===----------------------------------------------------------------------===//8//9//  This file defines a checker to detect possible reversed order of privilege10//  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 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 from58  /// 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 namespace65 66/// Store if there was a call to 'setuid(getuid())' or 'setgid(getgid())' not67/// followed by other different privilege-change functions.68/// If the value \c Setuid is stored and a 'setgid(getgid())' call is found we69/// have found the bug to be reported. Value \c Setgid is used too to prevent70/// 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 to73/// detect if the result is compared to -1 and avoid warnings on that branch74/// (which is the failure branch of the call), and for identification of note75/// tags.76REGISTER_TRAIT_WITH_PROGRAMSTATE(LastSetuidCallSVal, SymbolRef)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 correct101  // too. The "invalid failure check" is a different bug that is not the scope102  // 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 ? Setgid155                                                                : 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}197