
GCC 12 static analyzer
靜態分析器檢測流程,分析一下gcc12 目前檢測流程
Analyzer Internals
https://gcc.gnu.org/onlinedocs/gccint/Analyzer-Internals.html
debug gcc compiler

sudo apt install gcc-multilib
sudo make -j 16 STAGE1_CXXFLAGS="-g3"
需要編譯 cc1 記得-g
gdb command
gdb --args cc1 test.c -fanalyzer -Wanalyzer-double-free
gcc/cgraphunit.c
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
/* 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
b /gcc/main.c:36
do_compile (no_backend)
到這邊屬於前置處理器前端檢測規則部分,這行程式碼後 開始進行gimple pass流程檢查
/gcc/toplev.c:2308

ipa_passes ()
開始處理ipa_passes
/gcc/cgraphunit.c:2529
/gcc/cgraphunit.c:2267

execute_ipa_pass_list (opt_pass *pass)
走 Lto流程,開始走訪所有Pass,發現 pass_analyzer 分析器

(anonymous namespace)::pass_analyzer::gate
b /gcc/analyzer/analyzer-pass.cc:78

(anonymous namespace)::pass_analyzer::execute
/gcc/analyzer/analyzer-pass.cc:87

ana::run_checkers ();
可以看到這邊在建立supergraph
We can identify problems directly when processing a
/gcc/analyzer/engine.cc:5436

ana::make_malloc_state_machine
狀態機, state: {"ptr": freed}> ptr可能是 malloc 假設更新兩次就是double-free
/gcc/analyzer/sm-malloc.cc:2067
make_checkers
/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
/gcc/analyzer/engine.cc:2825

malloc_state_machine::on_stmt
從process_worklist 觀察callstack 可以發現這邊是每個stmt都會觸發這個on_stmt function
(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
p debug(stmt)
c
可以看到on_stmt這個fucntion 有看到已經開始整合c++的部分 根據 function name
c++
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
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
b malloc_state_machine::on_deallocator_call
可以看到每次on_stmt 後就會更新一次節點,假設是free function 就on_deallocator_call 檢查一次看
state: {"ptr": freed}> 是否已經 m_freed 有則觸發 double-free檢測規則
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
b double_free
到這邊已經可以看到假設在on_deallocator_call 檢查發生有double-free問題則新增cwe漏洞 415 double free
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);


Comments