| /* Proof-of-concept of a -fanalyzer plugin for the Linux kernel. */ |
| /* { dg-options "-g" } */ |
| |
| #include "analyzer/common.h" |
| #include "gcc-plugin.h" |
| #include "tree.h" |
| #include "function.h" |
| #include "basic-block.h" |
| #include "gimple.h" |
| #include "gimple-iterator.h" |
| #include "diagnostic-core.h" |
| #include "graphviz.h" |
| #include "options.h" |
| #include "cgraph.h" |
| #include "tree-dfa.h" |
| #include "stringpool.h" |
| #include "convert.h" |
| #include "target.h" |
| #include "fold-const.h" |
| #include "tree-pretty-print.h" |
| #include "diagnostics/color.h" |
| #include "diagnostics/metadata.h" |
| #include "tristate.h" |
| #include "bitmap.h" |
| #include "selftest.h" |
| #include "function.h" |
| #include "json.h" |
| #include "analyzer/common.h" |
| #include "analyzer/analyzer-logging.h" |
| #include "ordered-hash-map.h" |
| #include "options.h" |
| #include "cgraph.h" |
| #include "cfg.h" |
| #include "digraph.h" |
| #include "analyzer/supergraph.h" |
| #include "sbitmap.h" |
| #include "context.h" |
| #include "channels.h" |
| #include "analyzer/call-string.h" |
| #include "analyzer/program-point.h" |
| #include "analyzer/store.h" |
| #include "analyzer/region-model.h" |
| #include "analyzer/call-details.h" |
| #include "analyzer/call-info.h" |
| |
| int plugin_is_GPL_compatible; |
| |
| #if ENABLE_ANALYZER |
| |
| namespace ana { |
| |
| /* Implementation of "copy_from_user" and "copy_to_user". */ |
| |
| class copy_across_boundary_fn : public known_function |
| { |
| public: |
| virtual bool untrusted_source_p () const = 0; |
| virtual bool untrusted_destination_p () const = 0; |
| |
| bool matches_call_types_p (const call_details &cd) const final override |
| { |
| return cd.num_args () == 3; |
| } |
| |
| void impl_call_pre (const call_details &cd) const final override |
| { |
| region_model_manager *mgr = cd.get_manager (); |
| region_model *model = cd.get_model (); |
| region_model_context *ctxt = cd.get_ctxt (); |
| |
| const svalue *dest_sval = cd.get_arg_svalue (0); |
| const svalue *src_sval = cd.get_arg_svalue (1); |
| const svalue *num_bytes_sval = cd.get_arg_svalue (2); |
| |
| const region *dest_reg = model->deref_rvalue (dest_sval, |
| cd.get_arg_tree (0), |
| ctxt); |
| const region *src_reg = model->deref_rvalue (src_sval, |
| cd.get_arg_tree (1), |
| ctxt); |
| if (const svalue *bounded_sval |
| = model->maybe_get_copy_bounds (src_reg, num_bytes_sval)) |
| num_bytes_sval = bounded_sval; |
| |
| if (tree cst = num_bytes_sval->maybe_get_constant ()) |
| if (zerop (cst)) |
| { |
| /* No-op. */ |
| model->update_for_zero_return (cd, true); |
| return; |
| } |
| |
| const region *sized_src_reg = mgr->get_sized_region (src_reg, |
| NULL_TREE, |
| num_bytes_sval); |
| |
| const svalue *copied_sval |
| = model->get_store_value (sized_src_reg, ctxt); |
| const region *sized_dest_reg = mgr->get_sized_region (dest_reg, |
| NULL_TREE, |
| num_bytes_sval); |
| |
| if (ctxt) |
| { |
| /* Bifurcate state, creating a "failure" out-edge. */ |
| ctxt->bifurcate (std::make_unique<copy_failure> (cd)); |
| |
| /* The "unbifurcated" state is the "success" case. */ |
| copy_success success (cd, |
| sized_dest_reg, |
| copied_sval, |
| sized_src_reg, |
| untrusted_source_p (), |
| untrusted_destination_p ()); |
| success.update_model (model, NULL, ctxt); |
| } |
| } |
| |
| private: |
| class copy_success : public success_call_info |
| { |
| public: |
| copy_success (const call_details &cd, |
| const region *sized_dest_reg, |
| const svalue *copied_sval, |
| const region *sized_src_reg, |
| bool untrusted_source, |
| bool untrusted_destination) |
| : success_call_info (cd), |
| m_sized_dest_reg (sized_dest_reg), |
| m_copied_sval (copied_sval), |
| m_sized_src_reg (sized_src_reg), |
| m_untrusted_source (untrusted_source), |
| m_untrusted_destination (untrusted_destination) |
| {} |
| |
| bool update_model (region_model *model, |
| const exploded_edge *, |
| region_model_context *ctxt) const final override |
| { |
| call_details cd (get_call_details (model, ctxt)); |
| model->update_for_zero_return (cd, true); |
| model->set_value (m_sized_dest_reg, m_copied_sval, ctxt); |
| if (ctxt && m_untrusted_source) |
| model->mark_as_tainted (m_copied_sval, ctxt); |
| if (m_untrusted_destination) |
| model->maybe_complain_about_infoleak (m_sized_dest_reg, |
| m_copied_sval, |
| m_sized_src_reg, |
| ctxt); |
| return true; |
| } |
| |
| const region *m_sized_dest_reg; |
| const svalue *m_copied_sval; |
| const region *m_sized_src_reg; |
| bool m_untrusted_source; |
| bool m_untrusted_destination; |
| }; |
| |
| class copy_failure : public failed_call_info |
| { |
| public: |
| copy_failure (const call_details &cd) |
| : failed_call_info (cd) |
| {} |
| |
| bool update_model (region_model *model, |
| const exploded_edge *, |
| region_model_context *ctxt) const final override |
| { |
| call_details cd (get_call_details (model, ctxt)); |
| model->update_for_nonzero_return (cd); |
| /* Leave the destination region untouched. */ |
| return true; |
| } |
| }; |
| }; |
| |
| /* "copy_from_user". */ |
| |
| class known_function_copy_from_user : public copy_across_boundary_fn |
| { |
| public: |
| bool untrusted_source_p () const final override |
| { |
| return true; |
| } |
| bool untrusted_destination_p () const final override |
| { |
| return false; |
| } |
| }; |
| |
| /* "copy_to_user". */ |
| |
| class known_function_copy_to_user : public copy_across_boundary_fn |
| { |
| public: |
| bool untrusted_source_p () const final override |
| { |
| return false; |
| } |
| bool untrusted_destination_p () const final override |
| { |
| return true; |
| } |
| }; |
| |
| /* Implementation of "__check_object_size". */ |
| |
| class known_function___check_object_size : public known_function |
| { |
| public: |
| bool matches_call_types_p (const call_details &cd) const final override |
| { |
| return cd.num_args () == 2; |
| } |
| |
| void impl_call_pre (const call_details &) const final override |
| { |
| /* No-op. */ |
| } |
| }; |
| |
| namespace analyzer_events = ::gcc::topics::analyzer_events; |
| |
| class kernel_analyzer_events_subscriber : public analyzer_events::subscriber |
| { |
| public: |
| void |
| on_message (const analyzer_events::on_ana_init &m) final override |
| { |
| LOG_SCOPE (m.get_logger ()); |
| m.register_known_function |
| ("copy_from_user", |
| std::make_unique<known_function_copy_from_user> ()); |
| m.register_known_function |
| ("copy_to_user", |
| std::make_unique<known_function_copy_to_user> ()); |
| m.register_known_function |
| ("__check_object_size", |
| std::make_unique<known_function___check_object_size> ()); |
| } |
| } kernel_sub; |
| |
| } // namespace ana |
| |
| #endif /* #if ENABLE_ANALYZER */ |
| |
| int |
| plugin_init (struct plugin_name_args *plugin_info, |
| struct plugin_gcc_version *version) |
| { |
| #if ENABLE_ANALYZER |
| const char *plugin_name = plugin_info->base_name; |
| if (0) |
| inform (input_location, "got here; %qs", plugin_name); |
| g->get_channels ().analyzer_events_channel.add_subscriber (ana::kernel_sub); |
| #else |
| sorry_no_analyzer (); |
| #endif |
| return 0; |
| } |