Branch data Line data Source code
1 : : /* An experimental state machine, for tracking exposure of sensitive
2 : : data (e.g. through logging).
3 : : Copyright (C) 2019-2025 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 : 1066034 : bool inherited_state_p () const final override { return true; }
45 : :
46 : : bool on_stmt (sm_context &sm_ctxt,
47 : : const supernode *node,
48 : : const gimple *stmt) const final override;
49 : :
50 : : bool can_purge_p (state_t s) const final override;
51 : :
52 : : /* State for "sensitive" data, such as a password. */
53 : : state_t m_sensitive;
54 : :
55 : : /* Stop state, for a value we don't want to track any more. */
56 : : state_t m_stop;
57 : :
58 : : private:
59 : : void warn_for_any_exposure (sm_context &sm_ctxt,
60 : : const supernode *node,
61 : : const gimple *stmt,
62 : : tree arg) const;
63 : : };
64 : :
65 : 0 : class exposure_through_output_file
66 : : : public pending_diagnostic_subclass<exposure_through_output_file>
67 : : {
68 : : public:
69 : 6 : exposure_through_output_file (const sensitive_state_machine &sm, tree arg)
70 : 6 : : m_sm (sm), m_arg (arg)
71 : : {}
72 : :
73 : 75 : const char *get_kind () const final override
74 : : {
75 : 75 : return "exposure_through_output_file";
76 : : }
77 : :
78 : 6 : bool operator== (const exposure_through_output_file &other) const
79 : : {
80 : 6 : return same_tree_p (m_arg, other.m_arg);
81 : : }
82 : :
83 : 12 : int get_controlling_option () const final override
84 : : {
85 : 12 : return OPT_Wanalyzer_exposure_through_output_file;
86 : : }
87 : :
88 : 6 : bool emit (diagnostic_emission_context &ctxt) final override
89 : : {
90 : : /* CWE-532: Information Exposure Through Log Files */
91 : 6 : ctxt.add_cwe (532);
92 : 6 : return ctxt.warn ("sensitive value %qE written to output file",
93 : 6 : m_arg);
94 : : }
95 : :
96 : : bool
97 : 12 : describe_state_change (pretty_printer &pp,
98 : : const evdesc::state_change &change) final override
99 : : {
100 : 12 : if (change.m_new_state == m_sm.m_sensitive)
101 : : {
102 : 12 : m_first_sensitive_event = change.m_event_id;
103 : 12 : pp_string (&pp, "sensitive value acquired here");
104 : 12 : return true;
105 : : }
106 : : return false;
107 : : }
108 : :
109 : : diagnostics::paths::event::meaning
110 : 0 : get_meaning_for_state_change (const evdesc::state_change &change)
111 : : const final override
112 : : {
113 : 0 : using event = diagnostics::paths::event;
114 : :
115 : 0 : if (change.m_new_state == m_sm.m_sensitive)
116 : 0 : return event::meaning (event::verb::acquire, event::noun::sensitive);
117 : 0 : return event::meaning ();
118 : : }
119 : : bool
120 : 2 : describe_call_with_state (pretty_printer &pp,
121 : : const evdesc::call_with_state &info) final override
122 : : {
123 : 2 : if (info.m_state == m_sm.m_sensitive)
124 : : {
125 : 2 : pp_printf (&pp,
126 : : "passing sensitive value %qE in call to %qE from %qE",
127 : 2 : info.m_expr, info.m_callee_fndecl, info.m_caller_fndecl);
128 : 2 : return true;
129 : : }
130 : : return false;
131 : : }
132 : :
133 : : bool
134 : 2 : describe_return_of_state (pretty_printer &pp,
135 : : const evdesc::return_of_state &info) final override
136 : : {
137 : 2 : if (info.m_state == m_sm.m_sensitive)
138 : : {
139 : 2 : pp_printf (&pp,
140 : : "returning sensitive value to %qE from %qE",
141 : 2 : info.m_caller_fndecl, info.m_callee_fndecl);
142 : 2 : return true;
143 : : }
144 : : return false;
145 : : }
146 : :
147 : : bool
148 : 12 : describe_final_event (pretty_printer &pp,
149 : : const evdesc::final_event &) final override
150 : : {
151 : 12 : if (m_first_sensitive_event.known_p ())
152 : 12 : pp_printf (&pp,
153 : : "sensitive value %qE written to output file"
154 : : "; acquired at %@",
155 : : m_arg, &m_first_sensitive_event);
156 : : else
157 : 0 : pp_printf (&pp,
158 : : "sensitive value %qE written to output file",
159 : : m_arg);
160 : 12 : return true;
161 : : }
162 : :
163 : : private:
164 : : const sensitive_state_machine &m_sm;
165 : : tree m_arg;
166 : : diagnostics::paths::event_id_t m_first_sensitive_event;
167 : : };
168 : :
169 : : /* sensitive_state_machine's ctor. */
170 : :
171 : 3281 : sensitive_state_machine::sensitive_state_machine (logger *logger)
172 : : : state_machine ("sensitive", logger),
173 : 6562 : m_sensitive (add_state ("sensitive")),
174 : 3281 : m_stop (add_state ("stop"))
175 : : {
176 : 3281 : }
177 : :
178 : : /* Warn about an exposure at NODE and STMT if ARG is in the "sensitive"
179 : : state. */
180 : :
181 : : void
182 : 2092 : sensitive_state_machine::warn_for_any_exposure (sm_context &sm_ctxt,
183 : : const supernode *node,
184 : : const gimple *stmt,
185 : : tree arg) const
186 : : {
187 : 2092 : if (sm_ctxt.get_state (stmt, arg) == m_sensitive)
188 : : {
189 : 6 : tree diag_arg = sm_ctxt.get_diagnostic_tree (arg);
190 : 6 : sm_ctxt.warn (node, stmt, arg,
191 : 6 : std::make_unique<exposure_through_output_file> (*this,
192 : : diag_arg));
193 : : }
194 : 2092 : }
195 : :
196 : : /* Implementation of state_machine::on_stmt vfunc for
197 : : sensitive_state_machine. */
198 : :
199 : : bool
200 : 269540 : sensitive_state_machine::on_stmt (sm_context &sm_ctxt,
201 : : const supernode *node,
202 : : const gimple *stmt) const
203 : : {
204 : 269540 : if (const gcall *call = dyn_cast <const gcall *> (stmt))
205 : 57779 : if (tree callee_fndecl = sm_ctxt.get_fndecl_for_call (*call))
206 : : {
207 : 56297 : if (is_named_call_p (callee_fndecl, "getpass", *call, 1))
208 : : {
209 : 7 : tree lhs = gimple_call_lhs (call);
210 : 7 : if (lhs)
211 : 7 : sm_ctxt.on_transition (node, stmt, lhs, m_start, m_sensitive);
212 : 7 : return true;
213 : : }
214 : 56290 : else if (is_named_call_p (callee_fndecl, "fprintf")
215 : 56290 : || is_named_call_p (callee_fndecl, "printf"))
216 : : {
217 : : /* Handle a match at any position in varargs. */
218 : 2947 : for (unsigned idx = 1; idx < gimple_call_num_args (call); idx++)
219 : : {
220 : 1935 : tree arg = gimple_call_arg (call, idx);
221 : 1935 : warn_for_any_exposure (sm_ctxt, node, stmt, arg);
222 : : }
223 : : return true;
224 : : }
225 : 55278 : else if (is_named_call_p (callee_fndecl, "fwrite", *call, 4))
226 : : {
227 : 157 : tree arg = gimple_call_arg (call, 0);
228 : 157 : warn_for_any_exposure (sm_ctxt, node, stmt, arg);
229 : 157 : return true;
230 : : }
231 : : // TODO: ...etc. This is just a proof-of-concept at this point.
232 : : }
233 : : return false;
234 : : }
235 : :
236 : : bool
237 : 1034917 : sensitive_state_machine::can_purge_p (state_t s ATTRIBUTE_UNUSED) const
238 : : {
239 : 1034917 : return true;
240 : : }
241 : :
242 : : } // anonymous namespace
243 : :
244 : : /* Internal interface to this file. */
245 : :
246 : : std::unique_ptr<state_machine>
247 : 3281 : make_sensitive_state_machine (logger *logger)
248 : : {
249 : 3281 : return std::make_unique<sensitive_state_machine> (logger);
250 : : }
251 : :
252 : : } // namespace ana
253 : :
254 : : #endif /* #if ENABLE_ANALYZER */
|