美文网首页
Phasar-IFDS框架学习笔记

Phasar-IFDS框架学习笔记

作者: Nevv | 来源:发表于2019-03-10 13:45 被阅读0次

IFDSAnalysis

Phasar框架中,为了解决IFDS / IDE问题,主要分成了以下三个步骤:

  • IFDS / IDE 求解器(slover)。

  • 流函数的形式定义IFDS / IDE分析问题(不同IFDS问题定义不同的流函数)。

  • 程序间控制流图的实现(ICFG)。

1.phasar 主函数流程

AnalysisController

在处理完配置文件和命令行解析后,实例化一个 AnalysisController 管理整个分析的流程:

    /********* 处理完参数解析后,下面的代码开始进行实际的分析 *********/
    // At this point we have set-up all the parameters and can start the actual
    // analyses that have been choosen.
    AnalysisController Controller(
        [&lg](bool usingModules) {
          PAMM_GET_INSTANCE;
          START_TIMER("IRDB Construction", PAMM_SEVERITY_LEVEL::Full);
          LOG_IF_ENABLE(BOOST_LOG_SEV(lg, INFO) << "Set-up IR database.");
          IRDBOptions Opt = IRDBOptions::NONE;
          if (VariablesMap["wpa"].as<bool>()) {
            Opt |= IRDBOptions::WPA;
          }
          if (VariablesMap["mem2reg"].as<bool>()) {
            Opt |= IRDBOptions::MEM2REG;
          }
          if (usingModules) {
            ProjectIRDB IRDB(
                VariablesMap["module"].as<std::vector<std::string>>(), Opt);
            STOP_TIMER("IRDB Construction", PAMM_SEVERITY_LEVEL::Full);
            return IRDB;
          } else {
            // perform a little trick to make OptionsParser only responsible for
            // the project sources
            int OnlyTakeCareOfSources = 2;
            const char *ProjectSources =
                VariablesMap["project"].as<std::string>().c_str();
            const char *DummyProgName = "not_important";
            const char *DummyArgs[] = {DummyProgName, ProjectSources};
            clang::tooling::CommonOptionsParser OptionsParser(
                OnlyTakeCareOfSources, DummyArgs, StaticAnalysisCategory,
                OccurrencesFlag);
            clang::tooling::CompilationDatabase &CompileDB =
                OptionsParser.getCompilations();
            ProjectIRDB IRDB(CompileDB, Opt);
            STOP_TIMER("IRDB Construction", PAMM_SEVERITY_LEVEL::Full);
            return IRDB;
          }
        }(VariablesMap.count("module")),
        // IRDB 管理和维护llvm生成的IR代码和相关信息
        ChosenDataFlowAnalyses,
        // 要执行的数据流分析实例,如IFDSTaintAnalysis
        VariablesMap["wpa"].as<bool>(),  
        // wpa 是否分析整个程序 Whole-program analysis mode (1 or 0)
        VariablesMap["printedgerec"].as<bool>(),
        VariablesMap["graph-id"].as<std::string>());
        // 指定图的id,其后由exportJson将图转化后发送到可视化的服务端

        /********* 使用一个ChosenDataFlowAnalyses来管理整个分析流程 *********/
  • 接下来看下 AnalysisController 的构造方法,其主要是完成以下功能:
    • 检查入口点合法性
    • 生成 ICFG
    • 调用用户指定的 Analyzer
