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 "diagnostic-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 : 1066537 : 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 : 24 : const char *get_kind () const final override
74 : : {
75 : 24 : 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 : : diagnostic_event::meaning
110 : 0 : get_meaning_for_state_change (const evdesc::state_change &change)
111 : : const final override
112 : : {
113 : 0 : if (change.m_new_state == m_sm.m_sensitive)
114 : 0 : return diagnostic_event::meaning (diagnostic_event::VERB_acquire,
115 : 0 : diagnostic_event::NOUN_sensitive);
116 : 0 : return diagnostic_event::meaning ();
117 : : }
118 : : bool
119 : 2 : describe_call_with_state (pretty_printer &pp,
120 : : const evdesc::call_with_state &info) final override
121 : : {
122 : 2 : if (info.m_state == m_sm.m_sensitive)
123 : : {
124 : 2 : pp_printf (&pp,
125 : : "passing sensitive value %qE in call to %qE from %qE",
126 : 2 : info.m_expr, info.m_callee_fndecl, info.m_caller_fndecl);
127 : 2 : return true;
128 : : }
129 : : return false;
130 : : }
131 : :
132 : : bool
133 : 2 : describe_return_of_state (pretty_printer &pp,
134 : : const evdesc::return_of_state &info) final override
135 : : {
136 : 2 : if (info.m_state == m_sm.m_sensitive)
137 : : {
138 : 2 : pp_printf (&pp,
139 : : "returning sensitive value to %qE from %qE",
140 : 2 : info.m_caller_fndecl, info.m_callee_fndecl);
141 : 2 : return true;
142 : : }
143 : : return false;
144 : : }
145 : :
146 : : bool
147 : 12 : describe_final_event (pretty_printer &pp,
148 : : const evdesc::final_event &) final override
149 : : {
150 : 12 : if (m_first_sensitive_event.known_p ())
151 : 12 : pp_printf (&pp,
152 : : "sensitive value %qE written to output file"
153 : : "; acquired at %@",
154 : : m_arg, &m_first_sensitive_event);
155 : : else
156 : 0 : pp_printf (&pp,
157 : : "sensitive value %qE written to output file",
158 : : m_arg);
159 : 12 : return true;
160 : : }
161 : :
162 : : private:
163 : : const sensitive_state_machine &m_sm;
164 : : tree m_arg;
165 : : diagnostic_event_id_t m_first_sensitive_event;
166 : : };
167 : :
168 : : /* sensitive_state_machine's ctor. */
169 : :
170 : 3313 : sensitive_state_machine::sensitive_state_machine (logger *logger)
171 : : : state_machine ("sensitive", logger),
172 : 6626 : m_sensitive (add_state ("sensitive")),
173 : 3313 : m_stop (add_state ("stop"))
174 : : {
175 : 3313 : }
176 : :
177 : : /* Warn about an exposure at NODE and STMT if ARG is in the "sensitive"
178 : : state. */
179 : :
180 : : void
181 : 2092 : sensitive_state_machine::warn_for_any_exposure (sm_context &sm_ctxt,
182 : : const supernode *node,
183 : : const gimple *stmt,
184 : : tree arg) const
185 : : {
186 : 2092 : if (sm_ctxt.get_state (stmt, arg) == m_sensitive)
187 : : {
188 : 6 : tree diag_arg = sm_ctxt.get_diagnostic_tree (arg);
189 : 6 : sm_ctxt.warn (node, stmt, arg,
190 : 6 : std::make_unique<exposure_through_output_file> (*this,
191 : : diag_arg));
192 : : }
193 : 2092 : }
194 : :
195 : : /* Implementation of state_machine::on_stmt vfunc for
196 : : sensitive_state_machine. */
197 : :
198 : : bool
199 : 267896 : sensitive_state_machine::on_stmt (sm_context &sm_ctxt,
200 : : const supernode *node,
201 : : const gimple *stmt) const
202 : : {
203 : 267896 : if (const gcall *call = dyn_cast <const gcall *> (stmt))
204 : 57632 : if (tree callee_fndecl = sm_ctxt.get_fndecl_for_call (*call))
205 : : {
206 : 56182 : if (is_named_call_p (callee_fndecl, "getpass", *call, 1))
207 : : {
208 : 7 : tree lhs = gimple_call_lhs (call);
209 : 7 : if (lhs)
210 : 7 : sm_ctxt.on_transition (node, stmt, lhs, m_start, m_sensitive);
211 : 7 : return true;
212 : : }
213 : 56175 : else if (is_named_call_p (callee_fndecl, "fprintf")
214 : 56175 : || is_named_call_p (callee_fndecl, "printf"))
215 : : {
216 : : /* Handle a match at any position in varargs. */
217 : 2947 : for (unsigned idx = 1; idx < gimple_call_num_args (call); idx++)
218 : : {
219 : 1935 : tree arg = gimple_call_arg (call, idx);
220 : 1935 : warn_for_any_exposure (sm_ctxt, node, stmt, arg);
221 : : }
222 : : return true;
223 : : }
224 : 55163 : else if (is_named_call_p (callee_fndecl, "fwrite", *call, 4))
225 : : {
226 : 157 : tree arg = gimple_call_arg (call, 0);
227 : 157 : warn_for_any_exposure (sm_ctxt, node, stmt, arg);
228 : 157 : return true;
229 : : }
230 : : // TODO: ...etc. This is just a proof-of-concept at this point.
231 : : }
232 : : return false;
233 : : }
234 : :
235 : : bool
236 : 1034725 : sensitive_state_machine::can_purge_p (state_t s ATTRIBUTE_UNUSED) const
237 : : {
238 : 1034725 : return true;
239 : : }
240 : :
241 : : } // anonymous namespace
242 : :
243 : : /* Internal interface to this file. */
244 : :
245 : : std::unique_ptr<state_machine>
246 : 3313 : make_sensitive_state_machine (logger *logger)
247 : : {
248 : 3313 : return std::make_unique<sensitive_state_machine> (logger);
249 : : }
250 : :
251 : : } // namespace ana
252 : :
253 : : #endif /* #if ENABLE_ANALYZER */
|