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 */
|