Line data Source code
1 : /* An experimental state machine, for tracking bad calls from within
2 : signal handlers.
3 :
4 : Copyright (C) 2019-2026 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 "diagnostics/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 1394967 : bool inherited_state_p () const final override { return false; }
64 :
65 : bool on_stmt (sm_context &sm_ctxt,
66 : const gimple *stmt) const final override;
67 :
68 : bool can_purge_p (state_t s) const final override;
69 :
70 : /* These states are "global", rather than per-expression. */
71 :
72 : /* State for when we're in a signal handler. */
73 : state_t m_in_signal_handler;
74 :
75 : /* Stop state. */
76 : state_t m_stop;
77 : };
78 :
79 : /* Concrete subclass for describing call to an async-signal-unsafe function
80 : from a signal handler. */
81 :
82 0 : class signal_unsafe_call
83 : : public pending_diagnostic_subclass<signal_unsafe_call>
84 : {
85 : public:
86 11 : signal_unsafe_call (const signal_state_machine &sm, const gcall &unsafe_call,
87 : tree unsafe_fndecl)
88 11 : : m_sm (sm), m_unsafe_call (unsafe_call), m_unsafe_fndecl (unsafe_fndecl)
89 : {
90 11 : gcc_assert (m_unsafe_fndecl);
91 : }
92 :
93 58 : const char *get_kind () const final override { return "signal_unsafe_call"; }
94 :
95 11 : bool operator== (const signal_unsafe_call &other) const
96 : {
97 11 : return &m_unsafe_call == &other.m_unsafe_call;
98 : }
99 :
100 22 : int get_controlling_option () const final override
101 : {
102 22 : return OPT_Wanalyzer_unsafe_call_within_signal_handler;
103 : }
104 :
105 11 : bool emit (diagnostic_emission_context &ctxt) final override
106 : {
107 11 : auto_diagnostic_group d;
108 : /* CWE-479: Signal Handler Use of a Non-reentrant Function. */
109 11 : ctxt.add_cwe (479);
110 11 : if (ctxt.warn ("call to %qD from within signal handler",
111 : m_unsafe_fndecl))
112 : {
113 : /* If we know a possible alternative function, add a note
114 : suggesting the replacement. */
115 11 : if (const char *replacement = get_replacement_fn ())
116 : {
117 2 : location_t note_loc = gimple_location (&m_unsafe_call);
118 : /* It would be nice to add a fixit, but the gimple call
119 : location covers the whole call expression. It isn't
120 : currently possible to cut this down to just the call
121 : symbol. So the fixit would replace too much.
122 : note_rich_loc.add_fixit_replace (replacement); */
123 2 : inform (note_loc,
124 : "%qs is a possible signal-safe alternative for %qD",
125 : replacement, m_unsafe_fndecl);
126 : }
127 11 : return true;
128 : }
129 : return false;
130 11 : }
131 :
132 : bool
133 22 : describe_state_change (pretty_printer &pp,
134 : const evdesc::state_change &change) final override
135 : {
136 22 : if (change.is_global_p ()
137 22 : && change.m_new_state == m_sm.m_in_signal_handler)
138 : {
139 22 : const function *handler = change.m_event.get_dest_function ();
140 22 : gcc_assert (handler);
141 22 : pp_printf (&pp,
142 : "registering %qD as signal handler",
143 22 : handler->decl);
144 22 : return true;
145 : }
146 : return false;
147 : }
148 :
149 : bool
150 22 : describe_final_event (pretty_printer &pp,
151 : const evdesc::final_event &) final override
152 : {
153 22 : pp_printf (&pp,
154 : "call to %qD from within signal handler",
155 : m_unsafe_fndecl);
156 22 : return true;
157 : }
158 :
159 : private:
160 : const signal_state_machine &m_sm;
161 : const gcall &m_unsafe_call;
162 : tree m_unsafe_fndecl;
163 :
164 : /* Returns a replacement function as text if it exists. Currently
165 : only "exit" has a signal-safe replacement "_exit", which does
166 : slightly less, but can be used in a signal handler. */
167 : const char *
168 11 : get_replacement_fn ()
169 : {
170 11 : gcc_assert (m_unsafe_fndecl && DECL_P (m_unsafe_fndecl));
171 :
172 11 : if (id_equal ("exit", DECL_NAME (m_unsafe_fndecl)))
173 2 : return "_exit";
174 :
175 : return nullptr;
176 : }
177 : };
178 :
179 : /* signal_state_machine's ctor. */
180 :
181 3427 : signal_state_machine::signal_state_machine (logger *logger)
182 : : state_machine ("signal", logger),
183 6854 : m_in_signal_handler (add_state ("in_signal_handler")),
184 3427 : m_stop (add_state ("stop"))
185 : {
186 3427 : }
187 :
188 : /* Update MODEL for edges that simulate HANDLER_FUN being called as
189 : an signal-handler in response to a signal. */
190 :
191 : static void
192 32 : update_model_for_signal_handler (region_model *model,
193 : const function &handler_fun)
194 : {
195 32 : gcc_assert (model);
196 : /* Purge all state within MODEL. */
197 32 : *model = region_model (model->get_manager ());
198 32 : model->push_frame (handler_fun, nullptr, nullptr, nullptr);
199 32 : }
200 :
201 : /* Custom exploded_edge info: entry into a signal-handler. */
202 :
203 10 : class signal_delivery_edge_info_t : public custom_edge_info
204 : {
205 : public:
206 0 : void print (pretty_printer *pp) const final override
207 : {
208 0 : pp_string (pp, "signal delivered");
209 0 : }
210 :
211 22 : bool update_model (region_model *model,
212 : const exploded_edge *eedge,
213 : region_model_context *) const final override
214 : {
215 22 : gcc_assert (eedge);
216 22 : gcc_assert (eedge->m_dest->get_function ());
217 22 : update_model_for_signal_handler (model,
218 22 : *eedge->m_dest->get_function ());
219 22 : return true;
220 : }
221 :
222 11 : void add_events_to_path (checker_path *emission_path,
223 : const exploded_edge &eedge ATTRIBUTE_UNUSED,
224 : pending_diagnostic &,
225 : const state_transition *)
226 : const final override
227 : {
228 11 : emission_path->add_event
229 11 : (std::make_unique<precanned_custom_event>
230 11 : (event_loc_info (UNKNOWN_LOCATION, NULL_TREE, 0),
231 : "later on,"
232 : " when the signal is delivered to the process"));
233 11 : }
234 : };
235 :
236 : /* Concrete subclass of custom_transition for modeling registration of a
237 : signal handler and the signal handler later being called. */
238 :
239 10 : class register_signal_handler : public custom_transition
240 : {
241 : public:
242 10 : register_signal_handler (const signal_state_machine &sm,
243 : tree fndecl)
244 10 : : m_sm (sm), m_fndecl (fndecl) {}
245 :
246 : /* Model a signal-handler FNDECL being called at some later point
247 : by injecting an edge to a new function-entry node with an empty
248 : callstring, setting the 'in-signal-handler' global state
249 : on the node. */
250 10 : void impl_transition (exploded_graph *eg,
251 : exploded_node *src_enode,
252 : int sm_idx) final override
253 : {
254 10 : function *handler_fun = DECL_STRUCT_FUNCTION (m_fndecl);
255 10 : if (!handler_fun)
256 0 : return;
257 10 : const extrinsic_state &ext_state = eg->get_ext_state ();
258 10 : program_point entering_handler
259 10 : = program_point::from_function_entry (*ext_state.get_model_manager (),
260 : eg->get_supergraph (),
261 : *handler_fun);
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, nullptr, /*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 16 : get_async_signal_unsafe_fns ()
286 : {
287 : // TODO: populate this list more fully
288 16 : 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 16 : const size_t count = ARRAY_SIZE (async_signal_unsafe_fns);
303 16 : function_set fs (async_signal_unsafe_fns, count);
304 16 : 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 12 : signal_unsafe_p (tree fndecl)
312 : {
313 12 : function_set fs = get_async_signal_unsafe_fns ();
314 12 : if (fs.contains_decl_p (fndecl))
315 : return true;
316 1 : if (is_std_function_p (fndecl)
317 1 : && 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 265165 : signal_state_machine::on_stmt (sm_context &sm_ctxt,
327 : const gimple *stmt) const
328 : {
329 265165 : const state_t global_state = sm_ctxt.get_global_state ();
330 265165 : if (global_state == m_start)
331 : {
332 251385 : if (const gcall *call = dyn_cast <const gcall *> (stmt))
333 49832 : if (tree callee_fndecl = sm_ctxt.get_fndecl_for_call (*call))
334 46033 : if (is_named_call_p (callee_fndecl, "signal", *call, 2)
335 46033 : || is_std_named_call_p (callee_fndecl, "signal", *call, 2))
336 : {
337 10 : tree handler = gimple_call_arg (call, 1);
338 10 : if (TREE_CODE (handler) == ADDR_EXPR
339 10 : && TREE_CODE (TREE_OPERAND (handler, 0)) == FUNCTION_DECL)
340 : {
341 10 : tree fndecl = TREE_OPERAND (handler, 0);
342 10 : register_signal_handler rsh (*this, fndecl);
343 10 : sm_ctxt.on_custom_transition (&rsh);
344 10 : }
345 : }
346 : }
347 13780 : else if (global_state == m_in_signal_handler)
348 : {
349 24 : if (const gcall *call = dyn_cast <const gcall *> (stmt))
350 12 : if (tree callee_fndecl = sm_ctxt.get_fndecl_for_call (*call))
351 12 : if (signal_unsafe_p (callee_fndecl))
352 11 : if (sm_ctxt.get_global_state () == m_in_signal_handler)
353 11 : sm_ctxt.warn (NULL_TREE,
354 : std::make_unique<signal_unsafe_call>
355 11 : (*this, *call, callee_fndecl));
356 : }
357 :
358 265165 : return false;
359 : }
360 :
361 : bool
362 1394967 : signal_state_machine::can_purge_p (state_t s ATTRIBUTE_UNUSED) const
363 : {
364 1394967 : return true;
365 : }
366 :
367 : } // anonymous namespace
368 :
369 : /* Internal interface to this file. */
370 :
371 : std::unique_ptr<state_machine>
372 3427 : make_signal_state_machine (logger *logger)
373 : {
374 3427 : return std::make_unique<signal_state_machine> (logger);
375 : }
376 :
377 : #if CHECKING_P
378 :
379 : namespace selftest {
380 :
381 : /* Run all of the selftests within this file. */
382 :
383 : void
384 4 : analyzer_sm_signal_cc_tests ()
385 : {
386 4 : function_set fs = get_async_signal_unsafe_fns ();
387 4 : fs.assert_sorted ();
388 4 : fs.assert_sane ();
389 4 : }
390 :
391 : } // namespace selftest
392 :
393 : #endif /* CHECKING_P */
394 :
395 : } // namespace ana
396 :
397 : #endif /* #if ENABLE_ANALYZER */
|