mirror of
https://github.com/autc04/Retro68.git
synced 2024-06-03 00:29:47 +00:00
403 lines
11 KiB
C++
403 lines
11 KiB
C++
/* An experimental state machine, for tracking bad calls from within
|
|
signal handlers.
|
|
|
|
Copyright (C) 2019-2022 Free Software Foundation, Inc.
|
|
Contributed by David Malcolm <dmalcolm@redhat.com>.
|
|
|
|
This file is part of GCC.
|
|
|
|
GCC is free software; you can redistribute it and/or modify it
|
|
under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 3, or (at your option)
|
|
any later version.
|
|
|
|
GCC is distributed in the hope that it will be useful, but
|
|
WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with GCC; see the file COPYING3. If not see
|
|
<http://www.gnu.org/licenses/>. */
|
|
|
|
#include "config.h"
|
|
#include "system.h"
|
|
#include "coretypes.h"
|
|
#include "tree.h"
|
|
#include "function.h"
|
|
#include "basic-block.h"
|
|
#include "gimple.h"
|
|
#include "options.h"
|
|
#include "bitmap.h"
|
|
#include "diagnostic-path.h"
|
|
#include "diagnostic-metadata.h"
|
|
#include "function.h"
|
|
#include "json.h"
|
|
#include "analyzer/analyzer.h"
|
|
#include "diagnostic-event-id.h"
|
|
#include "analyzer/analyzer-logging.h"
|
|
#include "analyzer/sm.h"
|
|
#include "analyzer/pending-diagnostic.h"
|
|
#include "sbitmap.h"
|
|
#include "tristate.h"
|
|
#include "ordered-hash-map.h"
|
|
#include "selftest.h"
|
|
#include "analyzer/call-string.h"
|
|
#include "analyzer/program-point.h"
|
|
#include "analyzer/store.h"
|
|
#include "analyzer/region-model.h"
|
|
#include "analyzer/program-state.h"
|
|
#include "analyzer/checker-path.h"
|
|
#include "digraph.h"
|
|
#include "cfg.h"
|
|
#include "gimple-iterator.h"
|
|
#include "cgraph.h"
|
|
#include "analyzer/supergraph.h"
|
|
#include "alloc-pool.h"
|
|
#include "fibonacci_heap.h"
|
|
#include "analyzer/diagnostic-manager.h"
|
|
#include "shortest-paths.h"
|
|
#include "analyzer/exploded-graph.h"
|
|
#include "analyzer/function-set.h"
|
|
#include "analyzer/analyzer-selftests.h"
|
|
|
|
#if ENABLE_ANALYZER
|
|
|
|
namespace ana {
|
|
|
|
namespace {
|
|
|
|
/* An experimental state machine, for tracking calls to async-signal-unsafe
|
|
functions from within signal handlers. */
|
|
|
|
class signal_state_machine : public state_machine
|
|
{
|
|
public:
|
|
signal_state_machine (logger *logger);
|
|
|
|
bool inherited_state_p () const FINAL OVERRIDE { return false; }
|
|
|
|
bool on_stmt (sm_context *sm_ctxt,
|
|
const supernode *node,
|
|
const gimple *stmt) const FINAL OVERRIDE;
|
|
|
|
bool can_purge_p (state_t s) const FINAL OVERRIDE;
|
|
|
|
/* These states are "global", rather than per-expression. */
|
|
|
|
/* State for when we're in a signal handler. */
|
|
state_t m_in_signal_handler;
|
|
|
|
/* Stop state. */
|
|
state_t m_stop;
|
|
};
|
|
|
|
/* Concrete subclass for describing call to an async-signal-unsafe function
|
|
from a signal handler. */
|
|
|
|
class signal_unsafe_call
|
|
: public pending_diagnostic_subclass<signal_unsafe_call>
|
|
{
|
|
public:
|
|
signal_unsafe_call (const signal_state_machine &sm, const gcall *unsafe_call,
|
|
tree unsafe_fndecl)
|
|
: m_sm (sm), m_unsafe_call (unsafe_call), m_unsafe_fndecl (unsafe_fndecl)
|
|
{
|
|
gcc_assert (m_unsafe_fndecl);
|
|
}
|
|
|
|
const char *get_kind () const FINAL OVERRIDE { return "signal_unsafe_call"; }
|
|
|
|
bool operator== (const signal_unsafe_call &other) const
|
|
{
|
|
return m_unsafe_call == other.m_unsafe_call;
|
|
}
|
|
|
|
int get_controlling_option () const FINAL OVERRIDE
|
|
{
|
|
return OPT_Wanalyzer_unsafe_call_within_signal_handler;
|
|
}
|
|
|
|
bool emit (rich_location *rich_loc) FINAL OVERRIDE
|
|
{
|
|
auto_diagnostic_group d;
|
|
diagnostic_metadata m;
|
|
/* CWE-479: Signal Handler Use of a Non-reentrant Function. */
|
|
m.add_cwe (479);
|
|
if (warning_meta (rich_loc, m, get_controlling_option (),
|
|
"call to %qD from within signal handler",
|
|
m_unsafe_fndecl))
|
|
{
|
|
/* If we know a possible alternative function, add a note
|
|
suggesting the replacement. */
|
|
if (const char *replacement = get_replacement_fn ())
|
|
{
|
|
location_t note_loc = gimple_location (m_unsafe_call);
|
|
/* It would be nice to add a fixit, but the gimple call
|
|
location covers the whole call expression. It isn't
|
|
currently possible to cut this down to just the call
|
|
symbol. So the fixit would replace too much.
|
|
note_rich_loc.add_fixit_replace (replacement); */
|
|
inform (note_loc,
|
|
"%qs is a possible signal-safe alternative for %qD",
|
|
replacement, m_unsafe_fndecl);
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
label_text describe_state_change (const evdesc::state_change &change)
|
|
FINAL OVERRIDE
|
|
{
|
|
if (change.is_global_p ()
|
|
&& change.m_new_state == m_sm.m_in_signal_handler)
|
|
{
|
|
function *handler = change.m_event.get_dest_function ();
|
|
return change.formatted_print ("registering %qD as signal handler",
|
|
handler->decl);
|
|
}
|
|
return label_text ();
|
|
}
|
|
|
|
label_text describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE
|
|
{
|
|
return ev.formatted_print ("call to %qD from within signal handler",
|
|
m_unsafe_fndecl);
|
|
}
|
|
|
|
private:
|
|
const signal_state_machine &m_sm;
|
|
const gcall *m_unsafe_call;
|
|
tree m_unsafe_fndecl;
|
|
|
|
/* Returns a replacement function as text if it exists. Currently
|
|
only "exit" has a signal-safe replacement "_exit", which does
|
|
slightly less, but can be used in a signal handler. */
|
|
const char *
|
|
get_replacement_fn ()
|
|
{
|
|
gcc_assert (m_unsafe_fndecl && DECL_P (m_unsafe_fndecl));
|
|
|
|
if (id_equal ("exit", DECL_NAME (m_unsafe_fndecl)))
|
|
return "_exit";
|
|
|
|
return NULL;
|
|
}
|
|
};
|
|
|
|
/* signal_state_machine's ctor. */
|
|
|
|
signal_state_machine::signal_state_machine (logger *logger)
|
|
: state_machine ("signal", logger)
|
|
{
|
|
m_in_signal_handler = add_state ("in_signal_handler");
|
|
m_stop = add_state ("stop");
|
|
}
|
|
|
|
/* Update MODEL for edges that simulate HANDLER_FUN being called as
|
|
an signal-handler in response to a signal. */
|
|
|
|
static void
|
|
update_model_for_signal_handler (region_model *model,
|
|
function *handler_fun)
|
|
{
|
|
gcc_assert (model);
|
|
/* Purge all state within MODEL. */
|
|
*model = region_model (model->get_manager ());
|
|
model->push_frame (handler_fun, NULL, NULL);
|
|
}
|
|
|
|
/* Custom exploded_edge info: entry into a signal-handler. */
|
|
|
|
class signal_delivery_edge_info_t : public custom_edge_info
|
|
{
|
|
public:
|
|
void print (pretty_printer *pp) const FINAL OVERRIDE
|
|
{
|
|
pp_string (pp, "signal delivered");
|
|
}
|
|
|
|
json::object *to_json () const
|
|
{
|
|
json::object *custom_obj = new json::object ();
|
|
return custom_obj;
|
|
}
|
|
|
|
bool update_model (region_model *model,
|
|
const exploded_edge *eedge,
|
|
region_model_context *) const FINAL OVERRIDE
|
|
{
|
|
gcc_assert (eedge);
|
|
update_model_for_signal_handler (model, eedge->m_dest->get_function ());
|
|
return true;
|
|
}
|
|
|
|
void add_events_to_path (checker_path *emission_path,
|
|
const exploded_edge &eedge ATTRIBUTE_UNUSED)
|
|
const FINAL OVERRIDE
|
|
{
|
|
emission_path->add_event
|
|
(new precanned_custom_event
|
|
(UNKNOWN_LOCATION, NULL_TREE, 0,
|
|
"later on,"
|
|
" when the signal is delivered to the process"));
|
|
}
|
|
};
|
|
|
|
/* Concrete subclass of custom_transition for modeling registration of a
|
|
signal handler and the signal handler later being called. */
|
|
|
|
class register_signal_handler : public custom_transition
|
|
{
|
|
public:
|
|
register_signal_handler (const signal_state_machine &sm,
|
|
tree fndecl)
|
|
: m_sm (sm), m_fndecl (fndecl) {}
|
|
|
|
/* Model a signal-handler FNDECL being called at some later point
|
|
by injecting an edge to a new function-entry node with an empty
|
|
callstring, setting the 'in-signal-handler' global state
|
|
on the node. */
|
|
void impl_transition (exploded_graph *eg,
|
|
exploded_node *src_enode,
|
|
int sm_idx) FINAL OVERRIDE
|
|
{
|
|
function *handler_fun = DECL_STRUCT_FUNCTION (m_fndecl);
|
|
if (!handler_fun)
|
|
return;
|
|
program_point entering_handler
|
|
= program_point::from_function_entry (eg->get_supergraph (),
|
|
handler_fun);
|
|
|
|
program_state state_entering_handler (eg->get_ext_state ());
|
|
update_model_for_signal_handler (state_entering_handler.m_region_model,
|
|
handler_fun);
|
|
state_entering_handler.m_checker_states[sm_idx]->set_global_state
|
|
(m_sm.m_in_signal_handler);
|
|
|
|
exploded_node *dst_enode = eg->get_or_create_node (entering_handler,
|
|
state_entering_handler,
|
|
src_enode);
|
|
if (dst_enode)
|
|
eg->add_edge (src_enode, dst_enode, NULL, /*state_change (),*/
|
|
new signal_delivery_edge_info_t ());
|
|
}
|
|
|
|
const signal_state_machine &m_sm;
|
|
tree m_fndecl;
|
|
};
|
|
|
|
/* Get a set of functions that are known to be unsafe to call from an
|
|
async signal handler. */
|
|
|
|
static function_set
|
|
get_async_signal_unsafe_fns ()
|
|
{
|
|
// TODO: populate this list more fully
|
|
static const char * const async_signal_unsafe_fns[] = {
|
|
/* This array must be kept sorted. */
|
|
"exit",
|
|
"fprintf",
|
|
"free",
|
|
"malloc",
|
|
"printf",
|
|
"snprintf",
|
|
"sprintf",
|
|
"vfprintf",
|
|
"vprintf",
|
|
"vsnprintf",
|
|
"vsprintf"
|
|
};
|
|
const size_t count
|
|
= sizeof(async_signal_unsafe_fns) / sizeof (async_signal_unsafe_fns[0]);
|
|
function_set fs (async_signal_unsafe_fns, count);
|
|
return fs;
|
|
}
|
|
|
|
/* Return true if FNDECL is known to be unsafe to call from a signal
|
|
handler. */
|
|
|
|
static bool
|
|
signal_unsafe_p (tree fndecl)
|
|
{
|
|
function_set fs = get_async_signal_unsafe_fns ();
|
|
return fs.contains_decl_p (fndecl);
|
|
}
|
|
|
|
/* Implementation of state_machine::on_stmt vfunc for signal_state_machine. */
|
|
|
|
bool
|
|
signal_state_machine::on_stmt (sm_context *sm_ctxt,
|
|
const supernode *node,
|
|
const gimple *stmt) const
|
|
{
|
|
const state_t global_state = sm_ctxt->get_global_state ();
|
|
if (global_state == m_start)
|
|
{
|
|
if (const gcall *call = dyn_cast <const gcall *> (stmt))
|
|
if (tree callee_fndecl = sm_ctxt->get_fndecl_for_call (call))
|
|
if (is_named_call_p (callee_fndecl, "signal", call, 2))
|
|
{
|
|
tree handler = gimple_call_arg (call, 1);
|
|
if (TREE_CODE (handler) == ADDR_EXPR
|
|
&& TREE_CODE (TREE_OPERAND (handler, 0)) == FUNCTION_DECL)
|
|
{
|
|
tree fndecl = TREE_OPERAND (handler, 0);
|
|
register_signal_handler rsh (*this, fndecl);
|
|
sm_ctxt->on_custom_transition (&rsh);
|
|
}
|
|
}
|
|
}
|
|
else if (global_state == m_in_signal_handler)
|
|
{
|
|
if (const gcall *call = dyn_cast <const gcall *> (stmt))
|
|
if (tree callee_fndecl = sm_ctxt->get_fndecl_for_call (call))
|
|
if (signal_unsafe_p (callee_fndecl))
|
|
if (sm_ctxt->get_global_state () == m_in_signal_handler)
|
|
sm_ctxt->warn (node, stmt, NULL_TREE,
|
|
new signal_unsafe_call (*this, call,
|
|
callee_fndecl));
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
signal_state_machine::can_purge_p (state_t s ATTRIBUTE_UNUSED) const
|
|
{
|
|
return true;
|
|
}
|
|
|
|
} // anonymous namespace
|
|
|
|
/* Internal interface to this file. */
|
|
|
|
state_machine *
|
|
make_signal_state_machine (logger *logger)
|
|
{
|
|
return new signal_state_machine (logger);
|
|
}
|
|
|
|
#if CHECKING_P
|
|
|
|
namespace selftest {
|
|
|
|
/* Run all of the selftests within this file. */
|
|
|
|
void
|
|
analyzer_sm_signal_cc_tests ()
|
|
{
|
|
function_set fs = get_async_signal_unsafe_fns ();
|
|
fs.assert_sorted ();
|
|
fs.assert_sane ();
|
|
}
|
|
|
|
} // namespace selftest
|
|
|
|
#endif /* CHECKING_P */
|
|
|
|
} // namespace ana
|
|
|
|
#endif /* #if ENABLE_ANALYZER */
|