d: Add test for PR d/108167 to the testsuite [PR108167]
[official-gcc.git] / gcc / analyzer / sm-sensitive.cc
blobd94d9e03ece6c4b0e08036cdaf76b4b76c6feb6c
1 /* An experimental state machine, for tracking exposure of sensitive
2 data (e.g. through logging).
3 Copyright (C) 2019-2023 Free Software Foundation, Inc.
4 Contributed by David Malcolm <dmalcolm@redhat.com>.
6 This file is part of GCC.
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.
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.
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/>. */
22 #include "config.h"
23 #define INCLUDE_MEMORY
24 #include "system.h"
25 #include "coretypes.h"
26 #include "make-unique.h"
27 #include "tree.h"
28 #include "function.h"
29 #include "basic-block.h"
30 #include "gimple.h"
31 #include "options.h"
32 #include "diagnostic-path.h"
33 #include "diagnostic-metadata.h"
34 #include "analyzer/analyzer.h"
35 #include "diagnostic-event-id.h"
36 #include "analyzer/analyzer-logging.h"
37 #include "analyzer/sm.h"
38 #include "analyzer/pending-diagnostic.h"
40 #if ENABLE_ANALYZER
42 namespace ana {
44 namespace {
46 /* An experimental state machine, for tracking exposure of sensitive
47 data (e.g. through logging). */
49 class sensitive_state_machine : public state_machine
51 public:
52 sensitive_state_machine (logger *logger);
54 bool inherited_state_p () const final override { return true; }
56 bool on_stmt (sm_context *sm_ctxt,
57 const supernode *node,
58 const gimple *stmt) const final override;
60 bool can_purge_p (state_t s) const final override;
62 /* State for "sensitive" data, such as a password. */
63 state_t m_sensitive;
65 /* Stop state, for a value we don't want to track any more. */
66 state_t m_stop;
68 private:
69 void warn_for_any_exposure (sm_context *sm_ctxt,
70 const supernode *node,
71 const gimple *stmt,
72 tree arg) const;
75 class exposure_through_output_file
76 : public pending_diagnostic_subclass<exposure_through_output_file>
78 public:
79 exposure_through_output_file (const sensitive_state_machine &sm, tree arg)
80 : m_sm (sm), m_arg (arg)
83 const char *get_kind () const final override
85 return "exposure_through_output_file";
88 bool operator== (const exposure_through_output_file &other) const
90 return same_tree_p (m_arg, other.m_arg);
93 int get_controlling_option () const final override
95 return OPT_Wanalyzer_exposure_through_output_file;
98 bool emit (rich_location *rich_loc) final override
100 diagnostic_metadata m;
101 /* CWE-532: Information Exposure Through Log Files */
102 m.add_cwe (532);
103 return warning_meta (rich_loc, m, get_controlling_option (),
104 "sensitive value %qE written to output file",
105 m_arg);
108 label_text describe_state_change (const evdesc::state_change &change)
109 final override
111 if (change.m_new_state == m_sm.m_sensitive)
113 m_first_sensitive_event = change.m_event_id;
114 return change.formatted_print ("sensitive value acquired here");
116 return label_text ();
119 diagnostic_event::meaning
120 get_meaning_for_state_change (const evdesc::state_change &change)
121 const final override
123 if (change.m_new_state == m_sm.m_sensitive)
124 return diagnostic_event::meaning (diagnostic_event::VERB_acquire,
125 diagnostic_event::NOUN_sensitive);
126 return diagnostic_event::meaning ();
128 label_text describe_call_with_state (const evdesc::call_with_state &info)
129 final override
131 if (info.m_state == m_sm.m_sensitive)
132 return info.formatted_print
133 ("passing sensitive value %qE in call to %qE from %qE",
134 info.m_expr, info.m_callee_fndecl, info.m_caller_fndecl);
135 return label_text ();
138 label_text describe_return_of_state (const evdesc::return_of_state &info)
139 final override
141 if (info.m_state == m_sm.m_sensitive)
142 return info.formatted_print ("returning sensitive value to %qE from %qE",
143 info.m_caller_fndecl, info.m_callee_fndecl);
144 return label_text ();
147 label_text describe_final_event (const evdesc::final_event &ev) final override
149 if (m_first_sensitive_event.known_p ())
150 return ev.formatted_print ("sensitive value %qE written to output file"
151 "; acquired at %@",
152 m_arg, &m_first_sensitive_event);
153 else
154 return ev.formatted_print ("sensitive value %qE written to output file",
155 m_arg);
158 private:
159 const sensitive_state_machine &m_sm;
160 tree m_arg;
161 diagnostic_event_id_t m_first_sensitive_event;
164 /* sensitive_state_machine's ctor. */
166 sensitive_state_machine::sensitive_state_machine (logger *logger)
167 : state_machine ("sensitive", logger)
169 m_sensitive = add_state ("sensitive");
170 m_stop = add_state ("stop");
173 /* Warn about an exposure at NODE and STMT if ARG is in the "sensitive"
174 state. */
176 void
177 sensitive_state_machine::warn_for_any_exposure (sm_context *sm_ctxt,
178 const supernode *node,
179 const gimple *stmt,
180 tree arg) const
182 if (sm_ctxt->get_state (stmt, arg) == m_sensitive)
184 tree diag_arg = sm_ctxt->get_diagnostic_tree (arg);
185 sm_ctxt->warn (node, stmt, arg,
186 make_unique<exposure_through_output_file> (*this,
187 diag_arg));
191 /* Implementation of state_machine::on_stmt vfunc for
192 sensitive_state_machine. */
194 bool
195 sensitive_state_machine::on_stmt (sm_context *sm_ctxt,
196 const supernode *node,
197 const gimple *stmt) const
199 if (const gcall *call = dyn_cast <const gcall *> (stmt))
200 if (tree callee_fndecl = sm_ctxt->get_fndecl_for_call (call))
202 if (is_named_call_p (callee_fndecl, "getpass", call, 1))
204 tree lhs = gimple_call_lhs (call);
205 if (lhs)
206 sm_ctxt->on_transition (node, stmt, lhs, m_start, m_sensitive);
207 return true;
209 else if (is_named_call_p (callee_fndecl, "fprintf")
210 || is_named_call_p (callee_fndecl, "printf"))
212 /* Handle a match at any position in varargs. */
213 for (unsigned idx = 1; idx < gimple_call_num_args (call); idx++)
215 tree arg = gimple_call_arg (call, idx);
216 warn_for_any_exposure (sm_ctxt, node, stmt, arg);
218 return true;
220 else if (is_named_call_p (callee_fndecl, "fwrite", call, 4))
222 tree arg = gimple_call_arg (call, 0);
223 warn_for_any_exposure (sm_ctxt, node, stmt, arg);
224 return true;
226 // TODO: ...etc. This is just a proof-of-concept at this point.
228 return false;
231 bool
232 sensitive_state_machine::can_purge_p (state_t s ATTRIBUTE_UNUSED) const
234 return true;
237 } // anonymous namespace
239 /* Internal interface to this file. */
241 state_machine *
242 make_sensitive_state_machine (logger *logger)
244 return new sensitive_state_machine (logger);
247 } // namespace ana
249 #endif /* #if ENABLE_ANALYZER */