1 /* An experimental state machine, for tracking bad calls from within 2 signal handlers. 3 4 Copyright (C) 2019-2024 Free Software Foundation, Inc. 5 Contributed by David Malcolm <dmalcolm (at) redhat.com>. 6 7 This file is part of GCC. 8 9 GCC is free software; you can redistribute it and/or modify it 10 under the terms of the GNU General Public License as published by 11 the Free Software Foundation; either version 3, or (at your option) 12 any later version. 13 14 GCC is distributed in the hope that it will be useful, but 15 WITHOUT ANY WARRANTY; without even the implied warranty of 16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 17 General Public License for more details. 18 19 You should have received a copy of the GNU General Public License 20 along with GCC; see the file COPYING3. If not see 21 <http://www.gnu.org/licenses/>. */ 22 23 #include "config.h" 24 #define INCLUDE_MEMORY 25 #include "system.h" 26 #include "coretypes.h" 27 #include "make-unique.h" 28 #include "tree.h" 29 #include "function.h" 30 #include "basic-block.h" 31 #include "gimple.h" 32 #include "options.h" 33 #include "bitmap.h" 34 #include "diagnostic-path.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 #include "sbitmap.h" 41 #include "ordered-hash-map.h" 42 #include "selftest.h" 43 #include "analyzer/call-string.h" 44 #include "analyzer/program-point.h" 45 #include "analyzer/store.h" 46 #include "analyzer/region-model.h" 47 #include "analyzer/program-state.h" 48 #include "analyzer/checker-path.h" 49 #include "cfg.h" 50 #include "gimple-iterator.h" 51 #include "cgraph.h" 52 #include "analyzer/supergraph.h" 53 #include "analyzer/diagnostic-manager.h" 54 #include "shortest-paths.h" 55 #include "analyzer/exploded-graph.h" 56 #include "analyzer/function-set.h" 57 #include "analyzer/analyzer-selftests.h" 58 59 #if ENABLE_ANALYZER 60 61 namespace ana { 62 63 namespace { 64 65 /* An experimental state machine, for tracking calls to async-signal-unsafe 66 functions from within signal handlers. */ 67 68 class signal_state_machine : public state_machine 69 { 70 public: 71 signal_state_machine (logger *logger); 72 73 bool inherited_state_p () const final override { return false; } 74 75 bool on_stmt (sm_context *sm_ctxt, 76 const supernode *node, 77 const gimple *stmt) const final override; 78 79 bool can_purge_p (state_t s) const final override; 80 81 /* These states are "global", rather than per-expression. */ 82 83 /* State for when we're in a signal handler. */ 84 state_t m_in_signal_handler; 85 86 /* Stop state. */ 87 state_t m_stop; 88 }; 89 90 /* Concrete subclass for describing call to an async-signal-unsafe function 91 from a signal handler. */ 92 93 class signal_unsafe_call 94 : public pending_diagnostic_subclass<signal_unsafe_call> 95 { 96 public: 97 signal_unsafe_call (const signal_state_machine &sm, const gcall *unsafe_call, 98 tree unsafe_fndecl) 99 : m_sm (sm), m_unsafe_call (unsafe_call), m_unsafe_fndecl (unsafe_fndecl) 100 { 101 gcc_assert (m_unsafe_fndecl); 102 } 103 104 const char *get_kind () const final override { return "signal_unsafe_call"; } 105 106 bool operator== (const signal_unsafe_call &other) const 107 { 108 return m_unsafe_call == other.m_unsafe_call; 109 } 110 111 int get_controlling_option () const final override 112 { 113 return OPT_Wanalyzer_unsafe_call_within_signal_handler; 114 } 115 116 bool emit (diagnostic_emission_context &ctxt) final override 117 { 118 auto_diagnostic_group d; 119 /* CWE-479: Signal Handler Use of a Non-reentrant Function. */ 120 ctxt.add_cwe (479); 121 if (ctxt.warn ("call to %qD from within signal handler", 122 m_unsafe_fndecl)) 123 { 124 /* If we know a possible alternative function, add a note 125 suggesting the replacement. */ 126 if (const char *replacement = get_replacement_fn ()) 127 { 128 location_t note_loc = gimple_location (m_unsafe_call); 129 /* It would be nice to add a fixit, but the gimple call 130 location covers the whole call expression. It isn't 131 currently possible to cut this down to just the call 132 symbol. So the fixit would replace too much. 133 note_rich_loc.add_fixit_replace (replacement); */ 134 inform (note_loc, 135 "%qs is a possible signal-safe alternative for %qD", 136 replacement, m_unsafe_fndecl); 137 } 138 return true; 139 } 140 return false; 141 } 142 143 label_text describe_state_change (const evdesc::state_change &change) 144 final override 145 { 146 if (change.is_global_p () 147 && change.m_new_state == m_sm.m_in_signal_handler) 148 { 149 const function *handler = change.m_event.get_dest_function (); 150 gcc_assert (handler); 151 return change.formatted_print ("registering %qD as signal handler", 152 handler->decl); 153 } 154 return label_text (); 155 } 156 157 label_text describe_final_event (const evdesc::final_event &ev) final override 158 { 159 return ev.formatted_print ("call to %qD from within signal handler", 160 m_unsafe_fndecl); 161 } 162 163 private: 164 const signal_state_machine &m_sm; 165 const gcall *m_unsafe_call; 166 tree m_unsafe_fndecl; 167 168 /* Returns a replacement function as text if it exists. Currently 169 only "exit" has a signal-safe replacement "_exit", which does 170 slightly less, but can be used in a signal handler. */ 171 const char * 172 get_replacement_fn () 173 { 174 gcc_assert (m_unsafe_fndecl && DECL_P (m_unsafe_fndecl)); 175 176 if (id_equal ("exit", DECL_NAME (m_unsafe_fndecl))) 177 return "_exit"; 178 179 return NULL; 180 } 181 }; 182 183 /* signal_state_machine's ctor. */ 184 185 signal_state_machine::signal_state_machine (logger *logger) 186 : state_machine ("signal", logger), 187 m_in_signal_handler (add_state ("in_signal_handler")), 188 m_stop (add_state ("stop")) 189 { 190 } 191 192 /* Update MODEL for edges that simulate HANDLER_FUN being called as 193 an signal-handler in response to a signal. */ 194 195 static void 196 update_model_for_signal_handler (region_model *model, 197 const function &handler_fun) 198 { 199 gcc_assert (model); 200 /* Purge all state within MODEL. */ 201 *model = region_model (model->get_manager ()); 202 model->push_frame (handler_fun, NULL, NULL); 203 } 204 205 /* Custom exploded_edge info: entry into a signal-handler. */ 206 207 class signal_delivery_edge_info_t : public custom_edge_info 208 { 209 public: 210 void print (pretty_printer *pp) const final override 211 { 212 pp_string (pp, "signal delivered"); 213 } 214 215 json::object *to_json () const 216 { 217 json::object *custom_obj = new json::object (); 218 return custom_obj; 219 } 220 221 bool update_model (region_model *model, 222 const exploded_edge *eedge, 223 region_model_context *) const final override 224 { 225 gcc_assert (eedge); 226 gcc_assert (eedge->m_dest->get_function ()); 227 update_model_for_signal_handler (model, 228 *eedge->m_dest->get_function ()); 229 return true; 230 } 231 232 void add_events_to_path (checker_path *emission_path, 233 const exploded_edge &eedge ATTRIBUTE_UNUSED) 234 const final override 235 { 236 emission_path->add_event 237 (make_unique<precanned_custom_event> 238 (event_loc_info (UNKNOWN_LOCATION, NULL_TREE, 0), 239 "later on," 240 " when the signal is delivered to the process")); 241 } 242 }; 243 244 /* Concrete subclass of custom_transition for modeling registration of a 245 signal handler and the signal handler later being called. */ 246 247 class register_signal_handler : public custom_transition 248 { 249 public: 250 register_signal_handler (const signal_state_machine &sm, 251 tree fndecl) 252 : m_sm (sm), m_fndecl (fndecl) {} 253 254 /* Model a signal-handler FNDECL being called at some later point 255 by injecting an edge to a new function-entry node with an empty 256 callstring, setting the 'in-signal-handler' global state 257 on the node. */ 258 void impl_transition (exploded_graph *eg, 259 exploded_node *src_enode, 260 int sm_idx) final override 261 { 262 function *handler_fun = DECL_STRUCT_FUNCTION (m_fndecl); 263 if (!handler_fun) 264 return; 265 const extrinsic_state &ext_state = eg->get_ext_state (); 266 program_point entering_handler 267 = program_point::from_function_entry (*ext_state.get_model_manager (), 268 eg->get_supergraph (), 269 *handler_fun); 270 271 program_state state_entering_handler (ext_state); 272 update_model_for_signal_handler (state_entering_handler.m_region_model, 273 *handler_fun); 274 state_entering_handler.m_checker_states[sm_idx]->set_global_state 275 (m_sm.m_in_signal_handler); 276 277 exploded_node *dst_enode = eg->get_or_create_node (entering_handler, 278 state_entering_handler, 279 src_enode); 280 if (dst_enode) 281 eg->add_edge (src_enode, dst_enode, NULL, /*state_change (),*/ 282 true, /* assume does work */ 283 make_unique<signal_delivery_edge_info_t> ()); 284 } 285 286 const signal_state_machine &m_sm; 287 tree m_fndecl; 288 }; 289 290 /* Get a set of functions that are known to be unsafe to call from an 291 async signal handler. */ 292 293 static function_set 294 get_async_signal_unsafe_fns () 295 { 296 // TODO: populate this list more fully 297 static const char * const async_signal_unsafe_fns[] = { 298 /* This array must be kept sorted. */ 299 "exit", 300 "fprintf", 301 "free", 302 "malloc", 303 "printf", 304 "snprintf", 305 "sprintf", 306 "vfprintf", 307 "vprintf", 308 "vsnprintf", 309 "vsprintf" 310 }; 311 const size_t count = ARRAY_SIZE (async_signal_unsafe_fns); 312 function_set fs (async_signal_unsafe_fns, count); 313 return fs; 314 } 315 316 /* Return true if FNDECL is known to be unsafe to call from a signal 317 handler. */ 318 319 static bool 320 signal_unsafe_p (tree fndecl) 321 { 322 function_set fs = get_async_signal_unsafe_fns (); 323 if (fs.contains_decl_p (fndecl)) 324 return true; 325 if (is_std_function_p (fndecl) 326 && fs.contains_name_p (IDENTIFIER_POINTER (DECL_NAME (fndecl)))) 327 return true; 328 329 return false; 330 } 331 332 /* Implementation of state_machine::on_stmt vfunc for signal_state_machine. */ 333 334 bool 335 signal_state_machine::on_stmt (sm_context *sm_ctxt, 336 const supernode *node, 337 const gimple *stmt) const 338 { 339 const state_t global_state = sm_ctxt->get_global_state (); 340 if (global_state == m_start) 341 { 342 if (const gcall *call = dyn_cast <const gcall *> (stmt)) 343 if (tree callee_fndecl = sm_ctxt->get_fndecl_for_call (call)) 344 if (is_named_call_p (callee_fndecl, "signal", call, 2) 345 || is_std_named_call_p (callee_fndecl, "signal", call, 2)) 346 { 347 tree handler = gimple_call_arg (call, 1); 348 if (TREE_CODE (handler) == ADDR_EXPR 349 && TREE_CODE (TREE_OPERAND (handler, 0)) == FUNCTION_DECL) 350 { 351 tree fndecl = TREE_OPERAND (handler, 0); 352 register_signal_handler rsh (*this, fndecl); 353 sm_ctxt->on_custom_transition (&rsh); 354 } 355 } 356 } 357 else if (global_state == m_in_signal_handler) 358 { 359 if (const gcall *call = dyn_cast <const gcall *> (stmt)) 360 if (tree callee_fndecl = sm_ctxt->get_fndecl_for_call (call)) 361 if (signal_unsafe_p (callee_fndecl)) 362 if (sm_ctxt->get_global_state () == m_in_signal_handler) 363 sm_ctxt->warn (node, stmt, NULL_TREE, 364 make_unique<signal_unsafe_call> 365 (*this, call, callee_fndecl)); 366 } 367 368 return false; 369 } 370 371 bool 372 signal_state_machine::can_purge_p (state_t s ATTRIBUTE_UNUSED) const 373 { 374 return true; 375 } 376 377 } // anonymous namespace 378 379 /* Internal interface to this file. */ 380 381 state_machine * 382 make_signal_state_machine (logger *logger) 383 { 384 return new signal_state_machine (logger); 385 } 386 387 #if CHECKING_P 388 389 namespace selftest { 390 391 /* Run all of the selftests within this file. */ 392 393 void 394 analyzer_sm_signal_cc_tests () 395 { 396 function_set fs = get_async_signal_unsafe_fns (); 397 fs.assert_sorted (); 398 fs.assert_sane (); 399 } 400 401 } // namespace selftest 402 403 #endif /* CHECKING_P */ 404 405 } // namespace ana 406 407 #endif /* #if ENABLE_ANALYZER */ 408