]>
Commit | Line | Data |
---|---|---|
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 | ||
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" | |
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 |
61 | namespace ana { |
62 | ||
757bf1df DM |
63 | namespace { |
64 | ||
65 | /* An experimental state machine, for tracking calls to async-signal-unsafe | |
66 | functions from within signal handlers. */ | |
67 | ||
68 | class signal_state_machine : public state_machine | |
69 | { | |
70 | public: | |
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 | ||
93 | class signal_unsafe_call | |
94 | : public pending_diagnostic_subclass<signal_unsafe_call> | |
95 | { | |
96 | public: | |
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 | ||
162 | private: | |
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 | ||
184 | signal_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 | ||
194 | static void | |
195 | update_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 | 206 | class signal_delivery_edge_info_t : public custom_edge_info |
757bf1df DM |
207 | { |
208 | public: | |
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 | ||
244 | class register_signal_handler : public custom_transition | |
245 | { | |
246 | public: | |
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 |
290 | static function_set |
291 | get_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 | ||
316 | static bool | |
317 | signal_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 | ||
325 | bool | |
326 | signal_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 |
361 | bool |
362 | signal_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 | ||
371 | state_machine * | |
372 | make_signal_state_machine (logger *logger) | |
373 | { | |
374 | return new signal_state_machine (logger); | |
375 | } | |
376 | ||
4804c5fe DM |
377 | #if CHECKING_P |
378 | ||
379 | namespace selftest { | |
380 | ||
381 | /* Run all of the selftests within this file. */ | |
382 | ||
383 | void | |
384 | analyzer_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 */ |