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 54 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: 89 exposure_through_output_file (const sensitive_state_machine &sm, tree arg) 90 : m_sm (sm), m_arg (arg) 91 {} 92 93 const char *get_kind () const FINAL OVERRIDE 94 { 95 return "exposure_through_output_file"; 96 } 97 98 bool operator== (const exposure_through_output_file &other) const 99 { 100 return same_tree_p (m_arg, other.m_arg); 101 } 102 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 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 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 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 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 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 175 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 188 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 225 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 236 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 * 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