Home | History | Annotate | Line # | Download | only in analyzer
      1 /* An experimental state machine, for tracking bad calls from within
      2    signal handlers.
      3 
      4    Copyright (C) 2019-2024 Free Software Foundation, Inc.
      5    Contributed by David Malcolm <dmalcolm (at) 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 "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"
     41 #include "ordered-hash-map.h"
     42 #include "selftest.h"
     43 #include "analyzer/call-string.h"
     44 #include "analyzer/program-point.h"
     45 #include "analyzer/store.h"
     46 #include "analyzer/region-model.h"
     47 #include "analyzer/program-state.h"
     48 #include "analyzer/checker-path.h"
     49 #include "cfg.h"
     50 #include "gimple-iterator.h"
     51 #include "cgraph.h"
     52 #include "analyzer/supergraph.h"
     53 #include "analyzer/diagnostic-manager.h"
     54 #include "shortest-paths.h"
     55 #include "analyzer/exploded-graph.h"
     56 #include "analyzer/function-set.h"
     57 #include "analyzer/analyzer-selftests.h"
     58 
     59 #if ENABLE_ANALYZER
     60 
     61 namespace ana {
     62 
     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 
     73   bool inherited_state_p () const final override { return false; }
     74 
     75   bool on_stmt (sm_context *sm_ctxt,
     76 		const supernode *node,
     77 		const gimple *stmt) const final override;
     78 
     79   bool can_purge_p (state_t s) const final override;
     80 
     81   /* These states are "global", rather than per-expression.  */
     82 
     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 
    104   const char *get_kind () const final override { return "signal_unsafe_call"; }
    105 
    106   bool operator== (const signal_unsafe_call &other) const
    107   {
    108     return m_unsafe_call == other.m_unsafe_call;
    109   }
    110 
    111   int get_controlling_option () const final override
    112   {
    113     return OPT_Wanalyzer_unsafe_call_within_signal_handler;
    114   }
    115 
    116   bool emit (diagnostic_emission_context &ctxt) final override
    117   {
    118     auto_diagnostic_group d;
    119     /* CWE-479: Signal Handler Use of a Non-reentrant Function.  */
    120     ctxt.add_cwe (479);
    121     if (ctxt.warn ("call to %qD from within signal handler",
    122 		   m_unsafe_fndecl))
    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;
    141   }
    142 
    143   label_text describe_state_change (const evdesc::state_change &change)
    144     final override
    145   {
    146     if (change.is_global_p ()
    147 	&& change.m_new_state == m_sm.m_in_signal_handler)
    148       {
    149 	const function *handler = change.m_event.get_dest_function ();
    150 	gcc_assert (handler);
    151 	return change.formatted_print ("registering %qD as signal handler",
    152 				       handler->decl);
    153       }
    154     return label_text ();
    155   }
    156 
    157   label_text describe_final_event (const evdesc::final_event &ev) final override
    158   {
    159     return ev.formatted_print ("call to %qD from within signal handler",
    160 			       m_unsafe_fndecl);
    161   }
    162 
    163 private:
    164   const signal_state_machine &m_sm;
    165   const gcall *m_unsafe_call;
    166   tree m_unsafe_fndecl;
    167 
    168   /* Returns a replacement function as text if it exists.  Currently
    169      only "exit" has a signal-safe replacement "_exit", which does
    170      slightly less, but can be used in a signal handler.  */
    171   const char *
    172   get_replacement_fn ()
    173   {
    174     gcc_assert (m_unsafe_fndecl && DECL_P (m_unsafe_fndecl));
    175 
    176     if (id_equal ("exit", DECL_NAME (m_unsafe_fndecl)))
    177       return "_exit";
    178 
    179     return NULL;
    180   }
    181 };
    182 
    183 /* signal_state_machine's ctor.  */
    184 
    185 signal_state_machine::signal_state_machine (logger *logger)
    186 : state_machine ("signal", logger),
    187   m_in_signal_handler (add_state ("in_signal_handler")),
    188   m_stop (add_state ("stop"))
    189 {
    190 }
    191 
    192 /* Update MODEL for edges that simulate HANDLER_FUN being called as
    193    an signal-handler in response to a signal.  */
    194 
    195 static void
    196 update_model_for_signal_handler (region_model *model,
    197 				 const function &handler_fun)
    198 {
    199   gcc_assert (model);
    200   /* Purge all state within MODEL.  */
    201   *model = region_model (model->get_manager ());
    202   model->push_frame (handler_fun, NULL, NULL);
    203 }
    204 
    205 /* Custom exploded_edge info: entry into a signal-handler.  */
    206 
    207 class signal_delivery_edge_info_t : public custom_edge_info
    208 {
    209 public:
    210   void print (pretty_printer *pp) const final override
    211   {
    212     pp_string (pp, "signal delivered");
    213   }
    214 
    215   json::object *to_json () const
    216   {
    217     json::object *custom_obj = new json::object ();
    218     return custom_obj;
    219   }
    220 
    221   bool update_model (region_model *model,
    222 		     const exploded_edge *eedge,
    223 		     region_model_context *) const final override
    224   {
    225     gcc_assert (eedge);
    226     gcc_assert (eedge->m_dest->get_function ());
    227     update_model_for_signal_handler (model,
    228 				     *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        (event_loc_info (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 		    true, /* assume does work  */
    283 		    make_unique<signal_delivery_edge_info_t> ());
    284   }
    285 
    286   const signal_state_machine &m_sm;
    287   tree m_fndecl;
    288 };
    289 
    290 /* Get a set of functions that are known to be unsafe to call from an
    291    async signal handler.  */
    292 
    293 static function_set
    294 get_async_signal_unsafe_fns ()
    295 {
    296   // TODO: populate this list more fully
    297   static const char * const async_signal_unsafe_fns[] = {
    298     /* This array must be kept sorted.  */
    299     "exit",
    300     "fprintf",
    301     "free",
    302     "malloc",
    303     "printf",
    304     "snprintf",
    305     "sprintf",
    306     "vfprintf",
    307     "vprintf",
    308     "vsnprintf",
    309     "vsprintf"
    310   };
    311   const size_t count = ARRAY_SIZE (async_signal_unsafe_fns);
    312   function_set fs (async_signal_unsafe_fns, count);
    313   return fs;
    314 }
    315 
    316 /* Return true if FNDECL is known to be unsafe to call from a signal
    317    handler.  */
    318 
    319 static bool
    320 signal_unsafe_p (tree fndecl)
    321 {
    322   function_set fs = get_async_signal_unsafe_fns ();
    323   if (fs.contains_decl_p (fndecl))
    324     return true;
    325   if (is_std_function_p (fndecl)
    326       && fs.contains_name_p (IDENTIFIER_POINTER (DECL_NAME (fndecl))))
    327     return true;
    328 
    329   return false;
    330 }
    331 
    332 /* Implementation of state_machine::on_stmt vfunc for signal_state_machine.  */
    333 
    334 bool
    335 signal_state_machine::on_stmt (sm_context *sm_ctxt,
    336 			       const supernode *node,
    337 			       const gimple *stmt) const
    338 {
    339   const state_t global_state = sm_ctxt->get_global_state ();
    340   if (global_state == m_start)
    341     {
    342       if (const gcall *call = dyn_cast <const gcall *> (stmt))
    343 	if (tree callee_fndecl = sm_ctxt->get_fndecl_for_call (call))
    344 	  if (is_named_call_p (callee_fndecl, "signal", call, 2)
    345 	      || is_std_named_call_p (callee_fndecl, "signal", call, 2))
    346 	    {
    347 	      tree handler = gimple_call_arg (call, 1);
    348 	      if (TREE_CODE (handler) == ADDR_EXPR
    349 		  && TREE_CODE (TREE_OPERAND (handler, 0)) == FUNCTION_DECL)
    350 		{
    351 		  tree fndecl = TREE_OPERAND (handler, 0);
    352 		  register_signal_handler rsh (*this, fndecl);
    353 		  sm_ctxt->on_custom_transition (&rsh);
    354 		}
    355 	    }
    356     }
    357   else if (global_state == m_in_signal_handler)
    358     {
    359       if (const gcall *call = dyn_cast <const gcall *> (stmt))
    360 	if (tree callee_fndecl = sm_ctxt->get_fndecl_for_call (call))
    361 	  if (signal_unsafe_p (callee_fndecl))
    362 	    if (sm_ctxt->get_global_state () == m_in_signal_handler)
    363 	      sm_ctxt->warn (node, stmt, NULL_TREE,
    364 			     make_unique<signal_unsafe_call>
    365 			       (*this, call, callee_fndecl));
    366     }
    367 
    368   return false;
    369 }
    370 
    371 bool
    372 signal_state_machine::can_purge_p (state_t s ATTRIBUTE_UNUSED) const
    373 {
    374   return true;
    375 }
    376 
    377 } // anonymous namespace
    378 
    379 /* Internal interface to this file. */
    380 
    381 state_machine *
    382 make_signal_state_machine (logger *logger)
    383 {
    384   return new signal_state_machine (logger);
    385 }
    386 
    387 #if CHECKING_P
    388 
    389 namespace selftest {
    390 
    391 /* Run all of the selftests within this file.  */
    392 
    393 void
    394 analyzer_sm_signal_cc_tests ()
    395 {
    396   function_set fs = get_async_signal_unsafe_fns ();
    397   fs.assert_sorted ();
    398   fs.assert_sane ();
    399 }
    400 
    401 } // namespace selftest
    402 
    403 #endif /* CHECKING_P */
    404 
    405 } // namespace ana
    406 
    407 #endif /* #if ENABLE_ANALYZER */
    408