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