AnalysisController::AnalysisController(
    ProjectIRDB &&IRDB, std::vector<DataFlowAnalysisType> Analyses,
    bool WPA_MODE, bool PrintEdgeRecorder, std::string graph_id)
    : FinalResultsJson() {
  PAMM_GET_INSTANCE;
  auto &lg = lg::get();
  LOG_IF_ENABLE(BOOST_LOG_SEV(lg, INFO)
                << "Constructed the analysis controller.");
  LOG_IF_ENABLE(BOOST_LOG_SEV(lg, INFO)
                << "Found the following IR files for this project: ");
  for (auto file : IRDB.getAllSourceFiles()) {
    LOG_IF_ENABLE(BOOST_LOG_SEV(lg, INFO) << "\t" << file);
  }
  // 检查入口点是否合法
  LOG_IF_ENABLE(BOOST_LOG_SEV(lg, INFO) << "Check for chosen entry points.");
  vector<string> EntryPoints = {"main"};
  if (VariablesMap.count("entry-points")) {
    std::vector<std::string> invalidEntryPoints;
    for (auto &entryPoint : VariablesMap["entry-points"].as<vector<string>>()) {
      if (IRDB.getFunction(entryPoint) == nullptr) {
        invalidEntryPoints.push_back(entryPoint);
      }
    }
    if (invalidEntryPoints.size()) {
      for (auto &invalidEntryPoint : invalidEntryPoints) {
        LOG_IF_ENABLE(BOOST_LOG_SEV(lg, ERROR)
                      << "Entry point '" << invalidEntryPoint
                      << "' is not valid.");
      }
      throw logic_error("invalid entry points");
    }
    if (VariablesMap["entry-points"].as<vector<string>>().size()) {
      EntryPoints = VariablesMap["entry-points"].as<vector<string>>();
    }
  }
  if (WPA_MODE) {
    // here we link every llvm module into a single module containing the entire
    // IR
    LOG_IF_ENABLE(
        BOOST_LOG_SEV(lg, INFO)
        << "link all llvm modules into a single module for WPA ...\n");
    START_TIMER("Link to WPA Module", PAMM_SEVERITY_LEVEL::Full);
    IRDB.linkForWPA();
    STOP_TIMER("Link to WPA Module", PAMM_SEVERITY_LEVEL::Full);
    LOG_IF_ENABLE(
        BOOST_LOG_SEV(lg, INFO)
        << "link all llvm modules into a single module for WPA ended\n");
  }
  IRDB.preprocessIR();

  // 重构 inter-modular 的类层次结构和虚函数表
  LOG_IF_ENABLE(BOOST_LOG_SEV(lg, INFO) << "Reconstruct the class hierarchy.");
  START_TIMER("CH Construction", PAMM_SEVERITY_LEVEL::Core);
  LLVMTypeHierarchy CH(IRDB);
  STOP_TIMER("CH Construction", PAMM_SEVERITY_LEVEL::Core);
  LOG_IF_ENABLE(BOOST_LOG_SEV(lg, INFO)
                << "Reconstruction of class hierarchy completed.");

  // 生成 CG
  CallGraphAnalysisType CGType(
      (VariablesMap.count("callgraph-analysis"))
          ? StringToCallGraphAnalysisType.at(
                VariablesMap["callgraph-analysis"].as<string>())
          : CallGraphAnalysisType::OTF);
  // 执行 WPA 分析
  if (WPA_MODE) {
    START_TIMER("CG Construction", PAMM_SEVERITY_LEVEL::Core);
    LLVMBasedICFG ICFG(CH, IRDB, CGType, EntryPoints);
    // 生成 ICFG
    if (VariablesMap.count("callgraph-plugin")) {
      throw runtime_error("callgraph plugin not found");
    }
    STOP_TIMER("CG Construction", PAMM_SEVERITY_LEVEL::Core);
    // ICFG.printAsDot("call_graph.dot");

    // CFG 只在 monotone 函数内分析框架中使用
    LLVMBasedCFG CFG;
    START_TIMER("DFA Runtime", PAMM_SEVERITY_LEVEL::Core);
    /*
     * 根据选择的分析模块执行
     */
    for (DataFlowAnalysisType analysis : Analyses) {
      LOG_IF_ENABLE(BOOST_LOG_SEV(lg, INFO)
                    << "Performing analysis: " << analysis);
      switch (analysis) {
      case DataFlowAnalysisType::IFDS_TaintAnalysis: {
        TaintSensitiveFunctions TSF;
        // 使用build-in的source和sink
        IFDSTaintAnalysis TaintAnalysisProblem(ICFG, TSF, EntryPoints);
        LLVMIFDSSolver<const llvm::Value *, LLVMBasedICFG &> LLVMTaintSolver(
            TaintAnalysisProblem, false);
        // 实例化一个 solver 进行数据流分析
        cout << "IFDS Taint Analysis ..." << endl;
        LLVMTaintSolver.solve();
        cout << "IFDS Taint Analysis ended" << endl;
        // FinalResultsJson += LLVMTaintSolver.getAsJson();
        if (PrintEdgeRecorder) {
          LLVMTaintSolver.exportJson(graph_id);
        }
        break;
      }
      case DataFlowAnalysisType::IDE_TaintAnalysis: {
        IDETaintAnalysis taintanalysisproblem(ICFG, EntryPoints);
        LLVMIDESolver<const llvm::Value *, const llvm::Value *, LLVMBasedICFG &>
            llvmtaintsolver(taintanalysisproblem, true);
        llvmtaintsolver.solve();
        FinalResultsJson += llvmtaintsolver.getAsJson();
        if (PrintEdgeRecorder) {
          llvmtaintsolver.exportJson(graph_id);
        }
        break;
      }
     …………
     …………
  }
  // 智能化模块分析?
  // Perform module-wise (MW) analysis
  else {
    throw runtime_error(
        "This code will follow soon with an accompanying paper!");
  }
}

下边代码是调用具体的 IFDS 框架求解污点分析问题的代码,其中 LLVMIFDSSolver 继承自 IFDSSolver ,最终的祖先类是 IDESolver ,最基本的函数基本上都封装在这里。

        IFDSTaintAnalysis TaintAnalysisProblem(ICFG, TSF, EntryPoints);
        LLVMIFDSSolver<const llvm::Value *, LLVMBasedICFG &> LLVMTaintSolver(
            TaintAnalysisProblem, false);
        // 实例化一个 solver 进行数据流分析
        cout << "IFDS Taint Analysis ..." << endl;
        LLVMTaintSolver.solve();

2. IDESolver(IFDS的基类)

solve 函数实现流程如下:

  • 调用 submitInitalSeeds
    • 根据 initseed 初始化
    • 生成 edge function 构建 exploded supergraph
  • 调用 computeValues
    • 根据 edge function 计算并保存函数节点的数据流值并保存到 Table<N, D, V> valtab 中
  • 调用 computeAndPrintStatistics
    • 计算并通过之前保存的valtab打印运行中的一些信息
    • 通过从 computedIntraPathEdges 和 computedInterPathEdges 结构中获取信息
    • IntraPathEdges 和 InterPathEdges 由分析过程中调用 saveEdges 函数存储
  virtual void solve() {
      ……
    // computations starting here
    START_TIMER("DFA Phase I", PAMM_SEVERITY_LEVEL::Full);
    // We start our analysis and construct exploded supergraph
    LOG_IF_ENABLE(BOOST_LOG_SEV(lg, INFO)
                  << "Submit initial seeds, construct exploded super graph");
    /* 初始化,构建超级图 */
    submitInitalSeeds();
    STOP_TIMER("DFA Phase I", PAMM_SEVERITY_LEVEL::Full);
    if (computevalues) {
      START_TIMER("DFA Phase II", PAMM_SEVERITY_LEVEL::Full);
      // 计算最终的值
      // Computing the final values for the edge functions
      LOG_IF_ENABLE(
          BOOST_LOG_SEV(lg, INFO)
          << "Compute the final values according to the edge functions");
      computeValues();
      STOP_TIMER("DFA Phase II", PAMM_SEVERITY_LEVEL::Full);
    }
    LOG_IF_ENABLE(BOOST_LOG_SEV(lg, INFO) << "Problem solved");
    if constexpr (PAMM_CURR_SEV_LEVEL >= PAMM_SEVERITY_LEVEL::Core) {
      computeAndPrintStatistics();
    }
  }

subitInitalSeeds

submitInitalSeeds 的主要功能是初始化种子和初始化分析。

  void submitInitalSeeds() {
    auto &lg = lg::get();
    PAMM_GET_INSTANCE;
    for (const auto &seed : initialSeeds) {
      N startPoint = seed.first;
      LOG_IF_ENABLE(BOOST_LOG_SEV(lg, DEBUG)
                    << "Start point: "
                    << ideTabulationProblem.NtoString(startPoint));
      for (const D &value : seed.second) {
        LOG_IF_ENABLE(BOOST_LOG_SEV(lg, DEBUG)
                      << "      Value: "
                      << ideTabulationProblem.DtoString(value));
        LOG_IF_ENABLE(BOOST_LOG_SEV(lg, DEBUG) << ' ');
        if (!ideTabulationProblem.isZeroValue(value)) {
          INC_COUNTER("Gen facts", 1, PAMM_SEVERITY_LEVEL::Core);
        }
        propagate(zeroValue, startPoint, value, EdgeIdentity<V>::getInstance(),
                  nullptr, false);
      }
      jumpFn->addFunction(zeroValue, startPoint, zeroValue,
                          EdgeIdentity<V>::getInstance()); // 添加边函数
// jumpFn 为指向 std::shared_ptr<JumpFunctions<N, D, M, V, I>> 的智能指针
// addFunction 函数的功能为记录数据流前向和后向的流向,存储为{key=llvm::value,value=edgefunction}的形式
    }
  }
  • initialSeeds 变量来源于 tabulationProblem.initialSeeds() 方法,根据要分析的特定问题的不同,其生成初始化种子的方法也不同。比如对于 IFDSTaintAnalysis 来说,EntryPoint 为 main 函数的时候,插入的初始值是命令行参数(标记为污染的)和零值,对于其他入口点,只插入零值。
map<IFDSTaintAnalysis::n_t, set<IFDSTaintAnalysis::d_t>>
IFDSTaintAnalysis::initialSeeds() {
  auto &lg = lg::get();
  LOG_IF_ENABLE(BOOST_LOG_SEV(lg, DEBUG)
                << "IFDSTaintAnalysis::initialSeeds()");
  // If main function is the entry point, commandline arguments have to be
  // tainted. Otherwise we just use the zero value to initialize the analysis.
  map<IFDSTaintAnalysis::n_t, set<IFDSTaintAnalysis::d_t>> SeedMap;
  for (auto &EntryPoint : EntryPoints) {
    if (EntryPoint == "main") {
      set<IFDSTaintAnalysis::d_t> CmdArgs;
      for (auto &Arg : icfg.getMethod(EntryPoint)->args()) {
        CmdArgs.insert(&Arg);
      }
      CmdArgs.insert(zeroValue());
      SeedMap.insert(
          make_pair(&icfg.getMethod(EntryPoint)->front().front(), CmdArgs));
    } else {
      SeedMap.insert(make_pair(&icfg.getMethod(EntryPoint)->front().front(),
                               set<IFDSTaintAnalysis::d_t>({zeroValue()})));
    }
  }
  return SeedMap;
}

propagate

  • propagate 函数负责将数据流值沿着超级图传播,合并已经在目标语句处计算出的边函数
    • 参数 sourceVal 为边函数上的源 value
    • 参数 target 为 目标语句
    • 参数 targetval 为目标处的 value
    • 参数 f 为 (s0,sourceVal) 到(target,targetVal) 计算出来的 edge function
void
  propagate(D sourceVal, N target, D targetVal,
            std::shared_ptr<EdgeFunction<V>> f,
            /* deliberately exposed to clients */ N relatedCallSite,
            /* deliberately exposed to clients */ bool isUnbalancedReturn) {
    auto &lg = lg::get();
    LOG_IF_ENABLE(BOOST_LOG_SEV(lg, DEBUG) << "Propagate flow");
    std::shared_ptr<EdgeFunction<V>> jumpFnE = nullptr;
    std::shared_ptr<EdgeFunction<V>> fPrime;
    if (!jumpFn->reverseLookup(target, targetVal).empty()) {
       // reverseLookup 对于给定的目标语句和值,返回所有关联的源值,返回值是从源值到edge函数的映射。
       // 如果不为空,返回 sourceVal 到 targetVal 的所有 edge function
      jumpFnE = jumpFn->reverseLookup(target, targetVal)[sourceVal];
    }
    if (jumpFnE == nullptr) {
      jumpFnE = allTop; // jump function is initialized to all-top
    }
    fPrime = jumpFnE->joinWith(f); // 合并?
    bool newFunction = !(fPrime->equal_to(jumpFnE));
    if (newFunction) { // 如果是一个新edge function的话,进行传播,
      jumpFn->addFunction(sourceVal, target, targetVal, fPrime);
      PathEdge<N, D> edge(sourceVal, target, targetVal); // 添加边
      PathEdgeCount++;
      pathEdgeProcessingTask(edge);
      /*
      pathEdgeProcessingTask
      处理边,根据其 target 的不同做以下处理(对应于 tabulation 算法):
        processExit
        processNormalFlow
        processCall
      */
  }

pathEdgeProcessingTask

​ 这个函数主要根据传递过来的边,判断 target node 是不是 call 或者 exit 节点,分别做不同的处理传播

 void pathEdgeProcessingTask(PathEdge<N, D> edge) {
    bool isCall = icfg.isCallStmt(edge.getTarget());
    if (!isCall) {   
      if (icfg.isExitStmt(edge.getTarget())) {
        processExit(edge);
      }
      if (!icfg.getSuccsOf(edge.getTarget()).empty()) {
        processNormalFlow(edge);
      }
    } else {
      processCall(edge);
    }
  }

​ 调用的这三个函数实现的是论文中 IFDS 的基本算法

函数名 功能
processCall 对应算法的13-20行,处理call
processExit 对应算法的21-32行,处理exit
processNormalFlow 对应算法的33-37行,处理普通情况下的数据流传递(除了 call 和 exit )
  • processCall 主要流程:
    • 对于每一个可能的 callee,首先会检查是否存在 summary 边,有的话直接当作普通的数据流传递,否则就根据call 的函数计算数据流传递。
    • 然后处理 call2ret 节点间的数据流传递
  • processExit 主要流程:
    • 对于 caller 传入的数据流并且能最终通过 retsite 传递出来的,添加 summary 边
    • 将 caller 函数开始处的数据流值传递到 ret 处

computeValues

​ 根据 edge functions 计算最终值

  void computeValues() {
    auto &lg = lg::get();
    LOG_IF_ENABLE(BOOST_LOG_SEV(lg, DEBUG) << "Start computing values");
    // Phase II(i)
    std::map<N, std::set<D>> allSeeds(initialSeeds);
    for (N unbalancedRetSite : unbalancedRetSites) {
      if (allSeeds[unbalancedRetSite].empty()) {
        allSeeds.insert(make_pair(unbalancedRetSite, std::set<D>({zeroValue})));
      }
    }
    // do processing
      // 这里是设置节点的数据流值,并将其保存到 valtab 中
    for (const auto &seed : allSeeds) {
      N startPoint = seed.first;
      for (D val : seed.second) {
        // 设置起始节点数据流值
        setVal(startPoint, val, ideTabulationProblem.bottomElement());
        std::pair<N, D> superGraphNode(startPoint, val);
        valuePropagationTask(superGraphNode);
      }
    }
    // Phase II(ii)
    // we create an array of all nodes and then dispatch fractions of this array
    // to multiple threads
      
    std::set<N> allNonCallStartNodes = icfg.allNonCallStartNodes();
   // 返回既不是call也不是invoke节点的集合
    std::vector<N> nonCallStartNodesArray(allNonCallStartNodes.size());
    size_t i = 0;
    for (N n : allNonCallStartNodes) {
      nonCallStartNodesArray[i] = n;
      i++;
    }
    valueComputationTask(nonCallStartNodesArray);
  }

setVal

​ 给 node 设置数据流值,如果 l 是 top value 的话就从 valtab 中移除

  void setVal(N nHashN, D nHashD, V l) {
    auto &lg = lg::get();
    // 如果是 top value,则不存储
    // TOP is the implicit default value which we do not need to store.
    if (l == ideTabulationProblem.topElement()) {
      valtab.remove(nHashN, nHashD);
    } else {
      valtab.insert(nHashN, nHashD, l);
    }
  }

valuePropagationTask

  // should be made a callable at some point
  void valuePropagationTask(std::pair<N, D> nAndD) {
    N n = nAndD.first;
    // 初始化的种子不一定是函数的入口点
    // our initial seeds are not necessarily method-start points but here they
    // should be treated as such the same also for unbalanced return sites in
    // an unbalanced problem
    if (icfg.isStartPoint(n) || initialSeeds.count(n) ||
        unbalancedRetSites.count(n)) {
      propagateValueAtStart(nAndD, n);
    }
    if (icfg.isCallStmt(n)) {
      propagateValueAtCall(nAndD, n);
    }
  }

​ 如果是函数入口点或者在 initialSeeds 中的话,执行 propagateValueAtStart 进行数据流传递。

propagateValueAtStart

  void propagateValueAtStart(std::pair<N, D> nAndD, N n) {
    PAMM_GET_INSTANCE;
    D d = nAndD.second;
    M p = icfg.getMethodOf(n); // 获取 n 所在的函数
    for (N c : icfg.getCallsFromWithin(p)) {
      for (auto entry : jumpFn->forwardLookup(d, c)) { 
        // 这里的c最开始是 initialSeeds 中的初始数据流值
        /* forwardLookup函数根据给定的source value和目标语句,返回所有目标语句中受到影响的
           目标value和 edge function,(key=target value,value=edge function)      */ 
        D dPrime = entry.first;
        std::shared_ptr<EdgeFunction<V>> fPrime = entry.second;
        N sP = n;
        V value = val(sP, d);
        INC_COUNTER("Value Propagation", 1, PAMM_SEVERITY_LEVEL::Full);
        propagateValue(c, dPrime, fPrime->computeTarget(value)); // 传递数据流
      }
    }
  }
  • getCallsFromWithin 获取给定函数的所有调用位置

propagateValueAtCall

  void propagateValueAtCall(std::pair<N, D> nAndD, N n) {
    PAMM_GET_INSTANCE;
    D d = nAndD.second;
    for (M q : icfg.getCalleesOfCallAt(n)) { // 获取到 callee
      std::shared_ptr<FlowFunction<D>> callFlowFunction = // call指令的数据流传递函数
          cachedFlowEdgeFunctions.getCallFlowFunction(n, q); 
        // getCallFlowFunction 完成实参和形参的映射
      INC_COUNTER("FF Queries", 1, PAMM_SEVERITY_LEVEL::Full);
      for (D dPrime : callFlowFunction->computeTargets(d)) {
        std::shared_ptr<EdgeFunction<V>> edgeFn =
            cachedFlowEdgeFunctions.getCallEdgeFunction(n, d, q, dPrime); 
          // 获取 edge function
        INC_COUNTER("EF Queries", 1, PAMM_SEVERITY_LEVEL::Full);
        for (N startPoint : icfg.getStartPointsOf(q)) {
          INC_COUNTER("Value Propagation", 1, PAMM_SEVERITY_LEVEL::Full);
          propagateValue(startPoint, dPrime, edgeFn->computeTarget(val(n, d))); 
        }
      }
    }
  }
  • 这里的 cachedFlowEdgeFunctions = tabulationProblem

propagateValue

 void propagateValue(N nHashN, D nHashD, V v) {
    V valNHash = val(nHashN, nHashD);
    V vPrime = joinValueAt(nHashN, nHashD, valNHash, v);
    if (!(vPrime == valNHash)) {
      setVal(nHashN, nHashD, vPrime);
      valuePropagationTask(std::pair<N, D>(nHashN, nHashD));
    }
  }

3.IFDS问题分析实例

IFDSTaintAnalysis

这里以 IFDSTaintAnalysis 为例,这个类中主要实例化了 Tablution 算法中所定义的四个数据流传递函数:

  • getNormalFlowFunction 普通节点
  • getCallFlowFunction 函数调用点到被调用函数
  • getRetFlowFunction ret 节点,在污点分析问题中,用于在函数调用返回后,处理返回值和参数是否被污染,只关注 pointer/reference 类型的指针
  • getCallToRetFlowFunction call结点其return结点

其实例化的 IFDSTaintAnalysis 类如下:

namespace psr {

class LLVMBasedICFG;

/**
 *
 * 主要处理的数据流向是从 source 点到 sink 点,一旦发现有被标记为污染的数据
 * 流到达 sink 点,就会 leak 报告出来。
 * 
 * @see TaintSensitiveFunctions on how to specify your own
 * taint-sensitive source and sink functions.
 */
class IFDSTaintAnalysis : public DefaultIFDSTabulationProblem<
                              const llvm::Instruction *, const llvm::Value *,
                              const llvm::Function *, LLVMBasedICFG &> {
                                  // {N D M I}
public:
  typedef const llvm::Value *d_t;
  typedef const llvm::Instruction *n_t;
  typedef const llvm::Function *m_t;
  typedef LLVMBasedICFG &i_t;

private:
  TaintSensitiveFunctions SourceSinkFunctions   ; // 存储SourceSink函数
  std::vector<std::string> EntryPoints;  // 函数入口点

public:
  /// Holds all leaks found during the analysis
  std::map<n_t, std::set<d_t>> Leaks;

  /**
   *
   * @param icfg
   * @param TSF
   * @param EntryPoints
   */
  IFDSTaintAnalysis(i_t icfg, TaintSensitiveFunctions TSF,
                    std::vector<std::string> EntryPoints = {"main"});                               
                                  
  virtual ~IFDSTaintAnalysis() = default;

  std::shared_ptr<FlowFunction<d_t>> getNormalFlowFunction(n_t curr,
                                                           n_t succ) override;
  std::shared_ptr<FlowFunction<d_t>> getCallFlowFunction(n_t callStmt,
                                                         m_t destMthd) override;

  std::shared_ptr<FlowFunction<d_t>> getRetFlowFunction(n_t callSite,
                                                        m_t calleeMthd,
                                                        n_t exitStmt,
                                                        n_t retSite) override;
  std::shared_ptr<FlowFunction<d_t>>
  getCallToRetFlowFunction(n_t callSite, n_t retSite,
                           std::set<m_t> callees) override;

// 计算summary的函数,将一个函数内部的数据流传递情况计算summary
  std::shared_ptr<FlowFunction<d_t>>
  getSummaryFlowFunction(n_t callStmt, m_t destMthd) override;

  std::map<n_t, std::set<d_t>> initialSeeds() override;
  d_t createZeroValue() override;
  bool isZeroValue(d_t d) const override;
  void printNode(std::ostream &os, n_t n) const override;
  void printDataFlowFact(std::ostream &os, d_t d) const override;
  void printMethod(std::ostream &os, m_t m) const override;
  void printIFDSReport(std::ostream &os,
                       SolverResults<n_t, d_t, BinaryDomain> &SR) override;
};
} // namespace psr
  • IFDSTaintAnalysis 在初始化的时候传入了一个 TaintSensitiveFunctions 对象,这个类主要是维护用于污点分析 source 函数和 sink 函数,并且缺省定义了一些常见的函数,并标记了其 Critical argument

DefaultIFDSTabulationProblem

​ 这是一个 IFDS 问题的默认模板类,用于定义一个 IFDS 问题,设置其 solver 的参数:

namespace psr {
template <typename N, typename D, typename M, typename I>
class DefaultIFDSTabulationProblem : public IFDSTabulationProblem<N, D, M, I> {
protected:
  I icfg;
  virtual D createZeroValue() = 0;
  D zerovalue;

public:
    // 进行一些默认的 solver 的配置
  DefaultIFDSTabulationProblem(I icfg) : icfg(icfg) {
    this->solver_config.followReturnsPastSeeds = false;
    this->solver_config.autoAddZero = true;
    this->solver_config.computeValues = true;
    this->solver_config.recordEdges = true;
    this->solver_config.computePersistedSummaries = true;
  }

  virtual ~DefaultIFDSTabulationProblem() = default;

  virtual std::shared_ptr<FlowFunction<D>>
  getSummaryFlowFunction(N callStmt, M destMthd) override {
    return nullptr;
  }
    
  I interproceduralCFG() override { return icfg; }
  D zeroValue() override { return zerovalue; }
};

} // namespace psr

#endif

因此在用户实现自己的 IFDS分析问题的时候,只需要 override 特定的数据流传递函数即可。

4. ICFG 实现

TODO

5. 代码结构

Phasar使用AnalysisController管理整个分析流程,根据选择的特定的数据流问题进行分析。并将IFDS问题分别使用两个类管理,一个是Problem类,用于定义要分析的问题,另一类是Solver类,用于求解IFDS问题:

  1. Problem类的继承关系及基类简述
    • DefaultIFDSTabulationProblem:
      • IFDSTabulationProblem
      • FlowFunctions
      • NodePrinter
      • DataFlowFactPrinter
      • MethodPrinter
简述
DefaultIFDSTabulationProblem 所有特定 IFDS 问题的基类
IFDSTabulationProblem 保存了zerovalue、配置信息和初始化值
FlowFunctions 定义基本的数据流传递函数
NodePrinter、DataFlowFactPrinter、MethodPrinter 打印特定数据结构的模板类
  • FlowFunctions 类中实现的方法接口
方法名 功能
getNormalFlowFunction 普通节点的数据流传递函数
getCallFlowFunction 函数调用的数据流传递函数,实参映射到形参
getRetFlowFunction 返回节点的数据流传递函数,在被调函数的最后将数据流值传回caller中其ret节点
getCallToRetFlowFunction call到ret的数据流传递函数,处理没有被callee影响到的数据流值
getSummaryFlowFunction 默认是返回空指针,用于处理libc库函数或者llvm生成的不能follow进去的函数
  • IFDSTabulationProblem 类中的方法接口和成员变量

    名称 简述
    initialSeeds 分析起始程序点和其的初始数据流值
    setSolverConfiguration 设置solver
    isZeroValue 判断数据流值是否是0值
    interproceduralCFG 返回分析问题的ICFG
  1. Slover类的继承关系及简述

    LLVMIFDSSolver最终也是继承自 IDESolver,对于特定的IFDS问题的求解主要是调用solve( )方法。

    • LLVMIFDSSolver
      • IFDSSolver
        • IDESolver
  • LLVMIFDSSolver

    LLVMIFDSSolver这个类中实现了打印过程间、过程内 pathEdge 方法以及将结果可视化传送给web端的接口。

  • IFDSSolver

    IFDSSolver实现了一个返回给定stmt处ifds结果的方法。

  • IDESolver

    这个类中slove( )函数实现的是对IFDS问题的求解,solve函数主要有三个步骤:

    1. submitInitalSeeds 提交初始化数据流值的seed,构建 exploded supergraph
    2. computeValues 根据 edge functions 计算最终的数据流值
    3. 主要是计算和打印一些分析过程中的信息,比如gen/kill的数据流数量等。
函数名 功能
propagate 负责将数据流沿着exploded super graph向下传递,合并任何可能已经在目标语句处算出数据流值的edge function
setVal 保存每个程序点的数据流值
saveEdges 向computedInterPathEdges和computedIntraPathEdges存储已经计算出来的pathEdges
computeValues 根据边函数计算每一个程序点的数据流值,调用setval函数存入valtab
addEndSummary 添加函数的摘要边
变量名 功能
std::shared_ptr<JumpFunctions<N, D, M, V, I>> jumpFn; 存储并管理exploded super graph中的数据流边
Table<N, D, V> valtab; 存储节点的数据流值
computedInterPathEdges 函数间的边(打印信息用)
computedIntraPathEdges 函数内的边(打印信息用)
Table<N, D, std::map<N, std::set<D>>> incomingtab; 记录所有的函数调用信息(处理exit节点的时候使用)
Table<N, D, Table<N, D, std::shared_ptr<EdgeFunction<V>>>> endsummarytab 存储已经计算出来的summay边。

propagate函数

  • 从jumpFn中反向找能够从source处传递到target处的edge function,如果找不到就用alltop
    • 如果是新的edgefuntion的话,说明可以直接从最初点到当前点添加一条边(比如存在a-b的边,然后当前的数据流传递是b-c,会添加一条a-c的边到jumpFunctions中)
    • 然后会调用pathEdgeProcessingTask处理从source到target的pathEdge
    • pathEdgeProcessingTask函数会根据target的类型做三种不同处理:
      • processExit
        • 首先找到该exit节点的所有caller
        • 根据这些caller传递过来的数据流值和从函数start节点传递到exit节点的数据流值,调用propagate将数据流值从callee的start处传递到ret site处
      • processNormalFlow
        • 处理正常的数据流传递,通过getNormalFlowFunction获取数据流传递函数,然后通过computeNormalFlowFunction计算传递后的数据流值
        • 计算出传递后的数据流值后再次调用propagate函数传递数据流值
      • processCall
        • 处理call语句的时候,首先看callee的函数是不是之前计算过,即通过getSummaryFlowFunction获取其summary flow function,如果存在就当作普通的数据流传递
        • 否则就处理函数实参和形参的映射(记录在incomingtab中)
        • 最后把能够直接传递到ret-site的数据流值进行传递

6.参考:

1. github

2. wiki

7.问题

  1. Table<N, D, std::map<N, std::set<D>>> incomingtab
  • edges going along calls

  • incomingtab相关的方法接口

  std::map<N, std::set<D>> incoming(D d1, N sP) {
    return incomingtab.get(sP, d1);
  }

  void addIncoming(N sP, D d3, N n, D d2) {
    incomingtab.get(sP, d3)[n].insert(d2);
  }
  • 使用 incomingtab

    • processCall 函数中,遇到 call 节点的并找到其 call 的函数的时候,就注册下 callee 的 startpoint 处的数据流值是来自哪里。

      for (N sP : startPointsOf) {
        saveEdges(n, sP, d2, res, true);
        // for each result node of the call-flow function
        for (D d3 : res) {
          // create initial self-loop
          propagate(d3, sP, d3, EdgeIdentity<V>::getInstance(), n,
                    false); // line 15
          // register the fact that <sp,d3> has an incoming edge from <n,d2>
          // line 15.1 of Naeem/Lhotak/Rodriguez
          addIncoming(sP, d3, n, d2);
      
    • processExit 函数中,处理 exit 节点的时候,先找到其对应的 start point,然后对于 start point,添加一条从 start point 处到 exit 的边(存储在 fSummaryReuse 中),然后通过去寻找其 incoming calls,计算 call 到 ret 的数据流值,最后将数据流值从 call 传递到 retsite。

      D d1 = edge.factAtSource();
      D d2 = edge.factAtTarget();
      // for each of the method's start points, determine incoming calls
      std::set<N> startPointsOf = icfg.getStartPointsOf(methodThatNeedsSummary);
      std::map<N, std::set<D>> inc;
      for (N sP : startPointsOf) {
        // line 21.1 of Naeem/Lhotak/Rodriguez
        // register end-summary
        addEndSummary(sP, d1, n, d2, f);
        for (auto entry : incoming(d1, sP)) {
          inc[entry.first] = std::set<D>{entry.second};
        }
      }
      printEndSummaryTab();
      printIncomingTab();
      // for each incoming call edge already processed
      //(see processCall(..))
      for (auto entry : inc) {
        // line 22
        N c = entry.first;
        // for each return site
        for (N retSiteC : icfg.getReturnSitesOfCallAt(c)) {
          // compute return-flow function
          std::shared_ptr<FlowFunction<D>> retFunction =
              cachedFlowEdgeFunctions.getRetFlowFunction(
                  c, methodThatNeedsSummary, n, retSiteC);
          INC_COUNTER("FF Queries", 1, PAMM_SEVERITY_LEVEL::Full);
          // for each incoming-call value
          for (D d4 : entry.second) {
            std::set<D> targets =
                computeReturnFlowFunction(retFunction, d1, d2, c, entry.second);
            ADD_TO_HISTOGRAM("Data-flow facts", targets.size(), 1,
                             PAMM_SEVERITY_LEVEL::Full);
      
  1. 实参形参映射

    以污点分析为例,在 getCallFlowFunction 函数中实现参数的映射:

shared_ptr<FlowFunction<IFDSTaintAnalysis::d_t>>
IFDSTaintAnalysis::getCallFlowFunction(IFDSTaintAnalysis::n_t callStmt,
                                       IFDSTaintAnalysis::m_t destMthd) {
  auto &lg = lg::get();
  LOG_IF_ENABLE(BOOST_LOG_SEV(lg, DEBUG)
                << "IFDSTaintAnalysis::getCallFlowFunction()");
  string FunctionName = cxx_demangle(destMthd->getName().str());
  // Check if a source or sink function is called:
  // We then can kill all data-flow facts not following the called function.
  // The respective taints or leaks are then generated in the corresponding
  // call to return flow function.
  if (SourceSinkFunctions.isSource(FunctionName) ||
      (SourceSinkFunctions.isSink(FunctionName))) {
    return KillAll<IFDSTaintAnalysis::d_t>::getInstance();
  }
  // Map the actual into the formal parameters
  if (llvm::isa<llvm::CallInst>(callStmt) ||
      llvm::isa<llvm::InvokeInst>(callStmt)) {
    return make_shared<MapFactsToCallee>(llvm::ImmutableCallSite(callStmt),
                                         destMthd);
  }
  // Pass everything else as identity
  return Identity<IFDSTaintAnalysis::d_t>::getInstance();
}

​ 具体实现:

MapFactsToCallee::MapFactsToCallee(
    const llvm::ImmutableCallSite &callSite, const llvm::Function *destMthd,
    function<bool(const llvm::Value *)> predicate)
    : destMthd(destMthd), predicate(predicate) {
  // Set up the actual parameters
  for (unsigned idx = 0; idx < callSite.getNumArgOperands(); ++idx) {
    actuals.push_back(callSite.getArgOperand(idx));
  }
  // Set up the formal parameters
  for (unsigned idx = 0; idx < destMthd->arg_size(); ++idx) {
    formals.push_back(getNthFunctionArgument(destMthd, idx));
  }
}

​ 在 processCall 函数中:

    D d1 = edge.factAtSource();
    N n = edge.getTarget(); // a call node; line 14...
    D d2 = edge.factAtTarget();
    std::shared_ptr<FlowFunction<D>> function =
            cachedFlowEdgeFunctions.getCallFlowFunction(n, sCalledProcN);
    std::set<D> res = computeCallFlowFunction(function, d1, d2);
    for (N sP : startPointsOf) {
          saveEdges(n, sP, d2, res, true);
          // for each result node of the call-flow function
          for (D d3 : res) {
            // create initial self-loop
            propagate(d3, sP, d3, EdgeIdentity<V>::getInstance(), n,
                      false); // line 15
            // register the fact that <sp,d3> has an incoming edge from <n,d2>
            // line 15.1 of Naeem/Lhotak/Rodriguez
            addIncoming(sP, d3, n, d2);

相关文章

网友评论

      本文标题:Phasar-IFDS框架学习笔记

      本文链接:https://www.haomeiwen.com/subject/gkmupqtx.html