1/* An experimental state machine, for tracking exposure of sensitive
2 data (e.g. through logging).
3 Copyright (C) 2019-2024 Free Software Foundation, Inc.
4 Contributed by David Malcolm <dmalcolm@redhat.com>.
5
6This file is part of GCC.
7
8GCC is free software; you can redistribute it and/or modify it
9under the terms of the GNU General Public License as published by
10the Free Software Foundation; either version 3, or (at your option)
11any later version.
12
13GCC is distributed in the hope that it will be useful, but
14WITHOUT ANY WARRANTY; without even the implied warranty of
15MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16General Public License for more details.
17
18You should have received a copy of the GNU General Public License
19along with GCC; see the file COPYING3. If not see
20<http://www.gnu.org/licenses/>. */
21
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 "analyzer/analyzer.h"
34#include "diagnostic-event-id.h"
35#include "analyzer/analyzer-logging.h"
36#include "analyzer/sm.h"
37#include "analyzer/pending-diagnostic.h"
38
39#if ENABLE_ANALYZER
40
41namespace ana {
42
43namespace {
44
45/* An experimental state machine, for tracking exposure of sensitive
46 data (e.g. through logging). */
47
48class sensitive_state_machine : public state_machine
49{
50public:
51 sensitive_state_machine (logger *logger);
52
53 bool inherited_state_p () const final override { return true; }
54
55 bool on_stmt (sm_context *sm_ctxt,
56 const supernode *node,
57 const gimple *stmt) const final override;
58
59 bool can_purge_p (state_t s) const final override;
60
61 /* State for "sensitive" data, such as a password. */
62 state_t m_sensitive;
63
64 /* Stop state, for a value we don't want to track any more. */
65 state_t m_stop;
66
67private:
68 void warn_for_any_exposure (sm_context *sm_ctxt,
69 const supernode *node,
70 const gimple *stmt,
71 tree arg) const;
72};
73
74class exposure_through_output_file
75 : public pending_diagnostic_subclass<exposure_through_output_file>
76{
77public:
78 exposure_through_output_file (const sensitive_state_machine &sm, tree arg)
79 : m_sm (sm), m_arg (arg)
80 {}
81
82 const char *get_kind () const final override
83 {
84 return "exposure_through_output_file";
85 }
86
87 bool operator== (const exposure_through_output_file &other) const
88 {
89 return same_tree_p (t1: m_arg, t2: other.m_arg);
90 }
91
92 int get_controlling_option () const final override
93 {
94 return OPT_Wanalyzer_exposure_through_output_file;
95 }
96
97 bool emit (diagnostic_emission_context &ctxt) final override
98 {
99 /* CWE-532: Information Exposure Through Log Files */
100 ctxt.add_cwe (cwe: 532);
101 return ctxt.warn ("sensitive value %qE written to output file",
102 m_arg);
103 }
104
105 label_text describe_state_change (const evdesc::state_change &change)
106 final override
107 {
108 if (change.m_new_state == m_sm.m_sensitive)
109 {
110 m_first_sensitive_event = change.m_event_id;
111 return change.formatted_print (fmt: "sensitive value acquired here");
112 }
113 return label_text ();
114 }
115
116 diagnostic_event::meaning
117 get_meaning_for_state_change (const evdesc::state_change &change)
118 const final override
119 {
120 if (change.m_new_state == m_sm.m_sensitive)
121 return diagnostic_event::meaning (diagnostic_event::VERB_acquire,
122 diagnostic_event::NOUN_sensitive);
123 return diagnostic_event::meaning ();
124 }
125 label_text describe_call_with_state (const evdesc::call_with_state &info)
126 final override
127 {
128 if (info.m_state == m_sm.m_sensitive)
129 return info.formatted_print
130 (fmt: "passing sensitive value %qE in call to %qE from %qE",
131 info.m_expr, info.m_callee_fndecl, info.m_caller_fndecl);
132 return label_text ();
133 }
134
135 label_text describe_return_of_state (const evdesc::return_of_state &info)
136 final override
137 {
138 if (info.m_state == m_sm.m_sensitive)
139 return info.formatted_print (fmt: "returning sensitive value to %qE from %qE",
140 info.m_caller_fndecl, info.m_callee_fndecl);
141 return label_text ();
142 }
143
144 label_text describe_final_event (const evdesc::final_event &ev) final override
145 {
146 if (m_first_sensitive_event.known_p ())
147 return ev.formatted_print (fmt: "sensitive value %qE written to output file"
148 "; acquired at %@",
149 m_arg, &m_first_sensitive_event);
150 else
151 return ev.formatted_print (fmt: "sensitive value %qE written to output file",
152 m_arg);
153 }
154
155private:
156 const sensitive_state_machine &m_sm;
157 tree m_arg;
158 diagnostic_event_id_t m_first_sensitive_event;
159};
160
161/* sensitive_state_machine's ctor. */
162
163sensitive_state_machine::sensitive_state_machine (logger *logger)
164: state_machine ("sensitive", logger),
165 m_sensitive (add_state (name: "sensitive")),
166 m_stop (add_state (name: "stop"))
167{
168}
169
170/* Warn about an exposure at NODE and STMT if ARG is in the "sensitive"
171 state. */
172
173void
174sensitive_state_machine::warn_for_any_exposure (sm_context *sm_ctxt,
175 const supernode *node,
176 const gimple *stmt,
177 tree arg) const
178{
179 if (sm_ctxt->get_state (stmt, var: arg) == m_sensitive)
180 {
181 tree diag_arg = sm_ctxt->get_diagnostic_tree (expr: arg);
182 sm_ctxt->warn (node, stmt, var: arg,
183 d: make_unique<exposure_through_output_file> (args: *this,
184 args&: diag_arg));
185 }
186}
187
188/* Implementation of state_machine::on_stmt vfunc for
189 sensitive_state_machine. */
190
191bool
192sensitive_state_machine::on_stmt (sm_context *sm_ctxt,
193 const supernode *node,
194 const gimple *stmt) const
195{
196 if (const gcall *call = dyn_cast <const gcall *> (p: stmt))
197 if (tree callee_fndecl = sm_ctxt->get_fndecl_for_call (call))
198 {
199 if (is_named_call_p (fndecl: callee_fndecl, funcname: "getpass", call, num_args: 1))
200 {
201 tree lhs = gimple_call_lhs (gs: call);
202 if (lhs)
203 sm_ctxt->on_transition (node, stmt, var: lhs, from: m_start, to: m_sensitive);
204 return true;
205 }
206 else if (is_named_call_p (fndecl: callee_fndecl, funcname: "fprintf")
207 || is_named_call_p (fndecl: callee_fndecl, funcname: "printf"))
208 {
209 /* Handle a match at any position in varargs. */
210 for (unsigned idx = 1; idx < gimple_call_num_args (gs: call); idx++)
211 {
212 tree arg = gimple_call_arg (gs: call, index: idx);
213 warn_for_any_exposure (sm_ctxt, node, stmt, arg);
214 }
215 return true;
216 }
217 else if (is_named_call_p (fndecl: callee_fndecl, funcname: "fwrite", call, num_args: 4))
218 {
219 tree arg = gimple_call_arg (gs: call, index: 0);
220 warn_for_any_exposure (sm_ctxt, node, stmt, arg);
221 return true;
222 }
223 // TODO: ...etc. This is just a proof-of-concept at this point.
224 }
225 return false;
226}
227
228bool
229sensitive_state_machine::can_purge_p (state_t s ATTRIBUTE_UNUSED) const
230{
231 return true;
232}
233
234} // anonymous namespace
235
236/* Internal interface to this file. */
237
238state_machine *
239make_sensitive_state_machine (logger *logger)
240{
241 return new sensitive_state_machine (logger);
242}
243
244} // namespace ana
245
246#endif /* #if ENABLE_ANALYZER */
247

source code of gcc/analyzer/sm-sensitive.cc