GCC 12 static analyzer

靜態分析器檢測流程,分析一下gcc12 目前檢測流程

Analyzer Internals

https://gcc.gnu.org/onlinedocs/gccint/Analyzer-Internals.html

debug gcc compiler

code
sudo apt install gcc-multilib
sudo make -j 16 STAGE1_CXXFLAGS="-g3"

需要編譯 cc1 記得-g

gdb command

code
 gdb --args cc1 test.c   -fanalyzer -Wanalyzer-double-free

gcc/cgraphunit.c

code
void
symbol_table::finalize_compilation_unit (void)
{
  timevar_push (TV_CGRAPH);

  /* If we're here there's no current function anymore.  Some frontends
     are lazy in clearing these.  */
  current_function_decl = NULL;
  set_cfun (NULL);

  /* Do not skip analyzing the functions if there were errors, we
     miss diagnostics for following functions otherwise.  */

  /* Emit size functions we didn't inline.  */
  finalize_size_functions ();

  /* Mark alias targets necessary and emit diagnostics.  */
  handle_alias_pairs ();

  if (!quiet_flag)
    {
      fprintf (stderr, "\nAnalyzing compilation unit\n");
      fflush (stderr);
    }

  if (flag_dump_passes)
    dump_passes ();

  /* Gimplify and lower all functions, compute reachability and
     remove unreachable nodes.  */
  analyze_functions (/*first_time=*/true);

  /* Mark alias targets necessary and emit diagnostics.  */
  handle_alias_pairs ();

  /* Gimplify and lower thunks.  */
  analyze_functions (/*first_time=*/false);

  /* All nested functions should be lowered now.  */
  nested_function_info::release ();

  /* Offloading requires LTO infrastructure.  */
  if (!in_lto_p && g->have_offload)
    flag_generate_offload = 1;

  if (!seen_error ())
    {
      /* Give the frontends the chance to emit early debug based on
     what is still reachable in the TU.  */
      (*lang_hooks.finalize_early_debug) ();

      /* Clean up anything that needs cleaning up after initial debug
     generation.  */
      debuginfo_early_start ();
      (*debug_hooks->early_finish) (main_input_filename);
      debuginfo_early_stop ();
    }

  /* Finally drive the pass manager.  */
  compile ();

  timevar_pop (TV_CGRAPH);
}

/mnt/c/Users/hpclab/gcc/gcc/cgraphunit.c

code
  /* Don't run the IPA passes if there was any error or sorry messages.  */
  if (!seen_error ())
  {
    timevar_start (TV_CGRAPH_IPA_PASSES);
    ipa_passes ();
    timevar_stop (TV_CGRAPH_IPA_PASSES);
  }

定位至double free

main

code
b /gcc/main.c:36

do_compile (no_backend)

到這邊屬於前置處理器前端檢測規則部分,這行程式碼後 開始進行gimple pass流程檢查

code
/gcc/toplev.c:2308

ipa_passes ()

開始處理ipa_passes

code
/gcc/cgraphunit.c:2529
/gcc/cgraphunit.c:2267

execute_ipa_pass_list (opt_pass *pass)

走 Lto流程,開始走訪所有Pass,發現 pass_analyzer 分析器

(anonymous namespace)::pass_analyzer::gate

code
b /gcc/analyzer/analyzer-pass.cc:78

(anonymous namespace)::pass_analyzer::execute

code
/gcc/analyzer/analyzer-pass.cc:87

ana::run_checkers ();

可以看到這邊在建立supergraph We can identify problems directly when processing a instance. For example, if we’re finding the successors of

code
/gcc/analyzer/engine.cc:5436

ana::make_malloc_state_machine

狀態機, state: {"ptr": freed}> ptr可能是 malloc 假設更新兩次就是double-free

code
/gcc/analyzer/sm-malloc.cc:2067

make_checkers

code
/gcc/analyzer/sm.cc:169

process_worklist

