超越语法:利用ClangChecker拓展进行高级代码分析,提升漏洞检测和预防能力


序幕

ClangChecker是Clang的一个静态分析框架,可以用于检测C/C++代码中的错误。本文将介绍如何通过对ClangChecker拓展进行高级代码分析,提升漏洞检测和预防能力。

ClangChecker

  1. 简介

    ClangChecker是Clang的一个静态分析框架,可以用于检测C/C++代码中的错误。ClangChecker的工作原理是:通过对源码进行语法分析,生成抽象语法树(AST),然后对AST进行遍历,检测错误。ClangChecker的检测能力可以通过拓展进行扩展,本文将介绍如何通过对ClangChecker拓展进行高级代码分析,提升漏洞检测和预防能力。

  2. 获取

    ClangChecker是LLVM的一部分,可以参考上一篇文章:定制你的编译过程:像专业人士一样从源码构建和安装LLVM&Clang

  3. 拓展

    ClangChecker的检测能力可以通过拓展进行扩展,本文将介绍如何通过对ClangChecker拓展进行高级代码分析,提升漏洞检测和预防能力。

拓展ClangChecker

  1. 在已有的检查器包——alpha.core中添加自定义的检查器,那么在Checkers.td文件中添加如下内容:

    def MainCallChecker : Checker<"MainCall">,
      HelpText<"Check for calls to main">,
      Documentation;
    

    修改后的内容如下:

    let ParentPackage = CoreAlpha in {
    
    // 省略 ...
    
    def MainCallChecker : Checker<"MainCall">,
      HelpText<"Check for calls to main">,
      Documentation;
    
    } // end "alpha.core"
    

    注:

    • Checkers.td文件位于clang/include/clang/StaticAnalyzer/Checkers/目录中。
    • clang -cc1 -analyzer-checker-helpclang -cc1 -analyzer-checker-help-alpha
      等命令所显示的检查器列表来源于Checkers.td文件。
    • def MainCallChecker,表示检查器的注册名称为MainCallChecker
    • "MainCall",表示检查器的名称为MainCall。也就是说,可以通过-analyzer-checker=alpha.core.MainCall标志来启用该检查器。
    • HelpText选项,用于指定该检查器对应的描述。从而,在执行类似于-help命令时显示。
    • Documentation选项,用于指定检查器文档的URI地址。

    需要注意的是, 修改的Checkers.td文件在重新编译安装 Clang 后才生效。

  2. 添加检查器的实现代码

    clang/lib/StaticAnalyzer/Checkers/目录中新建MainCallChecker.cpp文件,内容如下:

    #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/CallEvent.h"
    #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
    
    using namespace clang;
    using namespace ento;
    
    namespace {
    
    class MainCallChecker : public Checker {
    public:
      void checkPreCall(const CallEvent &Call, CheckerContext &Ctx) const;
    
    private:
      mutable std::unique_ptr BT;
    };
    
    } // anonymous namespace
    
    void MainCallChecker::checkPreCall(const CallEvent &Call,
                                       CheckerContext &Ctx) const {
      if (const IdentifierInfo *II = Call.getCalleeIdentifier()) {
        if (II->isStr("main")) {
          if (!BT) {
            BT.reset(new BugType(this, "Call to main", "Example checker"));
          }
          ExplodedNode *Node = Ctx.generateErrorNode();
          auto Report
            = std::make_unique(*BT, BT->getDescription(), Node);
          Ctx.emitReport(std::move(Report));
        }
      }
    }
    
    void ento::registerMainCallChecker(CheckerManager &Mgr) {
      Mgr.registerChecker();
    }
    
    bool ento::shouldRegisterMainCallChecker(const CheckerManager &mgr) {
      return true;
    }
    

    注:

    • 新建的源文件名称不必是MainCallChecker.cpp,也可以是其他名称。
    • 检查器的类名不必与在Checkers.td文件中定义的检查器的注册名称保持一致。
    • void ento::registerXXX(CheckerManager &Mgr)中的XXX必须与在Checkers.td文件中定义的检查器的注册名称保持一致。
    • Mgr.registerChecker();中的XXX必须与检查器的类名保持一致。
    • ento::shouldRegisterXXX(const CheckerManager &mgr)中的XXX必须与在Checkers.td文件中定义的检查器的注册名称保持一致。
  3. 修改 CMakeLists.txt

    clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt文件中添加如下内容:

    MainCallChecker.cpp
    

    注:MainCallChecker.cpp即上文中新建的用于存放检查器实现代码的源文件。

    修改后的内容如下:

    add_clang_library( # 省略 … MainCallChecker.cpp # 省略 …)

  4. 编译安装 Clang

    可以参考上一篇文章:定制你的编译过程:像专业人士一样从源码构建和安装LLVM&Clang

  5. 查看自定义的检查器

    clang -cc1 -analyzer-checker-help-alpha | grep MainCall  alpha.core.MainCall      (Enable only for development!) Check for calls to main
    

    从上面的输出可以看出:

    • alpha.core.MainCall,即我们自定义的检查器的完整名称。
    • Check for calls to main,即我们在Checkers.td文件中为自定义的检查器所添加的描述。
  6. 编写测试用例 Example_Test.c

    typedef int (*main_t)(int, char **);
    int main(int argc, char **argv) &#123;
      main_t foo = main;
      int exit_code = foo(argc, argv);   // actually calls main()!
      return exit_code;
    &#125;
    

    上述测试程序存在这样的错误:通过函数指针变量foo调用main函数。

  7. 运行自定义的检查器

    clang --analyze -Xanalyzer -analyzer-checker=alpha.core.MainCall Example_Test.c
    

    output

    Example_Test.c:4:19: warning: Call to main [alpha.core.MainCall]
      int exit_code = foo(argc, argv);   // actually calls main()!
                      ^~~~~~~~~~~~~~~
    1 warning generated.
    

文章作者: 椰子
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 椰子 !
  目录