LCOV - code coverage report
Current view: top level - gcc/analyzer - sm-sensitive.cc (source / functions) Coverage Total Hit
Test: gcc.info Lines: 90.0 % 70 63
Test Date: 2026-02-28 14:20:25 Functions: 92.9 % 14 13
Legend: Lines:     hit not hit

            Line data    Source code
       1              : /* An experimental state machine, for tracking exposure of sensitive
       2              :    data (e.g. through logging).
       3              :    Copyright (C) 2019-2026 Free Software Foundation, Inc.
       4              :    Contributed by David Malcolm <dmalcolm@redhat.com>.
       5              : 
       6              : This file is part of GCC.
       7              : 
       8              : GCC is free software; you can redistribute it and/or modify it
       9              : under the terms of the GNU General Public License as published by
      10              : the Free Software Foundation; either version 3, or (at your option)
      11              : any later version.
      12              : 
      13              : GCC is distributed in the hope that it will be useful, but
      14              : WITHOUT ANY WARRANTY; without even the implied warranty of
      15              : MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
      16              : General Public License for more details.
      17              : 
      18              : You should have received a copy of the GNU General Public License
      19              : along with GCC; see the file COPYING3.  If not see
      20              : <http://www.gnu.org/licenses/>.  */
      21              : 
      22              : #include "analyzer/common.h"
      23              : 
      24              : #include "diagnostics/event-id.h"
      25              : 
      26              : #include "analyzer/analyzer-logging.h"
      27              : #include "analyzer/sm.h"
      28              : #include "analyzer/pending-diagnostic.h"
      29              : 
      30              : #if ENABLE_ANALYZER
      31              : 
      32              : namespace ana {
      33              : 
      34              : namespace {
      35              : 
      36              : /* An experimental state machine, for tracking exposure of sensitive
      37              :    data (e.g. through logging).  */
      38              : 
      39              : class sensitive_state_machine : public state_machine
      40              : {
      41              : public:
      42              :   sensitive_state_machine (logger *logger);
      43              : 
      44      1445655 :   bool inherited_state_p () const final override { return true; }
      45              : 
      46              :   bool on_stmt (sm_context &sm_ctxt,
      47              :                 const gimple *stmt) const final override;
      48              : 
      49              :   bool can_purge_p (state_t s) const final override;
      50              : 
      51              :   /* State for "sensitive" data, such as a password.  */
      52              :   state_t m_sensitive;
      53              : 
      54              :   /* Stop state, for a value we don't want to track any more.  */
      55              :   state_t m_stop;
      56              : 
      57              : private:
      58              :   void warn_for_any_exposure (sm_context &sm_ctxt,
      59              :                               tree arg) const;
      60              : };
      61              : 
      62            0 : class exposure_through_output_file
      63              :   : public pending_diagnostic_subclass<exposure_through_output_file>
      64              : {
      65              : public:
      66            6 :   exposure_through_output_file (const sensitive_state_machine &sm, tree arg)
      67            6 :   : m_sm (sm), m_arg (arg)
      68              :   {}
      69              : 
      70           75 :   const char *get_kind () const final override
      71              :   {
      72           75 :     return "exposure_through_output_file";
      73              :   }
      74              : 
      75            6 :   bool operator== (const exposure_through_output_file &other) const
      76              :   {
      77            6 :     return same_tree_p (m_arg, other.m_arg);
      78              :   }
      79              : 
      80           12 :   int get_controlling_option () const final override
      81              :   {
      82           12 :     return OPT_Wanalyzer_exposure_through_output_file;
      83              :   }
      84              : 
      85            6 :   bool emit (diagnostic_emission_context &ctxt) final override
      86              :   {
      87              :     /* CWE-532: Information Exposure Through Log Files */
      88            6 :     ctxt.add_cwe (532);
      89            6 :     return ctxt.warn ("sensitive value %qE written to output file",
      90            6 :                       m_arg);
      91              :   }
      92              : 
      93              :   bool
      94           12 :   describe_state_change (pretty_printer &pp,
      95              :                          const evdesc::state_change &change) final override
      96              :   {
      97           12 :     if (change.m_new_state == m_sm.m_sensitive)
      98              :       {
      99           12 :         m_first_sensitive_event = change.m_event_id;
     100           12 :         pp_string (&pp, "sensitive value acquired here");
     101           12 :         return true;
     102              :       }
     103              :     return false;
     104              :   }
     105              : 
     106              :   diagnostics::paths::event::meaning
     107            0 :   get_meaning_for_state_change (const evdesc::state_change &change)
     108              :     const final override
     109              :   {
     110            0 :     using event = diagnostics::paths::event;
     111              : 
     112            0 :     if (change.m_new_state == m_sm.m_sensitive)
     113            0 :       return event::meaning (event::verb::acquire, event::noun::sensitive);
     114            0 :     return event::meaning ();
     115              :   }
     116              :   bool
     117            2 :   describe_call_with_state (pretty_printer &pp,
     118              :                             const evdesc::call_with_state &info) final override
     119              :   {
     120            2 :     if (info.m_state == m_sm.m_sensitive)
     121              :       {
     122            2 :         pp_printf (&pp,
     123              :                    "passing sensitive value %qE in call to %qE from %qE",
     124            2 :                    info.m_expr, info.m_callee_fndecl, info.m_caller_fndecl);
     125            2 :         return true;
     126              :       }
     127              :     return false;
     128              :   }
     129              : 
     130              :   bool
     131            2 :   describe_return_of_state (pretty_printer &pp,
     132              :                             const evdesc::return_of_state &info) final override
     133              :   {
     134            2 :     if (info.m_state == m_sm.m_sensitive)
     135              :       {
     136            2 :         pp_printf (&pp,
     137              :                    "returning sensitive value to %qE from %qE",
     138            2 :                    info.m_caller_fndecl, info.m_callee_fndecl);
     139            2 :         return true;
     140              :       }
     141              :     return false;
     142              :   }
     143              : 
     144              :   bool
     145           12 :   describe_final_event (pretty_printer &pp,
     146              :                         const evdesc::final_event &) final override
     147              :   {
     148           12 :     if (m_first_sensitive_event.known_p ())
     149           12 :       pp_printf (&pp,
     150              :                  "sensitive value %qE written to output file"
     151              :                  "; acquired at %@",
     152              :                  m_arg, &m_first_sensitive_event);
     153              :     else
     154            0 :       pp_printf (&pp,
     155              :                  "sensitive value %qE written to output file",
     156              :                  m_arg);
     157           12 :     return true;
     158              :   }
     159              : 
     160              : private:
     161              :   const sensitive_state_machine &m_sm;
     162              :   tree m_arg;
     163              :   diagnostics::paths::event_id_t m_first_sensitive_event;
     164              : };
     165              : 
     166              : /* sensitive_state_machine's ctor.  */
     167              : 
     168         3377 : sensitive_state_machine::sensitive_state_machine (logger *logger)
     169              : : state_machine ("sensitive", logger),
     170         6754 :   m_sensitive (add_state ("sensitive")),
     171         3377 :   m_stop (add_state ("stop"))
     172              : {
     173         3377 : }
     174              : 
     175              : /* Warn about an exposure at NODE and STMT if ARG is in the "sensitive"
     176              :    state.  */
     177              : 
     178              : void
     179         1818 : sensitive_state_machine::warn_for_any_exposure (sm_context &sm_ctxt,
     180              :                                                 tree arg) const
     181              : {
     182         1818 :   if (sm_ctxt.get_state (arg) == m_sensitive)
     183              :     {
     184            6 :       tree diag_arg = sm_ctxt.get_diagnostic_tree (arg);
     185            6 :       sm_ctxt.warn (arg,
     186            6 :                     std::make_unique<exposure_through_output_file> (*this,
     187              :                                                                     diag_arg));
     188              :     }
     189         1818 : }
     190              : 
     191              : /* Implementation of state_machine::on_stmt vfunc for
     192              :    sensitive_state_machine.  */
     193              : 
     194              : bool
     195       263888 : sensitive_state_machine::on_stmt (sm_context &sm_ctxt,
     196              :                                   const gimple *stmt) const
     197              : {
     198       263888 :   if (const gcall *call = dyn_cast <const gcall *> (stmt))
     199        49635 :     if (tree callee_fndecl = sm_ctxt.get_fndecl_for_call (*call))
     200              :       {
     201        45837 :         if (is_named_call_p (callee_fndecl, "getpass", *call, 1))
     202              :           {
     203            7 :             tree lhs = gimple_call_lhs (call);
     204            7 :             if (lhs)
     205            7 :               sm_ctxt.on_transition (lhs, m_start, m_sensitive);
     206            7 :             return true;
     207              :           }
     208        45830 :         else if (is_named_call_p (callee_fndecl, "fprintf")
     209        45830 :                  || is_named_call_p (callee_fndecl, "printf"))
     210              :           {
     211              :             /* Handle a match at any position in varargs.  */
     212         1668 :             for (unsigned idx = 1; idx < gimple_call_num_args (call); idx++)
     213              :               {
     214         1077 :                 tree arg = gimple_call_arg (call, idx);
     215         1077 :                 warn_for_any_exposure (sm_ctxt, arg);
     216              :               }
     217              :             return true;
     218              :           }
     219        45239 :         else if (is_named_call_p (callee_fndecl, "fwrite", *call, 4))
     220              :           {
     221          741 :             tree arg = gimple_call_arg (call, 0);
     222          741 :             warn_for_any_exposure (sm_ctxt, arg);
     223          741 :             return true;
     224              :           }
     225              :         // TODO: ...etc.  This is just a proof-of-concept at this point.
     226              :       }
     227              :   return false;
     228              : }
     229              : 
     230              : bool
     231      1420647 : sensitive_state_machine::can_purge_p (state_t s ATTRIBUTE_UNUSED) const
     232              : {
     233      1420647 :   return true;
     234              : }
     235              : 
     236              : } // anonymous namespace
     237              : 
     238              : /* Internal interface to this file. */
     239              : 
     240              : std::unique_ptr<state_machine>
     241         3377 : make_sensitive_state_machine (logger *logger)
     242              : {
     243         3377 :   return std::make_unique<sensitive_state_machine> (logger);
     244              : }
     245              : 
     246              : } // namespace ana
     247              : 
     248              : #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.