LCOV - code coverage report
Current view: top level - gcc/analyzer - sm-signal.cc (source / functions) Coverage Total Hit
Test: gcc.info Lines: 95.8 % 118 113
Test Date: 2024-11-30 13:30:02 Functions: 89.5 % 19 17
Legend: Lines: hit not hit | Branches: + taken - not taken # not executed Branches: - 0 0

             Branch data     Line data    Source code
       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@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_VECTOR
      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-core.h"
      35                 :             : #include "diagnostic-path.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                 :      992645 :   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                 :           0 : class signal_unsafe_call
      95                 :             :   : public pending_diagnostic_subclass<signal_unsafe_call>
      96                 :             : {
      97                 :             : public:
      98                 :          18 :   signal_unsafe_call (const signal_state_machine &sm, const gcall *unsafe_call,
      99                 :             :                       tree unsafe_fndecl)
     100                 :          18 :   : m_sm (sm), m_unsafe_call (unsafe_call), m_unsafe_fndecl (unsafe_fndecl)
     101                 :             :   {
     102                 :          18 :     gcc_assert (m_unsafe_fndecl);
     103                 :             :   }
     104                 :             : 
     105                 :          65 :   const char *get_kind () const final override { return "signal_unsafe_call"; }
     106                 :             : 
     107                 :          18 :   bool operator== (const signal_unsafe_call &other) const
     108                 :             :   {
     109                 :          18 :     return m_unsafe_call == other.m_unsafe_call;
     110                 :             :   }
     111                 :             : 
     112                 :          29 :   int get_controlling_option () const final override
     113                 :             :   {
     114                 :          29 :     return OPT_Wanalyzer_unsafe_call_within_signal_handler;
     115                 :             :   }
     116                 :             : 
     117                 :          11 :   bool emit (diagnostic_emission_context &ctxt) final override
     118                 :             :   {
     119                 :          11 :     auto_diagnostic_group d;
     120                 :             :     /* CWE-479: Signal Handler Use of a Non-reentrant Function.  */
     121                 :          11 :     ctxt.add_cwe (479);
     122                 :          11 :     if (ctxt.warn ("call to %qD from within signal handler",
     123                 :             :                    m_unsafe_fndecl))
     124                 :             :       {
     125                 :             :         /* If we know a possible alternative function, add a note
     126                 :             :            suggesting the replacement.  */
     127                 :          11 :         if (const char *replacement = get_replacement_fn ())
     128                 :             :           {
     129                 :           2 :             location_t note_loc = gimple_location (m_unsafe_call);
     130                 :             :             /* It would be nice to add a fixit, but the gimple call
     131                 :             :                location covers the whole call expression.  It isn't
     132                 :             :                currently possible to cut this down to just the call
     133                 :             :                symbol.  So the fixit would replace too much.
     134                 :             :                note_rich_loc.add_fixit_replace (replacement); */
     135                 :           2 :             inform (note_loc,
     136                 :             :                     "%qs is a possible signal-safe alternative for %qD",
     137                 :             :                     replacement, m_unsafe_fndecl);
     138                 :             :           }
     139                 :          11 :         return true;
     140                 :             :       }
     141                 :             :     return false;
     142                 :          11 :   }
     143                 :             : 
     144                 :             :   bool
     145                 :          22 :   describe_state_change (pretty_printer &pp,
     146                 :             :                          const evdesc::state_change &change) final override
     147                 :             :   {
     148                 :          22 :     if (change.is_global_p ()
     149                 :          22 :         && change.m_new_state == m_sm.m_in_signal_handler)
     150                 :             :       {
     151                 :          22 :         const function *handler = change.m_event.get_dest_function ();
     152                 :          22 :         gcc_assert (handler);
     153                 :          22 :         pp_printf (&pp,
     154                 :             :                    "registering %qD as signal handler",
     155                 :          22 :                    handler->decl);
     156                 :          22 :         return true;
     157                 :             :       }
     158                 :             :     return false;
     159                 :             :   }
     160                 :             : 
     161                 :             :   bool
     162                 :          22 :   describe_final_event (pretty_printer &pp,
     163                 :             :                         const evdesc::final_event &) final override
     164                 :             :   {
     165                 :          22 :     pp_printf (&pp,
     166                 :             :                "call to %qD from within signal handler",
     167                 :             :                m_unsafe_fndecl);
     168                 :          22 :     return true;
     169                 :             :   }
     170                 :             : 
     171                 :             : private:
     172                 :             :   const signal_state_machine &m_sm;
     173                 :             :   const gcall *m_unsafe_call;
     174                 :             :   tree m_unsafe_fndecl;
     175                 :             : 
     176                 :             :   /* Returns a replacement function as text if it exists.  Currently
     177                 :             :      only "exit" has a signal-safe replacement "_exit", which does
     178                 :             :      slightly less, but can be used in a signal handler.  */
     179                 :             :   const char *
     180                 :          11 :   get_replacement_fn ()
     181                 :             :   {
     182                 :          11 :     gcc_assert (m_unsafe_fndecl && DECL_P (m_unsafe_fndecl));
     183                 :             : 
     184                 :          11 :     if (id_equal ("exit", DECL_NAME (m_unsafe_fndecl)))
     185                 :           2 :       return "_exit";
     186                 :             : 
     187                 :             :     return NULL;
     188                 :             :   }
     189                 :             : };
     190                 :             : 
     191                 :             : /* signal_state_machine's ctor.  */
     192                 :             : 
     193                 :        3201 : signal_state_machine::signal_state_machine (logger *logger)
     194                 :             : : state_machine ("signal", logger),
     195                 :        6402 :   m_in_signal_handler (add_state ("in_signal_handler")),
     196                 :        3201 :   m_stop (add_state ("stop"))
     197                 :             : {
     198                 :        3201 : }
     199                 :             : 
     200                 :             : /* Update MODEL for edges that simulate HANDLER_FUN being called as
     201                 :             :    an signal-handler in response to a signal.  */
     202                 :             : 
     203                 :             : static void
     204                 :          28 : update_model_for_signal_handler (region_model *model,
     205                 :             :                                  const function &handler_fun)
     206                 :             : {
     207                 :          28 :   gcc_assert (model);
     208                 :             :   /* Purge all state within MODEL.  */
     209                 :          28 :   *model = region_model (model->get_manager ());
     210                 :          28 :   model->push_frame (handler_fun, NULL, NULL);
     211                 :          28 : }
     212                 :             : 
     213                 :             : /* Custom exploded_edge info: entry into a signal-handler.  */
     214                 :             : 
     215                 :          10 : class signal_delivery_edge_info_t : public custom_edge_info
     216                 :             : {
     217                 :             : public:
     218                 :           0 :   void print (pretty_printer *pp) const final override
     219                 :             :   {
     220                 :           0 :     pp_string (pp, "signal delivered");
     221                 :           0 :   }
     222                 :             : 
     223                 :          18 :   bool update_model (region_model *model,
     224                 :             :                      const exploded_edge *eedge,
     225                 :             :                      region_model_context *) const final override
     226                 :             :   {
     227                 :          18 :     gcc_assert (eedge);
     228                 :          18 :     gcc_assert (eedge->m_dest->get_function ());
     229                 :          36 :     update_model_for_signal_handler (model,
     230                 :          18 :                                      *eedge->m_dest->get_function ());
     231                 :          18 :     return true;
     232                 :             :   }
     233                 :             : 
     234                 :          11 :   void add_events_to_path (checker_path *emission_path,
     235                 :             :                            const exploded_edge &eedge ATTRIBUTE_UNUSED)
     236                 :             :     const final override
     237                 :             :   {
     238                 :          11 :     emission_path->add_event
     239                 :          11 :       (make_unique<precanned_custom_event>
     240                 :          22 :        (event_loc_info (UNKNOWN_LOCATION, NULL_TREE, 0),
     241                 :             :         "later on,"
     242                 :             :         " when the signal is delivered to the process"));
     243                 :          11 :   }
     244                 :             : };
     245                 :             : 
     246                 :             : /* Concrete subclass of custom_transition for modeling registration of a
     247                 :             :    signal handler and the signal handler later being called.  */
     248                 :             : 
     249                 :          10 : class register_signal_handler : public custom_transition
     250                 :             : {
     251                 :             : public:
     252                 :          10 :   register_signal_handler (const signal_state_machine &sm,
     253                 :             :                            tree fndecl)
     254                 :          10 :   : m_sm (sm), m_fndecl (fndecl) {}
     255                 :             : 
     256                 :             :   /* Model a signal-handler FNDECL being called at some later point
     257                 :             :      by injecting an edge to a new function-entry node with an empty
     258                 :             :      callstring, setting the 'in-signal-handler' global state
     259                 :             :      on the node.  */
     260                 :          10 :   void impl_transition (exploded_graph *eg,
     261                 :             :                         exploded_node *src_enode,
     262                 :             :                         int sm_idx) final override
     263                 :             :   {
     264                 :          10 :     function *handler_fun = DECL_STRUCT_FUNCTION (m_fndecl);
     265                 :          10 :     if (!handler_fun)
     266                 :           0 :       return;
     267                 :          10 :     const extrinsic_state &ext_state = eg->get_ext_state ();
     268                 :          10 :     program_point entering_handler
     269                 :          10 :       = program_point::from_function_entry (*ext_state.get_model_manager (),
     270                 :             :                                             eg->get_supergraph (),
     271                 :             :                                             *handler_fun);
     272                 :             : 
     273                 :          10 :     program_state state_entering_handler (ext_state);
     274                 :          10 :     update_model_for_signal_handler (state_entering_handler.m_region_model,
     275                 :             :                                      *handler_fun);
     276                 :          10 :     state_entering_handler.m_checker_states[sm_idx]->set_global_state
     277                 :          10 :       (m_sm.m_in_signal_handler);
     278                 :             : 
     279                 :          10 :     exploded_node *dst_enode = eg->get_or_create_node (entering_handler,
     280                 :             :                                                        state_entering_handler,
     281                 :             :                                                        src_enode);
     282                 :          10 :     if (dst_enode)
     283                 :          10 :       eg->add_edge (src_enode, dst_enode, NULL, /*state_change (),*/
     284                 :             :                     true, /* assume does work  */
     285                 :          20 :                     make_unique<signal_delivery_edge_info_t> ());
     286                 :          10 :   }
     287                 :             : 
     288                 :             :   const signal_state_machine &m_sm;
     289                 :             :   tree m_fndecl;
     290                 :             : };
     291                 :             : 
     292                 :             : /* Get a set of functions that are known to be unsafe to call from an
     293                 :             :    async signal handler.  */
     294                 :             : 
     295                 :             : static function_set
     296                 :          28 : get_async_signal_unsafe_fns ()
     297                 :             : {
     298                 :             :   // TODO: populate this list more fully
     299                 :          28 :   static const char * const async_signal_unsafe_fns[] = {
     300                 :             :     /* This array must be kept sorted.  */
     301                 :             :     "exit",
     302                 :             :     "fprintf",
     303                 :             :     "free",
     304                 :             :     "malloc",
     305                 :             :     "printf",
     306                 :             :     "snprintf",
     307                 :             :     "sprintf",
     308                 :             :     "vfprintf",
     309                 :             :     "vprintf",
     310                 :             :     "vsnprintf",
     311                 :             :     "vsprintf"
     312                 :             :   };
     313                 :          28 :   const size_t count = ARRAY_SIZE (async_signal_unsafe_fns);
     314                 :          28 :   function_set fs (async_signal_unsafe_fns, count);
     315                 :          28 :   return fs;
     316                 :             : }
     317                 :             : 
     318                 :             : /* Return true if FNDECL is known to be unsafe to call from a signal
     319                 :             :    handler.  */
     320                 :             : 
     321                 :             : static bool
     322                 :          24 : signal_unsafe_p (tree fndecl)
     323                 :             : {
     324                 :          24 :   function_set fs = get_async_signal_unsafe_fns ();
     325                 :          24 :   if (fs.contains_decl_p (fndecl))
     326                 :             :     return true;
     327                 :           6 :   if (is_std_function_p (fndecl)
     328                 :           6 :       && fs.contains_name_p (IDENTIFIER_POINTER (DECL_NAME (fndecl))))
     329                 :             :     return true;
     330                 :             : 
     331                 :             :   return false;
     332                 :             : }
     333                 :             : 
     334                 :             : /* Implementation of state_machine::on_stmt vfunc for signal_state_machine.  */
     335                 :             : 
     336                 :             : bool
     337                 :      267622 : signal_state_machine::on_stmt (sm_context &sm_ctxt,
     338                 :             :                                const supernode *node,
     339                 :             :                                const gimple *stmt) const
     340                 :             : {
     341                 :      267622 :   const state_t global_state = sm_ctxt.get_global_state ();
     342                 :      267622 :   if (global_state == m_start)
     343                 :             :     {
     344                 :      237816 :       if (const gcall *call = dyn_cast <const gcall *> (stmt))
     345                 :       55507 :         if (tree callee_fndecl = sm_ctxt.get_fndecl_for_call (call))
     346                 :       54146 :           if (is_named_call_p (callee_fndecl, "signal", call, 2)
     347                 :       54146 :               || is_std_named_call_p (callee_fndecl, "signal", call, 2))
     348                 :             :             {
     349                 :          10 :               tree handler = gimple_call_arg (call, 1);
     350                 :          10 :               if (TREE_CODE (handler) == ADDR_EXPR
     351                 :          10 :                   && TREE_CODE (TREE_OPERAND (handler, 0)) == FUNCTION_DECL)
     352                 :             :                 {
     353                 :          10 :                   tree fndecl = TREE_OPERAND (handler, 0);
     354                 :          10 :                   register_signal_handler rsh (*this, fndecl);
     355                 :          10 :                   sm_ctxt.on_custom_transition (&rsh);
     356                 :          10 :                 }
     357                 :             :             }
     358                 :             :     }
     359                 :       29806 :   else if (global_state == m_in_signal_handler)
     360                 :             :     {
     361                 :          72 :       if (const gcall *call = dyn_cast <const gcall *> (stmt))
     362                 :          24 :         if (tree callee_fndecl = sm_ctxt.get_fndecl_for_call (call))
     363                 :          24 :           if (signal_unsafe_p (callee_fndecl))
     364                 :          18 :             if (sm_ctxt.get_global_state () == m_in_signal_handler)
     365                 :          18 :               sm_ctxt.warn (node, stmt, NULL_TREE,
     366                 :             :                             make_unique<signal_unsafe_call>
     367                 :          36 :                              (*this, call, callee_fndecl));
     368                 :             :     }
     369                 :             : 
     370                 :      267622 :   return false;
     371                 :             : }
     372                 :             : 
     373                 :             : bool
     374                 :      992645 : signal_state_machine::can_purge_p (state_t s ATTRIBUTE_UNUSED) const
     375                 :             : {
     376                 :      992645 :   return true;
     377                 :             : }
     378                 :             : 
     379                 :             : } // anonymous namespace
     380                 :             : 
     381                 :             : /* Internal interface to this file. */
     382                 :             : 
     383                 :             : state_machine *
     384                 :        3201 : make_signal_state_machine (logger *logger)
     385                 :             : {
     386                 :        3201 :   return new signal_state_machine (logger);
     387                 :             : }
     388                 :             : 
     389                 :             : #if CHECKING_P
     390                 :             : 
     391                 :             : namespace selftest {
     392                 :             : 
     393                 :             : /* Run all of the selftests within this file.  */
     394                 :             : 
     395                 :             : void
     396                 :           4 : analyzer_sm_signal_cc_tests ()
     397                 :             : {
     398                 :           4 :   function_set fs = get_async_signal_unsafe_fns ();
     399                 :           4 :   fs.assert_sorted ();
     400                 :           4 :   fs.assert_sane ();
     401                 :           4 : }
     402                 :             : 
     403                 :             : } // namespace selftest
     404                 :             : 
     405                 :             : #endif /* CHECKING_P */
     406                 :             : 
     407                 :             : } // namespace ana
     408                 :             : 
     409                 :             : #endif /* #if ENABLE_ANALYZER */
        

Generated by: LCOV version 2.1-beta

LCOV profile is generated on x86_64 machine using following configure options: configure --disable-bootstrap --enable-coverage=opt --enable-languages=c,c++,fortran,go,jit,lto,rust,m2 --enable-host-shared. GCC test suite is run with the built compiler.