87e21a417158a843837d935927299d17407d3662
[platform/upstream/gcc.git] / gcc / analyzer / sm-signal.cc
1 /* An experimental state machine, for tracking bad calls from within
2    signal handlers.
3
4    Copyright (C) 2019-2022 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 "config.h"
24 #define INCLUDE_MEMORY
25 #include "system.h"
26 #include "coretypes.h"
27 #include "make-unique.h"
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"
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 "ordered-hash-map.h"
43 #include "selftest.h"
44 #include "analyzer/call-string.h"
45 #include "analyzer/program-point.h"
46 #include "analyzer/store.h"
47 #include "analyzer/region-model.h"
48 #include "analyzer/program-state.h"
49 #include "analyzer/checker-path.h"
50 #include "cfg.h"
51 #include "gimple-iterator.h"
52 #include "cgraph.h"
53 #include "analyzer/supergraph.h"
54 #include "analyzer/diagnostic-manager.h"
55 #include "shortest-paths.h"
56 #include "analyzer/exploded-graph.h"
57 #include "analyzer/function-set.h"
58 #include "analyzer/analyzer-selftests.h"
59
60 #if ENABLE_ANALYZER
61
62 namespace ana {
63
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
74   bool inherited_state_p () const final override { return false; }
75
76   bool on_stmt (sm_context *sm_ctxt,
77                 const supernode *node,
78                 const gimple *stmt) const final override;
79
80   bool can_purge_p (state_t s) const final override;
81
82   /* These states are "global", rather than per-expression.  */
83
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
105   const char *get_kind () const final override { return "signal_unsafe_call"; }
106
107   bool operator== (const signal_unsafe_call &other) const
108   {
109     return m_unsafe_call == other.m_unsafe_call;
110   }
111
112   int get_controlling_option () const final override
113   {
114     return OPT_Wanalyzer_unsafe_call_within_signal_handler;
115   }
116
117   bool emit (rich_location *rich_loc) final override
118   {
119     auto_diagnostic_group d;
120     diagnostic_metadata m;
121     /* CWE-479: Signal Handler Use of a Non-reentrant Function.  */
122     m.add_cwe (479);
123     if (warning_meta (rich_loc, m, get_controlling_option (),
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;
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       {
152         function *handler = change.m_event.get_dest_function ();
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;
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   }
183 };
184
185 /* signal_state_machine's ctor.  */
186
187 signal_state_machine::signal_state_machine (logger *logger)
188 : state_machine ("signal", logger)
189 {
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 {
201   gcc_assert (model);
202   /* Purge all state within MODEL.  */
203   *model = region_model (model->get_manager ());
204   model->push_frame (handler_fun, NULL, NULL);
205 }
206
207 /* Custom exploded_edge info: entry into a signal-handler.  */
208
209 class signal_delivery_edge_info_t : public custom_edge_info
210 {
211 public:
212   void print (pretty_printer *pp) const final override
213   {
214     pp_string (pp, "signal delivered");
215   }
216
217   json::object *to_json () const
218   {
219     json::object *custom_obj = new json::object ();
220     return custom_obj;
221   }
222
223   bool update_model (region_model *model,
224                      const exploded_edge *eedge,
225                      region_model_context *) const final override
226   {
227     gcc_assert (eedge);
228     update_model_for_signal_handler (model, eedge->m_dest->get_function ());
229     return true;
230   }
231
232   void add_events_to_path (checker_path *emission_path,
233                            const exploded_edge &eedge ATTRIBUTE_UNUSED)
234     const final override
235   {
236     emission_path->add_event
237       (make_unique<precanned_custom_event>
238        (UNKNOWN_LOCATION, NULL_TREE, 0,
239         "later on,"
240         " when the signal is delivered to the process"));
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     const extrinsic_state &ext_state = eg->get_ext_state ();
266     program_point entering_handler
267       = program_point::from_function_entry (*ext_state.get_model_manager (),
268                                             eg->get_supergraph (),
269                                             handler_fun);
270
271     program_state state_entering_handler (ext_state);
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,
279                                                        src_enode);
280     if (dst_enode)
281       eg->add_edge (src_enode, dst_enode, NULL, /*state_change (),*/
282                     make_unique<signal_delivery_edge_info_t> ());
283   }
284
285   const signal_state_machine &m_sm;
286   tree m_fndecl;
287 };
288
289 /* Get a set of functions that are known to be unsafe to call from an
290    async signal handler.  */
291
292 static function_set
293 get_async_signal_unsafe_fns ()
294 {
295   // TODO: populate this list more fully
296   static const char * const async_signal_unsafe_fns[] = {
297     /* This array must be kept sorted.  */
298     "exit",
299     "fprintf",
300     "free",
301     "malloc",
302     "printf",
303     "snprintf",
304     "sprintf",
305     "vfprintf",
306     "vprintf",
307     "vsnprintf",
308     "vsprintf"
309   };
310   const size_t count = ARRAY_SIZE (async_signal_unsafe_fns);
311   function_set fs (async_signal_unsafe_fns, count);
312   return fs;
313 }
314
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);
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))
354             if (sm_ctxt->get_global_state () == m_in_signal_handler)
355               sm_ctxt->warn (node, stmt, NULL_TREE,
356                              make_unique<signal_unsafe_call>
357                                (*this, call, callee_fndecl));
358     }
359
360   return false;
361 }
362
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
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
395 #endif /* CHECKING_P */
396
397 } // namespace ana
398
399 #endif /* #if ENABLE_ANALYZER */