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