1 /* An experimental state machine, for tracking bad calls from within
4 Copyright (C) 2019-2024 Free Software Foundation, Inc.
5 Contributed by David Malcolm <dmalcolm@redhat.com>.
7 This file is part of GCC.
9 GCC is free software; you can redistribute it and/or modify it
10 under the terms of the GNU General Public License as published by
11 the Free Software Foundation; either version 3, or (at your option)
14 GCC is distributed in the hope that it will be useful, but
15 WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 General Public License for more details.
19 You should have received a copy of the GNU General Public License
20 along with GCC; see the file COPYING3. If not see
21 <http://www.gnu.org/licenses/>. */
24 #define INCLUDE_MEMORY
26 #include "coretypes.h"
27 #include "make-unique.h"
30 #include "basic-block.h"
34 #include "diagnostic-path.h"
35 #include "analyzer/analyzer.h"
36 #include "diagnostic-event-id.h"
37 #include "analyzer/analyzer-logging.h"
38 #include "analyzer/sm.h"
39 #include "analyzer/pending-diagnostic.h"
41 #include "ordered-hash-map.h"
43 #include "analyzer/call-string.h"
44 #include "analyzer/program-point.h"
45 #include "analyzer/store.h"
46 #include "analyzer/region-model.h"
47 #include "analyzer/program-state.h"
48 #include "analyzer/checker-path.h"
50 #include "gimple-iterator.h"
52 #include "analyzer/supergraph.h"
53 #include "analyzer/diagnostic-manager.h"
54 #include "shortest-paths.h"
55 #include "analyzer/exploded-graph.h"
56 #include "analyzer/function-set.h"
57 #include "analyzer/analyzer-selftests.h"
65 /* An experimental state machine, for tracking calls to async-signal-unsafe
66 functions from within signal handlers. */
68 class signal_state_machine
: public state_machine
71 signal_state_machine (logger
*logger
);
73 bool inherited_state_p () const final override
{ return false; }
75 bool on_stmt (sm_context
*sm_ctxt
,
76 const supernode
*node
,
77 const gimple
*stmt
) const final override
;
79 bool can_purge_p (state_t s
) const final override
;
81 /* These states are "global", rather than per-expression. */
83 /* State for when we're in a signal handler. */
84 state_t m_in_signal_handler
;
90 /* Concrete subclass for describing call to an async-signal-unsafe function
91 from a signal handler. */
93 class signal_unsafe_call
94 : public pending_diagnostic_subclass
<signal_unsafe_call
>
97 signal_unsafe_call (const signal_state_machine
&sm
, const gcall
*unsafe_call
,
99 : m_sm (sm
), m_unsafe_call (unsafe_call
), m_unsafe_fndecl (unsafe_fndecl
)
101 gcc_assert (m_unsafe_fndecl
);
104 const char *get_kind () const final override
{ return "signal_unsafe_call"; }
106 bool operator== (const signal_unsafe_call
&other
) const
108 return m_unsafe_call
== other
.m_unsafe_call
;
111 int get_controlling_option () const final override
113 return OPT_Wanalyzer_unsafe_call_within_signal_handler
;
116 bool emit (diagnostic_emission_context
&ctxt
) final override
118 auto_diagnostic_group d
;
119 /* CWE-479: Signal Handler Use of a Non-reentrant Function. */
121 if (ctxt
.warn ("call to %qD from within signal handler",
124 /* If we know a possible alternative function, add a note
125 suggesting the replacement. */
126 if (const char *replacement
= get_replacement_fn ())
128 location_t note_loc
= gimple_location (m_unsafe_call
);
129 /* It would be nice to add a fixit, but the gimple call
130 location covers the whole call expression. It isn't
131 currently possible to cut this down to just the call
132 symbol. So the fixit would replace too much.
133 note_rich_loc.add_fixit_replace (replacement); */
135 "%qs is a possible signal-safe alternative for %qD",
136 replacement
, m_unsafe_fndecl
);
143 label_text
describe_state_change (const evdesc::state_change
&change
)
146 if (change
.is_global_p ()
147 && change
.m_new_state
== m_sm
.m_in_signal_handler
)
149 function
*handler
= change
.m_event
.get_dest_function ();
150 return change
.formatted_print ("registering %qD as signal handler",
153 return label_text ();
156 label_text
describe_final_event (const evdesc::final_event
&ev
) final override
158 return ev
.formatted_print ("call to %qD from within signal handler",
163 const signal_state_machine
&m_sm
;
164 const gcall
*m_unsafe_call
;
165 tree m_unsafe_fndecl
;
167 /* Returns a replacement function as text if it exists. Currently
168 only "exit" has a signal-safe replacement "_exit", which does
169 slightly less, but can be used in a signal handler. */
171 get_replacement_fn ()
173 gcc_assert (m_unsafe_fndecl
&& DECL_P (m_unsafe_fndecl
));
175 if (id_equal ("exit", DECL_NAME (m_unsafe_fndecl
)))
182 /* signal_state_machine's ctor. */
184 signal_state_machine::signal_state_machine (logger
*logger
)
185 : state_machine ("signal", logger
),
186 m_in_signal_handler (add_state ("in_signal_handler")),
187 m_stop (add_state ("stop"))
191 /* Update MODEL for edges that simulate HANDLER_FUN being called as
192 an signal-handler in response to a signal. */
195 update_model_for_signal_handler (region_model
*model
,
196 function
*handler_fun
)
199 /* Purge all state within MODEL. */
200 *model
= region_model (model
->get_manager ());
201 model
->push_frame (handler_fun
, NULL
, NULL
);
204 /* Custom exploded_edge info: entry into a signal-handler. */
206 class signal_delivery_edge_info_t
: public custom_edge_info
209 void print (pretty_printer
*pp
) const final override
211 pp_string (pp
, "signal delivered");
214 json::object
*to_json () const
216 json::object
*custom_obj
= new json::object ();
220 bool update_model (region_model
*model
,
221 const exploded_edge
*eedge
,
222 region_model_context
*) const final override
225 update_model_for_signal_handler (model
, eedge
->m_dest
->get_function ());
229 void add_events_to_path (checker_path
*emission_path
,
230 const exploded_edge
&eedge ATTRIBUTE_UNUSED
)
233 emission_path
->add_event
234 (make_unique
<precanned_custom_event
>
235 (event_loc_info (UNKNOWN_LOCATION
, NULL_TREE
, 0),
237 " when the signal is delivered to the process"));
241 /* Concrete subclass of custom_transition for modeling registration of a
242 signal handler and the signal handler later being called. */
244 class register_signal_handler
: public custom_transition
247 register_signal_handler (const signal_state_machine
&sm
,
249 : m_sm (sm
), m_fndecl (fndecl
) {}
251 /* Model a signal-handler FNDECL being called at some later point
252 by injecting an edge to a new function-entry node with an empty
253 callstring, setting the 'in-signal-handler' global state
255 void impl_transition (exploded_graph
*eg
,
256 exploded_node
*src_enode
,
257 int sm_idx
) final override
259 function
*handler_fun
= DECL_STRUCT_FUNCTION (m_fndecl
);
262 const extrinsic_state
&ext_state
= eg
->get_ext_state ();
263 program_point entering_handler
264 = program_point::from_function_entry (*ext_state
.get_model_manager (),
265 eg
->get_supergraph (),
268 program_state
state_entering_handler (ext_state
);
269 update_model_for_signal_handler (state_entering_handler
.m_region_model
,
271 state_entering_handler
.m_checker_states
[sm_idx
]->set_global_state
272 (m_sm
.m_in_signal_handler
);
274 exploded_node
*dst_enode
= eg
->get_or_create_node (entering_handler
,
275 state_entering_handler
,
278 eg
->add_edge (src_enode
, dst_enode
, NULL
, /*state_change (),*/
279 true, /* assume does work */
280 make_unique
<signal_delivery_edge_info_t
> ());
283 const signal_state_machine
&m_sm
;
287 /* Get a set of functions that are known to be unsafe to call from an
288 async signal handler. */
291 get_async_signal_unsafe_fns ()
293 // TODO: populate this list more fully
294 static const char * const async_signal_unsafe_fns
[] = {
295 /* This array must be kept sorted. */
308 const size_t count
= ARRAY_SIZE (async_signal_unsafe_fns
);
309 function_set
fs (async_signal_unsafe_fns
, count
);
313 /* Return true if FNDECL is known to be unsafe to call from a signal
317 signal_unsafe_p (tree fndecl
)
319 function_set fs
= get_async_signal_unsafe_fns ();
320 return fs
.contains_decl_p (fndecl
);
323 /* Implementation of state_machine::on_stmt vfunc for signal_state_machine. */
326 signal_state_machine::on_stmt (sm_context
*sm_ctxt
,
327 const supernode
*node
,
328 const gimple
*stmt
) const
330 const state_t global_state
= sm_ctxt
->get_global_state ();
331 if (global_state
== m_start
)
333 if (const gcall
*call
= dyn_cast
<const gcall
*> (stmt
))
334 if (tree callee_fndecl
= sm_ctxt
->get_fndecl_for_call (call
))
335 if (is_named_call_p (callee_fndecl
, "signal", call
, 2))
337 tree handler
= gimple_call_arg (call
, 1);
338 if (TREE_CODE (handler
) == ADDR_EXPR
339 && TREE_CODE (TREE_OPERAND (handler
, 0)) == FUNCTION_DECL
)
341 tree fndecl
= TREE_OPERAND (handler
, 0);
342 register_signal_handler
rsh (*this, fndecl
);
343 sm_ctxt
->on_custom_transition (&rsh
);
347 else if (global_state
== m_in_signal_handler
)
349 if (const gcall
*call
= dyn_cast
<const gcall
*> (stmt
))
350 if (tree callee_fndecl
= sm_ctxt
->get_fndecl_for_call (call
))
351 if (signal_unsafe_p (callee_fndecl
))
352 if (sm_ctxt
->get_global_state () == m_in_signal_handler
)
353 sm_ctxt
->warn (node
, stmt
, NULL_TREE
,
354 make_unique
<signal_unsafe_call
>
355 (*this, call
, callee_fndecl
));
362 signal_state_machine::can_purge_p (state_t s ATTRIBUTE_UNUSED
) const
367 } // anonymous namespace
369 /* Internal interface to this file. */
372 make_signal_state_machine (logger
*logger
)
374 return new signal_state_machine (logger
);
381 /* Run all of the selftests within this file. */
384 analyzer_sm_signal_cc_tests ()
386 function_set fs
= get_async_signal_unsafe_fns ();
391 } // namespace selftest
393 #endif /* CHECKING_P */
397 #endif /* #if ENABLE_ANALYZER */