]>
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" | |
35 | #include "diagnostic-metadata.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" | |
757bf1df DM |
42 | #include "ordered-hash-map.h" |
43 | #include "selftest.h" | |
808f4dfe DM |
44 | #include "analyzer/call-string.h" |
45 | #include "analyzer/program-point.h" | |
46 | #include "analyzer/store.h" | |
757bf1df DM |
47 | #include "analyzer/region-model.h" |
48 | #include "analyzer/program-state.h" | |
49 | #include "analyzer/checker-path.h" | |
757bf1df DM |
50 | #include "cfg.h" |
51 | #include "gimple-iterator.h" | |
52 | #include "cgraph.h" | |
53 | #include "analyzer/supergraph.h" | |
757bf1df DM |
54 | #include "analyzer/diagnostic-manager.h" |
55 | #include "shortest-paths.h" | |
56 | #include "analyzer/exploded-graph.h" | |
4804c5fe DM |
57 | #include "analyzer/function-set.h" |
58 | #include "analyzer/analyzer-selftests.h" | |
757bf1df DM |
59 | |
60 | #if ENABLE_ANALYZER | |
61 | ||
75038aa6 DM |
62 | namespace ana { |
63 | ||
757bf1df DM |
64 | namespace { |
65 | ||
66 | /* An experimental state machine, for tracking calls to async-signal-unsafe | |
67 | functions from within signal handlers. */ | |
68 | ||
69 | class signal_state_machine : public state_machine | |
70 | { | |
71 | public: | |
72 | signal_state_machine (logger *logger); | |
73 | ||
ff171cb1 | 74 | bool inherited_state_p () const final override { return false; } |
757bf1df DM |
75 | |
76 | bool on_stmt (sm_context *sm_ctxt, | |
77 | const supernode *node, | |
ff171cb1 | 78 | const gimple *stmt) const final override; |
757bf1df | 79 | |
ff171cb1 | 80 | bool can_purge_p (state_t s) const final override; |
757bf1df DM |
81 | |
82 | /* These states are "global", rather than per-expression. */ | |
83 | ||
757bf1df DM |
84 | /* State for when we're in a signal handler. */ |
85 | state_t m_in_signal_handler; | |
86 | ||
87 | /* Stop state. */ | |
88 | state_t m_stop; | |
89 | }; | |
90 | ||
91 | /* Concrete subclass for describing call to an async-signal-unsafe function | |
92 | from a signal handler. */ | |
93 | ||
94 | class signal_unsafe_call | |
95 | : public pending_diagnostic_subclass<signal_unsafe_call> | |
96 | { | |
97 | public: | |
98 | signal_unsafe_call (const signal_state_machine &sm, const gcall *unsafe_call, | |
99 | tree unsafe_fndecl) | |
100 | : m_sm (sm), m_unsafe_call (unsafe_call), m_unsafe_fndecl (unsafe_fndecl) | |
101 | { | |
102 | gcc_assert (m_unsafe_fndecl); | |
103 | } | |
104 | ||
ff171cb1 | 105 | const char *get_kind () const final override { return "signal_unsafe_call"; } |
757bf1df DM |
106 | |
107 | bool operator== (const signal_unsafe_call &other) const | |
108 | { | |
109 | return m_unsafe_call == other.m_unsafe_call; | |
110 | } | |
111 | ||
ff171cb1 | 112 | int get_controlling_option () const final override |
7fd6e36e DM |
113 | { |
114 | return OPT_Wanalyzer_unsafe_call_within_signal_handler; | |
115 | } | |
116 | ||
ff171cb1 | 117 | bool emit (rich_location *rich_loc) final override |
757bf1df | 118 | { |
2221fb6f | 119 | auto_diagnostic_group d; |
757bf1df DM |
120 | diagnostic_metadata m; |
121 | /* CWE-479: Signal Handler Use of a Non-reentrant Function. */ | |
122 | m.add_cwe (479); | |
7fd6e36e | 123 | if (warning_meta (rich_loc, m, get_controlling_option (), |
2221fb6f MW |
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) | |
ff171cb1 | 147 | final override |
757bf1df DM |
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 | ||
ff171cb1 | 159 | label_text describe_final_event (const evdesc::final_event &ev) final override |
757bf1df DM |
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: | |
ff171cb1 | 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, | |
ff171cb1 | 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) | |
ff171cb1 | 234 | const final override |
757bf1df DM |
235 | { |
236 | emission_path->add_event | |
d60b40b8 | 237 | (make_unique<precanned_custom_event> |
e24fe128 | 238 | (event_loc_info (UNKNOWN_LOCATION, NULL_TREE, 0), |
86606d2a DM |
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, | |
ff171cb1 | 260 | int sm_idx) final override |
757bf1df DM |
261 | { |
262 | function *handler_fun = DECL_STRUCT_FUNCTION (m_fndecl); | |
263 | if (!handler_fun) | |
264 | return; | |
bb8e93eb | 265 | const extrinsic_state &ext_state = eg->get_ext_state (); |
757bf1df | 266 | program_point entering_handler |
bb8e93eb DM |
267 | = program_point::from_function_entry (*ext_state.get_model_manager (), |
268 | eg->get_supergraph (), | |
757bf1df DM |
269 | handler_fun); |
270 | ||
bb8e93eb | 271 | program_state state_entering_handler (ext_state); |
757bf1df DM |
272 | update_model_for_signal_handler (state_entering_handler.m_region_model, |
273 | handler_fun); | |
274 | state_entering_handler.m_checker_states[sm_idx]->set_global_state | |
275 | (m_sm.m_in_signal_handler); | |
276 | ||
277 | exploded_node *dst_enode = eg->get_or_create_node (entering_handler, | |
278 | state_entering_handler, | |
808f4dfe | 279 | src_enode); |
757bf1df | 280 | if (dst_enode) |
808f4dfe | 281 | eg->add_edge (src_enode, dst_enode, NULL, /*state_change (),*/ |
ca5ff105 | 282 | make_unique<signal_delivery_edge_info_t> ()); |
757bf1df DM |
283 | } |
284 | ||
285 | const signal_state_machine &m_sm; | |
286 | tree m_fndecl; | |
287 | }; | |
288 | ||
4804c5fe DM |
289 | /* Get a set of functions that are known to be unsafe to call from an |
290 | async signal handler. */ | |
757bf1df | 291 | |
4804c5fe DM |
292 | static function_set |
293 | get_async_signal_unsafe_fns () | |
757bf1df | 294 | { |
4804c5fe DM |
295 | // TODO: populate this list more fully |
296 | static const char * const async_signal_unsafe_fns[] = { | |
297 | /* This array must be kept sorted. */ | |
2221fb6f | 298 | "exit", |
4804c5fe DM |
299 | "fprintf", |
300 | "free", | |
301 | "malloc", | |
302 | "printf", | |
303 | "snprintf", | |
304 | "sprintf", | |
305 | "vfprintf", | |
306 | "vprintf", | |
307 | "vsnprintf", | |
308 | "vsprintf" | |
309 | }; | |
ca32b29e | 310 | const size_t count = ARRAY_SIZE (async_signal_unsafe_fns); |
4804c5fe DM |
311 | function_set fs (async_signal_unsafe_fns, count); |
312 | return fs; | |
16485ea9 | 313 | } |
757bf1df | 314 | |
4804c5fe DM |
315 | /* Return true if FNDECL is known to be unsafe to call from a signal |
316 | handler. */ | |
317 | ||
318 | static bool | |
319 | signal_unsafe_p (tree fndecl) | |
320 | { | |
321 | function_set fs = get_async_signal_unsafe_fns (); | |
322 | return fs.contains_decl_p (fndecl); | |
757bf1df DM |
323 | } |
324 | ||
325 | /* Implementation of state_machine::on_stmt vfunc for signal_state_machine. */ | |
326 | ||
327 | bool | |
328 | signal_state_machine::on_stmt (sm_context *sm_ctxt, | |
329 | const supernode *node, | |
330 | const gimple *stmt) const | |
331 | { | |
332 | const state_t global_state = sm_ctxt->get_global_state (); | |
333 | if (global_state == m_start) | |
334 | { | |
335 | if (const gcall *call = dyn_cast <const gcall *> (stmt)) | |
336 | if (tree callee_fndecl = sm_ctxt->get_fndecl_for_call (call)) | |
337 | if (is_named_call_p (callee_fndecl, "signal", call, 2)) | |
338 | { | |
339 | tree handler = gimple_call_arg (call, 1); | |
340 | if (TREE_CODE (handler) == ADDR_EXPR | |
341 | && TREE_CODE (TREE_OPERAND (handler, 0)) == FUNCTION_DECL) | |
342 | { | |
343 | tree fndecl = TREE_OPERAND (handler, 0); | |
344 | register_signal_handler rsh (*this, fndecl); | |
345 | sm_ctxt->on_custom_transition (&rsh); | |
346 | } | |
347 | } | |
348 | } | |
349 | else if (global_state == m_in_signal_handler) | |
350 | { | |
351 | if (const gcall *call = dyn_cast <const gcall *> (stmt)) | |
352 | if (tree callee_fndecl = sm_ctxt->get_fndecl_for_call (call)) | |
353 | if (signal_unsafe_p (callee_fndecl)) | |
25ef215a DM |
354 | if (sm_ctxt->get_global_state () == m_in_signal_handler) |
355 | sm_ctxt->warn (node, stmt, NULL_TREE, | |
6341f14e DM |
356 | make_unique<signal_unsafe_call> |
357 | (*this, call, callee_fndecl)); | |
757bf1df DM |
358 | } |
359 | ||
360 | return false; | |
361 | } | |
362 | ||
757bf1df DM |
363 | bool |
364 | signal_state_machine::can_purge_p (state_t s ATTRIBUTE_UNUSED) const | |
365 | { | |
366 | return true; | |
367 | } | |
368 | ||
369 | } // anonymous namespace | |
370 | ||
371 | /* Internal interface to this file. */ | |
372 | ||
373 | state_machine * | |
374 | make_signal_state_machine (logger *logger) | |
375 | { | |
376 | return new signal_state_machine (logger); | |
377 | } | |
378 | ||
4804c5fe DM |
379 | #if CHECKING_P |
380 | ||
381 | namespace selftest { | |
382 | ||
383 | /* Run all of the selftests within this file. */ | |
384 | ||
385 | void | |
386 | analyzer_sm_signal_cc_tests () | |
387 | { | |
388 | function_set fs = get_async_signal_unsafe_fns (); | |
389 | fs.assert_sorted (); | |
390 | fs.assert_sane (); | |
391 | } | |
392 | ||
393 | } // namespace selftest | |
394 | ||
c9c8aef4 DM |
395 | #endif /* CHECKING_P */ |
396 | ||
75038aa6 DM |
397 | } // namespace ana |
398 | ||
757bf1df | 399 | #endif /* #if ENABLE_ANALYZER */ |