1 /* An experimental state machine, for tracking bad calls from within
4 Copyright (C) 2019-2023 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 "diagnostic-metadata.h"
36 #include "analyzer/analyzer.h"
37 #include "diagnostic-event-id.h"
38 #include "analyzer/analyzer-logging.h"
39 #include "analyzer/sm.h"
40 #include "analyzer/pending-diagnostic.h"
42 #include "ordered-hash-map.h"
44 #include "analyzer/call-string.h"
45 #include "analyzer/program-point.h"
46 #include "analyzer/store.h"
47 #include "analyzer/region-model.h"
48 #include "analyzer/program-state.h"
49 #include "analyzer/checker-path.h"
51 #include "gimple-iterator.h"
53 #include "analyzer/supergraph.h"
54 #include "analyzer/diagnostic-manager.h"
55 #include "shortest-paths.h"
56 #include "analyzer/exploded-graph.h"
57 #include "analyzer/function-set.h"
58 #include "analyzer/analyzer-selftests.h"
66 /* An experimental state machine, for tracking calls to async-signal-unsafe
67 functions from within signal handlers. */
69 class signal_state_machine
: public state_machine
72 signal_state_machine (logger
*logger
);
74 bool inherited_state_p () const final override
{ return false; }
76 bool on_stmt (sm_context
*sm_ctxt
,
77 const supernode
*node
,
78 const gimple
*stmt
) const final override
;
80 bool can_purge_p (state_t s
) const final override
;
82 /* These states are "global", rather than per-expression. */
84 /* State for when we're in a signal handler. */
85 state_t m_in_signal_handler
;
91 /* Concrete subclass for describing call to an async-signal-unsafe function
92 from a signal handler. */
94 class signal_unsafe_call
95 : public pending_diagnostic_subclass
<signal_unsafe_call
>
98 signal_unsafe_call (const signal_state_machine
&sm
, const gcall
*unsafe_call
,
100 : m_sm (sm
), m_unsafe_call (unsafe_call
), m_unsafe_fndecl (unsafe_fndecl
)
102 gcc_assert (m_unsafe_fndecl
);
105 const char *get_kind () const final override
{ return "signal_unsafe_call"; }
107 bool operator== (const signal_unsafe_call
&other
) const
109 return m_unsafe_call
== other
.m_unsafe_call
;
112 int get_controlling_option () const final override
114 return OPT_Wanalyzer_unsafe_call_within_signal_handler
;
117 bool emit (rich_location
*rich_loc
, logger
*) final override
119 auto_diagnostic_group d
;
120 diagnostic_metadata m
;
121 /* CWE-479: Signal Handler Use of a Non-reentrant Function. */
123 if (warning_meta (rich_loc
, m
, get_controlling_option (),
124 "call to %qD from within signal handler",
127 /* If we know a possible alternative function, add a note
128 suggesting the replacement. */
129 if (const char *replacement
= get_replacement_fn ())
131 location_t note_loc
= gimple_location (m_unsafe_call
);
132 /* It would be nice to add a fixit, but the gimple call
133 location covers the whole call expression. It isn't
134 currently possible to cut this down to just the call
135 symbol. So the fixit would replace too much.
136 note_rich_loc.add_fixit_replace (replacement); */
138 "%qs is a possible signal-safe alternative for %qD",
139 replacement
, m_unsafe_fndecl
);
146 label_text
describe_state_change (const evdesc::state_change
&change
)
149 if (change
.is_global_p ()
150 && change
.m_new_state
== m_sm
.m_in_signal_handler
)
152 function
*handler
= change
.m_event
.get_dest_function ();
153 return change
.formatted_print ("registering %qD as signal handler",
156 return label_text ();
159 label_text
describe_final_event (const evdesc::final_event
&ev
) final override
161 return ev
.formatted_print ("call to %qD from within signal handler",
166 const signal_state_machine
&m_sm
;
167 const gcall
*m_unsafe_call
;
168 tree m_unsafe_fndecl
;
170 /* Returns a replacement function as text if it exists. Currently
171 only "exit" has a signal-safe replacement "_exit", which does
172 slightly less, but can be used in a signal handler. */
174 get_replacement_fn ()
176 gcc_assert (m_unsafe_fndecl
&& DECL_P (m_unsafe_fndecl
));
178 if (id_equal ("exit", DECL_NAME (m_unsafe_fndecl
)))
185 /* signal_state_machine's ctor. */
187 signal_state_machine::signal_state_machine (logger
*logger
)
188 : state_machine ("signal", logger
)
190 m_in_signal_handler
= add_state ("in_signal_handler");
191 m_stop
= add_state ("stop");
194 /* Update MODEL for edges that simulate HANDLER_FUN being called as
195 an signal-handler in response to a signal. */
198 update_model_for_signal_handler (region_model
*model
,
199 function
*handler_fun
)
202 /* Purge all state within MODEL. */
203 *model
= region_model (model
->get_manager ());
204 model
->push_frame (handler_fun
, NULL
, NULL
);
207 /* Custom exploded_edge info: entry into a signal-handler. */
209 class signal_delivery_edge_info_t
: public custom_edge_info
212 void print (pretty_printer
*pp
) const final override
214 pp_string (pp
, "signal delivered");
217 json::object
*to_json () const
219 json::object
*custom_obj
= new json::object ();
223 bool update_model (region_model
*model
,
224 const exploded_edge
*eedge
,
225 region_model_context
*) const final override
228 update_model_for_signal_handler (model
, eedge
->m_dest
->get_function ());
232 void add_events_to_path (checker_path
*emission_path
,
233 const exploded_edge
&eedge ATTRIBUTE_UNUSED
)
236 emission_path
->add_event
237 (make_unique
<precanned_custom_event
>
238 (event_loc_info (UNKNOWN_LOCATION
, NULL_TREE
, 0),
240 " when the signal is delivered to the process"));
244 /* Concrete subclass of custom_transition for modeling registration of a
245 signal handler and the signal handler later being called. */
247 class register_signal_handler
: public custom_transition
250 register_signal_handler (const signal_state_machine
&sm
,
252 : m_sm (sm
), m_fndecl (fndecl
) {}
254 /* Model a signal-handler FNDECL being called at some later point
255 by injecting an edge to a new function-entry node with an empty
256 callstring, setting the 'in-signal-handler' global state
258 void impl_transition (exploded_graph
*eg
,
259 exploded_node
*src_enode
,
260 int sm_idx
) final override
262 function
*handler_fun
= DECL_STRUCT_FUNCTION (m_fndecl
);
265 const extrinsic_state
&ext_state
= eg
->get_ext_state ();
266 program_point entering_handler
267 = program_point::from_function_entry (*ext_state
.get_model_manager (),
268 eg
->get_supergraph (),
271 program_state
state_entering_handler (ext_state
);
272 update_model_for_signal_handler (state_entering_handler
.m_region_model
,
274 state_entering_handler
.m_checker_states
[sm_idx
]->set_global_state
275 (m_sm
.m_in_signal_handler
);
277 exploded_node
*dst_enode
= eg
->get_or_create_node (entering_handler
,
278 state_entering_handler
,
281 eg
->add_edge (src_enode
, dst_enode
, NULL
, /*state_change (),*/
282 make_unique
<signal_delivery_edge_info_t
> ());
285 const signal_state_machine
&m_sm
;
289 /* Get a set of functions that are known to be unsafe to call from an
290 async signal handler. */
293 get_async_signal_unsafe_fns ()
295 // TODO: populate this list more fully
296 static const char * const async_signal_unsafe_fns
[] = {
297 /* This array must be kept sorted. */
310 const size_t count
= ARRAY_SIZE (async_signal_unsafe_fns
);
311 function_set
fs (async_signal_unsafe_fns
, count
);
315 /* Return true if FNDECL is known to be unsafe to call from a signal
319 signal_unsafe_p (tree fndecl
)
321 function_set fs
= get_async_signal_unsafe_fns ();
322 return fs
.contains_decl_p (fndecl
);
325 /* Implementation of state_machine::on_stmt vfunc for signal_state_machine. */
328 signal_state_machine::on_stmt (sm_context
*sm_ctxt
,
329 const supernode
*node
,
330 const gimple
*stmt
) const
332 const state_t global_state
= sm_ctxt
->get_global_state ();
333 if (global_state
== m_start
)
335 if (const gcall
*call
= dyn_cast
<const gcall
*> (stmt
))
336 if (tree callee_fndecl
= sm_ctxt
->get_fndecl_for_call (call
))
337 if (is_named_call_p (callee_fndecl
, "signal", call
, 2))
339 tree handler
= gimple_call_arg (call
, 1);
340 if (TREE_CODE (handler
) == ADDR_EXPR
341 && TREE_CODE (TREE_OPERAND (handler
, 0)) == FUNCTION_DECL
)
343 tree fndecl
= TREE_OPERAND (handler
, 0);
344 register_signal_handler
rsh (*this, fndecl
);
345 sm_ctxt
->on_custom_transition (&rsh
);
349 else if (global_state
== m_in_signal_handler
)
351 if (const gcall
*call
= dyn_cast
<const gcall
*> (stmt
))
352 if (tree callee_fndecl
= sm_ctxt
->get_fndecl_for_call (call
))
353 if (signal_unsafe_p (callee_fndecl
))
354 if (sm_ctxt
->get_global_state () == m_in_signal_handler
)
355 sm_ctxt
->warn (node
, stmt
, NULL_TREE
,
356 make_unique
<signal_unsafe_call
>
357 (*this, call
, callee_fndecl
));
364 signal_state_machine::can_purge_p (state_t s ATTRIBUTE_UNUSED
) const
369 } // anonymous namespace
371 /* Internal interface to this file. */
374 make_signal_state_machine (logger
*logger
)
376 return new signal_state_machine (logger
);
383 /* Run all of the selftests within this file. */
386 analyzer_sm_signal_cc_tests ()
388 function_set fs
= get_async_signal_unsafe_fns ();
393 } // namespace selftest
395 #endif /* CHECKING_P */
399 #endif /* #if ENABLE_ANALYZER */