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 1420647 : 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 3381 : signal_state_machine::signal_state_machine (logger *logger)
182 : : state_machine ("signal", logger),
183 6762 : m_in_signal_handler (add_state ("in_signal_handler")),
184 3381 : m_stop (add_state ("stop"))
185 : {
186 3381 : }
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 21 : update_model_for_signal_handler (region_model *model,
193 : const function &handler_fun)
194 : {
195 21 : gcc_assert (model);
196 : /* Purge all state within MODEL. */
197 21 : *model = region_model (model->get_manager ());
198 21 : model->push_frame (handler_fun, nullptr, nullptr, nullptr);
199 21 : }
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 11 : bool update_model (region_model *model,
212 : const exploded_edge *eedge,
213 : region_model_context *) const final override
214 : {
215 11 : gcc_assert (eedge);
216 11 : gcc_assert (eedge->m_dest->get_function ());
217 11 : update_model_for_signal_handler (model,
218 11 : *eedge->m_dest->get_function ());
219 11 : 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 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 10 : program_state state_entering_handler (ext_state);
262 10 : update_model_for_signal_handler (state_entering_handler.m_region_model,
263 : *handler_fun);
264 10 : state_entering_handler.m_checker_states[sm_idx]->set_global_state
265 10 : (m_sm.m_in_signal_handler);
266 :
267 10 : exploded_node *dst_enode = eg->get_or_create_node (entering_handler,
268 : state_entering_handler,
269 : src_enode);
270 10 : if (dst_enode)
271 10 : eg->add_edge (src_enode, dst_enode, nullptr, /*state_change (),*/
272 : true, /* assume does work */
273 10 : std::make_unique<signal_delivery_edge_info_t> ());
274 10 : }
275 :
276 : const signal_state_machine &m_sm;
277 : tree m_fndecl;
278 : };
279 :
280 : /* Get a set of functions that are known to be unsafe to call from an
281 : async signal handler. */
282 :
283 : static function_set
284 16 : get_async_signal_unsafe_fns ()
285 : {
286 : // TODO: populate this list more fully
287 16 : static const char * const async_signal_unsafe_fns[] = {
288 : /* This array must be kept sorted. */
289 : "exit",
290 : "fprintf",
291 : "free",
292 : "malloc",
293 : "printf",
294 : "snprintf",
295 : "sprintf",
296 : "vfprintf",
297 : "vprintf",
298 : "vsnprintf",
299 : "vsprintf"
300 : };
301 16 : const size_t count = ARRAY_SIZE (async_signal_unsafe_fns);
302 16 : function_set fs (async_signal_unsafe_fns, count);
303 16 : return fs;
304 : }
305 :
306 : /* Return true if FNDECL is known to be unsafe to call from a signal
307 : handler. */
308 :
309 : static bool
310 12 : signal_unsafe_p (tree fndecl)
311 : {
312 12 : function_set fs = get_async_signal_unsafe_fns ();
313 12 : if (fs.contains_decl_p (fndecl))
314 : return true;
315 1 : if (is_std_function_p (fndecl)
316 1 : && fs.contains_name_p (IDENTIFIER_POINTER (DECL_NAME (fndecl))))
317 : return true;
318 :
319 : return false;
320 : }
321 :
322 : /* Implementation of state_machine::on_stmt vfunc for signal_state_machine. */
323 :
324 : bool
325 263888 : signal_state_machine::on_stmt (sm_context &sm_ctxt,
326 : const gimple *stmt) const
327 : {
328 263888 : const state_t global_state = sm_ctxt.get_global_state ();
329 263888 : if (global_state == m_start)
330 : {
331 250438 : if (const gcall *call = dyn_cast <const gcall *> (stmt))
332 49623 : if (tree callee_fndecl = sm_ctxt.get_fndecl_for_call (*call))
333 45825 : if (is_named_call_p (callee_fndecl, "signal", *call, 2)
334 45825 : || is_std_named_call_p (callee_fndecl, "signal", *call, 2))
335 : {
336 10 : tree handler = gimple_call_arg (call, 1);
337 10 : if (TREE_CODE (handler) == ADDR_EXPR
338 10 : && TREE_CODE (TREE_OPERAND (handler, 0)) == FUNCTION_DECL)
339 : {
340 10 : tree fndecl = TREE_OPERAND (handler, 0);
341 10 : register_signal_handler rsh (*this, fndecl);
342 10 : sm_ctxt.on_custom_transition (&rsh);
343 10 : }
344 : }
345 : }
346 13450 : else if (global_state == m_in_signal_handler)
347 : {
348 24 : if (const gcall *call = dyn_cast <const gcall *> (stmt))
349 12 : if (tree callee_fndecl = sm_ctxt.get_fndecl_for_call (*call))
350 12 : if (signal_unsafe_p (callee_fndecl))
351 11 : if (sm_ctxt.get_global_state () == m_in_signal_handler)
352 11 : sm_ctxt.warn (NULL_TREE,
353 : std::make_unique<signal_unsafe_call>
354 11 : (*this, *call, callee_fndecl));
355 : }
356 :
357 263888 : return false;
358 : }
359 :
360 : bool
361 1420647 : signal_state_machine::can_purge_p (state_t s ATTRIBUTE_UNUSED) const
362 : {
363 1420647 : return true;
364 : }
365 :
366 : } // anonymous namespace
367 :
368 : /* Internal interface to this file. */
369 :
370 : std::unique_ptr<state_machine>
371 3381 : make_signal_state_machine (logger *logger)
372 : {
373 3381 : return std::make_unique<signal_state_machine> (logger);
374 : }
375 :
376 : #if CHECKING_P
377 :
378 : namespace selftest {
379 :
380 : /* Run all of the selftests within this file. */
381 :
382 : void
383 4 : analyzer_sm_signal_cc_tests ()
384 : {
385 4 : function_set fs = get_async_signal_unsafe_fns ();
386 4 : fs.assert_sorted ();
387 4 : fs.assert_sane ();
388 4 : }
389 :
390 : } // namespace selftest
391 :
392 : #endif /* CHECKING_P */
393 :
394 : } // namespace ana
395 :
396 : #endif /* #if ENABLE_ANALYZER */
|