Branch data Line data Source code
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 : :
7 : : This file is part of GCC.
8 : :
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)
12 : : any later version.
13 : :
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.
18 : :
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/>. */
22 : :
23 : : #include "config.h"
24 : : #define INCLUDE_VECTOR
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-core.h"
35 : : #include "diagnostic-path.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"
41 : : #include "sbitmap.h"
42 : : #include "ordered-hash-map.h"
43 : : #include "selftest.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"
50 : : #include "cfg.h"
51 : : #include "gimple-iterator.h"
52 : : #include "cgraph.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"
59 : :
60 : : #if ENABLE_ANALYZER
61 : :
62 : : namespace ana {
63 : :
64 : : namespace {
65 : :
66 : : /* An experimental state machine, for tracking calls to async-signal-unsafe
67 : : functions from within signal handlers. */
68 : :
69 : : class signal_state_machine : public state_machine
70 : : {
71 : : public:
72 : : signal_state_machine (logger *logger);
73 : :
74 : 1001238 : bool inherited_state_p () const final override { return false; }
75 : :
76 : : bool on_stmt (sm_context &sm_ctxt,
77 : : const supernode *node,
78 : : const gimple *stmt) const final override;
79 : :
80 : : bool can_purge_p (state_t s) const final override;
81 : :
82 : : /* These states are "global", rather than per-expression. */
83 : :
84 : : /* State for when we're in a signal handler. */
85 : : state_t m_in_signal_handler;
86 : :
87 : : /* Stop state. */
88 : : state_t m_stop;
89 : : };
90 : :
91 : : /* Concrete subclass for describing call to an async-signal-unsafe function
92 : : from a signal handler. */
93 : :
94 : 0 : class signal_unsafe_call
95 : : : public pending_diagnostic_subclass<signal_unsafe_call>
96 : : {
97 : : public:
98 : 18 : signal_unsafe_call (const signal_state_machine &sm, const gcall *unsafe_call,
99 : : tree unsafe_fndecl)
100 : 18 : : m_sm (sm), m_unsafe_call (unsafe_call), m_unsafe_fndecl (unsafe_fndecl)
101 : : {
102 : 18 : gcc_assert (m_unsafe_fndecl);
103 : : }
104 : :
105 : 65 : const char *get_kind () const final override { return "signal_unsafe_call"; }
106 : :
107 : 18 : bool operator== (const signal_unsafe_call &other) const
108 : : {
109 : 18 : return m_unsafe_call == other.m_unsafe_call;
110 : : }
111 : :
112 : 29 : int get_controlling_option () const final override
113 : : {
114 : 29 : return OPT_Wanalyzer_unsafe_call_within_signal_handler;
115 : : }
116 : :
117 : 11 : bool emit (diagnostic_emission_context &ctxt) final override
118 : : {
119 : 11 : auto_diagnostic_group d;
120 : : /* CWE-479: Signal Handler Use of a Non-reentrant Function. */
121 : 11 : ctxt.add_cwe (479);
122 : 11 : if (ctxt.warn ("call to %qD from within signal handler",
123 : : m_unsafe_fndecl))
124 : : {
125 : : /* If we know a possible alternative function, add a note
126 : : suggesting the replacement. */
127 : 11 : if (const char *replacement = get_replacement_fn ())
128 : : {
129 : 2 : location_t note_loc = gimple_location (m_unsafe_call);
130 : : /* It would be nice to add a fixit, but the gimple call
131 : : location covers the whole call expression. It isn't
132 : : currently possible to cut this down to just the call
133 : : symbol. So the fixit would replace too much.
134 : : note_rich_loc.add_fixit_replace (replacement); */
135 : 2 : inform (note_loc,
136 : : "%qs is a possible signal-safe alternative for %qD",
137 : : replacement, m_unsafe_fndecl);
138 : : }
139 : 11 : return true;
140 : : }
141 : : return false;
142 : 11 : }
143 : :
144 : : bool
145 : 22 : describe_state_change (pretty_printer &pp,
146 : : const evdesc::state_change &change) final override
147 : : {
148 : 22 : if (change.is_global_p ()
149 : 22 : && change.m_new_state == m_sm.m_in_signal_handler)
150 : : {
151 : 22 : const function *handler = change.m_event.get_dest_function ();
152 : 22 : gcc_assert (handler);
153 : 22 : pp_printf (&pp,
154 : : "registering %qD as signal handler",
155 : 22 : handler->decl);
156 : 22 : return true;
157 : : }
158 : : return false;
159 : : }
160 : :
161 : : bool
162 : 22 : describe_final_event (pretty_printer &pp,
163 : : const evdesc::final_event &) final override
164 : : {
165 : 22 : pp_printf (&pp,
166 : : "call to %qD from within signal handler",
167 : : m_unsafe_fndecl);
168 : 22 : return true;
169 : : }
170 : :
171 : : private:
172 : : const signal_state_machine &m_sm;
173 : : const gcall *m_unsafe_call;
174 : : tree m_unsafe_fndecl;
175 : :
176 : : /* Returns a replacement function as text if it exists. Currently
177 : : only "exit" has a signal-safe replacement "_exit", which does
178 : : slightly less, but can be used in a signal handler. */
179 : : const char *
180 : 11 : get_replacement_fn ()
181 : : {
182 : 11 : gcc_assert (m_unsafe_fndecl && DECL_P (m_unsafe_fndecl));
183 : :
184 : 11 : if (id_equal ("exit", DECL_NAME (m_unsafe_fndecl)))
185 : 2 : return "_exit";
186 : :
187 : : return NULL;
188 : : }
189 : : };
190 : :
191 : : /* signal_state_machine's ctor. */
192 : :
193 : 3203 : signal_state_machine::signal_state_machine (logger *logger)
194 : : : state_machine ("signal", logger),
195 : 6406 : m_in_signal_handler (add_state ("in_signal_handler")),
196 : 3203 : m_stop (add_state ("stop"))
197 : : {
198 : 3203 : }
199 : :
200 : : /* Update MODEL for edges that simulate HANDLER_FUN being called as
201 : : an signal-handler in response to a signal. */
202 : :
203 : : static void
204 : 28 : update_model_for_signal_handler (region_model *model,
205 : : const function &handler_fun)
206 : : {
207 : 28 : gcc_assert (model);
208 : : /* Purge all state within MODEL. */
209 : 28 : *model = region_model (model->get_manager ());
210 : 28 : model->push_frame (handler_fun, NULL, NULL);
211 : 28 : }
212 : :
213 : : /* Custom exploded_edge info: entry into a signal-handler. */
214 : :
215 : 10 : class signal_delivery_edge_info_t : public custom_edge_info
216 : : {
217 : : public:
218 : 0 : void print (pretty_printer *pp) const final override
219 : : {
220 : 0 : pp_string (pp, "signal delivered");
221 : 0 : }
222 : :
223 : 18 : bool update_model (region_model *model,
224 : : const exploded_edge *eedge,
225 : : region_model_context *) const final override
226 : : {
227 : 18 : gcc_assert (eedge);
228 : 18 : gcc_assert (eedge->m_dest->get_function ());
229 : 36 : update_model_for_signal_handler (model,
230 : 18 : *eedge->m_dest->get_function ());
231 : 18 : return true;
232 : : }
233 : :
234 : 11 : void add_events_to_path (checker_path *emission_path,
235 : : const exploded_edge &eedge ATTRIBUTE_UNUSED)
236 : : const final override
237 : : {
238 : 11 : emission_path->add_event
239 : 11 : (make_unique<precanned_custom_event>
240 : 22 : (event_loc_info (UNKNOWN_LOCATION, NULL_TREE, 0),
241 : : "later on,"
242 : : " when the signal is delivered to the process"));
243 : 11 : }
244 : : };
245 : :
246 : : /* Concrete subclass of custom_transition for modeling registration of a
247 : : signal handler and the signal handler later being called. */
248 : :
249 : 10 : class register_signal_handler : public custom_transition
250 : : {
251 : : public:
252 : 10 : register_signal_handler (const signal_state_machine &sm,
253 : : tree fndecl)
254 : 10 : : m_sm (sm), m_fndecl (fndecl) {}
255 : :
256 : : /* Model a signal-handler FNDECL being called at some later point
257 : : by injecting an edge to a new function-entry node with an empty
258 : : callstring, setting the 'in-signal-handler' global state
259 : : on the node. */
260 : 10 : void impl_transition (exploded_graph *eg,
261 : : exploded_node *src_enode,
262 : : int sm_idx) final override
263 : : {
264 : 10 : function *handler_fun = DECL_STRUCT_FUNCTION (m_fndecl);
265 : 10 : if (!handler_fun)
266 : 0 : return;
267 : 10 : const extrinsic_state &ext_state = eg->get_ext_state ();
268 : 10 : program_point entering_handler
269 : 10 : = program_point::from_function_entry (*ext_state.get_model_manager (),
270 : : eg->get_supergraph (),
271 : : *handler_fun);
272 : :
273 : 10 : program_state state_entering_handler (ext_state);
274 : 10 : update_model_for_signal_handler (state_entering_handler.m_region_model,
275 : : *handler_fun);
276 : 10 : state_entering_handler.m_checker_states[sm_idx]->set_global_state
277 : 10 : (m_sm.m_in_signal_handler);
278 : :
279 : 10 : exploded_node *dst_enode = eg->get_or_create_node (entering_handler,
280 : : state_entering_handler,
281 : : src_enode);
282 : 10 : if (dst_enode)
283 : 10 : eg->add_edge (src_enode, dst_enode, NULL, /*state_change (),*/
284 : : true, /* assume does work */
285 : 20 : make_unique<signal_delivery_edge_info_t> ());
286 : 10 : }
287 : :
288 : : const signal_state_machine &m_sm;
289 : : tree m_fndecl;
290 : : };
291 : :
292 : : /* Get a set of functions that are known to be unsafe to call from an
293 : : async signal handler. */
294 : :
295 : : static function_set
296 : 28 : get_async_signal_unsafe_fns ()
297 : : {
298 : : // TODO: populate this list more fully
299 : 28 : static const char * const async_signal_unsafe_fns[] = {
300 : : /* This array must be kept sorted. */
301 : : "exit",
302 : : "fprintf",
303 : : "free",
304 : : "malloc",
305 : : "printf",
306 : : "snprintf",
307 : : "sprintf",
308 : : "vfprintf",
309 : : "vprintf",
310 : : "vsnprintf",
311 : : "vsprintf"
312 : : };
313 : 28 : const size_t count = ARRAY_SIZE (async_signal_unsafe_fns);
314 : 28 : function_set fs (async_signal_unsafe_fns, count);
315 : 28 : return fs;
316 : : }
317 : :
318 : : /* Return true if FNDECL is known to be unsafe to call from a signal
319 : : handler. */
320 : :
321 : : static bool
322 : 24 : signal_unsafe_p (tree fndecl)
323 : : {
324 : 24 : function_set fs = get_async_signal_unsafe_fns ();
325 : 24 : if (fs.contains_decl_p (fndecl))
326 : : return true;
327 : 6 : if (is_std_function_p (fndecl)
328 : 6 : && fs.contains_name_p (IDENTIFIER_POINTER (DECL_NAME (fndecl))))
329 : : return true;
330 : :
331 : : return false;
332 : : }
333 : :
334 : : /* Implementation of state_machine::on_stmt vfunc for signal_state_machine. */
335 : :
336 : : bool
337 : 267689 : signal_state_machine::on_stmt (sm_context &sm_ctxt,
338 : : const supernode *node,
339 : : const gimple *stmt) const
340 : : {
341 : 267689 : const state_t global_state = sm_ctxt.get_global_state ();
342 : 267689 : if (global_state == m_start)
343 : : {
344 : 237877 : if (const gcall *call = dyn_cast <const gcall *> (stmt))
345 : 55515 : if (tree callee_fndecl = sm_ctxt.get_fndecl_for_call (call))
346 : 54154 : if (is_named_call_p (callee_fndecl, "signal", call, 2)
347 : 54154 : || is_std_named_call_p (callee_fndecl, "signal", call, 2))
348 : : {
349 : 10 : tree handler = gimple_call_arg (call, 1);
350 : 10 : if (TREE_CODE (handler) == ADDR_EXPR
351 : 10 : && TREE_CODE (TREE_OPERAND (handler, 0)) == FUNCTION_DECL)
352 : : {
353 : 10 : tree fndecl = TREE_OPERAND (handler, 0);
354 : 10 : register_signal_handler rsh (*this, fndecl);
355 : 10 : sm_ctxt.on_custom_transition (&rsh);
356 : 10 : }
357 : : }
358 : : }
359 : 29812 : else if (global_state == m_in_signal_handler)
360 : : {
361 : 72 : if (const gcall *call = dyn_cast <const gcall *> (stmt))
362 : 24 : if (tree callee_fndecl = sm_ctxt.get_fndecl_for_call (call))
363 : 24 : if (signal_unsafe_p (callee_fndecl))
364 : 18 : if (sm_ctxt.get_global_state () == m_in_signal_handler)
365 : 18 : sm_ctxt.warn (node, stmt, NULL_TREE,
366 : : make_unique<signal_unsafe_call>
367 : 36 : (*this, call, callee_fndecl));
368 : : }
369 : :
370 : 267689 : return false;
371 : : }
372 : :
373 : : bool
374 : 1001238 : signal_state_machine::can_purge_p (state_t s ATTRIBUTE_UNUSED) const
375 : : {
376 : 1001238 : return true;
377 : : }
378 : :
379 : : } // anonymous namespace
380 : :
381 : : /* Internal interface to this file. */
382 : :
383 : : state_machine *
384 : 3203 : make_signal_state_machine (logger *logger)
385 : : {
386 : 3203 : return new signal_state_machine (logger);
387 : : }
388 : :
389 : : #if CHECKING_P
390 : :
391 : : namespace selftest {
392 : :
393 : : /* Run all of the selftests within this file. */
394 : :
395 : : void
396 : 4 : analyzer_sm_signal_cc_tests ()
397 : : {
398 : 4 : function_set fs = get_async_signal_unsafe_fns ();
399 : 4 : fs.assert_sorted ();
400 : 4 : fs.assert_sane ();
401 : 4 : }
402 : :
403 : : } // namespace selftest
404 : :
405 : : #endif /* CHECKING_P */
406 : :
407 : : } // namespace ana
408 : :
409 : : #endif /* #if ENABLE_ANALYZER */
|