]> git.ipfire.org Git - thirdparty/gcc.git/blob - gcc/analyzer/sm-sensitive.cc
Update copyright years.
[thirdparty/gcc.git] / gcc / analyzer / sm-sensitive.cc
1 /* An experimental state machine, for tracking exposure of sensitive
2 data (e.g. through logging).
3 Copyright (C) 2019-2021 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 "config.h"
23 #include "system.h"
24 #include "coretypes.h"
25 #include "tree.h"
26 #include "function.h"
27 #include "function.h"
28 #include "basic-block.h"
29 #include "gimple.h"
30 #include "options.h"
31 #include "diagnostic-path.h"
32 #include "diagnostic-metadata.h"
33 #include "function.h"
34 #include "json.h"
35 #include "analyzer/analyzer.h"
36 #include "diagnostic-event-id.h"
37 #include "analyzer/analyzer-logging.h"
38 #include "analyzer/sm.h"
39 #include "analyzer/pending-diagnostic.h"
40
41 #if ENABLE_ANALYZER
42
43 namespace ana {
44
45 namespace {
46
47 /* An experimental state machine, for tracking exposure of sensitive
48 data (e.g. through logging). */
49
50 class sensitive_state_machine : public state_machine
51 {
52 public:
53 sensitive_state_machine (logger *logger);
54
55 bool inherited_state_p () const FINAL OVERRIDE { return true; }
56
57 bool on_stmt (sm_context *sm_ctxt,
58 const supernode *node,
59 const gimple *stmt) const FINAL OVERRIDE;
60
61 void on_condition (sm_context *sm_ctxt,
62 const supernode *node,
63 const gimple *stmt,
64 tree lhs,
65 enum tree_code op,
66 tree rhs) const FINAL OVERRIDE;
67
68 bool can_purge_p (state_t s) const FINAL OVERRIDE;
69
70 /* State for "sensitive" data, such as a password. */
71 state_t m_sensitive;
72
73 /* Stop state, for a value we don't want to track any more. */
74 state_t m_stop;
75
76 private:
77 void warn_for_any_exposure (sm_context *sm_ctxt,
78 const supernode *node,
79 const gimple *stmt,
80 tree arg) const;
81 };
82
83 class exposure_through_output_file
84 : public pending_diagnostic_subclass<exposure_through_output_file>
85 {
86 public:
87 exposure_through_output_file (const sensitive_state_machine &sm, tree arg)
88 : m_sm (sm), m_arg (arg)
89 {}
90
91 const char *get_kind () const FINAL OVERRIDE
92 {
93 return "exposure_through_output_file";
94 }
95
96 bool operator== (const exposure_through_output_file &other) const
97 {
98 return same_tree_p (m_arg, other.m_arg);
99 }
100
101 bool emit (rich_location *rich_loc) FINAL OVERRIDE
102 {
103 diagnostic_metadata m;
104 /* CWE-532: Information Exposure Through Log Files */
105 m.add_cwe (532);
106 return warning_meta (rich_loc, m,
107 OPT_Wanalyzer_exposure_through_output_file,
108 "sensitive value %qE written to output file",
109 m_arg);
110 }
111
112 label_text describe_state_change (const evdesc::state_change &change)
113 FINAL OVERRIDE
114 {
115 if (change.m_new_state == m_sm.m_sensitive)
116 {
117 m_first_sensitive_event = change.m_event_id;
118 return change.formatted_print ("sensitive value acquired here");
119 }
120 return label_text ();
121 }
122
123 label_text describe_call_with_state (const evdesc::call_with_state &info)
124 FINAL OVERRIDE
125 {
126 if (info.m_state == m_sm.m_sensitive)
127 return info.formatted_print
128 ("passing sensitive value %qE in call to %qE from %qE",
129 info.m_expr, info.m_callee_fndecl, info.m_caller_fndecl);
130 return label_text ();
131 }
132
133 label_text describe_return_of_state (const evdesc::return_of_state &info)
134 FINAL OVERRIDE
135 {
136 if (info.m_state == m_sm.m_sensitive)
137 return info.formatted_print ("returning sensitive value to %qE from %qE",
138 info.m_caller_fndecl, info.m_callee_fndecl);
139 return label_text ();
140 }
141
142 label_text describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE
143 {
144 if (m_first_sensitive_event.known_p ())
145 return ev.formatted_print ("sensitive value %qE written to output file"
146 "; acquired at %@",
147 m_arg, &m_first_sensitive_event);
148 else
149 return ev.formatted_print ("sensitive value %qE written to output file",
150 m_arg);
151 }
152
153 private:
154 const sensitive_state_machine &m_sm;
155 tree m_arg;
156 diagnostic_event_id_t m_first_sensitive_event;
157 };
158
159 /* sensitive_state_machine's ctor. */
160
161 sensitive_state_machine::sensitive_state_machine (logger *logger)
162 : state_machine ("sensitive", logger)
163 {
164 m_sensitive = add_state ("sensitive");
165 m_stop = add_state ("stop");
166 }
167
168 /* Warn about an exposure at NODE and STMT if ARG is in the "sensitive"
169 state. */
170
171 void
172 sensitive_state_machine::warn_for_any_exposure (sm_context *sm_ctxt,
173 const supernode *node,
174 const gimple *stmt,
175 tree arg) const
176 {
177 tree diag_arg = sm_ctxt->get_diagnostic_tree (arg);
178 if (sm_ctxt->get_state (stmt, arg) == m_sensitive)
179 sm_ctxt->warn (node, stmt, arg,
180 new exposure_through_output_file (*this, diag_arg));
181 }
182
183 /* Implementation of state_machine::on_stmt vfunc for
184 sensitive_state_machine. */
185
186 bool
187 sensitive_state_machine::on_stmt (sm_context *sm_ctxt,
188 const supernode *node,
189 const gimple *stmt) const
190 {
191 if (const gcall *call = dyn_cast <const gcall *> (stmt))
192 if (tree callee_fndecl = sm_ctxt->get_fndecl_for_call (call))
193 {
194 if (is_named_call_p (callee_fndecl, "getpass", call, 1))
195 {
196 tree lhs = gimple_call_lhs (call);
197 if (lhs)
198 sm_ctxt->on_transition (node, stmt, lhs, m_start, m_sensitive);
199 return true;
200 }
201 else if (is_named_call_p (callee_fndecl, "fprintf")
202 || is_named_call_p (callee_fndecl, "printf"))
203 {
204 /* Handle a match at any position in varargs. */
205 for (unsigned idx = 1; idx < gimple_call_num_args (call); idx++)
206 {
207 tree arg = gimple_call_arg (call, idx);
208 warn_for_any_exposure (sm_ctxt, node, stmt, arg);
209 }
210 return true;
211 }
212 else if (is_named_call_p (callee_fndecl, "fwrite", call, 4))
213 {
214 tree arg = gimple_call_arg (call, 0);
215 warn_for_any_exposure (sm_ctxt, node, stmt, arg);
216 return true;
217 }
218 // TODO: ...etc. This is just a proof-of-concept at this point.
219 }
220 return false;
221 }
222
223 void
224 sensitive_state_machine::on_condition (sm_context *sm_ctxt ATTRIBUTE_UNUSED,
225 const supernode *node ATTRIBUTE_UNUSED,
226 const gimple *stmt ATTRIBUTE_UNUSED,
227 tree lhs ATTRIBUTE_UNUSED,
228 enum tree_code op ATTRIBUTE_UNUSED,
229 tree rhs ATTRIBUTE_UNUSED) const
230 {
231 /* Empty. */
232 }
233
234 bool
235 sensitive_state_machine::can_purge_p (state_t s ATTRIBUTE_UNUSED) const
236 {
237 return true;
238 }
239
240 } // anonymous namespace
241
242 /* Internal interface to this file. */
243
244 state_machine *
245 make_sensitive_state_machine (logger *logger)
246 {
247 return new sensitive_state_machine (logger);
248 }
249
250 } // namespace ana
251
252 #endif /* #if ENABLE_ANALYZER */