]> git.ipfire.org Git - thirdparty/gcc.git/blame - gcc/analyzer/sm-signal.cc
analyzer: add sarif properties for bounds checking diagnostics
[thirdparty/gcc.git] / gcc / analyzer / sm-signal.cc
CommitLineData
757bf1df
DM
1/* An experimental state machine, for tracking bad calls from within
2 signal handlers.
3
83ffe9cd 4 Copyright (C) 2019-2023 Free Software Foundation, Inc.
757bf1df
DM
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"
6341f14e 24#define INCLUDE_MEMORY
757bf1df
DM
25#include "system.h"
26#include "coretypes.h"
6341f14e 27#include "make-unique.h"
757bf1df
DM
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"
757bf1df
DM
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"
757bf1df
DM
41#include "ordered-hash-map.h"
42#include "selftest.h"
808f4dfe
DM
43#include "analyzer/call-string.h"
44#include "analyzer/program-point.h"
45#include "analyzer/store.h"
757bf1df
DM
46#include "analyzer/region-model.h"
47#include "analyzer/program-state.h"
48#include "analyzer/checker-path.h"
757bf1df
DM
49#include "cfg.h"
50#include "gimple-iterator.h"
51#include "cgraph.h"
52#include "analyzer/supergraph.h"
757bf1df
DM
53#include "analyzer/diagnostic-manager.h"
54#include "shortest-paths.h"
55#include "analyzer/exploded-graph.h"
4804c5fe
DM
56#include "analyzer/function-set.h"
57#include "analyzer/analyzer-selftests.h"
757bf1df
DM
58
59#if ENABLE_ANALYZER
60
75038aa6
DM
61namespace ana {
62
757bf1df
DM
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
ff171cb1 73 bool inherited_state_p () const final override { return false; }
757bf1df
DM
74
75 bool on_stmt (sm_context *sm_ctxt,
76 const supernode *node,
ff171cb1 77 const gimple *stmt) const final override;
757bf1df 78
ff171cb1 79 bool can_purge_p (state_t s) const final override;
757bf1df
DM
80
81 /* These states are "global", rather than per-expression. */
82
757bf1df
DM
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
ff171cb1 104 const char *get_kind () const final override { return "signal_unsafe_call"; }
757bf1df
DM
105
106 bool operator== (const signal_unsafe_call &other) const
107 {
108 return m_unsafe_call == other.m_unsafe_call;
109 }
110
ff171cb1 111 int get_controlling_option () const final override
7fd6e36e
DM
112 {
113 return OPT_Wanalyzer_unsafe_call_within_signal_handler;
114 }
115
12b67d1e 116 bool emit (diagnostic_emission_context &ctxt) final override
757bf1df 117 {
2221fb6f 118 auto_diagnostic_group d;
757bf1df 119 /* CWE-479: Signal Handler Use of a Non-reentrant Function. */
12b67d1e
DM
120 ctxt.add_cwe (479);
121 if (ctxt.warn ("call to %qD from within signal handler",
122 m_unsafe_fndecl))
2221fb6f
MW
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 (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;
757bf1df
DM
141 }
142
143 label_text describe_state_change (const evdesc::state_change &change)
ff171cb1 144 final override
757bf1df
DM
145 {
146 if (change.is_global_p ()
147 && change.m_new_state == m_sm.m_in_signal_handler)
148 {
808f4dfe 149 function *handler = change.m_event.get_dest_function ();
757bf1df
DM
150 return change.formatted_print ("registering %qD as signal handler",
151 handler->decl);
152 }
153 return label_text ();
154 }
155
ff171cb1 156 label_text describe_final_event (const evdesc::final_event &ev) final override
757bf1df
DM
157 {
158 return ev.formatted_print ("call to %qD from within signal handler",
159 m_unsafe_fndecl);
160 }
161
162private:
163 const signal_state_machine &m_sm;
164 const gcall *m_unsafe_call;
165 tree m_unsafe_fndecl;
2221fb6f
MW
166
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. */
170 const char *
171 get_replacement_fn ()
172 {
173 gcc_assert (m_unsafe_fndecl && DECL_P (m_unsafe_fndecl));
174
175 if (id_equal ("exit", DECL_NAME (m_unsafe_fndecl)))
176 return "_exit";
177
178 return NULL;
179 }
757bf1df
DM
180};
181
182/* signal_state_machine's ctor. */
183
184signal_state_machine::signal_state_machine (logger *logger)
8cf5afba
DM
185: state_machine ("signal", logger),
186 m_in_signal_handler (add_state ("in_signal_handler")),
187 m_stop (add_state ("stop"))
757bf1df 188{
757bf1df
DM
189}
190
191/* Update MODEL for edges that simulate HANDLER_FUN being called as
192 an signal-handler in response to a signal. */
193
194static void
195update_model_for_signal_handler (region_model *model,
196 function *handler_fun)
197{
808f4dfe 198 gcc_assert (model);
757bf1df 199 /* Purge all state within MODEL. */
808f4dfe 200 *model = region_model (model->get_manager ());
757bf1df
DM
201 model->push_frame (handler_fun, NULL, NULL);
202}
203
204/* Custom exploded_edge info: entry into a signal-handler. */
205
eafa9d96 206class signal_delivery_edge_info_t : public custom_edge_info
757bf1df
DM
207{
208public:
ff171cb1 209 void print (pretty_printer *pp) const final override
757bf1df
DM
210 {
211 pp_string (pp, "signal delivered");
212 }
213
809192e7
DM
214 json::object *to_json () const
215 {
216 json::object *custom_obj = new json::object ();
217 return custom_obj;
218 }
219
eafa9d96
DM
220 bool update_model (region_model *model,
221 const exploded_edge *eedge,
ff171cb1 222 region_model_context *) const final override
757bf1df 223 {
eafa9d96
DM
224 gcc_assert (eedge);
225 update_model_for_signal_handler (model, eedge->m_dest->get_function ());
226 return true;
757bf1df
DM
227 }
228
229 void add_events_to_path (checker_path *emission_path,
230 const exploded_edge &eedge ATTRIBUTE_UNUSED)
ff171cb1 231 const final override
757bf1df
DM
232 {
233 emission_path->add_event
d60b40b8 234 (make_unique<precanned_custom_event>
e24fe128 235 (event_loc_info (UNKNOWN_LOCATION, NULL_TREE, 0),
86606d2a
DM
236 "later on,"
237 " when the signal is delivered to the process"));
757bf1df
DM
238 }
239};
240
241/* Concrete subclass of custom_transition for modeling registration of a
242 signal handler and the signal handler later being called. */
243
244class register_signal_handler : public custom_transition
245{
246public:
247 register_signal_handler (const signal_state_machine &sm,
248 tree fndecl)
249 : m_sm (sm), m_fndecl (fndecl) {}
250
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
254 on the node. */
255 void impl_transition (exploded_graph *eg,
256 exploded_node *src_enode,
ff171cb1 257 int sm_idx) final override
757bf1df
DM
258 {
259 function *handler_fun = DECL_STRUCT_FUNCTION (m_fndecl);
260 if (!handler_fun)
261 return;
bb8e93eb 262 const extrinsic_state &ext_state = eg->get_ext_state ();
757bf1df 263 program_point entering_handler
bb8e93eb
DM
264 = program_point::from_function_entry (*ext_state.get_model_manager (),
265 eg->get_supergraph (),
757bf1df
DM
266 handler_fun);
267
bb8e93eb 268 program_state state_entering_handler (ext_state);
757bf1df
DM
269 update_model_for_signal_handler (state_entering_handler.m_region_model,
270 handler_fun);
271 state_entering_handler.m_checker_states[sm_idx]->set_global_state
272 (m_sm.m_in_signal_handler);
273
274 exploded_node *dst_enode = eg->get_or_create_node (entering_handler,
275 state_entering_handler,
808f4dfe 276 src_enode);
757bf1df 277 if (dst_enode)
808f4dfe 278 eg->add_edge (src_enode, dst_enode, NULL, /*state_change (),*/
841008d3 279 true, /* assume does work */
ca5ff105 280 make_unique<signal_delivery_edge_info_t> ());
757bf1df
DM
281 }
282
283 const signal_state_machine &m_sm;
284 tree m_fndecl;
285};
286
4804c5fe
DM
287/* Get a set of functions that are known to be unsafe to call from an
288 async signal handler. */
757bf1df 289
4804c5fe
DM
290static function_set
291get_async_signal_unsafe_fns ()
757bf1df 292{
4804c5fe
DM
293 // TODO: populate this list more fully
294 static const char * const async_signal_unsafe_fns[] = {
295 /* This array must be kept sorted. */
2221fb6f 296 "exit",
4804c5fe
DM
297 "fprintf",
298 "free",
299 "malloc",
300 "printf",
301 "snprintf",
302 "sprintf",
303 "vfprintf",
304 "vprintf",
305 "vsnprintf",
306 "vsprintf"
307 };
ca32b29e 308 const size_t count = ARRAY_SIZE (async_signal_unsafe_fns);
4804c5fe
DM
309 function_set fs (async_signal_unsafe_fns, count);
310 return fs;
16485ea9 311}
757bf1df 312
4804c5fe
DM
313/* Return true if FNDECL is known to be unsafe to call from a signal
314 handler. */
315
316static bool
317signal_unsafe_p (tree fndecl)
318{
319 function_set fs = get_async_signal_unsafe_fns ();
320 return fs.contains_decl_p (fndecl);
757bf1df
DM
321}
322
323/* Implementation of state_machine::on_stmt vfunc for signal_state_machine. */
324
325bool
326signal_state_machine::on_stmt (sm_context *sm_ctxt,
327 const supernode *node,
328 const gimple *stmt) const
329{
330 const state_t global_state = sm_ctxt->get_global_state ();
331 if (global_state == m_start)
332 {
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))
336 {
337 tree handler = gimple_call_arg (call, 1);
338 if (TREE_CODE (handler) == ADDR_EXPR
339 && TREE_CODE (TREE_OPERAND (handler, 0)) == FUNCTION_DECL)
340 {
341 tree fndecl = TREE_OPERAND (handler, 0);
342 register_signal_handler rsh (*this, fndecl);
343 sm_ctxt->on_custom_transition (&rsh);
344 }
345 }
346 }
347 else if (global_state == m_in_signal_handler)
348 {
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))
25ef215a
DM
352 if (sm_ctxt->get_global_state () == m_in_signal_handler)
353 sm_ctxt->warn (node, stmt, NULL_TREE,
6341f14e
DM
354 make_unique<signal_unsafe_call>
355 (*this, call, callee_fndecl));
757bf1df
DM
356 }
357
358 return false;
359}
360
757bf1df
DM
361bool
362signal_state_machine::can_purge_p (state_t s ATTRIBUTE_UNUSED) const
363{
364 return true;
365}
366
367} // anonymous namespace
368
369/* Internal interface to this file. */
370
371state_machine *
372make_signal_state_machine (logger *logger)
373{
374 return new signal_state_machine (logger);
375}
376
4804c5fe
DM
377#if CHECKING_P
378
379namespace selftest {
380
381/* Run all of the selftests within this file. */
382
383void
384analyzer_sm_signal_cc_tests ()
385{
386 function_set fs = get_async_signal_unsafe_fns ();
387 fs.assert_sorted ();
388 fs.assert_sane ();
389}
390
391} // namespace selftest
392
c9c8aef4
DM
393#endif /* CHECKING_P */
394
75038aa6
DM
395} // namespace ana
396
757bf1df 397#endif /* #if ENABLE_ANALYZER */