/ The main loop of the analysis. Take freshly-created exploded_nodes from the worklist, calling process_node on them to explore the graph. Add edges to their successors, potentially creating new successors (which are also added to the worklist). /

code
/gcc/analyzer/engine.cc:2825

malloc_state_machine::on_stmt

從process_worklist 觀察callstack 可以發現這邊是每個stmt都會觸發這個on_stmt function

code
(gdb) bt
#0  ana::(anonymous namespace)::malloc_state_machine::on_stmt (this=0x2589950, sm_ctxt=0x7ffffffeceb0, node=0x2567d30,
    stmt=0x7ffffed51090) at ../../gcc/analyzer/sm-malloc.cc:1574
#1  0x0000000000ef26b0 in ana::exploded_node::on_stmt (this=<optimized out>, eg=..., snode=0x2567d30,
    stmt=0x7ffffed51090, state=0x7ffffffecfb0, uncertainty=<optimized out>, path_ctxt=0x7ffffffed030)
    at ../../gcc/analyzer/engine.cc:1310
#2  0x0000000000ef4e2c in ana::exploded_graph::process_node (this=0x7ffffffed370, node=0x25d6530)
    at ../../gcc/analyzer/engine.cc:3366
#3  0x0000000000ef5be3 in ana::exploded_graph::process_worklist (this=0x7ffffffed370)

dump gimple stmt

code
 p debug(stmt)

c

可以看到on_stmt這個fucntion 有看到已經開始整合c++的部分 根據 function name

c++

code
    if (is_named_call_p (callee_fndecl, "operator new", call, 1))
      on_allocator_call (sm_ctxt, call, &m_scalar_delete);
    else if (is_named_call_p (callee_fndecl, "operator new []", call, 1))
      on_allocator_call (sm_ctxt, call, &m_vector_delete);
    else if (is_named_call_p (callee_fndecl, "operator delete", call, 1)
         || is_named_call_p (callee_fndecl, "operator delete", call, 2))

c

code
    if (is_named_call_p (callee_fndecl, "free", call, 1)
        || is_std_named_call_p (callee_fndecl, "free", call, 1)
        || is_named_call_p (callee_fndecl, "__builtin_free", call, 1))
      {
        on_deallocator_call (sm_ctxt, node, call,
                 &m_free.m_deallocator, 0);
        return true;
      }

    if (is_named_call_p (callee_fndecl, "realloc", call, 2)
        || is_named_call_p (callee_fndecl, "__builtin_realloc", call, 2))
      {
        on_realloc_call (sm_ctxt, node, call);
        return true;
      }

malloc_state_machine::on_deallocator_call

code
b malloc_state_machine::on_deallocator_call

可以看到每次on_stmt 後就會更新一次節點,假設是free function 就on_deallocator_call 檢查一次看

state: {"ptr": freed}> 是否已經 m_freed 有則觸發 double-free檢測規則

code
  else if (state == d->m_freed)
    {
      /* freed -> stop, with warning.  */
      tree diag_arg = sm_ctxt->get_diagnostic_tree (arg);
      sm_ctxt->warn (node, call, arg,
             new double_free (*this, diag_arg, d->m_name));
      sm_ctxt->set_next_state (call, arg, m_stop);
    }

double_free

code
b double_free

到這邊已經可以看到假設在on_deallocator_call 檢查發生有double-free問題則新增cwe漏洞 415 double free

code
  bool emit (rich_location *rich_loc) FINAL OVERRIDE
  {
    auto_diagnostic_group d;
    diagnostic_metadata m;
    m.add_cwe (415); /* CWE-415: Double Free.  */
    return warning_meta (rich_loc, m, OPT_Wanalyzer_double_free,
             "double-%qs of %qE", m_funcname, m_arg);
  }

顯示報錯

warning_meta (rich_loc, m, OPT_Wanalyzer_double_free, "double-%qs of %qE", m_funcname, m_arg);