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