diff --git a/gcc/analyzer/analyzer.opt b/gcc/analyzer/analyzer.opt index 437ea92e130..dbab3b82deb 100644 --- a/gcc/analyzer/analyzer.opt +++ b/gcc/analyzer/analyzer.opt @@ -70,6 +70,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-fd-access-mode-mismatch Common Var(warn_analyzer_fd_mode_mismatch) Init(1) Warning Warn about code paths in which read on a write-only file descriptor is attempted, or vice versa. diff --git a/gcc/analyzer/checker-path.cc b/gcc/analyzer/checker-path.cc index 273f40d3a57..22bae2f34b1 100644 --- a/gcc/analyzer/checker-path.cc +++ b/gcc/analyzer/checker-path.cc @@ -288,16 +288,25 @@ statement_event::get_desc (bool) const /* 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_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. */ + 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 @@ -311,14 +320,50 @@ region_creation_event::get_desc (bool can_colorize) const return custom_desc; } - switch (m_reg->get_memory_space ()) + switch (m_rce_kind) { 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"); + 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; } } @@ -1207,15 +1252,33 @@ checker_path::debug () const } } -/* Add region_creation_event instance to this path for REG, - describing whether REG is on the stack or heap. */ +/* 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_event (const region *reg, - location_t loc, - tree fndecl, int depth) +checker_path::add_region_creation_events (const region *reg, + const region_model *model, + location_t loc, + tree fndecl, int depth, + bool debug) { - add_event (new region_creation_event (reg, loc, fndecl, depth)); + 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. */ diff --git a/gcc/analyzer/checker-path.h b/gcc/analyzer/checker-path.h index 8e48d8a07ab..5d009340189 100644 --- a/gcc/analyzer/checker-path.h +++ b/gcc/analyzer/checker-path.h @@ -210,19 +210,43 @@ 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 e.g. "region created on stack here". */ + 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 can_colorize) 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. */ @@ -632,9 +656,11 @@ public: m_events[idx] = new_event; } - void add_region_creation_event (const region *reg, - location_t loc, - tree fndecl, int depth); + 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, diff --git a/gcc/analyzer/diagnostic-manager.cc b/gcc/analyzer/diagnostic-manager.cc index fded8281e57..2d185a1f3e6 100644 --- a/gcc/analyzer/diagnostic-manager.cc +++ b/gcc/analyzer/diagnostic-manager.cc @@ -1460,11 +1460,12 @@ diagnostic_manager::build_emission_path (const path_builder &pb, if (DECL_P (decl) && DECL_SOURCE_LOCATION (decl) != UNKNOWN_LOCATION) { - emission_path->add_region_creation_event - (reg, + emission_path->add_region_creation_events + (reg, NULL, DECL_SOURCE_LOCATION (decl), NULL_TREE, - 0); + 0, + m_verbosity > 3); } } } @@ -1524,11 +1525,13 @@ diagnostic_manager::add_event_on_final_node (const exploded_node *final_enode, break; case RK_HEAP_ALLOCATED: case RK_ALLOCA: - emission_path->add_region_creation_event + emission_path->add_region_creation_events (reg, - src_point.get_location (), - src_point.get_fndecl (), - src_stack_depth); + dst_model, + src_point.get_location (), + src_point.get_fndecl (), + src_stack_depth, + false); emitted = true; break; } @@ -1939,11 +1942,12 @@ diagnostic_manager::add_events_for_eedge (const path_builder &pb, if (DECL_P (decl) && DECL_SOURCE_LOCATION (decl) != UNKNOWN_LOCATION) { - emission_path->add_region_creation_event - (reg, + emission_path->add_region_creation_events + (reg, dst_state.m_region_model, DECL_SOURCE_LOCATION (decl), dst_point.get_fndecl (), - dst_stack_depth); + dst_stack_depth, + m_verbosity > 3); } } } @@ -2033,11 +2037,12 @@ diagnostic_manager::add_events_for_eedge (const path_builder &pb, break; case RK_HEAP_ALLOCATED: case RK_ALLOCA: - emission_path->add_region_creation_event - (reg, + emission_path->add_region_creation_events + (reg, dst_model, src_point.get_location (), src_point.get_fndecl (), - src_stack_depth); + src_stack_depth, + m_verbosity > 3); break; } } diff --git a/gcc/analyzer/region-model-impl-calls.cc b/gcc/analyzer/region-model-impl-calls.cc index 617491be306..71fb2770143 100644 --- a/gcc/analyzer/region-model-impl-calls.cc +++ b/gcc/analyzer/region-model-impl-calls.cc @@ -91,6 +91,17 @@ call_details::get_manager () const return m_model->get_manager (); } +/* 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 bc9db69315f..6eeb684844d 100644 --- a/gcc/analyzer/region-model.cc +++ b/gcc/analyzer/region-model.cc @@ -19,6 +19,7 @@ along with GCC; see the file COPYING3. If not see . */ #include "config.h" +#define INCLUDE_MEMORY #include "system.h" #include "coretypes.h" #include "tree.h" @@ -74,6 +75,7 @@ along with GCC; see the file COPYING3. If not see #include "ssa-iterators.h" #include "calls.h" #include "is-a.h" +#include "gcc-rich-location.h" #if ENABLE_ANALYZER @@ -5784,6 +5786,572 @@ region_model::unset_dynamic_extents (const region *reg) m_dynamic_extents.remove (reg); } +/* 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 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 +{ +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); + } + + int get_controlling_option () const final override + { + return OPT_Wanalyzer_exposure_through_uninit_copy; + } + + 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, get_controlling_option (), + "potential exposure of sensitive information" + " by copying uninitialized data across trust boundary"); + break; + case MEMSPACE_STACK: + warned = warning_meta + (rich_loc, m, get_controlling_option (), + "potential exposure of sensitive information" + " by copying uninitialized data from stack across trust boundary"); + break; + case MEMSPACE_HEAP: + warned = warning_meta + (rich_loc, m, get_controlling_option (), + "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 (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 (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 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 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 (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; +} + +/* Function for use by plugins when simulating writing data through a + pointer to an "untrusted" region DST_REG (and thus crossing a security + boundary), such as copying data to user space in an OS kernel. + + 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)); +} + /* class noop_region_model_context : public region_model_context. */ void diff --git a/gcc/analyzer/region-model.h b/gcc/analyzer/region-model.h index 8d2e3daec28..e86720a645c 100644 --- a/gcc/analyzer/region-model.h +++ b/gcc/analyzer/region-model.h @@ -532,6 +532,8 @@ public: region_model *get_model () const { return m_model; } region_model_manager *get_manager () const; 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; } @@ -814,11 +816,20 @@ class region_model const svalue *get_string_size (const svalue *sval) const; const svalue *get_string_size (const region *reg) const; + void maybe_complain_about_infoleak (const region *dst_reg, + const svalue *copied_sval, + const region *src_reg, + region_model_context *ctxt); + /* Implemented in sm-malloc.cc */ void on_realloc_with_move (const call_details &cd, const svalue *old_ptr_sval, const svalue *new_ptr_sval); + /* Implemented in sm-taint.cc. */ + void mark_as_tainted (const svalue *sval, + region_model_context *ctxt); + private: const region *get_lvalue_1 (path_var pv, region_model_context *ctxt) const; const svalue *get_rvalue_1 (path_var pv, region_model_context *ctxt) const; diff --git a/gcc/analyzer/sm-taint.cc b/gcc/analyzer/sm-taint.cc index 549373b322d..f5c0cc13a2f 100644 --- a/gcc/analyzer/sm-taint.cc +++ b/gcc/analyzer/sm-taint.cc @@ -1365,6 +1365,33 @@ region_model::check_dynamic_size_for_taint (enum memory_space mem_space, } } +/* Mark SVAL as TAINTED. CTXT must be non-NULL. */ + +void +region_model::mark_as_tainted (const svalue *sval, + region_model_context *ctxt) +{ + gcc_assert (sval); + gcc_assert (ctxt); + + sm_state_map *smap; + const state_machine *sm; + unsigned sm_idx; + if (!ctxt->get_taint_map (&smap, &sm, &sm_idx)) + return; + + gcc_assert (smap); + gcc_assert (sm); + + const taint_state_machine &taint_sm = (const taint_state_machine &)*sm; + + const extrinsic_state *ext_state = ctxt->get_ext_state (); + if (!ext_state) + return; + + smap->set_state (this, sval, taint_sm.m_tainted, NULL, *ext_state); +} + } // namespace ana #endif /* #if ENABLE_ANALYZER */ diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi index 5c066219a7d..eaee5166822 100644 --- a/gcc/doc/invoke.texi +++ b/gcc/doc/invoke.texi @@ -447,6 +447,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-fd-access-mode-mismatch @gol -Wno-analyzer-fd-double-close @gol -Wno-analyzer-fd-leak @gol @@ -9797,6 +9798,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-fd-access-mode-mismatch @gol -Wanalyzer-fd-double-close @gol -Wanalyzer-fd-leak @gol @@ -9899,6 +9901,20 @@ security-sensitive value is written to an output file See @uref{https://cwe.mitre.org/data/definitions/532.html, CWE-532: Information Exposure Through Log Files}. +@item Wanalyzer-exposure-through-uninit-copy +@opindex Wanalyzer-exposure-through-uninit-copy +@opindex Wno-analyzer-exposure-through-uninit-copy +This warning requires both @option{-fanalyzer} and the use of a plugin +to specify a function that copies across a ``trust boundary''. 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 code within an OS kernel that copies a partially-initialized +struct on the stack to user space). + +See @uref{https://cwe.mitre.org/data/definitions/200.html, CWE-200: Exposure of Sensitive Information to an Unauthorized Actor}. + @item -Wno-analyzer-fd-access-mode-mismatch @opindex Wanalyzer-fd-access-mode-mismatch @opindex Wno-analyzer-fd-access-mode-mismatch diff --git a/gcc/testsuite/gcc.dg/plugin/analyzer_kernel_plugin.c b/gcc/testsuite/gcc.dg/plugin/analyzer_kernel_plugin.c new file mode 100644 index 00000000000..6ec08bff73c --- /dev/null +++ b/gcc/testsuite/gcc.dg/plugin/analyzer_kernel_plugin.c @@ -0,0 +1,237 @@ +/* Proof-of-concept of a -fanalyzer plugin for the Linux kernel. */ +/* { dg-options "-g" } */ + +#include "gcc-plugin.h" +#include "config.h" +#include "system.h" +#include "coretypes.h" +#include "tree.h" +#include "function.h" +#include "basic-block.h" +#include "gimple.h" +#include "gimple-iterator.h" +#include "diagnostic-core.h" +#include "graphviz.h" +#include "options.h" +#include "cgraph.h" +#include "tree-dfa.h" +#include "stringpool.h" +#include "convert.h" +#include "target.h" +#include "fold-const.h" +#include "tree-pretty-print.h" +#include "diagnostic-color.h" +#include "diagnostic-metadata.h" +#include "tristate.h" +#include "bitmap.h" +#include "selftest.h" +#include "function.h" +#include "json.h" +#include "analyzer/analyzer.h" +#include "analyzer/analyzer-logging.h" +#include "ordered-hash-map.h" +#include "options.h" +#include "cgraph.h" +#include "cfg.h" +#include "digraph.h" +#include "analyzer/supergraph.h" +#include "sbitmap.h" +#include "analyzer/call-string.h" +#include "analyzer/program-point.h" +#include "analyzer/store.h" +#include "analyzer/region-model.h" +#include "analyzer/call-info.h" + +int plugin_is_GPL_compatible; + +#if ENABLE_ANALYZER + +namespace ana { + +/* Implementation of "copy_from_user" and "copy_to_user". */ + +class copy_across_boundary_fn : public known_function +{ + public: + virtual bool untrusted_source_p () const = 0; + virtual bool untrusted_destination_p () const = 0; + + void impl_call_pre (const call_details &cd) const final override + { + region_model_manager *mgr = cd.get_manager (); + region_model *model = cd.get_model (); + region_model_context *ctxt = cd.get_ctxt (); + + const svalue *dest_sval = cd.get_arg_svalue (0); + const svalue *src_sval = cd.get_arg_svalue (1); + const svalue *num_bytes_sval = cd.get_arg_svalue (2); + + const region *dest_reg = model->deref_rvalue (dest_sval, + cd.get_arg_tree (0), + ctxt); + const region *src_reg = model->deref_rvalue (src_sval, + cd.get_arg_tree (1), + ctxt); + if (const svalue *bounded_sval + = model->maybe_get_copy_bounds (src_reg, num_bytes_sval)) + num_bytes_sval = bounded_sval; + + if (tree cst = num_bytes_sval->maybe_get_constant ()) + if (zerop (cst)) + /* No-op. */ + return; + + const region *sized_src_reg = mgr->get_sized_region (src_reg, + NULL_TREE, + num_bytes_sval); + + const svalue *copied_sval + = model->get_store_value (sized_src_reg, ctxt); + const region *sized_dest_reg = mgr->get_sized_region (dest_reg, + NULL_TREE, + num_bytes_sval); + + if (ctxt) + { + /* Bifurcate state, creating a "failure" out-edge. */ + ctxt->bifurcate (new copy_failure (cd)); + + /* The "unbifurcated" state is the "success" case. */ + copy_success success (cd, + sized_dest_reg, + copied_sval, + sized_src_reg, + untrusted_source_p (), + untrusted_destination_p ()); + success.update_model (model, NULL, ctxt); + } + } + + private: + class copy_success : public success_call_info + { + public: + copy_success (const call_details &cd, + const region *sized_dest_reg, + const svalue *copied_sval, + const region *sized_src_reg, + bool untrusted_source, + bool untrusted_destination) + : success_call_info (cd), + m_sized_dest_reg (sized_dest_reg), + m_copied_sval (copied_sval), + m_sized_src_reg (sized_src_reg), + m_untrusted_source (untrusted_source), + m_untrusted_destination (untrusted_destination) + {} + + bool update_model (region_model *model, + const exploded_edge *, + region_model_context *ctxt) const final override + { + call_details cd (get_call_details (model, ctxt)); + model->update_for_zero_return (cd, true); + model->set_value (m_sized_dest_reg, m_copied_sval, ctxt); + if (ctxt && m_untrusted_source) + model->mark_as_tainted (m_copied_sval, ctxt); + if (m_untrusted_destination) + model->maybe_complain_about_infoleak (m_sized_dest_reg, + m_copied_sval, + m_sized_src_reg, + ctxt); + return true; + } + + const region *m_sized_dest_reg; + const svalue *m_copied_sval; + const region *m_sized_src_reg; + bool m_untrusted_source; + bool m_untrusted_destination; + }; + + class copy_failure : public failed_call_info + { + public: + copy_failure (const call_details &cd) + : failed_call_info (cd) + {} + + bool update_model (region_model *model, + const exploded_edge *, + region_model_context *ctxt) const final override + { + call_details cd (get_call_details (model, ctxt)); + model->update_for_nonzero_return (cd); + /* Leave the destination region untouched. */ + return true; + } + }; +}; + +/* "copy_from_user". */ + +class known_function_copy_from_user : public copy_across_boundary_fn +{ +public: + bool untrusted_source_p () const final override + { + return true; + } + bool untrusted_destination_p () const final override + { + return false; + } +}; + +/* "copy_to_user". */ + +class known_function_copy_to_user : public copy_across_boundary_fn +{ +public: + bool untrusted_source_p () const final override + { + return false; + } + bool untrusted_destination_p () const final override + { + return true; + } +}; + +/* Callback handler for the PLUGIN_ANALYZER_INIT event. */ + +static void +kernel_analyzer_init_cb (void *gcc_data, void */*user_data*/) +{ + ana::plugin_analyzer_init_iface *iface + = (ana::plugin_analyzer_init_iface *)gcc_data; + LOG_SCOPE (iface->get_logger ()); + if (0) + inform (input_location, "got here: kernel_analyzer_init_cb"); + iface->register_known_function ("copy_from_user", + new known_function_copy_from_user ()); + iface->register_known_function ("copy_to_user", + new known_function_copy_to_user ()); +} + +} // namespace ana + +#endif /* #if ENABLE_ANALYZER */ + +int +plugin_init (struct plugin_name_args *plugin_info, + struct plugin_gcc_version *version) +{ +#if ENABLE_ANALYZER + const char *plugin_name = plugin_info->base_name; + if (0) + inform (input_location, "got here; %qs", plugin_name); + register_callback (plugin_info->base_name, + PLUGIN_ANALYZER_INIT, + ana::kernel_analyzer_init_cb, + NULL); /* void *user_data */ +#else + sorry_no_analyzer (); +#endif + return 0; +} diff --git a/gcc/testsuite/gcc.dg/plugin/copy_from_user-1.c b/gcc/testsuite/gcc.dg/plugin/copy_from_user-1.c new file mode 100644 index 00000000000..a1415f38aa6 --- /dev/null +++ b/gcc/testsuite/gcc.dg/plugin/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/plugin/infoleak-1.c b/gcc/testsuite/gcc.dg/plugin/infoleak-1.c new file mode 100644 index 00000000000..b4958e7bbb6 --- /dev/null +++ b/gcc/testsuite/gcc.dg/plugin/infoleak-1.c @@ -0,0 +1,185 @@ +/* { dg-do compile } */ +/* { dg-options "-fanalyzer" } */ +/* { dg-require-effective-target analyzer } */ + +#include + +#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/plugin/infoleak-2.c b/gcc/testsuite/gcc.dg/plugin/infoleak-2.c new file mode 100644 index 00000000000..252f8f25918 --- /dev/null +++ b/gcc/testsuite/gcc.dg/plugin/infoleak-2.c @@ -0,0 +1,33 @@ +/* { dg-do compile } */ +/* { dg-options "-fanalyzer" } */ +/* { dg-require-effective-target analyzer } */ + +#include + +#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/plugin/infoleak-3.c b/gcc/testsuite/gcc.dg/plugin/infoleak-3.c new file mode 100644 index 00000000000..097a0d8d33b --- /dev/null +++ b/gcc/testsuite/gcc.dg/plugin/infoleak-3.c @@ -0,0 +1,145 @@ +/* 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. */ + +/* { dg-do compile } */ +/* { dg-options "-fanalyzer" } */ +/* { dg-require-effective-target analyzer } */ + +#include "../analyzer/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/plugin/infoleak-CVE-2011-1078-1.c b/gcc/testsuite/gcc.dg/plugin/infoleak-CVE-2011-1078-1.c new file mode 100644 index 00000000000..3616fbe176b --- /dev/null +++ b/gcc/testsuite/gcc.dg/plugin/infoleak-CVE-2011-1078-1.c @@ -0,0 +1,138 @@ +/* "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. */ + +/* { dg-do compile } */ +/* { dg-options "-fanalyzer" } */ +/* { dg-require-effective-target analyzer } */ + +#include + +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/plugin/infoleak-CVE-2011-1078-2.c b/gcc/testsuite/gcc.dg/plugin/infoleak-CVE-2011-1078-2.c new file mode 100644 index 00000000000..2096bda7179 --- /dev/null +++ b/gcc/testsuite/gcc.dg/plugin/infoleak-CVE-2011-1078-2.c @@ -0,0 +1,46 @@ +/* Simplified versions of infoleak-CVE-2011-1078-1.c. */ + +/* { dg-do compile } */ +/* { dg-options "-fanalyzer" } */ +/* { dg-require-effective-target analyzer } */ + +#include + +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/plugin/infoleak-CVE-2014-1446-1.c b/gcc/testsuite/gcc.dg/plugin/infoleak-CVE-2014-1446-1.c new file mode 100644 index 00000000000..2726a9c0f38 --- /dev/null +++ b/gcc/testsuite/gcc.dg/plugin/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 + +#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/plugin/infoleak-CVE-2017-18549-1.c b/gcc/testsuite/gcc.dg/plugin/infoleak-CVE-2017-18549-1.c new file mode 100644 index 00000000000..8a1c816cc1b --- /dev/null +++ b/gcc/testsuite/gcc.dg/plugin/infoleak-CVE-2017-18549-1.c @@ -0,0 +1,105 @@ +/* "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). */ + +/* { dg-do compile } */ +/* { dg-options "-fanalyzer" } */ +/* { dg-require-effective-target analyzer } */ + +#include + +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/plugin/infoleak-CVE-2017-18550-1.c b/gcc/testsuite/gcc.dg/plugin/infoleak-CVE-2017-18550-1.c new file mode 100644 index 00000000000..4272da96bab --- /dev/null +++ b/gcc/testsuite/gcc.dg/plugin/infoleak-CVE-2017-18550-1.c @@ -0,0 +1,175 @@ +/* "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). */ + +/* { dg-do compile } */ +/* { dg-options "-fanalyzer" } */ +/* { dg-require-effective-target analyzer } */ + +#include + +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/plugin/infoleak-antipatterns-1.c b/gcc/testsuite/gcc.dg/plugin/infoleak-antipatterns-1.c new file mode 100644 index 00000000000..50084536438 --- /dev/null +++ b/gcc/testsuite/gcc.dg/plugin/infoleak-antipatterns-1.c @@ -0,0 +1,166 @@ +/* Adapted and simplified decls from linux kernel headers. */ + +/* { dg-do compile } */ +/* { dg-options "-fanalyzer" } */ +/* { dg-require-effective-target analyzer } */ + +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/plugin/infoleak-fixit-1.c b/gcc/testsuite/gcc.dg/plugin/infoleak-fixit-1.c new file mode 100644 index 00000000000..6961b44f76b --- /dev/null +++ b/gcc/testsuite/gcc.dg/plugin/infoleak-fixit-1.c @@ -0,0 +1,26 @@ +/* { dg-do compile } */ +/* { dg-options "-fanalyzer" } */ +/* { dg-require-effective-target analyzer } */ + +#include + +#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/plugin/infoleak-net-ethtool-ioctl.c b/gcc/testsuite/gcc.dg/plugin/infoleak-net-ethtool-ioctl.c new file mode 100644 index 00000000000..dce6e44ae13 --- /dev/null +++ b/gcc/testsuite/gcc.dg/plugin/infoleak-net-ethtool-ioctl.c @@ -0,0 +1,82 @@ +/* Reduced from infoleak false positive seen on Linux kernel with + net/ethtool/ioctl.c */ + +/* { dg-do compile } */ +/* { dg-options "-fanalyzer" } */ +/* { dg-require-effective-target analyzer } */ + +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/plugin/infoleak-vfio_iommu_type1.c b/gcc/testsuite/gcc.dg/plugin/infoleak-vfio_iommu_type1.c new file mode 100644 index 00000000000..51ad5db2bab --- /dev/null +++ b/gcc/testsuite/gcc.dg/plugin/infoleak-vfio_iommu_type1.c @@ -0,0 +1,44 @@ +/* Reduced from infoleak false positive in drivers/vfio/vfio_iommu_type1.c */ + +/* { dg-do compile } */ +/* { dg-options "-fanalyzer" } */ +/* { dg-require-effective-target analyzer } */ + +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" "" { xfail *-*-* } } */ + // TODO: false +ve due to not handling minsz being either 16 or 20 +} diff --git a/gcc/testsuite/gcc.dg/plugin/plugin.exp b/gcc/testsuite/gcc.dg/plugin/plugin.exp index c05366f3f85..5b7efa4afb6 100644 --- a/gcc/testsuite/gcc.dg/plugin/plugin.exp +++ b/gcc/testsuite/gcc.dg/plugin/plugin.exp @@ -125,6 +125,30 @@ set plugin_test_list [list \ gil-1.c } \ { analyzer_known_fns_plugin.c \ known-fns-1.c } \ + { analyzer_kernel_plugin.c \ + copy_from_user-1.c \ + infoleak-1.c \ + infoleak-2.c \ + infoleak-3.c \ + infoleak-CVE-2011-1078-1.c \ + infoleak-CVE-2011-1078-2.c \ + infoleak-CVE-2017-18549-1.c \ + infoleak-CVE-2017-18550-1.c \ + infoleak-antipatterns-1.c \ + infoleak-fixit-1.c \ + infoleak-net-ethtool-ioctl.c \ + infoleak-vfio_iommu_type1.c \ + taint-CVE-2011-0521-1-fixed.c \ + taint-CVE-2011-0521-1.c \ + taint-CVE-2011-0521-2-fixed.c \ + taint-CVE-2011-0521-2.c \ + taint-CVE-2011-0521-3-fixed.c \ + taint-CVE-2011-0521-3.c \ + taint-CVE-2011-0521-4.c \ + taint-CVE-2011-0521-5.c \ + taint-CVE-2011-0521-5-fixed.c \ + taint-CVE-2011-0521-6.c \ + taint-antipatterns-1.c } \ ] foreach plugin_test $plugin_test_list { diff --git a/gcc/testsuite/gcc.dg/plugin/taint-CVE-2011-0521-1-fixed.c b/gcc/testsuite/gcc.dg/plugin/taint-CVE-2011-0521-1-fixed.c new file mode 100644 index 00000000000..0ca8137c3ef --- /dev/null +++ b/gcc/testsuite/gcc.dg/plugin/taint-CVE-2011-0521-1-fixed.c @@ -0,0 +1,115 @@ +/* { dg-do compile } */ +// TODO: remove need for -fanalyzer-checker=taint here: +/* { dg-options "-fanalyzer -fanalyzer-checker=taint" } */ +/* { dg-require-effective-target analyzer } */ + +/* See notes in this header. */ +#include "taint-CVE-2011-0521.h" + +/* Adapted from drivers/media/dvb/ttpci/av7110_ca.c */ + +int dvb_ca_ioctl(struct file *file, unsigned int cmd, void *parg) +{ + struct dvb_device *dvbdev = file->private_data; + struct av7110 *av7110 = dvbdev->priv; + unsigned long arg = (unsigned long) parg; + + /* case CA_GET_SLOT_INFO: */ + { + ca_slot_info_t *info=(ca_slot_info_t *)parg; + + if (info->num < 0 || info->num > 1) + return -EINVAL; + av7110->ci_slot[info->num].num = info->num; /* { dg-bogus "attacker-controlled value" } */ + av7110->ci_slot[info->num].type = FW_CI_LL_SUPPORT(av7110->arm_app) ? + CA_CI_LINK : CA_CI; + memcpy(info, &av7110->ci_slot[info->num], sizeof(ca_slot_info_t)); + } + return 0; +} + +static struct dvb_device dvbdev_ca = { + .priv = NULL, + /* [...snip...] */ + .kernel_ioctl = dvb_ca_ioctl, +}; + +/* Adapted from drivers/media/dvb/dvb-core/dvbdev.c */ + +static DEFINE_MUTEX(dvbdev_mutex); + +int dvb_usercopy(struct file *file, + unsigned int cmd, unsigned long arg, + int (*func)(struct file *file, + unsigned int cmd, void *arg)) +{ + char sbuf[128]; + void *mbuf = NULL; + void *parg = NULL; + int err = -1; + + /* Copy arguments into temp kernel buffer */ + switch (_IOC_DIR(cmd)) { + case _IOC_NONE: + /* + * For this command, the pointer is actually an integer + * argument. + */ + parg = (void *) arg; + break; + case _IOC_READ: /* some v4l ioctls are marked wrong ... */ + case _IOC_WRITE: + case (_IOC_WRITE | _IOC_READ): + if (_IOC_SIZE(cmd) <= sizeof(sbuf)) { + parg = sbuf; + } else { + /* too big to allocate from stack */ + mbuf = kmalloc(_IOC_SIZE(cmd),GFP_KERNEL); + if (NULL == mbuf) + return -ENOMEM; + parg = mbuf; + } + + err = -EFAULT; + if (copy_from_user(parg, (void __user *)arg, _IOC_SIZE(cmd))) + goto out; + break; + } + + /* call driver */ + mutex_lock(&dvbdev_mutex); + if ((err = func(file, cmd, parg)) == -ENOIOCTLCMD) + err = -EINVAL; + mutex_unlock(&dvbdev_mutex); + + if (err < 0) + goto out; + + /* Copy results into user buffer */ + switch (_IOC_DIR(cmd)) + { + case _IOC_READ: + case (_IOC_WRITE | _IOC_READ): + if (copy_to_user((void __user *)arg, parg, _IOC_SIZE(cmd))) + err = -EFAULT; + break; + } + +out: + kfree(mbuf); + return err; +} + +long dvb_generic_ioctl(struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct dvb_device *dvbdev = file->private_data; + + if (!dvbdev) + return -ENODEV; + + if (!dvbdev->kernel_ioctl) + return -EINVAL; + + return dvb_usercopy(file, cmd, arg, dvbdev->kernel_ioctl); +} diff --git a/gcc/testsuite/gcc.dg/plugin/taint-CVE-2011-0521-1.c b/gcc/testsuite/gcc.dg/plugin/taint-CVE-2011-0521-1.c new file mode 100644 index 00000000000..cde12b3b761 --- /dev/null +++ b/gcc/testsuite/gcc.dg/plugin/taint-CVE-2011-0521-1.c @@ -0,0 +1,115 @@ +/* { dg-do compile } */ +// TODO: remove need for -fanalyzer-checker=taint here: +/* { dg-options "-fanalyzer -fanalyzer-checker=taint" } */ +/* { dg-require-effective-target analyzer } */ + +/* See notes in this header. */ +#include "taint-CVE-2011-0521.h" + +/* Adapted from drivers/media/dvb/ttpci/av7110_ca.c */ + +int dvb_ca_ioctl(struct file *file, unsigned int cmd, void *parg) +{ + struct dvb_device *dvbdev = file->private_data; + struct av7110 *av7110 = dvbdev->priv; + unsigned long arg = (unsigned long) parg; + + /* case CA_GET_SLOT_INFO: */ + { + ca_slot_info_t *info=(ca_slot_info_t *)parg; + + if (info->num > 1) + return -EINVAL; + av7110->ci_slot[info->num].num = info->num; /* { dg-warning "attacker-controlled value" "" { xfail *-*-* } } */ + av7110->ci_slot[info->num].type = FW_CI_LL_SUPPORT(av7110->arm_app) ? + CA_CI_LINK : CA_CI; + memcpy(info, &av7110->ci_slot[info->num], sizeof(ca_slot_info_t)); + } + return 0; +} + +static struct dvb_device dvbdev_ca = { + .priv = NULL, + /* [...snip...] */ + .kernel_ioctl = dvb_ca_ioctl, +}; + +/* Adapted from drivers/media/dvb/dvb-core/dvbdev.c */ + +static DEFINE_MUTEX(dvbdev_mutex); + +int dvb_usercopy(struct file *file, + unsigned int cmd, unsigned long arg, + int (*func)(struct file *file, + unsigned int cmd, void *arg)) +{ + char sbuf[128]; + void *mbuf = NULL; + void *parg = NULL; + int err = -1; + + /* Copy arguments into temp kernel buffer */ + switch (_IOC_DIR(cmd)) { + case _IOC_NONE: + /* + * For this command, the pointer is actually an integer + * argument. + */ + parg = (void *) arg; + break; + case _IOC_READ: /* some v4l ioctls are marked wrong ... */ + case _IOC_WRITE: + case (_IOC_WRITE | _IOC_READ): + if (_IOC_SIZE(cmd) <= sizeof(sbuf)) { + parg = sbuf; + } else { + /* too big to allocate from stack */ + mbuf = kmalloc(_IOC_SIZE(cmd),GFP_KERNEL); + if (NULL == mbuf) + return -ENOMEM; + parg = mbuf; + } + + err = -EFAULT; + if (copy_from_user(parg, (void __user *)arg, _IOC_SIZE(cmd))) + goto out; + break; + } + + /* call driver */ + mutex_lock(&dvbdev_mutex); + if ((err = func(file, cmd, parg)) == -ENOIOCTLCMD) + err = -EINVAL; + mutex_unlock(&dvbdev_mutex); + + if (err < 0) + goto out; + + /* Copy results into user buffer */ + switch (_IOC_DIR(cmd)) + { + case _IOC_READ: + case (_IOC_WRITE | _IOC_READ): + if (copy_to_user((void __user *)arg, parg, _IOC_SIZE(cmd))) + err = -EFAULT; + break; + } + +out: + kfree(mbuf); + return err; +} + +long dvb_generic_ioctl(struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct dvb_device *dvbdev = file->private_data; + + if (!dvbdev) + return -ENODEV; + + if (!dvbdev->kernel_ioctl) + return -EINVAL; + + return dvb_usercopy(file, cmd, arg, dvbdev->kernel_ioctl); +} diff --git a/gcc/testsuite/gcc.dg/plugin/taint-CVE-2011-0521-2-fixed.c b/gcc/testsuite/gcc.dg/plugin/taint-CVE-2011-0521-2-fixed.c new file mode 100644 index 00000000000..8a211cefe4e --- /dev/null +++ b/gcc/testsuite/gcc.dg/plugin/taint-CVE-2011-0521-2-fixed.c @@ -0,0 +1,98 @@ +/* { dg-do compile } */ +// TODO: remove need for -fanalyzer-checker=taint here: +/* { dg-options "-fanalyzer -fanalyzer-checker=taint" } */ +/* { dg-require-effective-target analyzer } */ + +/* See notes in this header. */ +#include "taint-CVE-2011-0521.h" + +// TODO: remove need for this option +/* { dg-additional-options "-fanalyzer-checker=taint" } */ + +/* Adapted from drivers/media/dvb/ttpci/av7110_ca.c */ + +int dvb_ca_ioctl(struct file *file, unsigned int cmd, void *parg) +{ + struct dvb_device *dvbdev = file->private_data; + struct av7110 *av7110 = dvbdev->priv; + unsigned long arg = (unsigned long) parg; + + /* case CA_GET_SLOT_INFO: */ + { + ca_slot_info_t *info=(ca_slot_info_t *)parg; + + if (info->num < 0 || info->num > 1) + return -EINVAL; + av7110->ci_slot[info->num].num = info->num; /* { dg-bogus "attacker-controlled value" } */ + av7110->ci_slot[info->num].type = FW_CI_LL_SUPPORT(av7110->arm_app) ? + CA_CI_LINK : CA_CI; + memcpy(info, &av7110->ci_slot[info->num], sizeof(ca_slot_info_t)); + } + return 0; +} + +/* Adapted from drivers/media/dvb/dvb-core/dvbdev.c + Somewhat simplified: rather than pass in a callback that can + be dvb_ca_ioctl, call dvb_ca_ioctl directly. */ + +static DEFINE_MUTEX(dvbdev_mutex); + +int dvb_usercopy(struct file *file, + unsigned int cmd, unsigned long arg) +{ + char sbuf[128]; + void *mbuf = NULL; + void *parg = NULL; + int err = -1; + + /* Copy arguments into temp kernel buffer */ + switch (_IOC_DIR(cmd)) { + case _IOC_NONE: + /* + * For this command, the pointer is actually an integer + * argument. + */ + parg = (void *) arg; + break; + case _IOC_READ: /* some v4l ioctls are marked wrong ... */ + case _IOC_WRITE: + case (_IOC_WRITE | _IOC_READ): + if (_IOC_SIZE(cmd) <= sizeof(sbuf)) { + parg = sbuf; + } else { + /* too big to allocate from stack */ + mbuf = kmalloc(_IOC_SIZE(cmd),GFP_KERNEL); + if (NULL == mbuf) + return -ENOMEM; + parg = mbuf; + } + + err = -EFAULT; + if (copy_from_user(parg, (void __user *)arg, _IOC_SIZE(cmd))) + goto out; + break; + } + + /* call driver */ + mutex_lock(&dvbdev_mutex); + if ((err = dvb_ca_ioctl(file, cmd, parg)) == -ENOIOCTLCMD) + err = -EINVAL; + mutex_unlock(&dvbdev_mutex); + + if (err < 0) + goto out; + + /* Copy results into user buffer */ + switch (_IOC_DIR(cmd)) + { + case _IOC_READ: + case (_IOC_WRITE | _IOC_READ): + if (copy_to_user((void __user *)arg, parg, _IOC_SIZE(cmd))) + err = -EFAULT; + break; + } + +out: + kfree(mbuf); + return err; +} diff --git a/gcc/testsuite/gcc.dg/plugin/taint-CVE-2011-0521-2.c b/gcc/testsuite/gcc.dg/plugin/taint-CVE-2011-0521-2.c new file mode 100644 index 00000000000..30cab38e002 --- /dev/null +++ b/gcc/testsuite/gcc.dg/plugin/taint-CVE-2011-0521-2.c @@ -0,0 +1,95 @@ +/* { dg-do compile } */ +// TODO: remove need for -fanalyzer-checker=taint here: +/* { dg-options "-fanalyzer -fanalyzer-checker=taint" } */ +/* { dg-require-effective-target analyzer } */ + +/* See notes in this header. */ +#include "taint-CVE-2011-0521.h" + +/* Adapted from drivers/media/dvb/ttpci/av7110_ca.c */ + +int dvb_ca_ioctl(struct file *file, unsigned int cmd, void *parg) +{ + struct dvb_device *dvbdev = file->private_data; + struct av7110 *av7110 = dvbdev->priv; + unsigned long arg = (unsigned long) parg; + + /* case CA_GET_SLOT_INFO: */ + { + ca_slot_info_t *info=(ca_slot_info_t *)parg; + + if (info->num > 1) + return -EINVAL; + av7110->ci_slot[info->num].num = info->num; /* { dg-warning "attacker-controlled value" "" { xfail *-*-* } } */ + av7110->ci_slot[info->num].type = FW_CI_LL_SUPPORT(av7110->arm_app) ? + CA_CI_LINK : CA_CI; + memcpy(info, &av7110->ci_slot[info->num], sizeof(ca_slot_info_t)); + } + return 0; +} + +/* Adapted from drivers/media/dvb/dvb-core/dvbdev.c + Somewhat simplified: rather than pass in a callback that can + be dvb_ca_ioctl, call dvb_ca_ioctl directly. */ + +static DEFINE_MUTEX(dvbdev_mutex); + +int dvb_usercopy(struct file *file, + unsigned int cmd, unsigned long arg) +{ + char sbuf[128]; + void *mbuf = NULL; + void *parg = NULL; + int err = -1; + + /* Copy arguments into temp kernel buffer */ + switch (_IOC_DIR(cmd)) { + case _IOC_NONE: + /* + * For this command, the pointer is actually an integer + * argument. + */ + parg = (void *) arg; + break; + case _IOC_READ: /* some v4l ioctls are marked wrong ... */ + case _IOC_WRITE: + case (_IOC_WRITE | _IOC_READ): + if (_IOC_SIZE(cmd) <= sizeof(sbuf)) { + parg = sbuf; + } else { + /* too big to allocate from stack */ + mbuf = kmalloc(_IOC_SIZE(cmd),GFP_KERNEL); + if (NULL == mbuf) + return -ENOMEM; + parg = mbuf; + } + + err = -EFAULT; + if (copy_from_user(parg, (void __user *)arg, _IOC_SIZE(cmd))) + goto out; + break; + } + + /* call driver */ + mutex_lock(&dvbdev_mutex); + if ((err = dvb_ca_ioctl(file, cmd, parg)) == -ENOIOCTLCMD) + err = -EINVAL; + mutex_unlock(&dvbdev_mutex); + + if (err < 0) + goto out; + + /* Copy results into user buffer */ + switch (_IOC_DIR(cmd)) + { + case _IOC_READ: + case (_IOC_WRITE | _IOC_READ): + if (copy_to_user((void __user *)arg, parg, _IOC_SIZE(cmd))) + err = -EFAULT; + break; + } + +out: + kfree(mbuf); + return err; +} diff --git a/gcc/testsuite/gcc.dg/plugin/taint-CVE-2011-0521-3-fixed.c b/gcc/testsuite/gcc.dg/plugin/taint-CVE-2011-0521-3-fixed.c new file mode 100644 index 00000000000..b7852b40dcf --- /dev/null +++ b/gcc/testsuite/gcc.dg/plugin/taint-CVE-2011-0521-3-fixed.c @@ -0,0 +1,61 @@ +/* { dg-do compile } */ +// TODO: remove need for -fanalyzer-checker=taint here: +/* { dg-options "-fanalyzer -fanalyzer-checker=taint" } */ +/* { dg-require-effective-target analyzer } */ + +/* See notes in this header. */ +#include "taint-CVE-2011-0521.h" + +// TODO: remove need for this option +/* { dg-additional-options "-fanalyzer-checker=taint" } */ + +/* Adapted from drivers/media/dvb/ttpci/av7110_ca.c */ + +int dvb_ca_ioctl(struct file *file, unsigned int cmd, void *parg) +{ + struct dvb_device *dvbdev = file->private_data; + struct av7110 *av7110 = dvbdev->priv; + unsigned long arg = (unsigned long) parg; + + /* case CA_GET_SLOT_INFO: */ + { + ca_slot_info_t *info=(ca_slot_info_t *)parg; + + if (info->num < 0 || info->num > 1) + return -EINVAL; + av7110->ci_slot[info->num].num = info->num; /* { dg-bogus "attacker-controlled value" } */ + av7110->ci_slot[info->num].type = FW_CI_LL_SUPPORT(av7110->arm_app) ? + CA_CI_LINK : CA_CI; + memcpy(info, &av7110->ci_slot[info->num], sizeof(ca_slot_info_t)); + } + return 0; +} + +/* Adapted from drivers/media/dvb/dvb-core/dvbdev.c + Further simplified from -2; always use an on-stack buffer. */ + +static DEFINE_MUTEX(dvbdev_mutex); + +int dvb_usercopy(struct file *file, + unsigned int cmd, unsigned long arg) +{ + char sbuf[128]; + void *parg = sbuf; + int err = -EFAULT; + if (copy_from_user(parg, (void __user *)arg, sizeof(sbuf))) + goto out; + + mutex_lock(&dvbdev_mutex); + if ((err = dvb_ca_ioctl(file, cmd, parg)) == -ENOIOCTLCMD) + err = -EINVAL; + mutex_unlock(&dvbdev_mutex); + + if (err < 0) + goto out; + + if (copy_to_user((void __user *)arg, parg, sizeof(sbuf))) + err = -EFAULT; + +out: + return err; +} diff --git a/gcc/testsuite/gcc.dg/plugin/taint-CVE-2011-0521-3.c b/gcc/testsuite/gcc.dg/plugin/taint-CVE-2011-0521-3.c new file mode 100644 index 00000000000..6b9e034eea7 --- /dev/null +++ b/gcc/testsuite/gcc.dg/plugin/taint-CVE-2011-0521-3.c @@ -0,0 +1,59 @@ +/* { dg-do compile } */ +// TODO: remove need for -fanalyzer-checker=taint here: +/* { dg-options "-fanalyzer -fanalyzer-checker=taint" } */ +/* { dg-require-effective-target analyzer } */ + +/* See notes in this header. */ +#include "taint-CVE-2011-0521.h" + +/* Adapted from drivers/media/dvb/ttpci/av7110_ca.c */ + +int dvb_ca_ioctl(struct file *file, unsigned int cmd, void *parg) +{ + struct dvb_device *dvbdev = file->private_data; + struct av7110 *av7110 = dvbdev->priv; + unsigned long arg = (unsigned long) parg; + + /* case CA_GET_SLOT_INFO: */ + { + ca_slot_info_t *info=(ca_slot_info_t *)parg; + + if (info->num > 1) + return -EINVAL; + av7110->ci_slot[info->num].num = info->num; /* { dg-warning "attacker-controlled value" "" { xfail *-*-* } } */ + // TODO(xfail) + av7110->ci_slot[info->num].type = FW_CI_LL_SUPPORT(av7110->arm_app) ? + CA_CI_LINK : CA_CI; + memcpy(info, &av7110->ci_slot[info->num], sizeof(ca_slot_info_t)); + } + return 0; +} + +/* Adapted from drivers/media/dvb/dvb-core/dvbdev.c + Further simplified from -2; always use an on-stack buffer. */ + +static DEFINE_MUTEX(dvbdev_mutex); + +int dvb_usercopy(struct file *file, + unsigned int cmd, unsigned long arg) +{ + char sbuf[128]; + void *parg = sbuf; + int err = -EFAULT; + if (copy_from_user(parg, (void __user *)arg, sizeof(sbuf))) + goto out; + + mutex_lock(&dvbdev_mutex); + if ((err = dvb_ca_ioctl(file, cmd, parg)) == -ENOIOCTLCMD) + err = -EINVAL; + mutex_unlock(&dvbdev_mutex); + + if (err < 0) + goto out; + + if (copy_to_user((void __user *)arg, parg, sizeof(sbuf))) + err = -EFAULT; + +out: + return err; +} diff --git a/gcc/testsuite/gcc.dg/plugin/taint-CVE-2011-0521-4.c b/gcc/testsuite/gcc.dg/plugin/taint-CVE-2011-0521-4.c new file mode 100644 index 00000000000..f314c64ce70 --- /dev/null +++ b/gcc/testsuite/gcc.dg/plugin/taint-CVE-2011-0521-4.c @@ -0,0 +1,45 @@ +/* { dg-do compile } */ +// TODO: remove need for -fanalyzer-checker=taint here: +// TODO: remove need for --param=analyzer-max-svalue-depth=25 here: +/* { dg-options "-fanalyzer -fanalyzer-checker=taint --param=analyzer-max-svalue-depth=25" } */ +/* { dg-options "-fanalyzer -fanalyzer-checker=taint" } */ +/* { dg-require-effective-target analyzer } */ + +/* See notes in this header. */ +#include "taint-CVE-2011-0521.h" + +/* Adapted from dvb_ca_ioctl in drivers/media/dvb/ttpci/av7110_ca.c and + dvb_usercopy in drivers/media/dvb/dvb-core/dvbdev.c + + Further simplified from -3; merge into a single function; drop the mutex, + remove control flow. */ + +int test_1(struct file *file, unsigned int cmd, unsigned long arg) +{ + char sbuf[128]; + void *parg = sbuf; + + if (copy_from_user(parg, (void __user *)arg, sizeof(sbuf))) + return -1; + + { + struct dvb_device *dvbdev = file->private_data; + struct av7110 *av7110 = dvbdev->priv; + unsigned long arg = (unsigned long) parg; + + /* case CA_GET_SLOT_INFO: */ + ca_slot_info_t *info=(ca_slot_info_t *)parg; + + if (info->num > 1) + return -EINVAL; + av7110->ci_slot[info->num].num = info->num; /* { dg-warning "attacker-controlled value" "" { xfail *-*-* } } */ + // TODO(xfail) + av7110->ci_slot[info->num].type = FW_CI_LL_SUPPORT(av7110->arm_app) ? + CA_CI_LINK : CA_CI; + memcpy(info, &av7110->ci_slot[info->num], sizeof(ca_slot_info_t)); + } + + copy_to_user((void __user *)arg, parg, sizeof(sbuf)); + + return 0; +} diff --git a/gcc/testsuite/gcc.dg/plugin/taint-CVE-2011-0521-5-fixed.c b/gcc/testsuite/gcc.dg/plugin/taint-CVE-2011-0521-5-fixed.c new file mode 100644 index 00000000000..8cb067c6542 --- /dev/null +++ b/gcc/testsuite/gcc.dg/plugin/taint-CVE-2011-0521-5-fixed.c @@ -0,0 +1,46 @@ +/* { dg-do compile } */ +// TODO: remove need for -fanalyzer-checker=taint here: +// TODO: remove need for --param=analyzer-max-svalue-depth=25 here: +/* { dg-options "-fanalyzer -fanalyzer-checker=taint --param=analyzer-max-svalue-depth=25" } */ +/* { dg-require-effective-target analyzer } */ + +/* See notes in this header. */ +#include "taint-CVE-2011-0521.h" + +/* Adapted from dvb_ca_ioctl in drivers/media/dvb/ttpci/av7110_ca.c and + dvb_usercopy in drivers/media/dvb/dvb-core/dvbdev.c + + Further simplified from -4; avoid parg and the cast to char[128]. */ + +int test_1(struct file *file, unsigned int cmd, unsigned long arg) +{ + ca_slot_info_t sbuf; + + if (copy_from_user(&sbuf, (void __user *)arg, sizeof(sbuf)) != 0) + return -1; + + { + struct dvb_device *dvbdev = file->private_data; + struct av7110 *av7110 = dvbdev->priv; + + /* case CA_GET_SLOT_INFO: */ + ca_slot_info_t *info= &sbuf; + + __analyzer_dump_state ("taint", info->num); /* { dg-warning "tainted" } */ + + if (info->num < 0 || info->num > 1) + return -EINVAL; + + __analyzer_dump_state ("taint", info->num); /* { dg-warning "stop" } */ + + av7110->ci_slot[info->num].num = info->num; + av7110->ci_slot[info->num].type = FW_CI_LL_SUPPORT(av7110->arm_app) ? + CA_CI_LINK : CA_CI; + memcpy(info, &av7110->ci_slot[info->num], sizeof(ca_slot_info_t)); /* { dg-bogus "use of attacker-controlled value in array lookup without bounds checking" "" { xfail *-*-* } } */ + // FIXME: why the above false +ve? + } + + copy_to_user((void __user *)arg, &sbuf, sizeof(sbuf)); + + return 0; +} diff --git a/gcc/testsuite/gcc.dg/plugin/taint-CVE-2011-0521-5.c b/gcc/testsuite/gcc.dg/plugin/taint-CVE-2011-0521-5.c new file mode 100644 index 00000000000..4ce047902d3 --- /dev/null +++ b/gcc/testsuite/gcc.dg/plugin/taint-CVE-2011-0521-5.c @@ -0,0 +1,45 @@ +/* { dg-do compile } */ +// TODO: remove need for -fanalyzer-checker=taint here: +// TODO: remove need for --param=analyzer-max-svalue-depth=25 here: +/* { dg-options "-fanalyzer -fanalyzer-checker=taint --param=analyzer-max-svalue-depth=25" } */ +/* { dg-require-effective-target analyzer } */ + +/* See notes in this header. */ +#include "taint-CVE-2011-0521.h" + +/* Adapted from dvb_ca_ioctl in drivers/media/dvb/ttpci/av7110_ca.c and + dvb_usercopy in drivers/media/dvb/dvb-core/dvbdev.c + + Further simplified from -4; avoid parg and the cast to char[128]. */ + +int test_1(struct file *file, unsigned int cmd, unsigned long arg) +{ + ca_slot_info_t sbuf; + + if (copy_from_user(&sbuf, (void __user *)arg, sizeof(sbuf)) != 0) + return -1; + + { + struct dvb_device *dvbdev = file->private_data; + struct av7110 *av7110 = dvbdev->priv; + + /* case CA_GET_SLOT_INFO: */ + ca_slot_info_t *info= &sbuf; + + __analyzer_dump_state ("taint", info->num); /* { dg-warning "tainted" } */ + + if (info->num > 1) + return -EINVAL; + + __analyzer_dump_state ("taint", info->num); /* { dg-warning "has_ub" } */ + + av7110->ci_slot[info->num].num = info->num; /* { dg-warning "use of attacker-controlled value '\\*info\\.num' in array lookup without checking for negative" } */ + av7110->ci_slot[info->num].type = FW_CI_LL_SUPPORT(av7110->arm_app) ? /* { dg-warning "use of attacker-controlled value '\\*info\\.num' in array lookup without checking for negative" } */ + CA_CI_LINK : CA_CI; + memcpy(info, &av7110->ci_slot[info->num], sizeof(ca_slot_info_t)); /* { dg-warning "use of attacker-controlled value in array lookup without bounds checking" } */ + } + + copy_to_user((void __user *)arg, &sbuf, sizeof(sbuf)); + + return 0; +} diff --git a/gcc/testsuite/gcc.dg/plugin/taint-CVE-2011-0521-6.c b/gcc/testsuite/gcc.dg/plugin/taint-CVE-2011-0521-6.c new file mode 100644 index 00000000000..c54af790a56 --- /dev/null +++ b/gcc/testsuite/gcc.dg/plugin/taint-CVE-2011-0521-6.c @@ -0,0 +1,42 @@ +/* { dg-do compile } */ +// TODO: remove need for -fanalyzer-checker=taint here: +// TODO: remove need for --param=analyzer-max-svalue-depth=25 here: +/* { dg-options "-fanalyzer -fanalyzer-checker=taint --param=analyzer-max-svalue-depth=25" } */ +/* { dg-require-effective-target analyzer } */ + +/* See notes in this header. */ +#include "taint-CVE-2011-0521.h" + +/* Adapted from dvb_ca_ioctl in drivers/media/dvb/ttpci/av7110_ca.c and + dvb_usercopy in drivers/media/dvb/dvb-core/dvbdev.c + + Further simplified from -5; remove all control flow. */ + +int test_1(struct file *file, unsigned int cmd, unsigned long arg) +{ + ca_slot_info_t sbuf; + + if (copy_from_user(&sbuf, (void __user *)arg, sizeof(sbuf)) != 0) + return -1; + + { + struct dvb_device *dvbdev = file->private_data; + struct av7110 *av7110 = dvbdev->priv; + + /* case CA_GET_SLOT_INFO: */ + ca_slot_info_t *info= &sbuf; + + __analyzer_dump_state ("taint", info->num); /* { dg-warning "tainted" } */ + + //__analyzer_break (); + + av7110->ci_slot[info->num].num = info->num; /* { dg-warning "use of attacker-controlled value '\\*info\\.num' in array lookup without bounds checking" } */ + av7110->ci_slot[info->num].type = FW_CI_LL_SUPPORT(av7110->arm_app) ? /* { dg-warning "use of attacker-controlled value '\\*info\\.num' in array lookup without bounds checking" } */ + CA_CI_LINK : CA_CI; + memcpy(info, &av7110->ci_slot[info->num], sizeof(ca_slot_info_t)); /* { dg-warning "use of attacker-controlled value in array lookup without bounds checking" } */ + } + + copy_to_user((void __user *)arg, &sbuf, sizeof(sbuf)); + + return 0; +} diff --git a/gcc/testsuite/gcc.dg/plugin/taint-CVE-2011-0521.h b/gcc/testsuite/gcc.dg/plugin/taint-CVE-2011-0521.h new file mode 100644 index 00000000000..29f66b6e76e --- /dev/null +++ b/gcc/testsuite/gcc.dg/plugin/taint-CVE-2011-0521.h @@ -0,0 +1,136 @@ +/* Shared header for the various taint-CVE-2011-0521-*.c tests. + These are a series of successively simpler reductions of the reproducer. + Ideally the analyzer would detect the issue in all of the testcases, + but currently requires some simplification of the code to do so. + + "The dvb_ca_ioctl function in drivers/media/dvb/ttpci/av7110_ca.c in the + Linux kernel before 2.6.38-rc2 does not check the sign of a certain integer + field, which allows local users to cause a denial of service (memory + corruption) or possibly have unspecified other impact via a negative value." + + Adapted from Linux 2.6.38, which is under the GPLv2. + + Fixed in e.g. cb26a24ee9706473f31d34cc259f4dcf45cd0644 on linux-2.6.38.y */ + +#include +#include "test-uaccess.h" +#include "../analyzer/analyzer-decls.h" + +typedef unsigned int u32; + +/* Adapted from include/linux/compiler.h */ + +#define __force + +/* Adapted from include/asm-generic/errno-base.h */ + +#define ENOMEM 12 /* Out of memory */ +#define EFAULT 14 /* Bad address */ +#define ENODEV 19 /* No such device */ +#define EINVAL 22 /* Invalid argument */ + +/* Adapted from include/linux/errno.h */ + +#define ENOIOCTLCMD 515 /* No ioctl command */ + +/* Adapted from include/linux/fs.h */ + +struct file { + /* [...snip...] */ + void *private_data; + /* [...snip...] */ +}; + +/* Adapted from drivers/media/dvb/dvb-core/dvbdev.h */ + +struct dvb_device { + /* [...snip...] */ + int (*kernel_ioctl)(struct file *file, unsigned int cmd, void *arg); + + void *priv; +}; + + +/* Adapted from include/linux/dvb/ca.h */ + +typedef struct ca_slot_info { + int num; /* slot number */ + + int type; /* CA interface this slot supports */ +#define CA_CI 1 /* CI high level interface */ +#define CA_CI_LINK 2 /* CI link layer level interface */ + /* [...snip...] */ +} ca_slot_info_t; + + +/* Adapted from drivers/media/dvb/ttpci/av7110.h */ + +struct av7110 { + /* [...snip...] */ + ca_slot_info_t ci_slot[2]; + /* [...snip...] */ + u32 arm_app; + /* [...snip...] */ +}; + +/* Adapted from drivers/media/dvb/ttpci/av7110_hw.h */ + +#define FW_CI_LL_SUPPORT(arm_app) ((arm_app) & 0x80000000) + +/* Adapted from include/asm-generic/ioctl.h */ + +#define _IOC_NRBITS 8 +#define _IOC_TYPEBITS 8 + +#define _IOC_SIZEBITS 14 +#define _IOC_DIRBITS 2 + +#define _IOC_SIZEMASK ((1 << _IOC_SIZEBITS)-1) +#define _IOC_DIRMASK ((1 << _IOC_DIRBITS)-1) +#define _IOC_NRSHIFT 0 +#define _IOC_TYPESHIFT (_IOC_NRSHIFT+_IOC_NRBITS) +#define _IOC_SIZESHIFT (_IOC_TYPESHIFT+_IOC_TYPEBITS) +#define _IOC_DIRSHIFT (_IOC_SIZESHIFT+_IOC_SIZEBITS) + +#define _IOC_NONE 0U +#define _IOC_WRITE 1U +#define _IOC_READ 2U + +#define _IOC_DIR(nr) (((nr) >> _IOC_DIRSHIFT) & _IOC_DIRMASK) +#define _IOC_SIZE(nr) (((nr) >> _IOC_SIZESHIFT) & _IOC_SIZEMASK) + +/* Adapted from include/linux/mutex.h */ + +struct mutex { + /* [...snip...] */ +}; + +#define __MUTEX_INITIALIZER(lockname) \ + { /* [...snip...] */ } + +#define DEFINE_MUTEX(mutexname) \ + struct mutex mutexname = __MUTEX_INITIALIZER(mutexname) + +extern void mutex_lock(struct mutex *lock); +extern void mutex_unlock(struct mutex *lock); + +/* Adapted from include/linux/types.h */ + +#define __bitwise__ +typedef unsigned __bitwise__ gfp_t; + +/* Adapted from include/linux/gfp.h */ + +#define ___GFP_WAIT 0x10u +#define ___GFP_IO 0x40u +#define ___GFP_FS 0x80u +#define __GFP_WAIT ((__force gfp_t)___GFP_WAIT) +#define __GFP_IO ((__force gfp_t)___GFP_IO) +#define __GFP_FS ((__force gfp_t)___GFP_FS) +#define GFP_KERNEL (__GFP_WAIT | __GFP_IO | __GFP_FS) + +/* Adapted from include/linux/slab.h */ + +void kfree(const void *); +void *kmalloc(size_t size, gfp_t flags) + __attribute__((malloc (kfree))); diff --git a/gcc/testsuite/gcc.dg/plugin/taint-antipatterns-1.c b/gcc/testsuite/gcc.dg/plugin/taint-antipatterns-1.c new file mode 100644 index 00000000000..6bb6f1be25c --- /dev/null +++ b/gcc/testsuite/gcc.dg/plugin/taint-antipatterns-1.c @@ -0,0 +1,139 @@ +/* { dg-do compile } */ +// TODO: remove need for -fanalyzer-checker=taint here: +/* { dg-options "-fanalyzer -fanalyzer-checker=taint" } */ +/* { dg-require-effective-target analyzer } */ + +#include "test-uaccess.h" + +/* Adapted and simplified decls from linux kernel headers. */ + +typedef unsigned char u8; +typedef unsigned __INT16_TYPE__ u16; +typedef unsigned __INT32_TYPE__ u32; +typedef signed __INT32_TYPE__ s32; +typedef __SIZE_TYPE__ size_t; + +#define EFAULT 14 + +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:taint.c (GPL-v2.0). */ + +struct cmd_1 +{ + u32 idx; + u32 val; +}; + +static u32 arr[16]; + +int taint_array_access(void __user *src) +{ + struct cmd_1 cmd; + if (copy_from_user(&cmd, src, sizeof(cmd))) + return -EFAULT; + /* + * cmd.idx is an unsanitized value from user-space, hence + * this is an arbitrary kernel memory access. + */ + arr[cmd.idx] = cmd.val; /* { dg-warning "use of attacker-controlled value 'cmd.idx' in array lookup without upper-bounds checking" } */ + return 0; +} + +struct cmd_2 +{ + s32 idx; + u32 val; +}; + +int taint_signed_array_access(void __user *src) +{ + struct cmd_2 cmd; + if (copy_from_user(&cmd, src, sizeof(cmd))) + return -EFAULT; + if (cmd.idx >= 16) + return -EFAULT; + + /* + * cmd.idx hasn't been checked for being negative, hence + * this is an arbitrary kernel memory access. + */ + arr[cmd.idx] = cmd.val; /* { dg-warning "use of attacker-controlled value 'cmd.idx' in array lookup without checking for negative" } */ + return 0; +} + +struct cmd_s32_binop +{ + s32 a; + s32 b; + s32 result; +}; + +int taint_divide_by_zero_direct(void __user *uptr) +{ + struct cmd_s32_binop cmd; + if (copy_from_user(&cmd, uptr, sizeof(cmd))) + return -EFAULT; + + /* cmd.b is attacker-controlled and could be zero */ + cmd.result = cmd.a / cmd.b; /* { dg-warning "use of attacker-controlled value 'cmd.b' as divisor without checking for zero" } */ + + if (copy_to_user (uptr, &cmd, sizeof(cmd))) + return -EFAULT; + return 0; +} + +int taint_divide_by_zero_compound(void __user *uptr) +{ + struct cmd_s32_binop cmd; + if (copy_from_user(&cmd, uptr, sizeof(cmd))) + return -EFAULT; + + /* + * cmd.b is attacker-controlled and could be -1, hence + * the divisor could be zero + */ + cmd.result = cmd.a / (cmd.b + 1); /* { dg-warning "use of attacker-controlled value 'cmd.b \\+ 1' as divisor without checking for zero" } */ + + if (copy_to_user (uptr, &cmd, sizeof(cmd))) + return -EFAULT; + return 0; +} + +int taint_mod_by_zero_direct(void __user *uptr) +{ + struct cmd_s32_binop cmd; + if (copy_from_user(&cmd, uptr, sizeof(cmd))) + return -EFAULT; + + /* cmd.b is attacker-controlled and could be zero */ + cmd.result = cmd.a % cmd.b; /* { dg-warning "use of attacker-controlled value 'cmd.b' as divisor without checking for zero" } */ + + if (copy_to_user (uptr, &cmd, sizeof(cmd))) + return -EFAULT; + return 0; +} + +int taint_mod_by_zero_compound(void __user *uptr) +{ + struct cmd_s32_binop cmd; + if (copy_from_user(&cmd, uptr, sizeof(cmd))) + return -EFAULT; + + /* + * cmd.b is attacker-controlled and could be -1, hence + * the divisor could be zero + */ + cmd.result = cmd.a % (cmd.b + 1); /* { dg-warning "use of attacker-controlled value 'cmd.b \\+ 1' as divisor without checking for zero" } */ + + if (copy_to_user (uptr, &cmd, sizeof(cmd))) + return -EFAULT; + return 0; +} + +/* TODO: etc. */ diff --git a/gcc/testsuite/gcc.dg/plugin/test-uaccess.h b/gcc/testsuite/gcc.dg/plugin/test-uaccess.h new file mode 100644 index 00000000000..42eac985258 --- /dev/null +++ b/gcc/testsuite/gcc.dg/plugin/test-uaccess.h @@ -0,0 +1,10 @@ +/* Shared header for testcases for copy_from_user/copy_to_user. */ + +/* Adapted from include/linux/compiler.h */ + +#define __user + +/* Adapted from include/asm-generic/uaccess.h */ + +extern long copy_from_user(void *to, const void __user *from, long n); +extern long copy_to_user(void __user *to, const void *from, long n);