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