1/* An experimental state machine, for tracking bad calls from within
2 signal handlers.
3
4 Copyright (C) 2019-2024 Free Software Foundation, Inc.
5 Contributed by David Malcolm <dmalcolm@redhat.com>.
6
7This file is part of GCC.
8
9GCC is free software; you can redistribute it and/or modify it
10under the terms of the GNU General Public License as published by
11the Free Software Foundation; either version 3, or (at your option)
12any later version.
13
14GCC is distributed in the hope that it will be useful, but
15WITHOUT ANY WARRANTY; without even the implied warranty of
16MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17General Public License for more details.
18
19You should have received a copy of the GNU General Public License
20along with GCC; see the file COPYING3. If not see
21<http://www.gnu.org/licenses/>. */
22
23#include "config.h"
24#define INCLUDE_MEMORY
25#include "system.h"
26#include "coretypes.h"
27#include "make-unique.h"
28#include "tree.h"
29#include "function.h"
30#include "basic-block.h"
31#include "gimple.h"
32#include "options.h"
33#include "bitmap.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"
40#include "sbitmap.h"
41#include "ordered-hash-map.h"
42#include "selftest.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"
49#include "cfg.h"
50#include "gimple-iterator.h"
51#include "cgraph.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"
58
59#if ENABLE_ANALYZER
60
61namespace ana {
62
63namespace {
64
65/* An experimental state machine, for tracking calls to async-signal-unsafe
66 functions from within signal handlers. */
67
68class signal_state_machine : public state_machine
69{
70public:
71 signal_state_machine (logger *logger);
72
73 bool inherited_state_p () const final override { return false; }
74
75 bool on_stmt (sm_context *sm_ctxt,
76 const supernode *node,
77 const gimple *stmt) const final override;
78
79 bool can_purge_p (state_t s) const final override;
80
81 /* These states are "global", rather than per-expression. */
82
83 /* State for when we're in a signal handler. */
84 state_t m_in_signal_handler;
85
86 /* Stop state. */
87 state_t m_stop;
88};
89
90/* Concrete subclass for describing call to an async-signal-unsafe function
91 from a signal handler. */
92
93class signal_unsafe_call
94 : public pending_diagnostic_subclass<signal_unsafe_call>
95{
96public:
97 signal_unsafe_call (const signal_state_machine &sm, const gcall *unsafe_call,
98 tree unsafe_fndecl)
99 : m_sm (sm), m_unsafe_call (unsafe_call), m_unsafe_fndecl (unsafe_fndecl)
100 {
101 gcc_assert (m_unsafe_fndecl);
102 }
103
104 const char *get_kind () const final override { return "signal_unsafe_call"; }
105
106 bool operator== (const signal_unsafe_call &other) const
107 {
108 return m_unsafe_call == other.m_unsafe_call;
109 }
110
111 int get_controlling_option () const final override
112 {
113 return OPT_Wanalyzer_unsafe_call_within_signal_handler;
114 }
115
116 bool emit (diagnostic_emission_context &ctxt) final override
117 {
118 auto_diagnostic_group d;
119 /* CWE-479: Signal Handler Use of a Non-reentrant Function. */
120 ctxt.add_cwe (cwe: 479);
121 if (ctxt.warn ("call to %qD from within signal handler",
122 m_unsafe_fndecl))
123 {
124 /* If we know a possible alternative function, add a note
125 suggesting the replacement. */
126 if (const char *replacement = get_replacement_fn ())
127 {
128 location_t note_loc = gimple_location (g: 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); */
134 inform (note_loc,
135 "%qs is a possible signal-safe alternative for %qD",
136 replacement, m_unsafe_fndecl);
137 }
138 return true;
139 }
140 return false;
141 }
142
143 label_text describe_state_change (const evdesc::state_change &change)
144 final override
145 {
146 if (change.is_global_p ()
147 && change.m_new_state == m_sm.m_in_signal_handler)
148 {
149 const function *handler = change.m_event.get_dest_function ();
150 gcc_assert (handler);
151 return change.formatted_print (fmt: "registering %qD as signal handler",
152 handler->decl);
153 }
154 return label_text ();
155 }
156
157 label_text describe_final_event (const evdesc::final_event &ev) final override
158 {
159 return ev.formatted_print (fmt: "call to %qD from within signal handler",
160 m_unsafe_fndecl);
161 }
162
163private:
164 const signal_state_machine &m_sm;
165 const gcall *m_unsafe_call;
166 tree m_unsafe_fndecl;
167
168 /* Returns a replacement function as text if it exists. Currently
169 only "exit" has a signal-safe replacement "_exit", which does
170 slightly less, but can be used in a signal handler. */
171 const char *
172 get_replacement_fn ()
173 {
174 gcc_assert (m_unsafe_fndecl && DECL_P (m_unsafe_fndecl));
175
176 if (id_equal (str: "exit", DECL_NAME (m_unsafe_fndecl)))
177 return "_exit";
178
179 return NULL;
180 }
181};
182
183/* signal_state_machine's ctor. */
184
185signal_state_machine::signal_state_machine (logger *logger)
186: state_machine ("signal", logger),
187 m_in_signal_handler (add_state (name: "in_signal_handler")),
188 m_stop (add_state (name: "stop"))
189{
190}
191
192/* Update MODEL for edges that simulate HANDLER_FUN being called as
193 an signal-handler in response to a signal. */
194
195static void
196update_model_for_signal_handler (region_model *model,
197 const function &handler_fun)
198{
199 gcc_assert (model);
200 /* Purge all state within MODEL. */
201 *model = region_model (model->get_manager ());
202 model->push_frame (fun: handler_fun, NULL, NULL);
203}
204
205/* Custom exploded_edge info: entry into a signal-handler. */
206
207class signal_delivery_edge_info_t : public custom_edge_info
208{
209public:
210 void print (pretty_printer *pp) const final override
211 {
212 pp_string (pp, "signal delivered");
213 }
214
215 json::object *to_json () const
216 {
217 json::object *custom_obj = new json::object ();
218 return custom_obj;
219 }
220
221 bool update_model (region_model *model,
222 const exploded_edge *eedge,
223 region_model_context *) const final override
224 {
225 gcc_assert (eedge);
226 gcc_assert (eedge->m_dest->get_function ());
227 update_model_for_signal_handler (model,
228 handler_fun: *eedge->m_dest->get_function ());
229 return true;
230 }
231
232 void add_events_to_path (checker_path *emission_path,
233 const exploded_edge &eedge ATTRIBUTE_UNUSED)
234 const final override
235 {
236 emission_path->add_event
237 (event: make_unique<precanned_custom_event>
238 (args: event_loc_info (UNKNOWN_LOCATION, NULL_TREE, 0),
239 args: "later on,"
240 " when the signal is delivered to the process"));
241 }
242};
243
244/* Concrete subclass of custom_transition for modeling registration of a
245 signal handler and the signal handler later being called. */
246
247class register_signal_handler : public custom_transition
248{
249public:
250 register_signal_handler (const signal_state_machine &sm,
251 tree fndecl)
252 : m_sm (sm), m_fndecl (fndecl) {}
253
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
257 on the node. */
258 void impl_transition (exploded_graph *eg,
259 exploded_node *src_enode,
260 int sm_idx) final override
261 {
262 function *handler_fun = DECL_STRUCT_FUNCTION (m_fndecl);
263 if (!handler_fun)
264 return;
265 const extrinsic_state &ext_state = eg->get_ext_state ();
266 program_point entering_handler
267 = program_point::from_function_entry (mgr: *ext_state.get_model_manager (),
268 sg: eg->get_supergraph (),
269 fun: *handler_fun);
270
271 program_state state_entering_handler (ext_state);
272 update_model_for_signal_handler (model: state_entering_handler.m_region_model,
273 handler_fun: *handler_fun);
274 state_entering_handler.m_checker_states[sm_idx]->set_global_state
275 (m_sm.m_in_signal_handler);
276
277 exploded_node *dst_enode = eg->get_or_create_node (point: entering_handler,
278 state: state_entering_handler,
279 enode_for_diag: src_enode);
280 if (dst_enode)
281 eg->add_edge (src: src_enode, dest: dst_enode, NULL, /*state_change (),*/
282 could_do_work: true, /* assume does work */
283 custom: make_unique<signal_delivery_edge_info_t> ());
284 }
285
286 const signal_state_machine &m_sm;
287 tree m_fndecl;
288};
289
290/* Get a set of functions that are known to be unsafe to call from an
291 async signal handler. */
292
293static function_set
294get_async_signal_unsafe_fns ()
295{
296 // TODO: populate this list more fully
297 static const char * const async_signal_unsafe_fns[] = {
298 /* This array must be kept sorted. */
299 "exit",
300 "fprintf",
301 "free",
302 "malloc",
303 "printf",
304 "snprintf",
305 "sprintf",
306 "vfprintf",
307 "vprintf",
308 "vsnprintf",
309 "vsprintf"
310 };
311 const size_t count = ARRAY_SIZE (async_signal_unsafe_fns);
312 function_set fs (async_signal_unsafe_fns, count);
313 return fs;
314}
315
316/* Return true if FNDECL is known to be unsafe to call from a signal
317 handler. */
318
319static bool
320signal_unsafe_p (tree fndecl)
321{
322 function_set fs = get_async_signal_unsafe_fns ();
323 return fs.contains_decl_p (fndecl);
324}
325
326/* Implementation of state_machine::on_stmt vfunc for signal_state_machine. */
327
328bool
329signal_state_machine::on_stmt (sm_context *sm_ctxt,
330 const supernode *node,
331 const gimple *stmt) const
332{
333 const state_t global_state = sm_ctxt->get_global_state ();
334 if (global_state == m_start)
335 {
336 if (const gcall *call = dyn_cast <const gcall *> (p: stmt))
337 if (tree callee_fndecl = sm_ctxt->get_fndecl_for_call (call))
338 if (is_named_call_p (fndecl: callee_fndecl, funcname: "signal", call, num_args: 2))
339 {
340 tree handler = gimple_call_arg (gs: call, index: 1);
341 if (TREE_CODE (handler) == ADDR_EXPR
342 && TREE_CODE (TREE_OPERAND (handler, 0)) == FUNCTION_DECL)
343 {
344 tree fndecl = TREE_OPERAND (handler, 0);
345 register_signal_handler rsh (*this, fndecl);
346 sm_ctxt->on_custom_transition (transition: &rsh);
347 }
348 }
349 }
350 else if (global_state == m_in_signal_handler)
351 {
352 if (const gcall *call = dyn_cast <const gcall *> (p: stmt))
353 if (tree callee_fndecl = sm_ctxt->get_fndecl_for_call (call))
354 if (signal_unsafe_p (fndecl: callee_fndecl))
355 if (sm_ctxt->get_global_state () == m_in_signal_handler)
356 sm_ctxt->warn (node, stmt, NULL_TREE,
357 d: make_unique<signal_unsafe_call>
358 (args: *this, args&: call, args&: callee_fndecl));
359 }
360
361 return false;
362}
363
364bool
365signal_state_machine::can_purge_p (state_t s ATTRIBUTE_UNUSED) const
366{
367 return true;
368}
369
370} // anonymous namespace
371
372/* Internal interface to this file. */
373
374state_machine *
375make_signal_state_machine (logger *logger)
376{
377 return new signal_state_machine (logger);
378}
379
380#if CHECKING_P
381
382namespace selftest {
383
384/* Run all of the selftests within this file. */
385
386void
387analyzer_sm_signal_cc_tests ()
388{
389 function_set fs = get_async_signal_unsafe_fns ();
390 fs.assert_sorted ();
391 fs.assert_sane ();
392}
393
394} // namespace selftest
395
396#endif /* CHECKING_P */
397
398} // namespace ana
399
400#endif /* #if ENABLE_ANALYZER */
401

source code of gcc/analyzer/sm-signal.cc