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);