This patch adds a new -Wanalyzer-exposure-through-uninit-copy, emitted by -fanalyzer if it detects copying of uninitialized data through a pointer to an untrusted region.
The patch uses region::untrusted_p in the analyzer and __user in the testsuite to identify untrusted regions, but the implementation of this is left to follow-up patches, so that they can be either via the custom_address_space pragma, or via __attribute__((untrusted). The diagnostic uses notes to express what fields and padding within a struct have not been initialized. For example: infoleak-CVE-2011-1078-2.c: In function ‘test_1’: infoleak-CVE-2011-1078-2.c:28:9: warning: potential exposure of sensitive information by copying uninitialized data from stack across trust boundary [CWE-200] [-Wanalyzer-exposure-through-uninit-copy] 28 | copy_to_user(optval, &cinfo, sizeof(cinfo)); | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ‘test_1’: events 1-3 | | 21 | struct sco_conninfo cinfo; | | ^~~~~ | | | | | (1) region created on stack here | | (2) capacity: 6 bytes |...... | 28 | copy_to_user(optval, &cinfo, sizeof(cinfo)); | | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | | | | | (3) uninitialized data copied from stack here | infoleak-CVE-2011-1078-2.c:28:9: note: 1 byte is uninitialized 28 | copy_to_user(optval, &cinfo, sizeof(cinfo)); | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ infoleak-CVE-2011-1078-2.c:14:15: note: padding after field ‘dev_class’ is uninitialized (1 byte) 14 | __u8 dev_class[3]; | ^~~~~~~~~ infoleak-CVE-2011-1078-2.c:21:29: note: suggest forcing zero-initialization by providing a ‘{0}’ initializer 21 | struct sco_conninfo cinfo; | ^~~~~ | = {0} gcc/ChangeLog: * Makefile.in (ANALYZER_OBJS): Add analyzer/trust-boundaries.o. * doc/invoke.texi (-Wanalyzer-exposure-through-uninit-copy): New. gcc/analyzer/ChangeLog: * analyzer.opt (Wanalyzer-exposure-through-uninit-copy): New. * checker-path.cc (event_kind_to_string): Handle EK_REGION_CREATION. (region_creation_event::region_creation_event): New. (region_creation_event::get_desc): New. (checker_path::add_region_creation_events): New. * checker-path.h (enum event_kind): Add EK_REGION_CREATION. (enum rce_kind): New. (class region_creation_event): New. (checker_path::add_region_creation_events): New. * diagnostic-manager.cc (diagnostic_manager::emit_saved_diagnostic): Pass NULL to add_events_for_eedge. (diagnostic_manager::build_emission_path): Create interesting_t instance and pass it to mark_interesting_stuff, and to add_events_for_eedge. (diagnostic_manager::add_events_for_eedge): Add "interest" param. Use it to create region_creation_events for on-stack regions when a stack frame is pushed and for heap-based and alloca regions. (diagnostic_manager::prune_for_sm_diagnostic): Handle EK_REGION_CREATION. * diagnostic-manager.h (diagnostic_manager::add_events_for_eedge): Add "interest" param. * pending-diagnostic.cc: Include "selftest.h", "tristate.h", "analyzer/call-string.h", "analyzer/program-point.h", "analyzer/store.h", and "analyzer/region-model.h". (interesting_t::add_region_creation): New. (interesting_t::dump_to_pp): New. * pending-diagnostic.h (struct interesting_t): New. (pending_diagnostic::mark_interesting_stuff): New. * region-model-impl-calls.cc (call_details::get_logger): New. * region-model.cc: Include "analyzer/call-info.h". (enum return_meaning): New. (get_return_meaning): New. (region_model::update_for_zero_return): New. (region_model::update_for_nonzero_return): New. (class maybe_returns_zero_call_info): New. (class copy_success): New. (class copy_failure): New. (maybe_simplify_upper_bound): New. (region_model::maybe_get_copy_bounds): New. (struct copy_fn_details): New. (is_copy_function): New. (region_model::handle_copy_function): New. (region_model::on_call_pre): Call is_copy_function and handle_copy_function. (region_model::set_value): Add param "src_reg". Call maybe_complain_about_infoleak for copies to untrusted regions. * region-model.h (call_details::get_logger): New. (struct copy_fn_details): New forward decl. (region_model::handle_copy_function): New. (region_model::maybe_get_copy_bounds): New. (region_model::update_for_zero_return): New. (region_model::update_for_nonzero_return): New. (region_model::set_value): Add param "src_reg". (region_model::maybe_complain_about_infoleak): New. * region.cc (region::get_addr_space): New. * region.h (region::get_addr_space): New. (region::untrusted_p): New. (frame_region::get_fndecl): New. * store.h (binding_cluster::get_base_region): New. * trust-boundaries.cc: New file. gcc/testsuite/ChangeLog: * gcc.dg/analyzer/copy-function-1.c: New test. * gcc.dg/analyzer/copy_from_user-1.c: New test. * gcc.dg/analyzer/infoleak-1.c: New test. * gcc.dg/analyzer/infoleak-2.c: New test. * gcc.dg/analyzer/infoleak-3.c: New test. * gcc.dg/analyzer/infoleak-5.c: New test. * gcc.dg/analyzer/infoleak-CVE-2011-1078-1.c: New test. * gcc.dg/analyzer/infoleak-CVE-2011-1078-2.c: New test. * gcc.dg/analyzer/infoleak-CVE-2014-1446-1.c: New test. * gcc.dg/analyzer/infoleak-CVE-2017-18549-1.c: New test. * gcc.dg/analyzer/infoleak-CVE-2017-18550-1.c: New test. * gcc.dg/analyzer/infoleak-antipatterns-1.c: New test. * gcc.dg/analyzer/infoleak-fixit-1.c: New test. * gcc.dg/analyzer/torture/infoleak-net-ethtool-ioctl.c: New test. * gcc.dg/analyzer/torture/infoleak-vfio_iommu_type1.c: New test. Signed-off-by: David Malcolm <dmalc...@redhat.com> --- gcc/Makefile.in | 3 +- gcc/analyzer/analyzer.opt | 4 + gcc/analyzer/checker-path.cc | 104 +++ gcc/analyzer/checker-path.h | 47 ++ gcc/analyzer/diagnostic-manager.cc | 75 ++- gcc/analyzer/diagnostic-manager.h | 3 +- gcc/analyzer/pending-diagnostic.cc | 30 + gcc/analyzer/pending-diagnostic.h | 24 + gcc/analyzer/region-model-impl-calls.cc | 11 + gcc/analyzer/region-model.cc | 457 ++++++++++++- gcc/analyzer/region-model.h | 19 +- gcc/analyzer/region.cc | 28 + gcc/analyzer/region.h | 4 + gcc/analyzer/store.h | 1 + gcc/analyzer/trust-boundaries.cc | 615 ++++++++++++++++++ gcc/doc/invoke.texi | 14 + .../gcc.dg/analyzer/copy-function-1.c | 98 +++ .../gcc.dg/analyzer/copy_from_user-1.c | 45 ++ gcc/testsuite/gcc.dg/analyzer/infoleak-1.c | 181 ++++++ gcc/testsuite/gcc.dg/analyzer/infoleak-2.c | 29 + gcc/testsuite/gcc.dg/analyzer/infoleak-3.c | 141 ++++ gcc/testsuite/gcc.dg/analyzer/infoleak-5.c | 35 + .../analyzer/infoleak-CVE-2011-1078-1.c | 134 ++++ .../analyzer/infoleak-CVE-2011-1078-2.c | 42 ++ .../analyzer/infoleak-CVE-2014-1446-1.c | 117 ++++ .../analyzer/infoleak-CVE-2017-18549-1.c | 101 +++ .../analyzer/infoleak-CVE-2017-18550-1.c | 171 +++++ .../gcc.dg/analyzer/infoleak-antipatterns-1.c | 162 +++++ .../gcc.dg/analyzer/infoleak-fixit-1.c | 22 + .../torture/infoleak-net-ethtool-ioctl.c | 78 +++ .../torture/infoleak-vfio_iommu_type1.c | 39 ++ 31 files changed, 2826 insertions(+), 8 deletions(-) create mode 100644 gcc/analyzer/trust-boundaries.cc create mode 100644 gcc/testsuite/gcc.dg/analyzer/copy-function-1.c create mode 100644 gcc/testsuite/gcc.dg/analyzer/copy_from_user-1.c create mode 100644 gcc/testsuite/gcc.dg/analyzer/infoleak-1.c create mode 100644 gcc/testsuite/gcc.dg/analyzer/infoleak-2.c create mode 100644 gcc/testsuite/gcc.dg/analyzer/infoleak-3.c create mode 100644 gcc/testsuite/gcc.dg/analyzer/infoleak-5.c create mode 100644 gcc/testsuite/gcc.dg/analyzer/infoleak-CVE-2011-1078-1.c create mode 100644 gcc/testsuite/gcc.dg/analyzer/infoleak-CVE-2011-1078-2.c create mode 100644 gcc/testsuite/gcc.dg/analyzer/infoleak-CVE-2014-1446-1.c create mode 100644 gcc/testsuite/gcc.dg/analyzer/infoleak-CVE-2017-18549-1.c create mode 100644 gcc/testsuite/gcc.dg/analyzer/infoleak-CVE-2017-18550-1.c create mode 100644 gcc/testsuite/gcc.dg/analyzer/infoleak-antipatterns-1.c create mode 100644 gcc/testsuite/gcc.dg/analyzer/infoleak-fixit-1.c create mode 100644 gcc/testsuite/gcc.dg/analyzer/torture/infoleak-net-ethtool-ioctl.c create mode 100644 gcc/testsuite/gcc.dg/analyzer/torture/infoleak-vfio_iommu_type1.c diff --git a/gcc/Makefile.in b/gcc/Makefile.in index 571e9c28e29..7e99dae0b1f 100644 --- a/gcc/Makefile.in +++ b/gcc/Makefile.in @@ -1273,7 +1273,8 @@ ANALYZER_OBJS = \ analyzer/store.o \ analyzer/supergraph.o \ analyzer/svalue.o \ - analyzer/trimmed-graph.o + analyzer/trimmed-graph.o \ + analyzer/trust-boundaries.o # Language-independent object files. # We put the *-match.o and insn-*.o files first so that a parallel make diff --git a/gcc/analyzer/analyzer.opt b/gcc/analyzer/analyzer.opt index c85e30f8b11..fa8a3a94d79 100644 --- a/gcc/analyzer/analyzer.opt +++ b/gcc/analyzer/analyzer.opt @@ -66,6 +66,10 @@ Wanalyzer-exposure-through-output-file Common Var(warn_analyzer_exposure_through_output_file) Init(1) Warning Warn about code paths in which sensitive data is written to a file. +Wanalyzer-exposure-through-uninit-copy +Common Var(warn_analyzer_exposure_through_uninit_copy) Init(1) Warning +Warn about code paths in which sensitive data is copied across a security boundary. + Wanalyzer-file-leak Common Var(warn_analyzer_file_leak) Init(1) Warning Warn about code paths in which a stdio FILE is not closed. diff --git a/gcc/analyzer/checker-path.cc b/gcc/analyzer/checker-path.cc index e132f003470..d7b04322638 100644 --- a/gcc/analyzer/checker-path.cc +++ b/gcc/analyzer/checker-path.cc @@ -81,6 +81,8 @@ event_kind_to_string (enum event_kind ek) return "EK_CUSTOM"; case EK_STMT: return "EK_STMT"; + case EK_REGION_CREATION: + return "EK_REGION_CREATION"; case EK_FUNCTION_ENTRY: return "EK_FUNCTION_ENTRY"; case EK_STATE_CHANGE: @@ -199,6 +201,79 @@ statement_event::get_desc (bool) const return label_text::take (xstrdup (pp_formatted_text (&pp))); } +/* class region_creation_event : public checker_event. */ + +region_creation_event::region_creation_event (const region *reg, + tree capacity, + enum rce_kind kind, + location_t loc, + tree fndecl, + int depth) +: checker_event (EK_REGION_CREATION, loc, fndecl, depth), + m_reg (reg), + m_capacity (capacity), + m_rce_kind (kind) +{ + if (m_rce_kind == RCE_CAPACITY) + gcc_assert (capacity); +} + +/* Implementation of diagnostic_event::get_desc vfunc for + region_creation_event. + There are effectively 3 kinds of region_region_event, to + avoid combinatorial explosion by trying to convy the + information in a single message. */ + +label_text +region_creation_event::get_desc (bool can_colorize) const +{ + switch (m_rce_kind) + { + default: + gcc_unreachable (); + + case RCE_MEM_SPACE: + switch (m_reg->get_memory_space ()) + { + default: + return label_text::borrow ("region created here"); + case MEMSPACE_STACK: + return label_text::borrow ("region created on stack here"); + case MEMSPACE_HEAP: + return label_text::borrow ("region created on heap here"); + } + break; + + case RCE_CAPACITY: + gcc_assert (m_capacity); + if (TREE_CODE (m_capacity) == INTEGER_CST) + { + unsigned HOST_WIDE_INT hwi = tree_to_uhwi (m_capacity); + if (hwi == 1) + return make_label_text (can_colorize, + "capacity: %wu byte", hwi); + else + return make_label_text (can_colorize, + "capacity: %wu bytes", hwi); + } + else + return make_label_text (can_colorize, + "capacity: %qE bytes", m_capacity); + + case RCE_DEBUG: + { + pretty_printer pp; + pp_format_decoder (&pp) = default_tree_printer; + pp_string (&pp, "region creation: "); + m_reg->dump_to_pp (&pp, true); + if (m_capacity) + pp_printf (&pp, " capacity: %qE", m_capacity); + return label_text::take (xstrdup (pp_formatted_text (&pp))); + } + break; + } +} + /* class function_entry_event : public checker_event. */ /* Implementation of diagnostic_event::get_desc vfunc for @@ -991,6 +1066,35 @@ checker_path::debug () const } } +/* Add region_creation_event instances to this path for REG, + describing whether REG is on the stack or heap and what + its capacity is (if known). + If DEBUG is true, also create an RCE_DEBUG event. */ + +void +checker_path::add_region_creation_events (const region *reg, + const region_model *model, + location_t loc, + tree fndecl, int depth, + bool debug) +{ + tree capacity = NULL_TREE; + if (model) + if (const svalue *capacity_sval = model->get_capacity (reg)) + capacity = model->get_representative_tree (capacity_sval); + + add_event (new region_creation_event (reg, capacity, RCE_MEM_SPACE, + loc, fndecl, depth)); + + if (capacity) + add_event (new region_creation_event (reg, capacity, RCE_CAPACITY, + loc, fndecl, depth)); + + if (debug) + add_event (new region_creation_event (reg, capacity, RCE_DEBUG, + loc, fndecl, depth)); +} + /* Add a warning_event to the end of this path. */ void diff --git a/gcc/analyzer/checker-path.h b/gcc/analyzer/checker-path.h index 27634c20864..63b11f13037 100644 --- a/gcc/analyzer/checker-path.h +++ b/gcc/analyzer/checker-path.h @@ -31,6 +31,7 @@ enum event_kind EK_DEBUG, EK_CUSTOM, EK_STMT, + EK_REGION_CREATION, EK_FUNCTION_ENTRY, EK_STATE_CHANGE, EK_START_CFG_EDGE, @@ -58,6 +59,7 @@ extern const char *event_kind_to_string (enum event_kind ek); custom_event (EK_CUSTOM) precanned_custom_event statement_event (EK_STMT) + region_creation_event (EK_REGION_CREATION) function_entry_event (EK_FUNCTION_ENTRY) state_change_event (EK_STATE_CHANGE) superedge_event @@ -194,6 +196,45 @@ public: const program_state m_dst_state; }; +/* There are too many combinations to express region creation in one message, + so we emit multiple region_creation_event instances when each pertinent + region is created. + + This enum distinguishes between the different messages. */ + +enum rce_kind +{ + /* Generate a message based on the memory space of the region + e.g. "region created on stack here". */ + RCE_MEM_SPACE, + + /* Generate a message based on the capacity of the region + e.g. "capacity: 100 bytes". */ + RCE_CAPACITY, + + /* Generate a debug message. */ + RCE_DEBUG +}; + +/* A concrete event subclass describing the creation of a region that + is significant for a diagnostic. */ + +class region_creation_event : public checker_event +{ +public: + region_creation_event (const region *reg, + tree capacity, + enum rce_kind kind, + location_t loc, tree fndecl, int depth); + + label_text get_desc (bool) const FINAL OVERRIDE; + +private: + const region *m_reg; + tree m_capacity; + enum rce_kind m_rce_kind; +}; + /* An event subclass describing the entry to a function. */ class function_entry_event : public checker_event @@ -561,6 +602,12 @@ public: m_events[idx] = new_event; } + void add_region_creation_events (const region *reg, + const region_model *model, + location_t loc, + tree fndecl, int depth, + bool debug); + void add_final_event (const state_machine *sm, const exploded_node *enode, const gimple *stmt, tree var, state_machine::state_t state); diff --git a/gcc/analyzer/diagnostic-manager.cc b/gcc/analyzer/diagnostic-manager.cc index 7ffe0004356..fdefa96d2f1 100644 --- a/gcc/analyzer/diagnostic-manager.cc +++ b/gcc/analyzer/diagnostic-manager.cc @@ -1219,7 +1219,7 @@ diagnostic_manager::emit_saved_diagnostic (const exploded_graph &eg, trailing eedge stashed, add any events for it. This is for use in handling longjmp, to show where a longjmp is rewinding to. */ if (sd.m_trailing_eedge) - add_events_for_eedge (pb, *sd.m_trailing_eedge, &emission_path); + add_events_for_eedge (pb, *sd.m_trailing_eedge, &emission_path, NULL); emission_path.prepare_for_emission (sd.m_d); @@ -1266,10 +1266,13 @@ diagnostic_manager::build_emission_path (const path_builder &pb, checker_path *emission_path) const { LOG_SCOPE (get_logger ()); + + interesting_t interest; + pb.get_pending_diagnostic ()->mark_interesting_stuff (&interest); for (unsigned i = 0; i < epath.m_edges.length (); i++) { const exploded_edge *eedge = epath.m_edges[i]; - add_events_for_eedge (pb, *eedge, emission_path); + add_events_for_eedge (pb, *eedge, emission_path, &interest); } } @@ -1577,10 +1580,12 @@ struct null_assignment_sm_context : public sm_context void diagnostic_manager::add_events_for_eedge (const path_builder &pb, const exploded_edge &eedge, - checker_path *emission_path) const + checker_path *emission_path, + interesting_t *interest) const { const exploded_node *src_node = eedge.m_src; const program_point &src_point = src_node->get_point (); + const int src_stack_depth = src_point.get_stack_depth (); const exploded_node *dst_node = eedge.m_dest; const program_point &dst_point = dst_node->get_point (); const int dst_stack_depth = dst_point.get_stack_depth (); @@ -1642,6 +1647,29 @@ diagnostic_manager::add_events_for_eedge (const path_builder &pb, (dst_point.get_supernode ()->get_start_location (), dst_point.get_fndecl (), dst_stack_depth)); + /* Create region_creation_events for on-stack regions within + this frame. */ + if (interest) + { + unsigned i; + const region *reg; + FOR_EACH_VEC_ELT (interest->m_region_creation, i, reg) + if (const frame_region *frame = reg->maybe_get_frame_region ()) + if (frame->get_fndecl () == dst_point.get_fndecl ()) + { + const region *base_reg = reg->get_base_region (); + if (tree decl = base_reg->maybe_get_decl ()) + if (DECL_SOURCE_LOCATION (decl) != UNKNOWN_LOCATION) + { + emission_path->add_region_creation_events + (reg, dst_state.m_region_model, + DECL_SOURCE_LOCATION (decl), + dst_point.get_fndecl (), + dst_stack_depth, + m_verbosity > 3); + } + } + } } break; case PK_BEFORE_STMT: @@ -1697,6 +1725,43 @@ diagnostic_manager::add_events_for_eedge (const path_builder &pb, == dst_node->m_succs[0]->m_dest->get_point ()))) break; } + + /* Look for changes in dynamic extents, which will identify + the creation of heap-based regions and alloca regions. */ + if (interest) + { + const region_model *src_model = src_state.m_region_model; + const region_model *dst_model = dst_state.m_region_model; + if (src_model->get_dynamic_extents () + != dst_model->get_dynamic_extents ()) + { + unsigned i; + const region *reg; + FOR_EACH_VEC_ELT (interest->m_region_creation, i, reg) + { + const region *base_reg = reg->get_base_region (); + const svalue *old_extents + = src_model->get_dynamic_extents (base_reg); + const svalue *new_extents + = dst_model->get_dynamic_extents (base_reg); + if (old_extents == NULL && new_extents != NULL) + switch (base_reg->get_kind ()) + { + default: + break; + case RK_HEAP_ALLOCATED: + case RK_ALLOCA: + emission_path->add_region_creation_events + (reg, dst_model, + src_point.get_location (), + src_point.get_fndecl (), + src_stack_depth, + m_verbosity > 3); + break; + } + } + } + } } } break; @@ -2001,6 +2066,10 @@ diagnostic_manager::prune_for_sm_diagnostic (checker_path *path, } break; + case EK_REGION_CREATION: + /* Don't filter these. */ + break; + case EK_FUNCTION_ENTRY: if (m_verbosity < 1) { diff --git a/gcc/analyzer/diagnostic-manager.h b/gcc/analyzer/diagnostic-manager.h index ad2eb4dfe65..b4836aaf532 100644 --- a/gcc/analyzer/diagnostic-manager.h +++ b/gcc/analyzer/diagnostic-manager.h @@ -141,7 +141,8 @@ private: void add_events_for_eedge (const path_builder &pb, const exploded_edge &eedge, - checker_path *emission_path) const; + checker_path *emission_path, + interesting_t *interest) const; bool significant_edge_p (const path_builder &pb, const exploded_edge &eedge) const; diff --git a/gcc/analyzer/pending-diagnostic.cc b/gcc/analyzer/pending-diagnostic.cc index adff25130fd..a0c1db4ebcc 100644 --- a/gcc/analyzer/pending-diagnostic.cc +++ b/gcc/analyzer/pending-diagnostic.cc @@ -33,11 +33,41 @@ along with GCC; see the file COPYING3. If not see #include "diagnostic-event-id.h" #include "analyzer/sm.h" #include "analyzer/pending-diagnostic.h" +#include "selftest.h" +#include "tristate.h" +#include "analyzer/call-string.h" +#include "analyzer/program-point.h" +#include "analyzer/store.h" +#include "analyzer/region-model.h" #if ENABLE_ANALYZER namespace ana { +/* struct interesting_t. */ + +void +interesting_t::add_region_creation (const region *reg) +{ + gcc_assert (reg); + m_region_creation.safe_push (reg); +} + +void +interesting_t::dump_to_pp (pretty_printer *pp, bool simple) const +{ + pp_string (pp, "{ region creation: ["); + unsigned i; + const region *reg; + FOR_EACH_VEC_ELT (m_region_creation, i, reg) + { + if (i > 0) + pp_string (pp, ", "); + reg->dump_to_pp (pp, simple); + } + pp_string (pp, "]}"); +} + /* Generate a label_text by printing FMT. Use a clone of the global_dc for formatting callbacks. diff --git a/gcc/analyzer/pending-diagnostic.h b/gcc/analyzer/pending-diagnostic.h index 48e2b3ec171..3d57afabae8 100644 --- a/gcc/analyzer/pending-diagnostic.h +++ b/gcc/analyzer/pending-diagnostic.h @@ -23,6 +23,22 @@ along with GCC; see the file COPYING3. If not see namespace ana { +/* A bundle of information about things that are of interest to a + pending_diagnostic. + + For now, merely the set of regions that are pertinent to the + diagnostic, so that we can notify the user about when they + were created. */ + +struct interesting_t +{ + void add_region_creation (const region *reg); + + void dump_to_pp (pretty_printer *pp, bool simple) const; + + auto_vec<const region *> m_region_creation; +}; + /* Various bundles of information used for generating more precise messages for events within a diagnostic_path, for passing to the various "describe_*" vfuncs of pending_diagnostic. See those @@ -282,6 +298,14 @@ class pending_diagnostic { return false; } + + /* Vfunc for registering additional information of interest to this + diagnostic. */ + + virtual void mark_interesting_stuff (interesting_t *) + { + /* Default no-op implementation. */ + } }; /* A template to make it easier to make subclasses of pending_diagnostic. diff --git a/gcc/analyzer/region-model-impl-calls.cc b/gcc/analyzer/region-model-impl-calls.cc index 90d4cf9c2db..f4b19e8cd7b 100644 --- a/gcc/analyzer/region-model-impl-calls.cc +++ b/gcc/analyzer/region-model-impl-calls.cc @@ -80,6 +80,17 @@ call_details::call_details (const gcall *call, region_model *model, } } +/* Get any logger associated with this object. */ + +logger * +call_details::get_logger () const +{ + if (m_ctxt) + return m_ctxt->get_logger (); + else + return NULL; +} + /* Get any uncertainty_t associated with the region_model_context. */ uncertainty_t * diff --git a/gcc/analyzer/region-model.cc b/gcc/analyzer/region-model.cc index 416a5ac7249..b4d60812271 100644 --- a/gcc/analyzer/region-model.cc +++ b/gcc/analyzer/region-model.cc @@ -67,6 +67,7 @@ along with GCC; see the file COPYING3. If not see #include "stor-layout.h" #include "attribs.h" #include "tree-object-size.h" +#include "analyzer/call-info.h" #if ENABLE_ANALYZER @@ -1041,6 +1042,445 @@ region_model::on_stmt_pre (const gimple *stmt, } } +/* An enum for capturing the presence of one of + __attribute (("returns_zero_on_failure")) or + __attribute (("returns_zero_on_success")). */ + +enum return_meaning +{ + RETURN_MEANING_UNKNOWN, // or no return value + RETURN_MEANING_ZERO_ON_FAILURE, + RETURN_MEANING_ZERO_ON_SUCCESS +}; + +/* Determine if FNDECL has been marked with one of + __attribute (("returns_zero_on_failure")) or + __attribute (("returns_zero_on_success")). */ + +static enum return_meaning +get_return_meaning (tree fndecl) +{ + tree attrs = TYPE_ATTRIBUTES (TREE_TYPE (fndecl)); + if (lookup_attribute ("returns_zero_on_failure", attrs)) + return RETURN_MEANING_ZERO_ON_FAILURE; + if (lookup_attribute ("returns_zero_on_success", attrs)) + return RETURN_MEANING_ZERO_ON_SUCCESS; + return RETURN_MEANING_UNKNOWN; +} + +/* Update this model for an outcome of a call that returns zero. */ + +void +region_model::update_for_zero_return (const call_details &cd) +{ + if (!cd.get_lhs_type ()) + return; + const svalue *zero + = m_mgr->get_or_create_int_cst (cd.get_lhs_type (), 0); + /* Need to make this unmergeable to prevent the state-merger code + from merging the success and failure outcomes. */ + set_value (cd.get_lhs_region (), + m_mgr->get_or_create_unmergeable (zero), + cd.get_ctxt ()); +} + +/* Update this model for an outcome of a call that returns non-zero. */ + +void +region_model::update_for_nonzero_return (const call_details &cd) +{ + if (!cd.get_lhs_type ()) + return; + const svalue *zero + = m_mgr->get_or_create_int_cst (cd.get_lhs_type (), 0); + const svalue *result + = get_store_value (cd.get_lhs_region (), cd.get_ctxt ()); + add_constraint (result, NE_EXPR, zero, cd.get_ctxt ()); +} + +/* Subclass of call_info for a that call that succeeds or fails, where + the return value is zero or nonzero to signify success/failure + adding a "when `FNDECL' succeeds/fails returning zero/nonzero" message. + (giving four combinations: success vs failure and zero vs nonzero). + + This is still abstract: the custom_edge_info::update_model vfunc + must be implemented. */ + +class maybe_returns_zero_call_info : public call_info +{ +public: + maybe_returns_zero_call_info (const call_details &cd, + enum return_meaning return_meaning, + bool success) + : call_info (cd), + m_return_meaning (return_meaning), + m_success (success) + {} + + label_text get_desc (bool can_colorize) const + { + switch (m_return_meaning) + { + default: + case RETURN_MEANING_UNKNOWN: + gcc_unreachable (); + break; + case RETURN_MEANING_ZERO_ON_FAILURE: + if (m_success) + return make_label_text (can_colorize, + "when %qE succeeds, returning non-zero", + get_fndecl ()); + else + return make_label_text (can_colorize, + "when %qE fails, returning zero", + get_fndecl ()); + case RETURN_MEANING_ZERO_ON_SUCCESS: + if (m_success) + return make_label_text (can_colorize, + "when %qE succeeds, returning zero", + get_fndecl ()); + else + return make_label_text (can_colorize, + "when %qE fails, returning non-zero", + get_fndecl ()); + } + } + +protected: + void update_model_for_return_value (region_model *model, + region_model_context *ctxt) const + { + const call_details cd (get_call_details (model, ctxt)); + + /* Update return value. */ + switch (m_return_meaning) + { + default: + case RETURN_MEANING_UNKNOWN: + gcc_unreachable (); + break; + case RETURN_MEANING_ZERO_ON_FAILURE: + if (m_success) + model->update_for_nonzero_return (cd); + else + model->update_for_zero_return (cd); + break; + case RETURN_MEANING_ZERO_ON_SUCCESS: + if (m_success) + model->update_for_zero_return (cd); + else + model->update_for_nonzero_return (cd); + break; + } + } + + enum return_meaning m_return_meaning; + bool m_success; +}; + +/* Concrete custom_edge_info: a copy that succeeds/fails, + with an appropriate return value. */ + +class copy_success : public maybe_returns_zero_call_info +{ +public: + copy_success (const call_details &cd, + enum return_meaning return_meaning, + const region *sized_dest_reg, + const svalue *copied_sval, + const region *sized_src_reg) + : maybe_returns_zero_call_info (cd, return_meaning, true), + m_sized_dest_reg (sized_dest_reg), + m_copied_sval (copied_sval), + m_sized_src_reg (sized_src_reg) + {} + + bool update_model (region_model *model, + const exploded_edge *, + region_model_context *ctxt) const FINAL OVERRIDE + { + update_model_for_return_value (model, ctxt); + model->set_value (m_sized_dest_reg, m_copied_sval, ctxt, m_sized_src_reg); + return true; + } + + const region *m_sized_dest_reg; + const svalue *m_copied_sval; + const region *m_sized_src_reg; +}; + +/* Concrete custom_edge_info: a copy that fails, + with an appropriate return value. */ + +class copy_failure : public maybe_returns_zero_call_info +{ +public: + copy_failure (const call_details &cd, + enum return_meaning return_meaning) + : maybe_returns_zero_call_info (cd, return_meaning, false) + {} + + bool update_model (region_model *model, + const exploded_edge *, + region_model_context *ctxt) const FINAL OVERRIDE + { + update_model_for_return_value (model, ctxt); + /* Leave the destination region untouched. */ + return true; + } + +private: + enum return_meaning m_return_meaning; +}; + +/* The Linux kernel commonly uses + min_t([unsigned] long, VAR, sizeof(T)); + to set an upper bound on the size of a copy_to_user. + Attempt to simplify such sizes by trying to get the upper bound as a + constant. + Return the simplified svalue if possible, or NULL otherwise. */ + +static const svalue * +maybe_simplify_upper_bound (const svalue *num_bytes_sval, + region_model_manager *mgr) +{ + tree type = num_bytes_sval->get_type (); + while (const svalue *raw = num_bytes_sval->maybe_undo_cast ()) + num_bytes_sval = raw; + if (const binop_svalue *binop_sval = num_bytes_sval->dyn_cast_binop_svalue ()) + if (binop_sval->get_op () == MIN_EXPR) + if (binop_sval->get_arg1 ()->get_kind () == SK_CONSTANT) + { + return mgr->get_or_create_cast (type, binop_sval->get_arg1 ()); + /* TODO: we might want to also capture the constraint + when recording the diagnostic, or note that we're using + the upper bound. */ + } + return NULL; +} + +/* Attempt to get an upper bound for the size of a copy when simulating a + copy function. + + NUM_BYTES_SVAL is the symbolic value for the size of the copy. + Use it if it's constant, otherwise try to simplify it. Failing + that, use the size of SRC_REG if constant. + + Return a symbolic value for an upper limit on the number of bytes + copied, or NULL if no such value could be determined. */ + +const svalue * +region_model::maybe_get_copy_bounds (const region *src_reg, + const svalue *num_bytes_sval) +{ + if (num_bytes_sval->maybe_get_constant ()) + return num_bytes_sval; + + if (const svalue *simplified + = maybe_simplify_upper_bound (num_bytes_sval, m_mgr)) + num_bytes_sval = simplified; + + if (num_bytes_sval->maybe_get_constant ()) + return num_bytes_sval; + + /* For now, try just guessing the size as the capacity of the + base region of the src. + This is a hack; we might get too large a value. */ + const region *src_base_reg = src_reg->get_base_region (); + num_bytes_sval = get_capacity (src_base_reg); + + if (num_bytes_sval->maybe_get_constant ()) + return num_bytes_sval; + + /* Non-constant: give up. */ + return NULL; +} + +/* Support for "copy functions". + + Heuristic: if a function has just an access((read_only)), + access((write_only)) and a size param (in any order), assume it copies + between buffers, and has no side effects. */ + +/* Struct for capturing the order of the params of a copy function. */ + +struct copy_fn_details +{ + int m_src_arg_idx; + int m_dst_arg_idx; + int m_sz_arg_idx; +}; + +/* Return true if FNDECL is a copy function and populate *OUT. + Return false otherwise. */ + +static bool +is_copy_function (tree fndecl, copy_fn_details *out) +{ + tree fntype = TREE_TYPE (fndecl); + /* Must have exactly 3 args. */ + if (type_num_arguments (fntype) != 3) + return false; + /* Reject variadic functions. */ + if (type_argument_type (fntype, 4) == NULL_TREE) + return false; + + rdwr_map rwm; + init_attr_rdwr_indices (&rwm, TYPE_ATTRIBUTES (fntype)); + + if (rwm.elements () != 3) + return false; + + int src_ptr_arg_idx = -1; + int src_sz_arg_idx = -1; + int dst_ptr_arg_idx = -1; + int dst_sz_arg_idx = -1; + + for (unsigned arg_idx = 0; arg_idx < 3; arg_idx++) + { + const attr_access *access = rwm.get (arg_idx); + if (!access) + return false; + switch (access->mode) + { + default: + gcc_unreachable (); + case access_none: + return false; + + case access_read_only: + if (src_ptr_arg_idx != -1 + && src_ptr_arg_idx != (int)access->ptrarg) + /* More than one read_only. */ + return false; + src_ptr_arg_idx = access->ptrarg; + src_sz_arg_idx = access->sizarg; + break; + + case access_write_only: + if (dst_ptr_arg_idx != -1 + && dst_ptr_arg_idx != (int)access->ptrarg) + /* More than one write_only. */ + return false; + dst_ptr_arg_idx = access->ptrarg; + dst_sz_arg_idx = access->sizarg; + break; + + case access_read_write: + case access_deferred: + return false; + } + } + + if (src_ptr_arg_idx == -1 + || src_sz_arg_idx == -1 + || dst_ptr_arg_idx == -1 + || dst_sz_arg_idx == -1 + || src_sz_arg_idx != dst_sz_arg_idx + || src_ptr_arg_idx == src_sz_arg_idx) + return false; + + /* The size param must have integer type; this is checked by the attribute + handler. + + type_argument_type takes a 1-based argno, rather than a 0-based arg + idx. */ + gcc_assert (INTEGRAL_TYPE_P (type_argument_type (fntype, + src_sz_arg_idx + 1))); + + /* If we get here we have a pair: + access (read_only, A), access (write_only, A). */ + out->m_src_arg_idx = src_ptr_arg_idx; + out->m_dst_arg_idx = dst_ptr_arg_idx; + out->m_sz_arg_idx = src_sz_arg_idx; + return true; +} + +/* Update this model assuming that CD is a call to CALLEE_FNDECL, a + copy function described by CFD. */ + +void +region_model::handle_copy_function (tree callee_fndecl, + const call_details &cd, + const copy_fn_details &cfd) +{ + LOG_SCOPE (cd.get_logger ()); + + gcc_assert (callee_fndecl); + enum return_meaning return_meaning = get_return_meaning (callee_fndecl); + + const svalue *dest_sval = cd.get_arg_svalue (cfd.m_dst_arg_idx); + const svalue *src_sval = cd.get_arg_svalue (cfd.m_src_arg_idx); + const svalue *num_bytes_sval = cd.get_arg_svalue (cfd.m_sz_arg_idx); + + const region *dest_reg = deref_rvalue (dest_sval, + cd.get_arg_tree (cfd.m_dst_arg_idx), + cd.get_ctxt ()); + const region *src_reg = deref_rvalue (src_sval, + cd.get_arg_tree (cfd.m_src_arg_idx), + cd.get_ctxt ()); + + if (const svalue * bounded_sval = 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. */ + return; + + const region *sized_src_reg = m_mgr->get_sized_region (src_reg, + NULL_TREE, + num_bytes_sval); + + const svalue *copied_sval = get_store_value (sized_src_reg, cd.get_ctxt ()); + + const region *sized_dest_reg = m_mgr->get_sized_region (dest_reg, + NULL_TREE, + num_bytes_sval); + + /* Heuristic for handling copies that can fail. + + In the Linux kernel, the functions copy_to_user and copy_from_user copy + an arbitrary amount of data to/from userspace. They return the amount + of uncopied data (i.e. zero means success, nonzero means failure). + + Support this kind of copy function by bifurcating the state into + success (all of N was copied) and failure (none was copied). We + don't bother with the "only a fraction was copied" case. + + This heuristic should help find problems in error-handling without + overcomplicated the analysis. */ + if (cd.get_ctxt ()) + { + switch (return_meaning) + { + default: + gcc_unreachable (); + case RETURN_MEANING_UNKNOWN: + /* Don't bifurcate state; assume a full copy. */ + set_value (sized_dest_reg, copied_sval, cd.get_ctxt (), + sized_src_reg); + break; + + case RETURN_MEANING_ZERO_ON_FAILURE: + case RETURN_MEANING_ZERO_ON_SUCCESS: + { + /* Bifurcate state, creating a "failure" out-edge. */ + cd.get_ctxt ()->bifurcate (new copy_failure (cd, return_meaning)); + + /* The "unbifurcated" state is the "success" case. */ + copy_success success (cd, return_meaning, + sized_dest_reg, + copied_sval, + sized_src_reg); + success.update_model (this, NULL, cd.get_ctxt ()); + } + break; + } + } +} + /* Update this model for the CALL stmt, using CTXT to report any diagnostics - the first half. @@ -1092,6 +1532,8 @@ region_model::on_call_pre (const gcall *call, region_model_context *ctxt, if (tree callee_fndecl = get_fndecl_for_call (call, ctxt)) { + copy_fn_details cfd; + /* The various impl_call_* member functions are implemented in region-model-impl-calls.cc. Having them split out into separate functions makes it easier @@ -1247,6 +1689,11 @@ region_model::on_call_pre (const gcall *call, region_model_context *ctxt, { /* Handle in "on_call_post". */ } + else if (is_copy_function (callee_fndecl, &cfd)) + { + handle_copy_function (callee_fndecl, cd, cfd); + return false; + } else if (!fndecl_has_gimple_body_p (callee_fndecl) && !DECL_PURE_P (callee_fndecl) && !fndecl_built_in_p (callee_fndecl)) @@ -2336,17 +2783,23 @@ region_model::check_region_for_read (const region *src_reg, /* Set the value of the region given by LHS_REG to the value given by RHS_SVAL. - Use CTXT to report any warnings associated with writing to LHS_REG. */ + Use CTXT to report any warnings associated with writing to LHS_REG. + SRC_REG can be NULL, if non-NULL it's a hint about where RHS_SVAL + came from, for precision-of-wording in diagnostics. */ void region_model::set_value (const region *lhs_reg, const svalue *rhs_sval, - region_model_context *ctxt) + region_model_context *ctxt, + const region *src_reg) { gcc_assert (lhs_reg); gcc_assert (rhs_sval); check_region_for_write (lhs_reg, ctxt); + if (ctxt && lhs_reg->untrusted_p ()) + maybe_complain_about_infoleak (lhs_reg, rhs_sval, src_reg, ctxt); + m_store.set_value (m_mgr->get_store_manager(), lhs_reg, rhs_sval, ctxt ? ctxt->get_uncertainty () : NULL); } diff --git a/gcc/analyzer/region-model.h b/gcc/analyzer/region-model.h index 13e8109aa51..322f8d5807b 100644 --- a/gcc/analyzer/region-model.h +++ b/gcc/analyzer/region-model.h @@ -479,6 +479,8 @@ public: region_model_context *ctxt); region_model_context *get_ctxt () const { return m_ctxt; } + logger *get_logger () const; + uncertainty_t *get_uncertainty () const; tree get_lhs_type () const { return m_lhs_type; } const region *get_lhs_region () const { return m_lhs_region; } @@ -509,6 +511,8 @@ private: const region *m_lhs_region; }; +struct copy_fn_details; + /* A region_model encapsulates a representation of the state of memory, with a tree of regions, along with their associated values. The representation is graph-like because values can be pointers to @@ -591,6 +595,13 @@ class region_model void impl_call_operator_new (const call_details &cd); void impl_call_operator_delete (const call_details &cd); void impl_deallocation_call (const call_details &cd); + void handle_copy_function (tree callee_fndecl, + const call_details &cd, + const copy_fn_details &cfd); + const svalue *maybe_get_copy_bounds (const region *src_reg, + const svalue *num_bytes_sval); + void update_for_zero_return (const call_details &cd); + void update_for_nonzero_return (const call_details &cd); void handle_unrecognized_call (const gcall *call, region_model_context *ctxt); @@ -648,7 +659,8 @@ class region_model region_model_context *ctxt) const; void set_value (const region *lhs_reg, const svalue *rhs_sval, - region_model_context *ctxt); + region_model_context *ctxt, + const region *src_reg = NULL); void set_value (tree lhs, tree rhs, region_model_context *ctxt); void clobber_region (const region *reg); void purge_region (const region *reg); @@ -813,6 +825,11 @@ class region_model void check_region_for_read (const region *src_reg, region_model_context *ctxt) const; + void maybe_complain_about_infoleak (const region *dst_reg, + const svalue *copied_sval, + const region *src_reg, + region_model_context *ctxt); + /* Storing this here to avoid passing it around everywhere. */ region_model_manager *const m_mgr; diff --git a/gcc/analyzer/region.cc b/gcc/analyzer/region.cc index fa187fde331..bb4f53b8802 100644 --- a/gcc/analyzer/region.cc +++ b/gcc/analyzer/region.cc @@ -204,6 +204,34 @@ region::get_memory_space () const return MEMSPACE_UNKNOWN; } +/* Get the address space of this region. */ + +addr_space_t +region::get_addr_space () const +{ + const region *iter = this; + while (iter) + { + if (iter->m_type) + return TYPE_ADDR_SPACE (iter->m_type); + switch (iter->get_kind ()) + { + case RK_FIELD: + case RK_ELEMENT: + case RK_OFFSET: + case RK_SIZED: + iter = iter->get_parent_region (); + continue; + case RK_CAST: + iter = iter->dyn_cast_cast_region ()->get_original_region (); + continue; + default: + return ADDR_SPACE_GENERIC; + } + } + return ADDR_SPACE_GENERIC; +} + /* Subroutine for use by region_model_manager::get_or_create_initial_value. Return true if this region has an initial_svalue. Return false if attempting to use INIT_VAL(this_region) should give diff --git a/gcc/analyzer/region.h b/gcc/analyzer/region.h index a17e73c30c9..58e28b998aa 100644 --- a/gcc/analyzer/region.h +++ b/gcc/analyzer/region.h @@ -136,6 +136,7 @@ public: bool descendent_of_p (const region *elder) const; const frame_region *maybe_get_frame_region () const; enum memory_space get_memory_space () const; + addr_space_t get_addr_space () const; bool can_have_initial_svalue_p () const; tree maybe_get_decl () const; @@ -189,6 +190,8 @@ public: const complexity &get_complexity () const { return m_complexity; } + bool untrusted_p () const; + protected: region (complexity c, unsigned id, const region *parent, tree type); @@ -296,6 +299,7 @@ public: /* Accessors. */ const frame_region *get_calling_frame () const { return m_calling_frame; } function *get_function () const { return m_fun; } + tree get_fndecl () const { return get_function ()->decl; } int get_index () const { return m_index; } int get_stack_depth () const { return m_index + 1; } diff --git a/gcc/analyzer/store.h b/gcc/analyzer/store.h index da82bd1bdec..fa824f0eee4 100644 --- a/gcc/analyzer/store.h +++ b/gcc/analyzer/store.h @@ -558,6 +558,7 @@ public: hashval_t hash () const; bool symbolic_p () const; + const region *get_base_region () const { return m_base_region; } void dump_to_pp (pretty_printer *pp, bool simple, bool multiline) const; void dump (bool simple) const; diff --git a/gcc/analyzer/trust-boundaries.cc b/gcc/analyzer/trust-boundaries.cc new file mode 100644 index 00000000000..0425531cd42 --- /dev/null +++ b/gcc/analyzer/trust-boundaries.cc @@ -0,0 +1,615 @@ +/* Handling of trust boundaries + Copyright (C) 2019-2021 Free Software Foundation, Inc. + Contributed by David Malcolm <dmalc...@redhat.com>. + +This file is part of GCC. + +GCC is free software; you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 3, or (at your option) +any later version. + +GCC is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GCC; see the file COPYING3. If not see +<http://www.gnu.org/licenses/>. */ + +#include "config.h" +#define INCLUDE_UNIQUE_PTR +#include "system.h" +#include "coretypes.h" +#include "tree.h" +#include "diagnostic-core.h" +#include "tree-pretty-print.h" +#include "diagnostic-metadata.h" +#include "tristate.h" +#include "selftest.h" +#include "function.h" +#include "json.h" +#include "analyzer/analyzer.h" +#include "analyzer/analyzer-logging.h" +#include "digraph.h" +#include "analyzer/call-string.h" +#include "analyzer/program-point.h" +#include "analyzer/store.h" +#include "analyzer/region-model.h" +#include "diagnostic-event-id.h" +#include "analyzer/sm.h" +#include "analyzer/pending-diagnostic.h" +#include "gcc-rich-location.h" + +#if ENABLE_ANALYZER + +namespace ana { + +/* Information of the layout of a RECORD_TYPE, capturing it as a vector + of items, where each item is either a field or padding. */ + +class record_layout +{ +public: + /* An item within a record; either a field, or padding after a field. */ + struct item + { + public: + item (const bit_range &br, + tree field, + bool is_padding) + : m_bit_range (br), + m_field (field), + m_is_padding (is_padding) + { + } + + bit_offset_t get_start_bit_offset () const + { + return m_bit_range.get_start_bit_offset (); + } + bit_offset_t get_next_bit_offset () const + { + return m_bit_range.get_next_bit_offset (); + } + + bool contains_p (bit_offset_t offset) const + { + return m_bit_range.contains_p (offset); + } + + void dump_to_pp (pretty_printer *pp) const + { + if (m_is_padding) + pp_printf (pp, "padding after %qD", m_field); + else + pp_printf (pp, "%qD", m_field); + pp_string (pp, ", "); + m_bit_range.dump_to_pp (pp); + } + + bit_range m_bit_range; + tree m_field; + bool m_is_padding; + }; + + record_layout (tree record_type) + : m_record_type (record_type) + { + gcc_assert (TREE_CODE (record_type) == RECORD_TYPE); + + for (tree iter = TYPE_FIELDS (record_type); iter != NULL_TREE; + iter = DECL_CHAIN (iter)) + { + if (TREE_CODE (iter) == FIELD_DECL) + { + int iter_field_offset = int_bit_position (iter); + bit_size_t size_in_bits; + if (!int_size_in_bits (TREE_TYPE (iter), &size_in_bits)) + size_in_bits = 0; + + maybe_pad_to (iter_field_offset); + + /* Add field. */ + m_items.safe_push (item (bit_range (iter_field_offset, + size_in_bits), + iter, false)); + } + } + + /* Add any trailing padding. */ + bit_size_t size_in_bits; + if (int_size_in_bits (record_type, &size_in_bits)) + maybe_pad_to (size_in_bits); + } + + void dump_to_pp (pretty_printer *pp) const + { + unsigned i; + item *it; + FOR_EACH_VEC_ELT (m_items, i, it) + { + it->dump_to_pp (pp); + pp_newline (pp); + } + } + + DEBUG_FUNCTION void dump () const + { + pretty_printer pp; + pp_format_decoder (&pp) = default_tree_printer; + pp.buffer->stream = stderr; + dump_to_pp (&pp); + pp_flush (&pp); + } + + const record_layout::item *get_item_at (bit_offset_t offset) const + { + unsigned i; + item *it; + FOR_EACH_VEC_ELT (m_items, i, it) + if (it->contains_p (offset)) + return it; + return NULL; + } + +private: + /* Subroutine of ctor. Add padding item to NEXT_OFFSET if necessary. */ + + void maybe_pad_to (bit_offset_t next_offset) + { + if (m_items.length () > 0) + { + const item &last_item = m_items[m_items.length () - 1]; + bit_offset_t offset_after_last_item + = last_item.get_next_bit_offset (); + if (next_offset > offset_after_last_item) + { + bit_size_t padding_size + = next_offset - offset_after_last_item; + m_items.safe_push (item (bit_range (offset_after_last_item, + padding_size), + last_item.m_field, true)); + } + } + } + + tree m_record_type; + auto_vec<item> m_items; +}; + +/* A subclass of pending_diagnostic for complaining about uninitialized data + being copied across a trust boundary to an untrusted output + (e.g. copy_to_user infoleaks in the Linux kernel). */ + +class exposure_through_uninit_copy + : public pending_diagnostic_subclass<exposure_through_uninit_copy> +{ +public: + exposure_through_uninit_copy (const region *src_region, + const region *dest_region, + const svalue *copied_sval, + region_model_manager *mgr) + : m_src_region (src_region), + m_dest_region (dest_region), + m_copied_sval (copied_sval), + m_mgr (mgr) + { + gcc_assert (m_copied_sval->get_kind () == SK_POISONED + || m_copied_sval->get_kind () == SK_COMPOUND); + } + + const char *get_kind () const FINAL OVERRIDE + { + return "exposure_through_uninit_copy"; + } + + bool operator== (const exposure_through_uninit_copy &other) const + { + return (m_src_region == other.m_src_region + && m_dest_region == other.m_dest_region + && m_copied_sval == other.m_copied_sval); + } + + bool emit (rich_location *rich_loc) FINAL OVERRIDE + { + diagnostic_metadata m; + /* CWE-200: Exposure of Sensitive Information to an Unauthorized Actor. */ + m.add_cwe (200); + enum memory_space mem_space = get_src_memory_space (); + bool warned; + switch (mem_space) + { + default: + warned = warning_meta + (rich_loc, m, + OPT_Wanalyzer_exposure_through_uninit_copy, + "potential exposure of sensitive information" + " by copying uninitialized data across trust boundary"); + break; + case MEMSPACE_STACK: + warned = warning_meta + (rich_loc, m, + OPT_Wanalyzer_exposure_through_uninit_copy, + "potential exposure of sensitive information" + " by copying uninitialized data from stack across trust boundary"); + break; + case MEMSPACE_HEAP: + warned = warning_meta + (rich_loc, m, + OPT_Wanalyzer_exposure_through_uninit_copy, + "potential exposure of sensitive information" + " by copying uninitialized data from heap across trust boundary"); + break; + } + if (warned) + { + location_t loc = rich_loc->get_loc (); + inform_number_of_uninit_bits (loc); + complain_about_uninit_ranges (loc); + + if (mem_space == MEMSPACE_STACK) + maybe_emit_fixit_hint (); + } + return warned; + } + + label_text describe_final_event (const evdesc::final_event &) FINAL OVERRIDE + { + enum memory_space mem_space = get_src_memory_space (); + switch (mem_space) + { + default: + return label_text::borrow ("uninitialized data copied here"); + + case MEMSPACE_STACK: + return label_text::borrow ("uninitialized data copied from stack here"); + + case MEMSPACE_HEAP: + return label_text::borrow ("uninitialized data copied from heap here"); + } + } + + void mark_interesting_stuff (interesting_t *interest) FINAL OVERRIDE + { + if (m_src_region) + interest->add_region_creation (m_src_region); + } + +private: + enum memory_space get_src_memory_space () const + { + return m_src_region ? m_src_region->get_memory_space () : MEMSPACE_UNKNOWN; + } + + bit_size_t calc_num_uninit_bits () const + { + switch (m_copied_sval->get_kind ()) + { + default: + gcc_unreachable (); + break; + case SK_POISONED: + { + const poisoned_svalue *poisoned_sval + = as_a <const poisoned_svalue *> (m_copied_sval); + gcc_assert (poisoned_sval->get_poison_kind () == POISON_KIND_UNINIT); + + /* Give up if don't have type information. */ + if (m_copied_sval->get_type () == NULL_TREE) + return 0; + + bit_size_t size_in_bits; + if (int_size_in_bits (m_copied_sval->get_type (), &size_in_bits)) + return size_in_bits; + + /* Give up if we can't get the size of the type. */ + return 0; + } + break; + case SK_COMPOUND: + { + const compound_svalue *compound_sval + = as_a <const compound_svalue *> (m_copied_sval); + bit_size_t result = 0; + /* Find keys for uninit svals. */ + for (auto iter : *compound_sval) + { + const svalue *sval = iter.second; + if (const poisoned_svalue *psval + = sval->dyn_cast_poisoned_svalue ()) + if (psval->get_poison_kind () == POISON_KIND_UNINIT) + { + const binding_key *key = iter.first; + const concrete_binding *ckey + = key->dyn_cast_concrete_binding (); + gcc_assert (ckey); + result += ckey->get_size_in_bits (); + } + } + return result; + } + } + } + + void inform_number_of_uninit_bits (location_t loc) const + { + bit_size_t num_uninit_bits = calc_num_uninit_bits (); + if (num_uninit_bits <= 0) + return; + if (num_uninit_bits % BITS_PER_UNIT == 0) + { + /* Express in bytes. */ + byte_size_t num_uninit_bytes = num_uninit_bits / BITS_PER_UNIT; + if (num_uninit_bytes == 1) + inform (loc, "1 byte is uninitialized"); + else + inform (loc, + "%wu bytes are uninitialized", num_uninit_bytes.to_uhwi ()); + } + else + { + /* Express in bits. */ + if (num_uninit_bits == 1) + inform (loc, "1 bit is uninitialized"); + else + inform (loc, + "%wu bits are uninitialized", num_uninit_bits.to_uhwi ()); + } + } + + void complain_about_uninit_ranges (location_t loc) const + { + if (const compound_svalue *compound_sval + = m_copied_sval->dyn_cast_compound_svalue ()) + { + /* Find keys for uninit svals. */ + auto_vec<const concrete_binding *> uninit_keys; + for (auto iter : *compound_sval) + { + const svalue *sval = iter.second; + if (const poisoned_svalue *psval + = sval->dyn_cast_poisoned_svalue ()) + if (psval->get_poison_kind () == POISON_KIND_UNINIT) + { + const binding_key *key = iter.first; + const concrete_binding *ckey + = key->dyn_cast_concrete_binding (); + gcc_assert (ckey); + uninit_keys.safe_push (ckey); + } + } + /* Complain about them in sorted order. */ + uninit_keys.qsort (concrete_binding::cmp_ptr_ptr); + + std::unique_ptr<record_layout> layout; + + tree type = m_copied_sval->get_type (); + if (type && TREE_CODE (type) == RECORD_TYPE) + { + // (std::make_unique is C++14) + layout = std::unique_ptr<record_layout> (new record_layout (type)); + + if (0) + layout->dump (); + } + + unsigned i; + const concrete_binding *ckey; + FOR_EACH_VEC_ELT (uninit_keys, i, ckey) + { + bit_offset_t start_bit = ckey->get_start_bit_offset (); + bit_offset_t next_bit = ckey->get_next_bit_offset (); + complain_about_uninit_range (loc, start_bit, next_bit, + layout.get ()); + } + } + } + + void complain_about_uninit_range (location_t loc, + bit_offset_t start_bit, + bit_offset_t next_bit, + const record_layout *layout) const + { + if (layout) + { + while (start_bit < next_bit) + { + if (const record_layout::item *item + = layout->get_item_at (start_bit)) + { + gcc_assert (start_bit >= item->get_start_bit_offset ()); + gcc_assert (start_bit < item->get_next_bit_offset ()); + if (item->get_start_bit_offset () == start_bit + && item->get_next_bit_offset () <= next_bit) + complain_about_fully_uninit_item (*item); + else + complain_about_partially_uninit_item (*item); + start_bit = item->get_next_bit_offset (); + continue; + } + else + break; + } + } + + if (start_bit >= next_bit) + return; + + if (start_bit % 8 == 0 && next_bit % 8 == 0) + { + /* Express in bytes. */ + byte_offset_t start_byte = start_bit / 8; + byte_offset_t last_byte = (next_bit / 8) - 1; + if (last_byte == start_byte) + inform (loc, + "byte %wu is uninitialized", + start_byte.to_uhwi ()); + else + inform (loc, + "bytes %wu - %wu are uninitialized", + start_byte.to_uhwi (), + last_byte.to_uhwi ()); + } + else + { + /* Express in bits. */ + bit_offset_t last_bit = next_bit - 1; + if (last_bit == start_bit) + inform (loc, + "bit %wu is uninitialized", + start_bit.to_uhwi ()); + else + inform (loc, + "bits %wu - %wu are uninitialized", + start_bit.to_uhwi (), + last_bit.to_uhwi ()); + } + } + + static void + complain_about_fully_uninit_item (const record_layout::item &item) + { + tree field = item.m_field; + bit_size_t num_bits = item.m_bit_range.m_size_in_bits; + if (item.m_is_padding) + { + if (num_bits % 8 == 0) + { + /* Express in bytes. */ + byte_size_t num_bytes = num_bits / BITS_PER_UNIT; + if (num_bytes == 1) + inform (DECL_SOURCE_LOCATION (field), + "padding after field %qD is uninitialized (1 byte)", + field); + else + inform (DECL_SOURCE_LOCATION (field), + "padding after field %qD is uninitialized (%wu bytes)", + field, num_bytes.to_uhwi ()); + } + else + { + /* Express in bits. */ + if (num_bits == 1) + inform (DECL_SOURCE_LOCATION (field), + "padding after field %qD is uninitialized (1 bit)", + field); + else + inform (DECL_SOURCE_LOCATION (field), + "padding after field %qD is uninitialized (%wu bits)", + field, num_bits.to_uhwi ()); + } + } + else + { + if (num_bits % 8 == 0) + { + /* Express in bytes. */ + byte_size_t num_bytes = num_bits / BITS_PER_UNIT; + if (num_bytes == 1) + inform (DECL_SOURCE_LOCATION (field), + "field %qD is uninitialized (1 byte)", field); + else + inform (DECL_SOURCE_LOCATION (field), + "field %qD is uninitialized (%wu bytes)", + field, num_bytes.to_uhwi ()); + } + else + { + /* Express in bits. */ + if (num_bits == 1) + inform (DECL_SOURCE_LOCATION (field), + "field %qD is uninitialized (1 bit)", field); + else + inform (DECL_SOURCE_LOCATION (field), + "field %qD is uninitialized (%wu bits)", + field, num_bits.to_uhwi ()); + } + } + } + + static void + complain_about_partially_uninit_item (const record_layout::item &item) + { + tree field = item.m_field; + if (item.m_is_padding) + inform (DECL_SOURCE_LOCATION (field), + "padding after field %qD is partially uninitialized", + field); + else + inform (DECL_SOURCE_LOCATION (field), + "field %qD is partially uninitialized", + field); + /* TODO: ideally we'd describe what parts are uninitialized. */ + } + + void maybe_emit_fixit_hint () const + { + if (tree decl = m_src_region->maybe_get_decl ()) + { + gcc_rich_location hint_richloc (DECL_SOURCE_LOCATION (decl)); + hint_richloc.add_fixit_insert_after (" = {0}"); + inform (&hint_richloc, + "suggest forcing zero-initialization by" + " providing a %<{0}%> initializer"); + } + } + +private: + const region *m_src_region; + const region *m_dest_region; + const svalue *m_copied_sval; + region_model_manager *m_mgr; +}; + +/* Return true if any part of SVAL is uninitialized. */ + +static bool +contains_uninit_p (const svalue *sval) +{ + struct uninit_finder : public visitor + { + public: + uninit_finder () : m_found_uninit (false) {} + void visit_poisoned_svalue (const poisoned_svalue *sval) + { + if (sval->get_poison_kind () == POISON_KIND_UNINIT) + m_found_uninit = true; + } + bool m_found_uninit; + }; + + uninit_finder v; + sval->accept (&v); + + return v.m_found_uninit; +} + +/* Subroutine of region_model::set_value, called when setting DST_REG + when writing through an untrusted pointer (and thus crossing a security + boundary). + + Check that COPIED_SVAL is fully initialized. If not, complain about + an infoleak to CTXT. + + SRC_REG can be NULL; if non-NULL it is used as a hint in the diagnostic + as to where COPIED_SVAL came from. */ + +void +region_model::maybe_complain_about_infoleak (const region *dst_reg, + const svalue *copied_sval, + const region *src_reg, + region_model_context *ctxt) +{ + /* Check for exposure. */ + if (contains_uninit_p (copied_sval)) + ctxt->warn (new exposure_through_uninit_copy (src_reg, + dst_reg, + copied_sval, + m_mgr)); +} + +} // namespace ana + +#endif /* #if ENABLE_ANALYZER */ diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi index 518a5216c73..e8b939a2fc4 100644 --- a/gcc/doc/invoke.texi +++ b/gcc/doc/invoke.texi @@ -438,6 +438,7 @@ Objective-C and Objective-C++ Dialects}. -Wno-analyzer-double-fclose @gol -Wno-analyzer-double-free @gol -Wno-analyzer-exposure-through-output-file @gol +-Wno-analyzer-exposure-through-uninit-copy @gol -Wno-analyzer-file-leak @gol -Wno-analyzer-free-of-non-heap @gol -Wno-analyzer-malloc-leak @gol @@ -9393,6 +9394,7 @@ Enabling this option effectively enables the following warnings: -Wanalyzer-double-fclose @gol -Wanalyzer-double-free @gol -Wanalyzer-exposure-through-output-file @gol +-Wanalyzer-exposure-through-uninit-copy @gol -Wanalyzer-file-leak @gol -Wanalyzer-free-of-non-heap @gol -Wanalyzer-malloc-leak @gol @@ -9461,6 +9463,18 @@ This diagnostic warns for paths through the code in which a security-sensitive value is written to an output file (such as writing a password to a log file). +@item Wanalyzer-exposure-through-uninit-copy +@opindex Wanalyzer-exposure-through-uninit-copy +@opindex Wno-analyzer-exposure-through-uninit-copy +This warning requires @option{-fanalyzer}, which enables it; use +@option{-Wno-analyzer-exposure-through-uninit-copy} +to disable it. + +This diagnostic warns for ``infoleaks'' - paths through the code in which +uninitialized values are copied across a security boundary +(such as copying a partially-initialized struct on the stack +to an untrusted custom address space). + @item -Wno-analyzer-file-leak @opindex Wanalyzer-file-leak @opindex Wno-analyzer-file-leak diff --git a/gcc/testsuite/gcc.dg/analyzer/copy-function-1.c b/gcc/testsuite/gcc.dg/analyzer/copy-function-1.c new file mode 100644 index 00000000000..1b372ac3415 --- /dev/null +++ b/gcc/testsuite/gcc.dg/analyzer/copy-function-1.c @@ -0,0 +1,98 @@ +#include "analyzer-decls.h" + +extern void copy_fn_always_succeeds (void *to, const void *from, long n) + __attribute__((access (write_only, 1, 3), + access (read_only, 2, 3) + )); + +extern int copy_fn_zero_on_success (void *to, const void *from, long n) + __attribute__((access (write_only, 1, 3), + access (read_only, 2, 3), + returns_zero_on_success + )); + +extern int copy_fn_zero_on_failure (void *to, const void *from, long n) + __attribute__((access (write_only, 1, 3), + access (read_only, 2, 3), + returns_zero_on_failure + )); + +void +test_1 (int a) +{ + int b; + copy_fn_always_succeeds (&b, &a, sizeof (a)); + __analyzer_eval (a == b); /* { dg-warning "TRUE" } */ +} + +int +test_2 (int a) +{ + int b = 42; + int r = copy_fn_zero_on_success (&b, &a, sizeof (a)); + if (r) + /* Failure. */ + __analyzer_eval (b == 42); /* { dg-warning "TRUE" } */ + else + /* Success. */ + __analyzer_eval (b == a); /* { dg-warning "TRUE" } */ + return r; +} + +int +test_3 (int a) +{ + int b = 42; + int r = copy_fn_zero_on_failure (&b, &a, sizeof (a)); + if (r) + /* Success. */ + __analyzer_eval (b == a); /* { dg-warning "TRUE" } */ + else + /* Failure. */ + __analyzer_eval (b == 42); /* { dg-warning "TRUE" } */ + return r; +} + +/* Different param order. */ + +extern int copy_fn_zero_on_failure_2 (long n, const void *from, void *to) + __attribute__((returns_zero_on_failure, + access (read_only, 2, 1), + access (write_only, 3, 1))); +int +test_4 (int a) +{ + int b = 42; + int r = copy_fn_zero_on_failure_2 (sizeof (a), &a, &b); + if (r) + /* Success. */ + __analyzer_eval (b == a); /* { dg-warning "TRUE" } */ + else + /* Failure. */ + __analyzer_eval (b == 42); /* { dg-warning "TRUE" } */ + return r; +} + +/* Not a copy-fn: too many arguments. */ +extern int too_many_args (void *to, const void *from, long n, int i) + __attribute__((access (write_only, 1, 3), + access (read_only, 2, 3))); + +int test_5 (int a) +{ + int b; + too_many_args (&b, &a, sizeof (a), 17); + __analyzer_eval (a == b); /* { dg-warning "UNKNOWN" } */ +} + +/* Not a copy-fn: variadic. */ +extern int variadic (void *to, const void *from, long n, ...) + __attribute__((access (write_only, 1, 3), + access (read_only, 2, 3))); + +int test_6 (int a) +{ + int b; + variadic (&b, &a, sizeof (a)); + __analyzer_eval (a == b); /* { dg-warning "UNKNOWN" } */ +} diff --git a/gcc/testsuite/gcc.dg/analyzer/copy_from_user-1.c b/gcc/testsuite/gcc.dg/analyzer/copy_from_user-1.c new file mode 100644 index 00000000000..a1415f38aa6 --- /dev/null +++ b/gcc/testsuite/gcc.dg/analyzer/copy_from_user-1.c @@ -0,0 +1,45 @@ +typedef __SIZE_TYPE__ size_t; + +#define __user + +extern int copy_from_user(void *to, const void __user *from, long n) + __attribute__((access (write_only, 1, 3), + access (read_only, 2, 3) + )); + +#define EFAULT 14 +#define EINVAL 22 + +/* Taken from Linux: fs/binfmt_misc.c (GPL-2.0-only). */ + +int parse_command(const char __user *buffer, size_t count) +{ + char s[4]; + + if (count > 3) + return -EINVAL; + if (copy_from_user(s, buffer, count)) + return -EFAULT; + if (!count) + return 0; + if (s[count - 1] == '\n') /* { dg-bogus "uninit" } */ + count--; + if (count == 1 && s[0] == '0') /* { dg-bogus "uninit" } */ + return 1; + if (count == 1 && s[0] == '1') /* { dg-bogus "uninit" } */ + return 2; + if (count == 2 && s[0] == '-' && s[1] == '1') /* { dg-bogus "uninit" } */ + return 3; + return -EINVAL; +} + +/* Not using return value from copy_from_user. */ + +int test_2 (const char __user *buffer, size_t count) +{ + char s[4]; + if (count > 3) + return -EINVAL; + copy_from_user(s, buffer, count); + return 0; +} diff --git a/gcc/testsuite/gcc.dg/analyzer/infoleak-1.c b/gcc/testsuite/gcc.dg/analyzer/infoleak-1.c new file mode 100644 index 00000000000..4b33055c33a --- /dev/null +++ b/gcc/testsuite/gcc.dg/analyzer/infoleak-1.c @@ -0,0 +1,181 @@ +#include <string.h> + +#include "test-uaccess.h" + +typedef unsigned char u8; +typedef unsigned __INT16_TYPE__ u16; +typedef unsigned __INT32_TYPE__ u32; + +struct s1 +{ + u32 i; +}; + +void test_1a (void __user *dst, u32 a) +{ + struct s1 s; + s.i = a; + copy_to_user(dst, &s, sizeof (struct s1)); /* { dg-bogus "" } */ +} + +void test_1b (void __user *dst, u32 a) +{ + struct s1 s; + copy_to_user(dst, &s, sizeof (struct s1)); /* { dg-warning "potential exposure of sensitive information by copying uninitialized data from stack" "warning" } */ + /* { dg-message "4 bytes are uninitialized" "note how much" { target *-*-* } .-1 } */ +} + +void test_1c (void __user *dst, u32 a) +{ + struct s1 s; + memset (&s, 0, sizeof (struct s1)); + copy_to_user(dst, &s, sizeof (struct s1)); /* { dg-bogus "" } */ +} + +void test_1d (void __user *dst, u32 a) +{ + struct s1 s = {0}; + copy_to_user(dst, &s, sizeof (struct s1)); /* { dg-bogus "" } */ +} + +struct s2 +{ + u32 i; + u32 j; /* { dg-message "field 'j' is uninitialized \\(4 bytes\\)" } */ +}; + +void test_2a (void __user *dst, u32 a) +{ + struct s2 s; /* { dg-message "region created on stack here" "where" } */ + /* { dg-message "capacity: 8 bytes" "capacity" { target *-*-* } .-1 } */ + s.i = a; + copy_to_user(dst, &s, sizeof (struct s2)); /* { dg-warning "potential exposure of sensitive information by copying uninitialized data from stack" "warning" } */ + /* { dg-message "4 bytes are uninitialized" "note how much" { target *-*-* } .-1 } */ +} + +void test_2b (void __user *dst, u32 a) +{ + struct s2 s; + s.i = a; + /* Copy with wrong size (only part of s2). */ + copy_to_user(dst, &s, sizeof (struct s1)); +} + +void test_2d (void __user *dst, u32 a) +{ + struct s2 s = {0}; + s.i = a; + copy_to_user(dst, &s, sizeof (struct s2)); /* { dg-bogus" } */ +} + +struct empty {}; + +void test_empty (void __user *dst) +{ + struct empty e; + copy_to_user(dst, &e, sizeof (struct empty)); +} + +union un_a +{ + u32 i; + u8 j; +}; + +/* As above, but in a different order. */ + +union un_b +{ + u8 j; + u32 i; +}; + +void test_union_1a (void __user *dst, u8 v) +{ + union un_a u; /* { dg-message "region created on stack here" "where" } */ + /* { dg-message "capacity: 4 bytes" "capacity" { target *-*-* } .-1 } */ + u.j = v; + copy_to_user(dst, &u, sizeof (union un_a)); /* { dg-warning "potential exposure of sensitive information by copying uninitialized data from stack" "warning" } */ + /* { dg-message "3 bytes are uninitialized" "note how much" { target *-*-* } .-1 } */ + /* { dg-message "bytes 1 - 3 are uninitialized" "note how much" { target *-*-* } .-2 } */ +} + +void test_union_1b (void __user *dst, u8 v) +{ + union un_b u; /* { dg-message "region created on stack here" "where" } */ + /* { dg-message "capacity: 4 bytes" "capacity" { target *-*-* } .-1 } */ + u.j = v; + copy_to_user(dst, &u, sizeof (union un_b)); /* { dg-warning "potential exposure of sensitive information by copying uninitialized data from stack" "warning" } */ + /* { dg-message "3 bytes are uninitialized" "note how much" { target *-*-* } .-1 } */ + /* { dg-message "bytes 1 - 3 are uninitialized" "note how much" { target *-*-* } .-2 } */ +} + +void test_union_2a (void __user *dst, u8 v) +{ + union un_a u = {0}; + u.j = v; + copy_to_user(dst, &u, sizeof (union un_a)); +} + +void test_union_2b (void __user *dst, u8 v) +{ + union un_b u = {0}; + u.j = v; + copy_to_user(dst, &u, sizeof (union un_b)); +} + +void test_union_3a (void __user *dst, u32 v) +{ + union un_a u; + u.i = v; + copy_to_user(dst, &u, sizeof (union un_a)); /* { dg-bogus "" } */ +} + +void test_union_3b (void __user *dst, u32 v) +{ + union un_b u; + u.i = v; + copy_to_user(dst, &u, sizeof (union un_b)); /* { dg-bogus "" } */ +} + +void test_union_4a (void __user *dst, u8 v) +{ + union un_a u = {0}; + copy_to_user(dst, &u, sizeof (union un_a)); /* { dg-bogus "" } */ +} + +void test_union_4b (void __user *dst, u8 v) +{ + union un_b u = {0}; + copy_to_user(dst, &u, sizeof (union un_b)); /* { dg-bogus "" } */ +} + +struct st_union_5 +{ + union { + u8 f1; + u32 f2; + } u; /* { dg-message "field 'u' is partially uninitialized" } */ +}; + +void test_union_5 (void __user *dst, u8 v) +{ + struct st_union_5 st; /* { dg-message "region created on stack here" "where" } */ + /* { dg-message "capacity: 4 bytes" "capacity" { target *-*-* } .-1 } */ + + /* This write only initializes the u8 within the union "u", + leaving the remaining 3 bytes uninitialized. */ + st.u.f1 = v; + + copy_to_user (dst, &st, sizeof(st)); /* { dg-warning "potential exposure of sensitive information by copying uninitialized data from stack" "warning" } */ + /* { dg-message "3 bytes are uninitialized" "note how much" { target *-*-* } .-1 } */ +} + +void test_one_byte (void __user *dst) +{ + char src; /* { dg-message "region created on stack here" "where" } */ + /* { dg-message "capacity: 1 byte" "capacity" { target *-*-* } .-1 } */ + + copy_to_user (dst, &src, sizeof(src)); /* { dg-warning "potential exposure of sensitive information by copying uninitialized data from stack" "warning" } */ + /* { dg-message "1 byte is uninitialized" "note how much" { target *-*-* } .-1 } */ +} diff --git a/gcc/testsuite/gcc.dg/analyzer/infoleak-2.c b/gcc/testsuite/gcc.dg/analyzer/infoleak-2.c new file mode 100644 index 00000000000..7510f80a2de --- /dev/null +++ b/gcc/testsuite/gcc.dg/analyzer/infoleak-2.c @@ -0,0 +1,29 @@ +#include <string.h> + +#include "test-uaccess.h" + +typedef unsigned char u8; +typedef unsigned __INT16_TYPE__ u16; +typedef unsigned __INT32_TYPE__ u32; + +/* Coverage for the various singular and plural forms of bits, bytes, and fields vs padding. */ + +struct st +{ + u32 a; /* { dg-message "field 'a' is uninitialized \\(4 bytes\\)" } */ + int b:1; /* { dg-message "field 'b' is uninitialized \\(1 bit\\)" "field" } */ + /* { dg-message "padding after field 'b' is uninitialized \\(7 bits\\)" "padding" { target *-*-* } .-1 } */ + u8 d; /* { dg-message "field 'd' is uninitialized \\(1 byte\\)" } */ + int c:7; /* { dg-message "padding after field 'c' is uninitialized \\(9 bits\\)" } */ + u16 e; /* { dg-message "padding after field 'e' is uninitialized \\(2 bytes\\)" } */ +}; + +void test (void __user *dst, u16 v) +{ + struct st s; /* { dg-message "region created on stack here" "where" } */ + /* { dg-message "capacity: 12 bytes" "capacity" { target *-*-* } .-1 } */ + /* { dg-message "suggest forcing zero-initialization by providing a '\\{0\\}' initializer" "fix-it" { target *-*-* } .-2 } */ + s.e = v; + copy_to_user(dst, &s, sizeof (struct st)); /* { dg-warning "potential exposure of sensitive information by copying uninitialized data from stack" "warning" } */ + /* { dg-message "10 bytes are uninitialized" "note how much" { target *-*-* } .-1 } */ +} diff --git a/gcc/testsuite/gcc.dg/analyzer/infoleak-3.c b/gcc/testsuite/gcc.dg/analyzer/infoleak-3.c new file mode 100644 index 00000000000..42dfffa8f65 --- /dev/null +++ b/gcc/testsuite/gcc.dg/analyzer/infoleak-3.c @@ -0,0 +1,141 @@ +/* Verify that -Wanalyzer-exposure-through-uninit-copy doesn't get confused + if size argument to copy_to_user is an upper bound, rather than a + constant. */ + +#include "analyzer-decls.h" + +typedef __SIZE_TYPE__ size_t; + +#include "test-uaccess.h" + +typedef unsigned __INT32_TYPE__ u32; + +/* min_t adapted from include/linux/kernel.h. */ + +#define min_t(type, x, y) ({ \ + type __min1 = (x); \ + type __min2 = (y); \ + __min1 < __min2 ? __min1: __min2; }) + +struct st +{ + u32 a; + u32 b; +}; + +/* Verify that we cope with min_t. */ + +void test_1_full_init (void __user *dst, u32 x, u32 y, unsigned long in_sz) +{ + struct st s; + s.a = x; + s.b = y; + unsigned long copy_sz = min_t(unsigned long, in_sz, sizeof(s)); + copy_to_user(dst, &s, copy_sz); /* { dg-bogus "exposure" } */ +} + +void test_1_partial_init (void __user *dst, u32 x, u32 y, unsigned long in_sz) +{ + struct st s; + s.a = x; + /* s.y not initialized. */ + unsigned long copy_sz = min_t(unsigned long, in_sz, sizeof(s)); + copy_to_user(dst, &s, copy_sz); /* { dg-warning "exposure" } */ +} + +/* Constant on LHS rather than RHS. */ + +void test_2_full_init (void __user *dst, u32 x, u32 y, unsigned long in_sz) +{ + struct st s; + s.a = x; + s.b = y; + unsigned long copy_sz = min_t(unsigned long, sizeof(s), in_sz); + copy_to_user(dst, &s, copy_sz); /* { dg-bogus "exposure" } */ +} + +void test_2_partial_init (void __user *dst, u32 x, u32 y, unsigned long in_sz) +{ + struct st s; + s.a = x; + /* s.y not initialized. */ + unsigned long copy_sz = min_t(unsigned long, sizeof(s), in_sz); + copy_to_user(dst, &s, copy_sz); /* { dg-warning "exposure" } */ +} + +/* min_t with various casts. */ + +void test_3_full_init (void __user *dst, u32 x, u32 y, int in_sz) +{ + struct st s; + s.a = x; + s.b = y; + int copy_sz = min_t(unsigned int, in_sz, sizeof(s)); + copy_to_user(dst, &s, copy_sz); /* { dg-bogus "exposure" } */ +} + +void test_3_partial_init (void __user *dst, u32 x, u32 y, int in_sz) +{ + struct st s; + s.a = x; + /* s.y not initialized. */ + int copy_sz = min_t(unsigned int, in_sz, sizeof(s)); + copy_to_user(dst, &s, copy_sz); /* { dg-warning "exposure" } */ +} + +/* Comparison against an upper bound. */ + +void test_4_full_init (void __user *dst, u32 x, u32 y, size_t in_sz) +{ + struct st s; + s.a = x; + s.b = y; + + size_t copy_sz = in_sz; + if (copy_sz > sizeof(s)) + copy_sz = sizeof(s); + + copy_to_user(dst, &s, copy_sz); /* { dg-bogus "exposure" } */ +} + +void test_4_partial_init (void __user *dst, u32 x, u32 y, size_t in_sz) +{ + struct st s; + s.a = x; + /* s.y not initialized. */ + + size_t copy_sz = in_sz; + if (copy_sz > sizeof(s)) + copy_sz = sizeof(s); + + copy_to_user(dst, &s, copy_sz); /* { dg-warning "exposure" } */ +} + +/* Comparison against an upper bound with casts. */ + +void test_5_full_init (void __user *dst, u32 x, u32 y, int in_sz) +{ + struct st s; + s.a = x; + s.b = y; + + int copy_sz = in_sz; + if (copy_sz > sizeof(s)) + copy_sz = sizeof(s); + copy_to_user(dst, &s, copy_sz); /* { dg-bogus "exposure" } */ +} + +/* Comparison against an upper bound with casts. */ + +void test_5_partial_init (void __user *dst, u32 x, u32 y, int in_sz) +{ + struct st s; + s.a = x; + /* s.y not initialized. */ + + int copy_sz = in_sz; + if (copy_sz > sizeof(s)) + copy_sz = sizeof(s); + + copy_to_user(dst, &s, copy_sz); /* { dg-warning "exposure" "" } */ +} diff --git a/gcc/testsuite/gcc.dg/analyzer/infoleak-5.c b/gcc/testsuite/gcc.dg/analyzer/infoleak-5.c new file mode 100644 index 00000000000..94ebb62062b --- /dev/null +++ b/gcc/testsuite/gcc.dg/analyzer/infoleak-5.c @@ -0,0 +1,35 @@ +#include "test-uaccess.h" + +typedef unsigned char u8; +typedef unsigned __INT16_TYPE__ u16; +typedef unsigned __INT32_TYPE__ u32; + +/* As per infoleak-1.c, but doing it here via deref assignment, + rather than copy_to_user. */ + +struct s1 +{ + u32 i; +}; + +void test_1a (struct s1 __user *dst, u32 a) +{ + struct s1 s; + s.i = a; + *dst = s; +} + +union un_a +{ + u32 i; + u8 j; +}; + +void test_union_1a (union un_a __user *dst, u8 v) +{ + union un_a u; + u.j = v; + *dst = u; /* { dg-warning "potential exposure of sensitive information by copying uninitialized data across trust boundary" "warning" } */ + /* { dg-message "3 bytes are uninitialized" "note how much" { target *-*-* } .-1 } */ + /* { dg-message "bytes 1 - 3 are uninitialized" "note how much" { target *-*-* } .-2 } */ +} diff --git a/gcc/testsuite/gcc.dg/analyzer/infoleak-CVE-2011-1078-1.c b/gcc/testsuite/gcc.dg/analyzer/infoleak-CVE-2011-1078-1.c new file mode 100644 index 00000000000..c3b22320e91 --- /dev/null +++ b/gcc/testsuite/gcc.dg/analyzer/infoleak-CVE-2011-1078-1.c @@ -0,0 +1,134 @@ +/* "The sco_sock_getsockopt_old function in net/bluetooth/sco.c in the + Linux kernel before 2.6.39 does not initialize a certain structure, + which allows local users to obtain potentially sensitive information + from kernel stack memory via the SCO_CONNINFO option." + + Fixed e.g. by c4c896e1471aec3b004a693c689f60be3b17ac86 on linux-2.6.39.y + in linux-stable. */ + +#include <string.h> + +typedef unsigned char __u8; +typedef unsigned short __u16; + +#include "test-uaccess.h" + +/* Adapted from include/asm-generic/uaccess.h. */ + +#define get_user(x, ptr) \ +({ \ + /* [...snip...] */ \ + __get_user_fn(sizeof (*(ptr)), ptr, &(x)); \ + /* [...snip...] */ \ +}) + +static inline int __get_user_fn(size_t size, const void __user *ptr, void *x) +{ + size = copy_from_user(x, ptr, size); + return size ? -1 : size; +} + +/* Adapted from include/linux/kernel.h. */ + +#define min_t(type, x, y) ({ \ + type __min1 = (x); \ + type __min2 = (y); \ + __min1 < __min2 ? __min1: __min2; }) + +/* Adapted from include/linux/net.h. */ + +struct socket { + /* [...snip...] */ + struct sock *sk; + /* [...snip...] */ +}; + +/* Adapted from include/net/bluetooth/sco.h. */ + +struct sco_conninfo { + __u16 hci_handle; + __u8 dev_class[3]; /* { dg-message "padding after field 'dev_class' is uninitialized \\(1 byte\\)" } */ +}; + +struct sco_conn { + + struct hci_conn *hcon; + /* [...snip...] */ +}; + +#define sco_pi(sk) ((struct sco_pinfo *) sk) + +struct sco_pinfo { + /* [...snip...] */ + struct sco_conn *conn; +}; + +/* Adapted from include/net/bluetooth/hci_core.h. */ + +struct hci_conn { + /* [...snip...] */ + __u16 handle; + /* [...snip...] */ + __u8 dev_class[3]; + /* [...snip...] */ +}; + +/* Adapted from sco_sock_getsockopt_old in net/bluetooth/sco.c. */ + +static int sco_sock_getsockopt_old_broken(struct socket *sock, int optname, char __user *optval, int __user *optlen) +{ + struct sock *sk = sock->sk; + /* [...snip...] */ + struct sco_conninfo cinfo; /* { dg-message "region created on stack here" "where" } */ + /* { dg-message "capacity: 6 bytes" "capacity" { target *-*-* } .-1 } */ + /* Note: 40 bits of fields, padded to 48. */ + + int len, err = 0; + + /* [...snip...] */ + + if (get_user(len, optlen)) + return -1; + + /* [...snip...] */ + + /* case SCO_CONNINFO: */ + cinfo.hci_handle = sco_pi(sk)->conn->hcon->handle; + memcpy(cinfo.dev_class, sco_pi(sk)->conn->hcon->dev_class, 3); + + len = min_t(unsigned int, len, sizeof(cinfo)); + if (copy_to_user(optval, (char *)&cinfo, len)) /* { dg-warning "potential exposure of sensitive information by copying uninitialized data from stack" "warning" { target *-*-* } } */ + /* { dg-message "1 byte is uninitialized" "how much note" { target *-*-* } .-1 } */ + err = -1; + + /* [...snip...] */ +} + +static int sco_sock_getsockopt_fixed(struct socket *sock, int optname, char __user *optval, int __user *optlen) +{ + struct sock *sk = sock->sk; + /* [...snip...] */ + struct sco_conninfo cinfo; + /* Note: 40 bits of fields, padded to 48. */ + + int len, err = 0; + + /* [...snip...] */ + + if (get_user(len, optlen)) + return -1; + + /* [...snip...] */ + + /* case SCO_CONNINFO: */ + /* Infoleak fixed by this memset call. */ + memset(&cinfo, 0, sizeof(cinfo)); + cinfo.hci_handle = sco_pi(sk)->conn->hcon->handle; + memcpy(cinfo.dev_class, sco_pi(sk)->conn->hcon->dev_class, 3); + + len = min_t(unsigned int, len, sizeof(cinfo)); + if (copy_to_user(optval, (char *)&cinfo, len)) /* { dg-bogus "exposure" } */ + err = -1; + + /* [...snip...] */ +} diff --git a/gcc/testsuite/gcc.dg/analyzer/infoleak-CVE-2011-1078-2.c b/gcc/testsuite/gcc.dg/analyzer/infoleak-CVE-2011-1078-2.c new file mode 100644 index 00000000000..811f5f6c605 --- /dev/null +++ b/gcc/testsuite/gcc.dg/analyzer/infoleak-CVE-2011-1078-2.c @@ -0,0 +1,42 @@ +/* Simplified versions of infoleak-CVE-2011-1078-1.c. */ + +#include <string.h> + +typedef unsigned char __u8; +typedef unsigned short __u16; + +#include "test-uaccess.h" + +/* Adapted from include/net/bluetooth/sco.h. */ + +struct sco_conninfo { + __u16 hci_handle; + __u8 dev_class[3]; /* { dg-message "padding after field 'dev_class' is uninitialized \\(1 byte\\)" } */ +}; + +/* Adapted from sco_sock_getsockopt_old in net/bluetooth/sco.c. */ + +int test_1 (char __user *optval, const struct sco_conninfo *in) +{ + struct sco_conninfo cinfo; /* { dg-message "region created on stack here" "where" } */ + /* { dg-message "capacity: 6 bytes" "capacity" { target *-*-* } .-1 } */ + /* Note: 40 bits of fields, padded to 48. */ + + cinfo.hci_handle = in->hci_handle; + memcpy(cinfo.dev_class, in->dev_class, 3); + + copy_to_user(optval, &cinfo, sizeof(cinfo)); /* { dg-warning "potential exposure of sensitive information by copying uninitialized data from stack" "warning" } */ + /* { dg-message "1 byte is uninitialized" "how much note" { target *-*-* } .-1 } */ +} + +int test_2 (char __user *optval, const struct sco_conninfo *in) +{ + struct sco_conninfo cinfo; + /* Note: 40 bits of fields, padded to 48. */ + + memset(&cinfo, 0, sizeof(cinfo)); + cinfo.hci_handle = in->hci_handle; + memcpy(cinfo.dev_class, in->dev_class, 3); + + copy_to_user(optval, &cinfo, sizeof(cinfo)); /* { dg-bogus "" } */ +} diff --git a/gcc/testsuite/gcc.dg/analyzer/infoleak-CVE-2014-1446-1.c b/gcc/testsuite/gcc.dg/analyzer/infoleak-CVE-2014-1446-1.c new file mode 100644 index 00000000000..2726a9c0f38 --- /dev/null +++ b/gcc/testsuite/gcc.dg/analyzer/infoleak-CVE-2014-1446-1.c @@ -0,0 +1,117 @@ +/* "The yam_ioctl function in drivers/net/hamradio/yam.c in the Linux kernel + before 3.12.8 does not initialize a certain structure member, which allows + local users to obtain sensitive information from kernel memory by + leveraging the CAP_NET_ADMIN capability for an SIOCYAMGCFG ioctl call." + + Fixed e.g. by e7834c71c2cacc621ddc64bd71f83ef2054f6539 on linux-3.12.y + in linux-stable. */ + +#include <string.h> + +#include "test-uaccess.h" + +/* Adapted from include/linux/yam.h */ + +struct yamcfg { + unsigned int mask; /* Mask of commands */ + unsigned int iobase; /* IO Base of COM port */ + unsigned int irq; /* IRQ of COM port */ + unsigned int bitrate; /* Bit rate of radio port */ + unsigned int baudrate; /* Baud rate of the RS232 port */ + unsigned int txdelay; /* TxDelay */ + unsigned int txtail; /* TxTail */ + unsigned int persist; /* Persistence */ + unsigned int slottime; /* Slottime */ + unsigned int mode; /* mode 0 (simp), 1(Dupl), 2(Dupl+delay) */ + unsigned int holddly; /* PTT delay in FullDuplex 2 mode */ +}; + +struct yamdrv_ioctl_cfg { + int cmd; /* { dg-message "field 'cmd' is uninitialized \\(4 bytes\\)" } */ + struct yamcfg cfg; +}; + +/* Adapted from include/asm-generic/errno-base.h */ + +#define EFAULT 14 /* Bad address */ + +/* Adapted from drivers/net/hamradio/yam.c */ + +struct yam_port { + /* [...snip...] */ + + int bitrate; + int baudrate; + int iobase; + int irq; + int dupmode; + + /* [...snip...] */ + + int txd; /* tx delay */ + int holdd; /* duplex ptt delay */ + int txtail; /* txtail delay */ + int slot; /* slottime */ + int pers; /* persistence */ + + /* [...snip...] */ +}; + +/* Broken version, leaving yi.cmd uninitialized. */ + +static int yam_ioctl(/* [...snip...] */ + void __user *dst, struct yam_port *yp) +{ + struct yamdrv_ioctl_cfg yi; /* { dg-message "region created on stack here" "memspace event" } */ + /* { dg-message "capacity: 48 bytes" "capacity event" { target *-*-* } .-1 } */ + + /* [...snip...] */ + + /* case SIOCYAMGCFG: */ + yi.cfg.mask = 0xffffffff; + yi.cfg.iobase = yp->iobase; + yi.cfg.irq = yp->irq; + yi.cfg.bitrate = yp->bitrate; + yi.cfg.baudrate = yp->baudrate; + yi.cfg.mode = yp->dupmode; + yi.cfg.txdelay = yp->txd; + yi.cfg.holddly = yp->holdd; + yi.cfg.txtail = yp->txtail; + yi.cfg.persist = yp->pers; + yi.cfg.slottime = yp->slot; + if (copy_to_user(dst, &yi, sizeof(struct yamdrv_ioctl_cfg))) /* { dg-warning "potential exposure of sensitive information by copying uninitialized data from stack" "warning" } */ + /* { dg-message "4 bytes are uninitialized" "how much note" { target *-*-* } .-1 } */ + return -EFAULT; + /* [...snip...] */ + + return 0; +} + +/* Fixed version, with a memset. */ + +static int yam_ioctl_fixed(/* [...snip...] */ + void __user *dst, struct yam_port *yp) +{ + struct yamdrv_ioctl_cfg yi; + + /* [...snip...] */ + + /* case SIOCYAMGCFG: */ + memset(&yi, 0, sizeof(yi)); + yi.cfg.mask = 0xffffffff; + yi.cfg.iobase = yp->iobase; + yi.cfg.irq = yp->irq; + yi.cfg.bitrate = yp->bitrate; + yi.cfg.baudrate = yp->baudrate; + yi.cfg.mode = yp->dupmode; + yi.cfg.txdelay = yp->txd; + yi.cfg.holddly = yp->holdd; + yi.cfg.txtail = yp->txtail; + yi.cfg.persist = yp->pers; + yi.cfg.slottime = yp->slot; + if (copy_to_user(dst, &yi, sizeof(struct yamdrv_ioctl_cfg))) /* { dg-bogus "" } */ + return -EFAULT; + /* [...snip...] */ + + return 0; +} diff --git a/gcc/testsuite/gcc.dg/analyzer/infoleak-CVE-2017-18549-1.c b/gcc/testsuite/gcc.dg/analyzer/infoleak-CVE-2017-18549-1.c new file mode 100644 index 00000000000..d40effb1cc6 --- /dev/null +++ b/gcc/testsuite/gcc.dg/analyzer/infoleak-CVE-2017-18549-1.c @@ -0,0 +1,101 @@ +/* "An issue was discovered in drivers/scsi/aacraid/commctrl.c in the + Linux kernel before 4.13. There is potential exposure of kernel stack + memory because aac_send_raw_srb does not initialize the reply structure." + + Fixed e.g. by 342ffc26693b528648bdc9377e51e4f2450b4860 on linux-4.13.y + in linux-stable. + + This is a very simplified version of that code (before and after the fix). */ + +#include <string.h> + +typedef unsigned int __u32; +typedef unsigned int u32; +typedef unsigned char u8; + +#include "test-uaccess.h" + +/* Adapted from include/uapi/linux/types.h */ + +#define __bitwise +typedef __u32 __bitwise __le32; + +/* Adapted from drivers/scsi/aacraid/aacraid.h */ + +#define AAC_SENSE_BUFFERSIZE 30 + +struct aac_srb_reply +{ + __le32 status; + __le32 srb_status; + __le32 scsi_status; + __le32 data_xfer_length; + __le32 sense_data_size; + u8 sense_data[AAC_SENSE_BUFFERSIZE]; /* { dg-message "padding after field 'sense_data' is uninitialized \\(2 bytes\\)" } */ +}; + +#define ST_OK 0 +#define SRB_STATUS_SUCCESS 0x01 + +/* Adapted from drivers/scsi/aacraid/commctrl.c */ + +static int aac_send_raw_srb(/* [...snip...] */ + void __user *user_reply) +{ + u32 byte_count = 0; + + /* [...snip...] */ + + struct aac_srb_reply reply; /* { dg-message "region created on stack here" "memspace message" } */ + /* { dg-message "capacity: 52 bytes" "capacity message" { target *-*-* } .-1 } */ + + reply.status = ST_OK; + + /* [...snip...] */ + + reply.srb_status = SRB_STATUS_SUCCESS; + reply.scsi_status = 0; + reply.data_xfer_length = byte_count; + reply.sense_data_size = 0; + memset(reply.sense_data, 0, AAC_SENSE_BUFFERSIZE); + + /* [...snip...] */ + + if (copy_to_user(user_reply, &reply, /* { dg-warning "potential exposure of sensitive information by copying uninitialized data from stack" } */ + /* { dg-message "2 bytes are uninitialized" "note how much" { target *-*-* } .-1 } */ + sizeof(struct aac_srb_reply))) { + /* [...snip...] */ + } + /* [...snip...] */ +} + +static int aac_send_raw_srb_fixed(/* [...snip...] */ + void __user *user_reply) +{ + u32 byte_count = 0; + + /* [...snip...] */ + + struct aac_srb_reply reply; + + /* This is the fix. */ + memset(&reply, 0, sizeof(reply)); + + reply.status = ST_OK; + + /* [...snip...] */ + + reply.srb_status = SRB_STATUS_SUCCESS; + reply.scsi_status = 0; + reply.data_xfer_length = byte_count; + reply.sense_data_size = 0; + memset(reply.sense_data, 0, AAC_SENSE_BUFFERSIZE); + + /* [...snip...] */ + + if (copy_to_user(user_reply, &reply, /* { dg-bogus "" } */ + sizeof(struct aac_srb_reply))) { + /* [...snip...] */ + } + /* [...snip...] */ +} diff --git a/gcc/testsuite/gcc.dg/analyzer/infoleak-CVE-2017-18550-1.c b/gcc/testsuite/gcc.dg/analyzer/infoleak-CVE-2017-18550-1.c new file mode 100644 index 00000000000..426efe037df --- /dev/null +++ b/gcc/testsuite/gcc.dg/analyzer/infoleak-CVE-2017-18550-1.c @@ -0,0 +1,171 @@ +/* "An issue was discovered in drivers/scsi/aacraid/commctrl.c in the + Linux kernel before 4.13. There is potential exposure of kernel stack + memory because aac_get_hba_info does not initialize the hbainfo structure." + + Fixed e.g. by 342ffc26693b528648bdc9377e51e4f2450b4860 on linux-4.13.y + in linux-stable. + + This is a simplified version of that code (before and after the fix). */ + +#include <string.h> + +typedef unsigned int __u32; +typedef unsigned int u32; +typedef unsigned char u8; + +#include "test-uaccess.h" + +/* Adapted from include/uapi/linux/types.h */ + +#define __bitwise +typedef __u32 __bitwise __le32; + +/* Adapted from drivers/scsi/aacraid/aacraid.h */ + +struct aac_hba_info { + + u8 driver_name[50]; /* { dg-message "field 'driver_name' is uninitialized \\(50 bytes\\)" } */ + u8 adapter_number; + u8 system_io_bus_number; + u8 device_number; /* { dg-message "padding after field 'device_number' is uninitialized \\(3 bytes\\)" } */ + u32 function_number; + u32 vendor_id; + u32 device_id; + u32 sub_vendor_id; + u32 sub_system_id; + u32 mapped_base_address_size; /* { dg-message "field 'mapped_base_address_size' is uninitialized \\(4 bytes\\)" } */ + u32 base_physical_address_high_part; + u32 base_physical_address_low_part; + + u32 max_command_size; + u32 max_fib_size; + u32 max_scatter_gather_from_os; + u32 max_scatter_gather_to_fw; + u32 max_outstanding_fibs; + + u32 queue_start_threshold; + u32 queue_dump_threshold; + u32 max_io_size_queued; + u32 outstanding_io; + + u32 firmware_build_number; + u32 bios_build_number; + u32 driver_build_number; + u32 serial_number_high_part; + u32 serial_number_low_part; + u32 supported_options; + u32 feature_bits; + u32 currentnumber_ports; + + u8 new_comm_interface:1; /* { dg-message "field 'new_comm_interface' is uninitialized \\(1 bit\\)" } */ + u8 new_commands_supported:1; + u8 disable_passthrough:1; + u8 expose_non_dasd:1; + u8 queue_allowed:1; + u8 bled_check_enabled:1; + u8 reserved1:1; + u8 reserted2:1; + + u32 reserved3[10]; /* { dg-message "field 'reserved3' is uninitialized \\(40 bytes\\)" } */ + +}; + +struct aac_dev +{ + /* [...snip...] */ + int id; + /* [...snip...] */ + struct pci_dev *pdev; /* Our PCI interface */ + /* [...snip...] */ +}; + +/* Adapted from include/linux/pci.h */ + +struct pci_dev { + /* [...snip...] */ + struct pci_bus *bus; /* bus this device is on */ + /* [...snip...] */ + unsigned int devfn; /* encoded device & function index */ + unsigned short vendor; + unsigned short device; + unsigned short subsystem_vendor; + unsigned short subsystem_device; + /* [...snip...] */ +}; + +struct pci_bus { + /* [...snip...] */ + unsigned char number; /* bus number */ + /* [...snip...] */ +}; + +/* Adapted from drivers/scsi/aacraid/commctrl.c */ + +static int aac_get_hba_info(struct aac_dev *dev, void __user *arg) +{ + struct aac_hba_info hbainfo; /* { dg-message "region created on stack here" "memspace message" } */ + /* { dg-message "capacity: 200 bytes" "capacity message" { target *-*-* } .-1 } */ + + hbainfo.adapter_number = (u8) dev->id; + hbainfo.system_io_bus_number = dev->pdev->bus->number; + hbainfo.device_number = (dev->pdev->devfn >> 3); + hbainfo.function_number = (dev->pdev->devfn & 0x0007); + + hbainfo.vendor_id = dev->pdev->vendor; + hbainfo.device_id = dev->pdev->device; + hbainfo.sub_vendor_id = dev->pdev->subsystem_vendor; + hbainfo.sub_system_id = dev->pdev->subsystem_device; + + if (copy_to_user(arg, &hbainfo, sizeof(struct aac_hba_info))) { /* { dg-warning "potential exposure of sensitive information by copying uninitialized data from stack" "warning" } */ + /* { dg-message "177 bytes are uninitialized" "how much" { target *-*-* } .-1 } */ + /* [...snip...] */ + } + + return 0; +} + +static int aac_get_hba_info_fixed(struct aac_dev *dev, void __user *arg) +{ + struct aac_hba_info hbainfo; + + memset(&hbainfo, 0, sizeof(hbainfo)); + hbainfo.adapter_number = (u8) dev->id; + hbainfo.system_io_bus_number = dev->pdev->bus->number; + hbainfo.device_number = (dev->pdev->devfn >> 3); + hbainfo.function_number = (dev->pdev->devfn & 0x0007); + + hbainfo.vendor_id = dev->pdev->vendor; + hbainfo.device_id = dev->pdev->device; + hbainfo.sub_vendor_id = dev->pdev->subsystem_vendor; + hbainfo.sub_system_id = dev->pdev->subsystem_device; + + if (copy_to_user(arg, &hbainfo, sizeof(struct aac_hba_info))) { /* { dg-bogus "" } */ + /* [...snip...] */ + } + + return 0; +} + +/* An alternate fix using "= {0}" rather than memset. */ + +static int aac_get_hba_info_fixed_alt(struct aac_dev *dev, void __user *arg) +{ + struct aac_hba_info hbainfo = {0}; + + memset(&hbainfo, 0, sizeof(hbainfo)); + hbainfo.adapter_number = (u8) dev->id; + hbainfo.system_io_bus_number = dev->pdev->bus->number; + hbainfo.device_number = (dev->pdev->devfn >> 3); + hbainfo.function_number = (dev->pdev->devfn & 0x0007); + + hbainfo.vendor_id = dev->pdev->vendor; + hbainfo.device_id = dev->pdev->device; + hbainfo.sub_vendor_id = dev->pdev->subsystem_vendor; + hbainfo.sub_system_id = dev->pdev->subsystem_device; + + if (copy_to_user(arg, &hbainfo, sizeof(struct aac_hba_info))) { /* { dg-bogus "" } */ + /* [...snip...] */ + } + + return 0; +} diff --git a/gcc/testsuite/gcc.dg/analyzer/infoleak-antipatterns-1.c b/gcc/testsuite/gcc.dg/analyzer/infoleak-antipatterns-1.c new file mode 100644 index 00000000000..449a4266b2e --- /dev/null +++ b/gcc/testsuite/gcc.dg/analyzer/infoleak-antipatterns-1.c @@ -0,0 +1,162 @@ +/* Adapted and simplified decls from linux kernel headers. */ + +typedef unsigned char u8; +typedef unsigned __INT16_TYPE__ u16; +typedef unsigned __INT32_TYPE__ u32; +typedef __SIZE_TYPE__ size_t; + +#define EFAULT 14 + +#include "test-uaccess.h" + +typedef unsigned int gfp_t; +#define GFP_KERNEL 0 + +void kfree(const void *); +void *kmalloc(size_t size, gfp_t flags) + __attribute__((malloc (kfree))); + +/* Adapted from antipatterns.ko:infoleak.c (GPL-v2.0). */ + +struct infoleak_buf +{ + char buf[256]; +}; + +int infoleak_stack_no_init(void __user *dst) +{ + struct infoleak_buf st; /* { dg-message "region created on stack here" "where" } */ + /* { dg-message "capacity: 256 bytes" "capacity" { target *-*-* } .-1 } */ + + /* No initialization of "st" at all. */ + if (copy_to_user(dst, &st, sizeof(st))) /* { dg-warning "potential exposure of sensitive information by copying uninitialized data from stack" "warning" } */ + /* { dg-message "256 bytes are uninitialized" "note how much" { target *-*-* } .-1 } */ + return -EFAULT; + return 0; +} + +int infoleak_heap_no_init(void __user *dst) +{ + struct infoleak_buf *heapbuf = kmalloc(sizeof(*heapbuf), GFP_KERNEL); + /* No initialization of "heapbuf" at all. */ + + /* TODO: we also don't check that heapbuf could be NULL when copying + from it. */ + if (copy_to_user(dst, heapbuf, sizeof(*heapbuf))) /* { dg-warning "exposure" "warning" { xfail *-*-* } } */ + /* TODO(xfail). */ + return -EFAULT; /* { dg-warning "leak of 'heapbuf'" } */ + + kfree(heapbuf); + return 0; +} + +struct infoleak_2 +{ + u32 a; + u32 b; /* { dg-message "field 'b' is uninitialized \\(4 bytes\\)" } */ +}; + +int infoleak_stack_missing_a_field(void __user *dst, u32 v) +{ + struct infoleak_2 st; /* { dg-message "region created on stack here" "where" } */ + /* { dg-message "capacity: 8 bytes" "capacity" { target *-*-* } .-1 } */ + + st.a = v; + /* No initialization of "st.b". */ + if (copy_to_user(dst, &st, sizeof(st))) /* { dg-warning "potential exposure of sensitive information by copying uninitialized data from stack" "warning" } */ + /* { dg-message "4 bytes are uninitialized" "note how much" { target *-*-* } .-1 } */ + return -EFAULT; + return 0; +} + +int infoleak_heap_missing_a_field(void __user *dst, u32 v) +{ + struct infoleak_2 *heapbuf = kmalloc(sizeof(*heapbuf), GFP_KERNEL); + heapbuf->a = v; /* { dg-warning "dereference of possibly-NULL 'heapbuf'" } */ + /* No initialization of "heapbuf->b". */ + if (copy_to_user(dst, heapbuf, sizeof(*heapbuf))) /* { dg-warning "exposure" "warning" { xfail *-*-* } } */ + /* TODO(xfail). */ + { + kfree(heapbuf); + return -EFAULT; + } + kfree(heapbuf); + return 0; +} + +struct infoleak_3 +{ + u8 a; /* { dg-message "padding after field 'a' is uninitialized \\(3 bytes\\)" } */ + /* padding here */ + u32 b; +}; + +int infoleak_stack_padding(void __user *dst, u8 p, u32 q) +{ + struct infoleak_3 st; /* { dg-message "region created on stack here" "where" } */ + /* { dg-message "capacity: 8 bytes" "capacity" { target *-*-* } .-1 } */ + + st.a = p; + st.b = q; + /* No initialization of padding. */ + if (copy_to_user(dst, &st, sizeof(st))) /* { dg-warning "potential exposure of sensitive information by copying uninitialized data from stack" "warning" } */ + /* { dg-message "3 bytes are uninitialized" "note how much" { target *-*-* } .-1 } */ + return -EFAULT; + return 0; +} + +int infoleak_stack_unchecked_err(void __user *dst, void __user *src) +{ + struct infoleak_buf st; /* { dg-message "region created on stack here" "where" } */ + /* { dg-message "capacity: 256 bytes" "capacity" { target *-*-* } .-1 } */ + + /* + * If the copy_from_user call fails, then st is still uninitialized, + * and if the copy_to_user call succeds, we have an infoleak. + */ + int err = copy_from_user (&st, src, sizeof(st)); /* { dg-message "when 'copy_from_user' fails" } */ + err |= copy_to_user (dst, &st, sizeof(st)); /* { dg-warning "exposure" "warning" } */ + /* { dg-message "256 bytes are uninitialized" "note how much" { target *-*-* } .-1 } */ + /* Actually, it's *up to* 256 bytes. */ + + if (err) + return -EFAULT; + return 0; +} + +struct infoleak_4 +{ + union { + u8 f1; + u32 f2; + } u; +}; + +int infoleak_stack_union(void __user *dst, u8 v) +{ + struct infoleak_4 st; + /* + * This write only initializes the u8 within the union "u", + * leaving the remaining 3 bytes uninitialized. + */ + st.u.f1 = v; + if (copy_to_user(dst, &st, sizeof(st))) /* { dg-warning "potential exposure of sensitive information by copying uninitialized data from stack" "warning" } */ + /* { dg-message "3 bytes are uninitialized" "note how much" { target *-*-* } .-1 } */ + return -EFAULT; + return 0; +} + +struct infoleak_5 +{ + void *ptr; +}; + +int infoleak_stack_kernel_ptr(void __user *dst, void *kp) +{ + struct infoleak_5 st; + /* This writes a kernel-space pointer into a user space buffer. */ + st.ptr = kp; + if (copy_to_user(dst, &st, sizeof(st))) // TODO: we don't complain about this yet + return -EFAULT; + return 0; +} diff --git a/gcc/testsuite/gcc.dg/analyzer/infoleak-fixit-1.c b/gcc/testsuite/gcc.dg/analyzer/infoleak-fixit-1.c new file mode 100644 index 00000000000..eb04fea8ac9 --- /dev/null +++ b/gcc/testsuite/gcc.dg/analyzer/infoleak-fixit-1.c @@ -0,0 +1,22 @@ +#include <string.h> + +#include "test-uaccess.h" + +typedef unsigned char u8; +typedef unsigned int u32; + +struct st +{ + u8 i; /* { dg-message "padding after field 'i' is uninitialized \\(3 bytes\\)" } */ + u32 j; /* { dg-message "field 'j' is uninitialized \\(4 bytes\\)" } */ +}; + +void test (void __user *dst, u8 a) +{ + struct st s; /* { dg-message "region created on stack here" "where" } */ + /* { dg-message "capacity: 8 bytes" "capacity" { target *-*-* } .-1 } */ + /* { dg-message "suggest forcing zero-initialization by providing a '.0.' initializer" "fix-it hint" { target *-*-* } .-2 } */ + s.i = a; + copy_to_user(dst, &s, sizeof (struct st)); /* { dg-warning "potential exposure of sensitive information by copying uninitialized data from stack" "warning" } */ + /* { dg-message "7 bytes are uninitialized" "note how much" { target *-*-* } .-1 } */ +} diff --git a/gcc/testsuite/gcc.dg/analyzer/torture/infoleak-net-ethtool-ioctl.c b/gcc/testsuite/gcc.dg/analyzer/torture/infoleak-net-ethtool-ioctl.c new file mode 100644 index 00000000000..20dfb9f3016 --- /dev/null +++ b/gcc/testsuite/gcc.dg/analyzer/torture/infoleak-net-ethtool-ioctl.c @@ -0,0 +1,78 @@ +/* Reduced from infoleak false positive seen on Linux kernel with + net/ethtool/ioctl.c */ + +typedef signed char __s8; +typedef unsigned char __u8; +typedef unsigned int __u32; +typedef __s8 s8; +typedef __u32 u32; +enum { false = 0, true = 1 }; +typedef unsigned long __kernel_ulong_t; +typedef __kernel_ulong_t __kernel_size_t; +typedef _Bool bool; +typedef __kernel_size_t size_t; + +void *memset(void *s, int c, size_t n); + +extern bool +check_copy_size(const void *addr, size_t bytes, bool is_source); +extern unsigned long +_copy_from_user(void *, const void *, unsigned long); +extern unsigned long +_copy_to_user(void *, const void *, unsigned long); + +static inline +__attribute__((__always_inline__)) unsigned long +copy_from_user(void *to, const void *from, unsigned long n) { + if (__builtin_expect(!!(check_copy_size(to, n, false)), 1)) + n = _copy_from_user(to, from, n); + return n; +} +static inline +__attribute__((__always_inline__)) unsigned long +copy_to_user(void *to, const void *from, unsigned long n) { + if (__builtin_expect(!!(check_copy_size(from, n, true)), 1)) + n = _copy_to_user(to, from, n); + return n; +} +enum ethtool_link_mode_bit_indices { + __ETHTOOL_LINK_MODE_MASK_NBITS = 92 +}; +struct ethtool_link_settings { + __u32 cmd; + /* [...snip...] */ + __s8 link_mode_masks_nwords; + /* [...snip...] */ +}; + +struct ethtool_link_ksettings { + struct ethtool_link_settings base; + u32 lanes; +}; + +int ethtool_get_link_ksettings(void *useraddr) { + int err = 0; + struct ethtool_link_ksettings link_ksettings; + + if (copy_from_user(&link_ksettings.base, useraddr, + sizeof(link_ksettings.base))) + return -14; + + if ((((__ETHTOOL_LINK_MODE_MASK_NBITS) + (32) - 1) / (32)) != + link_ksettings.base.link_mode_masks_nwords) { + + memset(&link_ksettings, 0, sizeof(link_ksettings)); + link_ksettings.base.cmd = 0x0000004c; + + link_ksettings.base.link_mode_masks_nwords = + -((s8)(((__ETHTOOL_LINK_MODE_MASK_NBITS) + (32) - 1) / (32))); + + if (copy_to_user(useraddr, &link_ksettings.base, + sizeof(link_ksettings.base))) + return -14; + + return 0; + } + + return 0; +} diff --git a/gcc/testsuite/gcc.dg/analyzer/torture/infoleak-vfio_iommu_type1.c b/gcc/testsuite/gcc.dg/analyzer/torture/infoleak-vfio_iommu_type1.c new file mode 100644 index 00000000000..ddd72d4529c --- /dev/null +++ b/gcc/testsuite/gcc.dg/analyzer/torture/infoleak-vfio_iommu_type1.c @@ -0,0 +1,39 @@ +/* Reduced from infoleak false positive in drivers/vfio/vfio_iommu_type1.c */ + +typedef unsigned int u32; +typedef unsigned long long u64; + +unsigned long +copy_from_user(void *to, const void *from, unsigned long n); + +unsigned long +copy_to_user(void *to, const void *from, unsigned long n); + +struct vfio_iommu_type1_info { + u32 argsz; + u32 flags; + u64 iova_pgsizes; + u32 cap_offset; + /* bytes 20-23 are padding. */ +}; + +int vfio_iommu_type1_get_info(unsigned long arg) +{ + struct vfio_iommu_type1_info info; + unsigned long minsz = 16; + + if (copy_from_user(&info, (void *)arg, 16)) + return -14; + + if (info.argsz < 16) + return -22; + + if (info.argsz >= 20) { + minsz = 20; + info.cap_offset = 0; + } + + /* The padding bytes (20-23) are uninitialized, but can't be written + back, since minsz is either 16 or 20. */ + return copy_to_user((void *)arg, &info, minsz) ? -14 : 0; /* { dg-bogus "exposure" } */ +} -- 2.26.3