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: 2026-02-28 14:20:25 Functions: 94.7 % 19 18
Legend: Lines:     hit not hit

            Line data    Source code
       1              : /* An experimental state machine, for tracking bad calls from within
       2              :    signal handlers.
       3              : 
       4              :    Copyright (C) 2019-2026 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 "diagnostics/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      1420647 :   bool inherited_state_p () const final override { return false; }
      64              : 
      65              :   bool  on_stmt (sm_context &sm_ctxt,
      66              :                 const gimple *stmt) const final override;
      67              : 
      68              :   bool can_purge_p (state_t s) const final override;
      69              : 
      70              :   /* These states are "global", rather than per-expression.  */
      71              : 
      72              :   /* State for when we're in a signal handler.  */
      73              :   state_t m_in_signal_handler;
      74              : 
      75              :   /* Stop state.  */
      76              :   state_t m_stop;
      77              : };
      78              : 
      79              : /* Concrete subclass for describing call to an async-signal-unsafe function
      80              :    from a signal handler.  */
      81              : 
      82            0 : class signal_unsafe_call
      83              :   : public pending_diagnostic_subclass<signal_unsafe_call>
      84              : {
      85              : public:
      86           11 :   signal_unsafe_call (const signal_state_machine &sm, const gcall &unsafe_call,
      87              :                       tree unsafe_fndecl)
      88           11 :   : m_sm (sm), m_unsafe_call (unsafe_call), m_unsafe_fndecl (unsafe_fndecl)
      89              :   {
      90           11 :     gcc_assert (m_unsafe_fndecl);
      91              :   }
      92              : 
      93           58 :   const char *get_kind () const final override { return "signal_unsafe_call"; }
      94              : 
      95           11 :   bool operator== (const signal_unsafe_call &other) const
      96              :   {
      97           11 :     return &m_unsafe_call == &other.m_unsafe_call;
      98              :   }
      99              : 
     100           22 :   int get_controlling_option () const final override
     101              :   {
     102           22 :     return OPT_Wanalyzer_unsafe_call_within_signal_handler;
     103              :   }
     104              : 
     105           11 :   bool emit (diagnostic_emission_context &ctxt) final override
     106              :   {
     107           11 :     auto_diagnostic_group d;
     108              :     /* CWE-479: Signal Handler Use of a Non-reentrant Function.  */
     109           11 :     ctxt.add_cwe (479);
     110           11 :     if (ctxt.warn ("call to %qD from within signal handler",
     111              :                    m_unsafe_fndecl))
     112              :       {
     113              :         /* If we know a possible alternative function, add a note
     114              :            suggesting the replacement.  */
     115           11 :         if (const char *replacement = get_replacement_fn ())
     116              :           {
     117            2 :             location_t note_loc = gimple_location (&m_unsafe_call);
     118              :             /* It would be nice to add a fixit, but the gimple call
     119              :                location covers the whole call expression.  It isn't
     120              :                currently possible to cut this down to just the call
     121              :                symbol.  So the fixit would replace too much.
     122              :                note_rich_loc.add_fixit_replace (replacement); */
     123            2 :             inform (note_loc,
     124              :                     "%qs is a possible signal-safe alternative for %qD",
     125              :                     replacement, m_unsafe_fndecl);
     126              :           }
     127           11 :         return true;
     128              :       }
     129              :     return false;
     130           11 :   }
     131              : 
     132              :   bool
     133           22 :   describe_state_change (pretty_printer &pp,
     134              :                          const evdesc::state_change &change) final override
     135              :   {
     136           22 :     if (change.is_global_p ()
     137           22 :         && change.m_new_state == m_sm.m_in_signal_handler)
     138              :       {
     139           22 :         const function *handler = change.m_event.get_dest_function ();
     140           22 :         gcc_assert (handler);
     141           22 :         pp_printf (&pp,
     142              :                    "registering %qD as signal handler",
     143           22 :                    handler->decl);
     144           22 :         return true;
     145              :       }
     146              :     return false;
     147              :   }
     148              : 
     149              :   bool
     150           22 :   describe_final_event (pretty_printer &pp,
     151              :                         const evdesc::final_event &) final override
     152              :   {
     153           22 :     pp_printf (&pp,
     154              :                "call to %qD from within signal handler",
     155              :                m_unsafe_fndecl);
     156           22 :     return true;
     157              :   }
     158              : 
     159              : private:
     160              :   const signal_state_machine &m_sm;
     161              :   const gcall &m_unsafe_call;
     162              :   tree m_unsafe_fndecl;
     163              : 
     164              :   /* Returns a replacement function as text if it exists.  Currently
     165              :      only "exit" has a signal-safe replacement "_exit", which does
     166              :      slightly less, but can be used in a signal handler.  */
     167              :   const char *
     168           11 :   get_replacement_fn ()
     169              :   {
     170           11 :     gcc_assert (m_unsafe_fndecl && DECL_P (m_unsafe_fndecl));
     171              : 
     172           11 :     if (id_equal ("exit", DECL_NAME (m_unsafe_fndecl)))
     173            2 :       return "_exit";
     174              : 
     175              :     return nullptr;
     176              :   }
     177              : };
     178              : 
     179              : /* signal_state_machine's ctor.  */
     180              : 
     181         3381 : signal_state_machine::signal_state_machine (logger *logger)
     182              : : state_machine ("signal", logger),
     183         6762 :   m_in_signal_handler (add_state ("in_signal_handler")),
     184         3381 :   m_stop (add_state ("stop"))
     185              : {
     186         3381 : }
     187              : 
     188              : /* Update MODEL for edges that simulate HANDLER_FUN being called as
     189              :    an signal-handler in response to a signal.  */
     190              : 
     191              : static void
     192           21 : update_model_for_signal_handler (region_model *model,
     193              :                                  const function &handler_fun)
     194              : {
     195           21 :   gcc_assert (model);
     196              :   /* Purge all state within MODEL.  */
     197           21 :   *model = region_model (model->get_manager ());
     198           21 :   model->push_frame (handler_fun, nullptr, nullptr, nullptr);
     199           21 : }
     200              : 
     201              : /* Custom exploded_edge info: entry into a signal-handler.  */
     202              : 
     203           10 : class signal_delivery_edge_info_t : public custom_edge_info
     204              : {
     205              : public:
     206            0 :   void print (pretty_printer *pp) const final override
     207              :   {
     208            0 :     pp_string (pp, "signal delivered");
     209            0 :   }
     210              : 
     211           11 :   bool update_model (region_model *model,
     212              :                      const exploded_edge *eedge,
     213              :                      region_model_context *) const final override
     214              :   {
     215           11 :     gcc_assert (eedge);
     216           11 :     gcc_assert (eedge->m_dest->get_function ());
     217           11 :     update_model_for_signal_handler (model,
     218           11 :                                      *eedge->m_dest->get_function ());
     219           11 :     return true;
     220              :   }
     221              : 
     222           11 :   void add_events_to_path (checker_path *emission_path,
     223              :                            const exploded_edge &eedge ATTRIBUTE_UNUSED,
     224              :                            pending_diagnostic &)
     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           10 :     program_state state_entering_handler (ext_state);
     262           10 :     update_model_for_signal_handler (state_entering_handler.m_region_model,
     263              :                                      *handler_fun);
     264           10 :     state_entering_handler.m_checker_states[sm_idx]->set_global_state
     265           10 :       (m_sm.m_in_signal_handler);
     266              : 
     267           10 :     exploded_node *dst_enode = eg->get_or_create_node (entering_handler,
     268              :                                                        state_entering_handler,
     269              :                                                        src_enode);
     270           10 :     if (dst_enode)
     271           10 :       eg->add_edge (src_enode, dst_enode, nullptr, /*state_change (),*/
     272              :                     true, /* assume does work  */
     273           10 :                     std::make_unique<signal_delivery_edge_info_t> ());
     274           10 :   }
     275              : 
     276              :   const signal_state_machine &m_sm;
     277              :   tree m_fndecl;
     278              : };
     279              : 
     280              : /* Get a set of functions that are known to be unsafe to call from an
     281              :    async signal handler.  */
     282              : 
     283              : static function_set
     284           16 : get_async_signal_unsafe_fns ()
     285              : {
     286              :   // TODO: populate this list more fully
     287           16 :   static const char * const async_signal_unsafe_fns[] = {
     288              :     /* This array must be kept sorted.  */
     289              :     "exit",
     290              :     "fprintf",
     291              :     "free",
     292              :     "malloc",
     293              :     "printf",
     294              :     "snprintf",
     295              :     "sprintf",
     296              :     "vfprintf",
     297              :     "vprintf",
     298              :     "vsnprintf",
     299              :     "vsprintf"
     300              :   };
     301           16 :   const size_t count = ARRAY_SIZE (async_signal_unsafe_fns);
     302           16 :   function_set fs (async_signal_unsafe_fns, count);
     303           16 :   return fs;
     304              : }
     305              : 
     306              : /* Return true if FNDECL is known to be unsafe to call from a signal
     307              :    handler.  */
     308              : 
     309              : static bool
     310           12 : signal_unsafe_p (tree fndecl)
     311              : {
     312           12 :   function_set fs = get_async_signal_unsafe_fns ();
     313           12 :   if (fs.contains_decl_p (fndecl))
     314              :     return true;
     315            1 :   if (is_std_function_p (fndecl)
     316            1 :       && fs.contains_name_p (IDENTIFIER_POINTER (DECL_NAME (fndecl))))
     317              :     return true;
     318              : 
     319              :   return false;
     320              : }
     321              : 
     322              : /* Implementation of state_machine::on_stmt vfunc for signal_state_machine.  */
     323              : 
     324              : bool
     325       263888 : signal_state_machine::on_stmt (sm_context &sm_ctxt,
     326              :                                const gimple *stmt) const
     327              : {
     328       263888 :   const state_t global_state = sm_ctxt.get_global_state ();
     329       263888 :   if (global_state == m_start)
     330              :     {
     331       250438 :       if (const gcall *call = dyn_cast <const gcall *> (stmt))
     332        49623 :         if (tree callee_fndecl = sm_ctxt.get_fndecl_for_call (*call))
     333        45825 :           if (is_named_call_p (callee_fndecl, "signal", *call, 2)
     334        45825 :               || is_std_named_call_p (callee_fndecl, "signal", *call, 2))
     335              :             {
     336           10 :               tree handler = gimple_call_arg (call, 1);
     337           10 :               if (TREE_CODE (handler) == ADDR_EXPR
     338           10 :                   && TREE_CODE (TREE_OPERAND (handler, 0)) == FUNCTION_DECL)
     339              :                 {
     340           10 :                   tree fndecl = TREE_OPERAND (handler, 0);
     341           10 :                   register_signal_handler rsh (*this, fndecl);
     342           10 :                   sm_ctxt.on_custom_transition (&rsh);
     343           10 :                 }
     344              :             }
     345              :     }
     346        13450 :   else if (global_state == m_in_signal_handler)
     347              :     {
     348           24 :       if (const gcall *call = dyn_cast <const gcall *> (stmt))
     349           12 :         if (tree callee_fndecl = sm_ctxt.get_fndecl_for_call (*call))
     350           12 :           if (signal_unsafe_p (callee_fndecl))
     351           11 :             if (sm_ctxt.get_global_state () == m_in_signal_handler)
     352           11 :               sm_ctxt.warn (NULL_TREE,
     353              :                             std::make_unique<signal_unsafe_call>
     354           11 :                              (*this, *call, callee_fndecl));
     355              :     }
     356              : 
     357       263888 :   return false;
     358              : }
     359              : 
     360              : bool
     361      1420647 : signal_state_machine::can_purge_p (state_t s ATTRIBUTE_UNUSED) const
     362              : {
     363      1420647 :   return true;
     364              : }
     365              : 
     366              : } // anonymous namespace
     367              : 
     368              : /* Internal interface to this file. */
     369              : 
     370              : std::unique_ptr<state_machine>
     371         3381 : make_signal_state_machine (logger *logger)
     372              : {
     373         3381 :   return std::make_unique<signal_state_machine> (logger);
     374              : }
     375              : 
     376              : #if CHECKING_P
     377              : 
     378              : namespace selftest {
     379              : 
     380              : /* Run all of the selftests within this file.  */
     381              : 
     382              : void
     383            4 : analyzer_sm_signal_cc_tests ()
     384              : {
     385            4 :   function_set fs = get_async_signal_unsafe_fns ();
     386            4 :   fs.assert_sorted ();
     387            4 :   fs.assert_sane ();
     388            4 : }
     389              : 
     390              : } // namespace selftest
     391              : 
     392              : #endif /* CHECKING_P */
     393              : 
     394              : } // namespace ana
     395              : 
     396              : #endif /* #if ENABLE_ANALYZER */
        

Generated by: LCOV version 2.4-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.