blob: f8644addbfdc5529d53589153b173425fee9b847 [file] [log] [blame]
/* Bounds-checking of reads and writes to memory regions.
Copyright (C) 2019-2024 Free Software Foundation, Inc.
This file is part of GCC.
GCC is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3, or (at your option)
any later version.
GCC is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
along with GCC; see the file COPYING3. If not see
<http://www.gnu.org/licenses/>. */
#include "config.h"
#define INCLUDE_MEMORY
#define INCLUDE_VECTOR
#include "system.h"
#include "coretypes.h"
#include "make-unique.h"
#include "tree.h"
#include "function.h"
#include "basic-block.h"
#include "intl.h"
#include "gimple.h"
#include "gimple-iterator.h"
#include "diagnostic-core.h"
#include "diagnostic-diagram.h"
#include "diagnostic-format-sarif.h"
#include "analyzer/analyzer.h"
#include "analyzer/analyzer-logging.h"
#include "analyzer/region-model.h"
#include "analyzer/checker-event.h"
#include "analyzer/checker-path.h"
#include "analyzer/access-diagram.h"
#if ENABLE_ANALYZER
namespace ana {
/* Abstract base class for all out-of-bounds warnings. */
class out_of_bounds : public pending_diagnostic
{
public:
class oob_region_creation_event_capacity : public region_creation_event_capacity
{
public:
oob_region_creation_event_capacity (tree byte_capacity,
const event_loc_info &loc_info,
out_of_bounds &oob)
: region_creation_event_capacity (byte_capacity,
loc_info),
m_oob (oob)
{
}
void prepare_for_emission (checker_path *path,
pending_diagnostic *pd,
diagnostic_event_id_t emission_id) override
{
region_creation_event_capacity::prepare_for_emission (path,
pd,
emission_id);
m_oob.m_region_creation_event_id = emission_id;
}
private:
out_of_bounds &m_oob;
};
out_of_bounds (const region_model &model,
const region *reg,
tree diag_arg,
const svalue *sval_hint)
: m_model (model), m_reg (reg), m_diag_arg (diag_arg), m_sval_hint (sval_hint)
{}
bool subclass_equal_p (const pending_diagnostic &base_other) const override
{
const out_of_bounds &other
(static_cast <const out_of_bounds &>(base_other));
return (m_reg == other.m_reg
&& pending_diagnostic::same_tree_p (m_diag_arg, other.m_diag_arg));
}
int get_controlling_option () const final override
{
return OPT_Wanalyzer_out_of_bounds;
}
void mark_interesting_stuff (interesting_t *interest) final override
{
interest->add_region_creation (m_reg->get_base_region ());
}
void add_region_creation_events (const region *,
tree byte_capacity,
const event_loc_info &loc_info,
checker_path &emission_path) override
{
/* The memory space is described in the diagnostic message itself,
so we don't need an event for that. */
if (byte_capacity)
emission_path.add_event
(make_unique<oob_region_creation_event_capacity> (byte_capacity,
loc_info,
*this));
}
void maybe_add_sarif_properties (sarif_object &result_obj)
const override
{
sarif_property_bag &props = result_obj.get_or_create_properties ();
#define PROPERTY_PREFIX "gcc/analyzer/out_of_bounds/"
props.set_string (PROPERTY_PREFIX "dir",
get_dir () == DIR_READ ? "read" : "write");
props.set (PROPERTY_PREFIX "model", m_model.to_json ());
props.set (PROPERTY_PREFIX "region", m_reg->to_json ());
props.set (PROPERTY_PREFIX "diag_arg", tree_to_json (m_diag_arg));
if (m_sval_hint)
props.set (PROPERTY_PREFIX "sval_hint", m_sval_hint->to_json ());
props.set (PROPERTY_PREFIX "region_creation_event_id",
diagnostic_event_id_to_json (m_region_creation_event_id));
#undef PROPERTY_PREFIX
}
virtual enum access_direction get_dir () const = 0;
protected:
enum memory_space get_memory_space () const
{
return m_reg->get_memory_space ();
}
void
maybe_show_notes (diagnostic_emission_context &ctxt) const
{
maybe_describe_array_bounds (ctxt.get_location ());
maybe_show_diagram (ctxt.get_logger ());
}
/* Potentially add a note about valid ways to index this array, such
as (given "int arr[10];"):
note: valid subscripts for 'arr' are '[0]' to '[9]'
We print the '[' and ']' characters so as to express the valid
subscripts using C syntax, rather than just as byte ranges,
which hopefully is more clear to the user. */
void
maybe_describe_array_bounds (location_t loc) const
{
if (!m_diag_arg)
return;
tree t = TREE_TYPE (m_diag_arg);
if (!t)
return;
if (TREE_CODE (t) != ARRAY_TYPE)
return;
tree domain = TYPE_DOMAIN (t);
if (!domain)
return;
tree max_idx = TYPE_MAX_VALUE (domain);
if (!max_idx)
return;
tree min_idx = TYPE_MIN_VALUE (domain);
inform (loc,
"valid subscripts for %qE are %<[%E]%> to %<[%E]%>",
m_diag_arg, min_idx, max_idx);
}
void
maybe_show_diagram (logger *logger) const
{
access_operation op (m_model, get_dir (), *m_reg, m_sval_hint);
/* Don't attempt to make a diagram if there's no valid way of
accessing the base region (e.g. a 0-element array). */
if (op.get_valid_bits ().empty_p ())
return;
if (const text_art::theme *theme = global_dc->get_diagram_theme ())
{
text_art::style_manager sm;
text_art::canvas canvas (make_access_diagram (op, sm, *theme, logger));
if (canvas.get_size ().w == 0 && canvas.get_size ().h == 0)
{
/* In lieu of exceptions, return a zero-sized diagram if there's
a problem. Give up if that's happened. */
return;
}
diagnostic_diagram diagram
(canvas,
/* Alt text. */
_("Diagram visualizing the predicted out-of-bounds access"));
global_dc->emit_diagram (diagram);
}
}
text_art::canvas
make_access_diagram (const access_operation &op,
text_art::style_manager &sm,
const text_art::theme &theme,
logger *logger) const
{
access_diagram d (op, m_region_creation_event_id, sm, theme, logger);
return d.to_canvas (sm);
}
region_model m_model;
const region *m_reg;
tree m_diag_arg;
const svalue *m_sval_hint;
diagnostic_event_id_t m_region_creation_event_id;
};
/* Abstract base class for all out-of-bounds warnings where the
out-of-bounds range is concrete. */
class concrete_out_of_bounds : public out_of_bounds
{
public:
concrete_out_of_bounds (const region_model &model,
const region *reg, tree diag_arg,
bit_range out_of_bounds_bits,
const svalue *sval_hint)
: out_of_bounds (model, reg, diag_arg, sval_hint),
m_out_of_bounds_bits (out_of_bounds_bits)
{}
bool subclass_equal_p (const pending_diagnostic &base_other) const override
{
const concrete_out_of_bounds &other
(static_cast <const concrete_out_of_bounds &>(base_other));
return (out_of_bounds::subclass_equal_p (other)
&& m_out_of_bounds_bits == other.m_out_of_bounds_bits);
}
void maybe_add_sarif_properties (sarif_object &result_obj)
const override
{
out_of_bounds::maybe_add_sarif_properties (result_obj);
sarif_property_bag &props = result_obj.get_or_create_properties ();
#define PROPERTY_PREFIX "gcc/analyzer/concrete_out_of_bounds/"
props.set (PROPERTY_PREFIX "out_of_bounds_bits",
m_out_of_bounds_bits.to_json ());
byte_range out_of_bounds_bytes (0, 0);
if (get_out_of_bounds_bytes (&out_of_bounds_bytes))
props.set (PROPERTY_PREFIX "out_of_bounds_bytes",
out_of_bounds_bytes.to_json ());
#undef PROPERTY_PREFIX
}
bool get_out_of_bounds_bytes (byte_range *out) const
{
return m_out_of_bounds_bits.as_byte_range (out);
}
protected:
bit_range m_out_of_bounds_bits;
};
/* Abstract subclass to complaing about concrete out-of-bounds
past the end of the buffer. */
class concrete_past_the_end : public concrete_out_of_bounds
{
public:
concrete_past_the_end (const region_model &model,
const region *reg, tree diag_arg, bit_range range,
tree bit_bound,
const svalue *sval_hint)
: concrete_out_of_bounds (model, reg, diag_arg, range, sval_hint),
m_bit_bound (bit_bound),
m_byte_bound (NULL_TREE)
{
if (m_bit_bound && TREE_CODE (m_bit_bound) == INTEGER_CST)
m_byte_bound
= wide_int_to_tree (size_type_node,
wi::to_offset (m_bit_bound) >> LOG2_BITS_PER_UNIT);
}
bool
subclass_equal_p (const pending_diagnostic &base_other) const final override
{
const concrete_past_the_end &other
(static_cast <const concrete_past_the_end &>(base_other));
return (concrete_out_of_bounds::subclass_equal_p (other)
&& pending_diagnostic::same_tree_p (m_bit_bound,
other.m_bit_bound));
}
void add_region_creation_events (const region *,
tree,
const event_loc_info &loc_info,
checker_path &emission_path) final override
{
if (m_byte_bound && TREE_CODE (m_byte_bound) == INTEGER_CST)
emission_path.add_event
(make_unique<oob_region_creation_event_capacity> (m_byte_bound,
loc_info,
*this));
}
void maybe_add_sarif_properties (sarif_object &result_obj)
const final override
{
concrete_out_of_bounds::maybe_add_sarif_properties (result_obj);
sarif_property_bag &props = result_obj.get_or_create_properties ();
#define PROPERTY_PREFIX "gcc/analyzer/concrete_past_the_end/"
props.set (PROPERTY_PREFIX "bit_bound",
tree_to_json (m_bit_bound));
props.set (PROPERTY_PREFIX "byte_bound",
tree_to_json (m_byte_bound));
#undef PROPERTY_PREFIX
}
protected:
tree m_bit_bound;
tree m_byte_bound;
};
/* Concrete subclass to complain about buffer overflows. */
class concrete_buffer_overflow : public concrete_past_the_end
{
public:
concrete_buffer_overflow (const region_model &model,
const region *reg, tree diag_arg,
bit_range range, tree bit_bound,
const svalue *sval_hint)
: concrete_past_the_end (model, reg, diag_arg, range, bit_bound, sval_hint)
{}
const char *get_kind () const final override
{
return "concrete_buffer_overflow";
}
bool emit (diagnostic_emission_context &ctxt) final override
{
bool warned;
switch (get_memory_space ())
{
default:
ctxt.add_cwe (787);
warned = ctxt.warn ("buffer overflow");
break;
case MEMSPACE_STACK:
ctxt.add_cwe (121);
warned = ctxt.warn ("stack-based buffer overflow");
break;
case MEMSPACE_HEAP:
ctxt.add_cwe (122);
warned = ctxt.warn ("heap-based buffer overflow");
break;
}
if (warned)
{
if (wi::fits_uhwi_p (m_out_of_bounds_bits.m_size_in_bits))
{
unsigned HOST_WIDE_INT num_bad_bits
= m_out_of_bounds_bits.m_size_in_bits.to_uhwi ();
if (num_bad_bits % BITS_PER_UNIT == 0)
{
unsigned HOST_WIDE_INT num_bad_bytes
= num_bad_bits / BITS_PER_UNIT;
if (m_diag_arg)
inform_n (ctxt.get_location (),
num_bad_bytes,
"write of %wu byte to beyond the end of %qE",
"write of %wu bytes to beyond the end of %qE",
num_bad_bytes,
m_diag_arg);
else
inform_n (ctxt.get_location (),
num_bad_bytes,
"write of %wu byte to beyond the end of the region",
"write of %wu bytes to beyond the end of the region",
num_bad_bytes);
}
else
{
if (m_diag_arg)
inform_n (ctxt.get_location (),
num_bad_bits,
"write of %wu bit to beyond the end of %qE",
"write of %wu bits to beyond the end of %qE",
num_bad_bits,
m_diag_arg);
else
inform_n (ctxt.get_location (),
num_bad_bits,
"write of %wu bit to beyond the end of the region",
"write of %wu bits to beyond the end of the region",
num_bad_bits);
}
}
else if (m_diag_arg)
inform (ctxt.get_location (),
"write to beyond the end of %qE",
m_diag_arg);
maybe_show_notes (ctxt);
}
return warned;
}
label_text describe_final_event (const evdesc::final_event &ev)
final override
{
if (m_byte_bound || !m_bit_bound)
{
byte_range out_of_bounds_bytes (0, 0);
if (get_out_of_bounds_bytes (&out_of_bounds_bytes))
return describe_final_event_as_bytes (ev, out_of_bounds_bytes);
}
return describe_final_event_as_bits (ev);
}
label_text
describe_final_event_as_bytes (const evdesc::final_event &ev,
const byte_range &out_of_bounds_bytes)
{
byte_size_t start = out_of_bounds_bytes.get_start_byte_offset ();
byte_size_t end = out_of_bounds_bytes.get_last_byte_offset ();
char start_buf[WIDE_INT_PRINT_BUFFER_SIZE];
print_dec (start, start_buf, SIGNED);
char end_buf[WIDE_INT_PRINT_BUFFER_SIZE];
print_dec (end, end_buf, SIGNED);
if (start == end)
{
if (m_diag_arg)
return ev.formatted_print ("out-of-bounds write at byte %s but %qE"
" ends at byte %E", start_buf, m_diag_arg,
m_byte_bound);
return ev.formatted_print ("out-of-bounds write at byte %s but region"
" ends at byte %E", start_buf,
m_byte_bound);
}
else
{
if (m_diag_arg)
return ev.formatted_print ("out-of-bounds write from byte %s till"
" byte %s but %qE ends at byte %E",
start_buf, end_buf, m_diag_arg,
m_byte_bound);
return ev.formatted_print ("out-of-bounds write from byte %s till"
" byte %s but region ends at byte %E",
start_buf, end_buf, m_byte_bound);
}
}
label_text describe_final_event_as_bits (const evdesc::final_event &ev)
{
bit_size_t start = m_out_of_bounds_bits.get_start_bit_offset ();
bit_size_t end = m_out_of_bounds_bits.get_last_bit_offset ();
char start_buf[WIDE_INT_PRINT_BUFFER_SIZE];
print_dec (start, start_buf, SIGNED);
char end_buf[WIDE_INT_PRINT_BUFFER_SIZE];
print_dec (end, end_buf, SIGNED);
if (start == end)
{
if (m_diag_arg)
return ev.formatted_print ("out-of-bounds write at bit %s but %qE"
" ends at bit %E", start_buf, m_diag_arg,
m_bit_bound);
return ev.formatted_print ("out-of-bounds write at bit %s but region"
" ends at bit %E", start_buf,
m_bit_bound);
}
else
{
if (m_diag_arg)
return ev.formatted_print ("out-of-bounds write from bit %s till"
" bit %s but %qE ends at bit %E",
start_buf, end_buf, m_diag_arg,
m_bit_bound);
return ev.formatted_print ("out-of-bounds write from bit %s till"
" bit %s but region ends at bit %E",
start_buf, end_buf, m_bit_bound);
}
}
enum access_direction get_dir () const final override { return DIR_WRITE; }
};
/* Concrete subclass to complain about buffer over-reads. */
class concrete_buffer_over_read : public concrete_past_the_end
{
public:
concrete_buffer_over_read (const region_model &model,
const region *reg, tree diag_arg,
bit_range range, tree bit_bound)
: concrete_past_the_end (model, reg, diag_arg, range, bit_bound, NULL)
{}
const char *get_kind () const final override
{
return "concrete_buffer_over_read";
}
bool emit (diagnostic_emission_context &ctxt) final override
{
bool warned;
ctxt.add_cwe (126);
switch (get_memory_space ())
{
default:
warned = ctxt.warn ("buffer over-read");
break;
case MEMSPACE_STACK:
warned = ctxt.warn ("stack-based buffer over-read");
break;
case MEMSPACE_HEAP:
warned = ctxt.warn ("heap-based buffer over-read");
break;
}
if (warned)
{
if (wi::fits_uhwi_p (m_out_of_bounds_bits.m_size_in_bits))
{
unsigned HOST_WIDE_INT num_bad_bits
= m_out_of_bounds_bits.m_size_in_bits.to_uhwi ();
if (num_bad_bits % BITS_PER_UNIT == 0)
{
unsigned HOST_WIDE_INT num_bad_bytes
= num_bad_bits / BITS_PER_UNIT;
if (m_diag_arg)
inform_n (ctxt.get_location (),
num_bad_bytes,
"read of %wu byte from after the end of %qE",
"read of %wu bytes from after the end of %qE",
num_bad_bytes,
m_diag_arg);
else
inform_n (ctxt.get_location (),
num_bad_bytes,
"read of %wu byte from after the end of the region",
"read of %wu bytes from after the end of the region",
num_bad_bytes);
}
else
{
if (m_diag_arg)
inform_n (ctxt.get_location (),
num_bad_bits,
"read of %wu bit from after the end of %qE",
"read of %wu bits from after the end of %qE",
num_bad_bits,
m_diag_arg);
else
inform_n (ctxt.get_location (),
num_bad_bits,
"read of %wu bit from after the end of the region",
"read of %wu bits from after the end of the region",
num_bad_bits);
}
}
else if (m_diag_arg)
inform (ctxt.get_location (),
"read from after the end of %qE",
m_diag_arg);
maybe_show_notes (ctxt);
}
return warned;
}
label_text describe_final_event (const evdesc::final_event &ev)
final override
{
if (m_byte_bound || !m_bit_bound)
{
byte_range out_of_bounds_bytes (0, 0);
if (get_out_of_bounds_bytes (&out_of_bounds_bytes))
return describe_final_event_as_bytes (ev, out_of_bounds_bytes);
}
return describe_final_event_as_bits (ev);
}
label_text
describe_final_event_as_bytes (const evdesc::final_event &ev,
const byte_range &out_of_bounds_bytes)
{
byte_size_t start = out_of_bounds_bytes.get_start_byte_offset ();
byte_size_t end = out_of_bounds_bytes.get_last_byte_offset ();
char start_buf[WIDE_INT_PRINT_BUFFER_SIZE];
print_dec (start, start_buf, SIGNED);
char end_buf[WIDE_INT_PRINT_BUFFER_SIZE];
print_dec (end, end_buf, SIGNED);
if (start == end)
{
if (m_diag_arg)
return ev.formatted_print ("out-of-bounds read at byte %s but %qE"
" ends at byte %E", start_buf, m_diag_arg,
m_byte_bound);
return ev.formatted_print ("out-of-bounds read at byte %s but region"
" ends at byte %E", start_buf,
m_byte_bound);
}
else
{
if (m_diag_arg)
return ev.formatted_print ("out-of-bounds read from byte %s till"
" byte %s but %qE ends at byte %E",
start_buf, end_buf, m_diag_arg,
m_byte_bound);
return ev.formatted_print ("out-of-bounds read from byte %s till"
" byte %s but region ends at byte %E",
start_buf, end_buf, m_byte_bound);
}
}
label_text describe_final_event_as_bits (const evdesc::final_event &ev)
{
bit_size_t start = m_out_of_bounds_bits.get_start_bit_offset ();
bit_size_t end = m_out_of_bounds_bits.get_last_bit_offset ();
char start_buf[WIDE_INT_PRINT_BUFFER_SIZE];
print_dec (start, start_buf, SIGNED);
char end_buf[WIDE_INT_PRINT_BUFFER_SIZE];
print_dec (end, end_buf, SIGNED);
if (start == end)
{
if (m_diag_arg)
return ev.formatted_print ("out-of-bounds read at bit %s but %qE"
" ends at bit %E", start_buf, m_diag_arg,
m_bit_bound);
return ev.formatted_print ("out-of-bounds read at bit %s but region"
" ends at bit %E", start_buf,
m_bit_bound);
}
else
{
if (m_diag_arg)
return ev.formatted_print ("out-of-bounds read from bit %s till"
" bit %s but %qE ends at bit %E",
start_buf, end_buf, m_diag_arg,
m_bit_bound);
return ev.formatted_print ("out-of-bounds read from bit %s till"
" bit %s but region ends at bit %E",
start_buf, end_buf, m_bit_bound);
}
}
enum access_direction get_dir () const final override { return DIR_READ; }
};
/* Concrete subclass to complain about buffer underwrites. */
class concrete_buffer_underwrite : public concrete_out_of_bounds
{
public:
concrete_buffer_underwrite (const region_model &model,
const region *reg, tree diag_arg,
bit_range range,
const svalue *sval_hint)
: concrete_out_of_bounds (model, reg, diag_arg, range, sval_hint)
{}
const char *get_kind () const final override
{
return "concrete_buffer_underwrite";
}
bool emit (diagnostic_emission_context &ctxt) final override
{
bool warned;
ctxt.add_cwe (124);
switch (get_memory_space ())
{
default:
warned = ctxt.warn ("buffer underwrite");
break;
case MEMSPACE_STACK:
warned = ctxt.warn ("stack-based buffer underwrite");
break;
case MEMSPACE_HEAP:
warned = ctxt.warn ("heap-based buffer underwrite");
break;
}
if (warned)
maybe_show_notes (ctxt);
return warned;
}
label_text describe_final_event (const evdesc::final_event &ev)
final override
{
byte_range out_of_bounds_bytes (0, 0);
if (get_out_of_bounds_bytes (&out_of_bounds_bytes))
return describe_final_event_as_bytes (ev, out_of_bounds_bytes);
return describe_final_event_as_bits (ev);
}
label_text
describe_final_event_as_bytes (const evdesc::final_event &ev,
const byte_range &out_of_bounds_bytes)
{
byte_size_t start = out_of_bounds_bytes.get_start_byte_offset ();
byte_size_t end = out_of_bounds_bytes.get_last_byte_offset ();
char start_buf[WIDE_INT_PRINT_BUFFER_SIZE];
print_dec (start, start_buf, SIGNED);
char end_buf[WIDE_INT_PRINT_BUFFER_SIZE];
print_dec (end, end_buf, SIGNED);
if (start == end)
{
if (m_diag_arg)
return ev.formatted_print ("out-of-bounds write at byte %s but %qE"
" starts at byte 0",
start_buf, m_diag_arg);
return ev.formatted_print ("out-of-bounds write at byte %s but region"
" starts at byte 0", start_buf);
}
else
{
if (m_diag_arg)
return ev.formatted_print ("out-of-bounds write from byte %s till"
" byte %s but %qE starts at byte 0",
start_buf, end_buf, m_diag_arg);
return ev.formatted_print ("out-of-bounds write from byte %s till"
" byte %s but region starts at byte 0",
start_buf, end_buf);;
}
}
label_text
describe_final_event_as_bits (const evdesc::final_event &ev)
{
bit_size_t start = m_out_of_bounds_bits.get_start_bit_offset ();
bit_size_t end = m_out_of_bounds_bits.get_last_bit_offset ();
char start_buf[WIDE_INT_PRINT_BUFFER_SIZE];
print_dec (start, start_buf, SIGNED);
char end_buf[WIDE_INT_PRINT_BUFFER_SIZE];
print_dec (end, end_buf, SIGNED);
if (start == end)
{
if (m_diag_arg)
return ev.formatted_print ("out-of-bounds write at bit %s but %qE"
" starts at bit 0",
start_buf, m_diag_arg);
return ev.formatted_print ("out-of-bounds write at bit %s but region"
" starts at bit 0", start_buf);
}
else
{
if (m_diag_arg)
return ev.formatted_print ("out-of-bounds write from bit %s till"
" bit %s but %qE starts at bit 0",
start_buf, end_buf, m_diag_arg);
return ev.formatted_print ("out-of-bounds write from bit %s till"
" bit %s but region starts at bit 0",
start_buf, end_buf);;
}
}
enum access_direction get_dir () const final override { return DIR_WRITE; }
};
/* Concrete subclass to complain about buffer under-reads. */
class concrete_buffer_under_read : public concrete_out_of_bounds
{
public:
concrete_buffer_under_read (const region_model &model,
const region *reg, tree diag_arg,
bit_range range)
: concrete_out_of_bounds (model, reg, diag_arg, range, NULL)
{}
const char *get_kind () const final override
{
return "concrete_buffer_under_read";
}
bool emit (diagnostic_emission_context &ctxt) final override
{
bool warned;
ctxt.add_cwe (127);
switch (get_memory_space ())
{
default:
warned = ctxt.warn ("buffer under-read");
break;
case MEMSPACE_STACK:
warned = ctxt.warn ("stack-based buffer under-read");
break;
case MEMSPACE_HEAP:
warned = ctxt.warn ("heap-based buffer under-read");
break;
}
if (warned)
maybe_show_notes (ctxt);
return warned;
}
label_text describe_final_event (const evdesc::final_event &ev)
final override
{
byte_range out_of_bounds_bytes (0, 0);
if (get_out_of_bounds_bytes (&out_of_bounds_bytes))
return describe_final_event_as_bytes (ev, out_of_bounds_bytes);
return describe_final_event_as_bits (ev);
}
label_text
describe_final_event_as_bytes (const evdesc::final_event &ev,
const byte_range &out_of_bounds_bytes)
{
byte_size_t start = out_of_bounds_bytes.get_start_byte_offset ();
byte_size_t end = out_of_bounds_bytes.get_last_byte_offset ();
char start_buf[WIDE_INT_PRINT_BUFFER_SIZE];
print_dec (start, start_buf, SIGNED);
char end_buf[WIDE_INT_PRINT_BUFFER_SIZE];
print_dec (end, end_buf, SIGNED);
if (start == end)
{
if (m_diag_arg)
return ev.formatted_print ("out-of-bounds read at byte %s but %qE"
" starts at byte 0", start_buf,
m_diag_arg);
return ev.formatted_print ("out-of-bounds read at byte %s but region"
" starts at byte 0", start_buf);
}
else
{
if (m_diag_arg)
return ev.formatted_print ("out-of-bounds read from byte %s till"
" byte %s but %qE starts at byte 0",
start_buf, end_buf, m_diag_arg);
return ev.formatted_print ("out-of-bounds read from byte %s till"
" byte %s but region starts at byte 0",
start_buf, end_buf);;
}
}
label_text describe_final_event_as_bits (const evdesc::final_event &ev)
{
bit_size_t start = m_out_of_bounds_bits.get_start_bit_offset ();
bit_size_t end = m_out_of_bounds_bits.get_last_bit_offset ();
char start_buf[WIDE_INT_PRINT_BUFFER_SIZE];
print_dec (start, start_buf, SIGNED);
char end_buf[WIDE_INT_PRINT_BUFFER_SIZE];
print_dec (end, end_buf, SIGNED);
if (start == end)
{
if (m_diag_arg)
return ev.formatted_print ("out-of-bounds read at bit %s but %qE"
" starts at bit 0", start_buf,
m_diag_arg);
return ev.formatted_print ("out-of-bounds read at bit %s but region"
" starts at bit 0", start_buf);
}
else
{
if (m_diag_arg)
return ev.formatted_print ("out-of-bounds read from bit %s till"
" bit %s but %qE starts at bit 0",
start_buf, end_buf, m_diag_arg);
return ev.formatted_print ("out-of-bounds read from bit %s till"
" bit %s but region starts at bit 0",
start_buf, end_buf);;
}
}
enum access_direction get_dir () const final override { return DIR_READ; }
};
/* Abstract class to complain about out-of-bounds read/writes where
the values are symbolic. */
class symbolic_past_the_end : public out_of_bounds
{
public:
symbolic_past_the_end (const region_model &model,
const region *reg, tree diag_arg, tree offset,
tree num_bytes, tree capacity,
const svalue *sval_hint)
: out_of_bounds (model, reg, diag_arg, sval_hint),
m_offset (offset),
m_num_bytes (num_bytes),
m_capacity (capacity)
{}
bool
subclass_equal_p (const pending_diagnostic &base_other) const final override
{
const symbolic_past_the_end &other
(static_cast <const symbolic_past_the_end &>(base_other));
return (out_of_bounds::subclass_equal_p (other)
&& pending_diagnostic::same_tree_p (m_offset, other.m_offset)
&& pending_diagnostic::same_tree_p (m_num_bytes, other.m_num_bytes)
&& pending_diagnostic::same_tree_p (m_capacity, other.m_capacity));
}
void maybe_add_sarif_properties (sarif_object &result_obj)
const final override
{
out_of_bounds::maybe_add_sarif_properties (result_obj);
sarif_property_bag &props = result_obj.get_or_create_properties ();
#define PROPERTY_PREFIX "gcc/analyzer/symbolic_past_the_end/"
props.set (PROPERTY_PREFIX "offset", tree_to_json (m_offset));
props.set (PROPERTY_PREFIX "num_bytes", tree_to_json (m_num_bytes));
props.set (PROPERTY_PREFIX "capacity", tree_to_json (m_capacity));
#undef PROPERTY_PREFIX
}
protected:
tree m_offset;
tree m_num_bytes;
tree m_capacity;
};
/* Concrete subclass to complain about overflows with symbolic values. */
class symbolic_buffer_overflow : public symbolic_past_the_end
{
public:
symbolic_buffer_overflow (const region_model &model,
const region *reg, tree diag_arg, tree offset,
tree num_bytes, tree capacity,
const svalue *sval_hint)
: symbolic_past_the_end (model, reg, diag_arg, offset, num_bytes, capacity,
sval_hint)
{
}
const char *get_kind () const final override
{
return "symbolic_buffer_overflow";
}
bool emit (diagnostic_emission_context &ctxt) final override
{
bool warned;
switch (get_memory_space ())
{
default:
ctxt.add_cwe (787);
warned = ctxt.warn ("buffer overflow");
break;
case MEMSPACE_STACK:
ctxt.add_cwe (121);
warned = ctxt.warn ("stack-based buffer overflow");
break;
case MEMSPACE_HEAP:
ctxt.add_cwe (122);
warned = ctxt.warn ("heap-based buffer overflow");
break;
}
if (warned)
maybe_show_notes (ctxt);
return warned;
}
label_text
describe_final_event (const evdesc::final_event &ev) final override
{
if (m_offset)
{
/* Known offset. */
if (m_num_bytes)
{
/* Known offset, known size. */
if (TREE_CODE (m_num_bytes) == INTEGER_CST)
{
/* Known offset, known constant size. */
if (pending_diagnostic::same_tree_p (m_num_bytes,
integer_one_node))
{
/* Singular m_num_bytes. */
if (m_diag_arg)
return ev.formatted_print
("write of %E byte at offset %qE exceeds %qE",
m_num_bytes, m_offset, m_diag_arg);
else
return ev.formatted_print
("write of %E byte at offset %qE exceeds the buffer",
m_num_bytes, m_offset);
}
else
{
/* Plural m_num_bytes. */
if (m_diag_arg)
return ev.formatted_print
("write of %E bytes at offset %qE exceeds %qE",
m_num_bytes, m_offset, m_diag_arg);
else
return ev.formatted_print
("write of %E bytes at offset %qE exceeds the buffer",
m_num_bytes, m_offset);
}
}
else
{
/* Known offset, known symbolic size. */
if (m_diag_arg)
return ev.formatted_print
("write of %qE bytes at offset %qE exceeds %qE",
m_num_bytes, m_offset, m_diag_arg);
else
return ev.formatted_print
("write of %qE bytes at offset %qE exceeds the buffer",
m_num_bytes, m_offset);
}
}
else
{
/* Known offset, unknown size. */
if (m_diag_arg)
return ev.formatted_print ("write at offset %qE exceeds %qE",
m_offset, m_diag_arg);
else
return ev.formatted_print ("write at offset %qE exceeds the"
" buffer", m_offset);
}
}
/* Unknown offset. */
if (m_diag_arg)
return ev.formatted_print ("out-of-bounds write on %qE",
m_diag_arg);
return ev.formatted_print ("out-of-bounds write");
}
enum access_direction get_dir () const final override { return DIR_WRITE; }
};
/* Concrete subclass to complain about over-reads with symbolic values. */
class symbolic_buffer_over_read : public symbolic_past_the_end
{
public:
symbolic_buffer_over_read (const region_model &model,
const region *reg, tree diag_arg, tree offset,
tree num_bytes, tree capacity)
: symbolic_past_the_end (model, reg, diag_arg, offset, num_bytes, capacity,
NULL)
{
}
const char *get_kind () const final override
{
return "symbolic_buffer_over_read";
}
bool emit (diagnostic_emission_context &ctxt) final override
{
ctxt.add_cwe (126);
bool warned;
switch (get_memory_space ())
{
default:
ctxt.add_cwe (787);
warned = ctxt.warn ("buffer over-read");
break;
case MEMSPACE_STACK:
ctxt.add_cwe (121);
warned = ctxt.warn ("stack-based buffer over-read");
break;
case MEMSPACE_HEAP:
ctxt.add_cwe (122);
warned = ctxt.warn ("heap-based buffer over-read");
break;
}
if (warned)
maybe_show_notes (ctxt);
return warned;
}
label_text
describe_final_event (const evdesc::final_event &ev) final override
{
if (m_offset)
{
/* Known offset. */
if (m_num_bytes)
{
/* Known offset, known size. */
if (TREE_CODE (m_num_bytes) == INTEGER_CST)
{
/* Known offset, known constant size. */
if (pending_diagnostic::same_tree_p (m_num_bytes,
integer_one_node))
{
/* Singular m_num_bytes. */
if (m_diag_arg)
return ev.formatted_print
("read of %E byte at offset %qE exceeds %qE",
m_num_bytes, m_offset, m_diag_arg);
else
return ev.formatted_print
("read of %E byte at offset %qE exceeds the buffer",
m_num_bytes, m_offset);
}
else
{
/* Plural m_num_bytes. */
if (m_diag_arg)
return ev.formatted_print
("read of %E bytes at offset %qE exceeds %qE",
m_num_bytes, m_offset, m_diag_arg);
else
return ev.formatted_print
("read of %E bytes at offset %qE exceeds the buffer",
m_num_bytes, m_offset);
}
}
else
{
/* Known offset, known symbolic size. */
if (m_diag_arg)
return ev.formatted_print
("read of %qE bytes at offset %qE exceeds %qE",
m_num_bytes, m_offset, m_diag_arg);
else
return ev.formatted_print
("read of %qE bytes at offset %qE exceeds the buffer",
m_num_bytes, m_offset);
}
}
else
{
/* Known offset, unknown size. */
if (m_diag_arg)
return ev.formatted_print ("read at offset %qE exceeds %qE",
m_offset, m_diag_arg);
else
return ev.formatted_print ("read at offset %qE exceeds the"
" buffer", m_offset);
}
}
/* Unknown offset. */
if (m_diag_arg)
return ev.formatted_print ("out-of-bounds read on %qE",
m_diag_arg);
return ev.formatted_print ("out-of-bounds read");
}
enum access_direction get_dir () const final override { return DIR_READ; }
};
const svalue *
strip_types (const svalue *sval,
region_model_manager &mgr)
{
switch (sval->get_kind ())
{
default:
gcc_unreachable ();
case SK_REGION:
{
const region_svalue *region_sval = (const region_svalue *)sval;
return mgr.get_ptr_svalue (NULL_TREE, region_sval->get_pointee ());
}
case SK_CONSTANT:
return sval;
case SK_UNKNOWN:
return mgr.get_or_create_unknown_svalue (NULL_TREE);
case SK_POISONED:
{
const poisoned_svalue *poisoned_sval = (const poisoned_svalue *)sval;
return mgr.get_or_create_poisoned_svalue
(poisoned_sval->get_poison_kind (),
NULL_TREE);
}
case SK_SETJMP:
return sval;
case SK_INITIAL:
return sval;
case SK_UNARYOP:
{
const unaryop_svalue *unaryop_sval = (const unaryop_svalue *)sval;
const enum tree_code op = unaryop_sval->get_op ();
if (op == VIEW_CONVERT_EXPR || op == NOP_EXPR)
return strip_types (unaryop_sval->get_arg (), mgr);
return mgr.get_or_create_unaryop
(NULL_TREE,
op,
strip_types (unaryop_sval->get_arg (), mgr));
}
case SK_BINOP:
{
const binop_svalue *binop_sval = (const binop_svalue *)sval;
const enum tree_code op = binop_sval->get_op ();
return mgr.get_or_create_binop
(NULL_TREE,
op,
strip_types (binop_sval->get_arg0 (), mgr),
strip_types (binop_sval->get_arg1 (), mgr));
}
case SK_SUB:
{
const sub_svalue *sub_sval = (const sub_svalue *)sval;
return mgr.get_or_create_sub_svalue
(NULL_TREE,
strip_types (sub_sval->get_parent (), mgr),
sub_sval->get_subregion ());
}
case SK_REPEATED:
{
const repeated_svalue *repeated_sval = (const repeated_svalue *)sval;
return mgr.get_or_create_repeated_svalue
(NULL_TREE,
strip_types (repeated_sval->get_outer_size (), mgr),
strip_types (repeated_sval->get_inner_svalue (), mgr));
}
case SK_BITS_WITHIN:
{
const bits_within_svalue *bits_within_sval
= (const bits_within_svalue *)sval;
return mgr.get_or_create_bits_within
(NULL_TREE,
bits_within_sval->get_bits (),
strip_types (bits_within_sval->get_inner_svalue (), mgr));
}
case SK_UNMERGEABLE:
{
const unmergeable_svalue *unmergeable_sval
= (const unmergeable_svalue *)sval;
return mgr.get_or_create_unmergeable
(strip_types (unmergeable_sval->get_arg (), mgr));
}
case SK_PLACEHOLDER:
return sval;
case SK_WIDENING:
{
const widening_svalue *widening_sval = (const widening_svalue *)sval;
return mgr.get_or_create_widening_svalue
(NULL_TREE,
widening_sval->get_point (),
strip_types (widening_sval->get_base_svalue (), mgr),
strip_types (widening_sval->get_iter_svalue (), mgr));
}
case SK_COMPOUND:
{
const compound_svalue *compound_sval = (const compound_svalue *)sval;
binding_map typeless_map;
for (auto iter : compound_sval->get_map ())
{
const binding_key *key = iter.first;
const svalue *bound_sval = iter.second;
typeless_map.put (key, strip_types (bound_sval, mgr));
}
return mgr.get_or_create_compound_svalue (NULL_TREE, typeless_map);
}
case SK_CONJURED:
return sval;
case SK_ASM_OUTPUT:
{
const asm_output_svalue *asm_output_sval
= (const asm_output_svalue *)sval;
auto_vec<const svalue *> typeless_inputs
(asm_output_sval->get_num_inputs ());
for (unsigned idx = 0; idx < asm_output_sval->get_num_inputs (); idx++)
typeless_inputs.quick_push
(strip_types (asm_output_sval->get_input (idx),
mgr));
return mgr.get_or_create_asm_output_svalue
(NULL_TREE,
asm_output_sval->get_asm_string (),
asm_output_sval->get_output_idx (),
asm_output_sval->get_num_outputs (),
typeless_inputs);
}
case SK_CONST_FN_RESULT:
{
const const_fn_result_svalue *const_fn_result_sval
= (const const_fn_result_svalue *)sval;
auto_vec<const svalue *> typeless_inputs
(const_fn_result_sval->get_num_inputs ());
for (unsigned idx = 0;
idx < const_fn_result_sval->get_num_inputs ();
idx++)
typeless_inputs.quick_push
(strip_types (const_fn_result_sval->get_input (idx),
mgr));
return mgr.get_or_create_const_fn_result_svalue
(NULL_TREE,
const_fn_result_sval->get_fndecl (),
typeless_inputs);
}
}
}
/* Check whether an access is past the end of the BASE_REG.
Return TRUE if the access was valid, FALSE otherwise. */
bool
region_model::check_symbolic_bounds (const region *base_reg,
const svalue *sym_byte_offset,
const svalue *num_bytes_sval,
const svalue *capacity,
enum access_direction dir,
const svalue *sval_hint,
region_model_context *ctxt) const
{
gcc_assert (ctxt);
const svalue *next_byte
= m_mgr->get_or_create_binop (NULL_TREE, PLUS_EXPR,
sym_byte_offset, num_bytes_sval);
next_byte = strip_types (next_byte, *m_mgr);
capacity = strip_types (capacity, *m_mgr);
if (eval_condition (next_byte, GT_EXPR, capacity).is_true ())
{
tree diag_arg = get_representative_tree (base_reg);
tree offset_tree = get_representative_tree (sym_byte_offset);
tree num_bytes_tree = get_representative_tree (num_bytes_sval);
tree capacity_tree = get_representative_tree (capacity);
const region *offset_reg = m_mgr->get_offset_region (base_reg,
NULL_TREE,
sym_byte_offset);
const region *sized_offset_reg = m_mgr->get_sized_region (offset_reg,
NULL_TREE,
num_bytes_sval);
switch (dir)
{
default:
gcc_unreachable ();
break;
case DIR_READ:
gcc_assert (sval_hint == nullptr);
ctxt->warn (make_unique<symbolic_buffer_over_read> (*this,
sized_offset_reg,
diag_arg,
offset_tree,
num_bytes_tree,
capacity_tree));
return false;
break;
case DIR_WRITE:
ctxt->warn (make_unique<symbolic_buffer_overflow> (*this,
sized_offset_reg,
diag_arg,
offset_tree,
num_bytes_tree,
capacity_tree,
sval_hint));
return false;
break;
}
}
return true;
}
static tree
maybe_get_integer_cst_tree (const svalue *sval)
{
tree cst_tree = sval->maybe_get_constant ();
if (cst_tree && TREE_CODE (cst_tree) == INTEGER_CST)
return cst_tree;
return NULL_TREE;
}
/* May complain when the access on REG is out-of-bounds.
Return TRUE if the access was valid, FALSE otherwise. */
bool
region_model::check_region_bounds (const region *reg,
enum access_direction dir,
const svalue *sval_hint,
region_model_context *ctxt) const
{
gcc_assert (ctxt);
/* Get the offset. */
region_offset reg_offset = reg->get_offset (m_mgr);
const region *base_reg = reg_offset.get_base_region ();
/* Find out how many bits were accessed. */
const svalue *num_bits_sval = reg->get_bit_size_sval (m_mgr);
tree num_bits_tree = maybe_get_integer_cst_tree (num_bits_sval);
/* Bail out if 0 bits are accessed. */
if (num_bits_tree && zerop (num_bits_tree))
return true;
/* Get the capacity of the buffer (in bytes). */
const svalue *byte_capacity = get_capacity (base_reg);
tree cst_byte_capacity_tree = maybe_get_integer_cst_tree (byte_capacity);
/* The constant offset from a pointer is represented internally as a sizetype
but should be interpreted as a signed value here. The statement below
converts the offset from bits to bytes and then to a signed integer with
the same precision the sizetype has on the target system.
For example, this is needed for out-of-bounds-3.c test1 to pass when
compiled with a 64-bit gcc build targeting 32-bit systems. */
bit_offset_t bit_offset;
if (!reg_offset.symbolic_p ())
bit_offset = wi::sext (reg_offset.get_bit_offset (),
TYPE_PRECISION (size_type_node));
/* If any of the base region, the offset, or the number of bytes accessed
are symbolic, we have to reason about symbolic values. */
if (base_reg->symbolic_p () || reg_offset.symbolic_p () || !num_bits_tree)
{
const svalue* byte_offset_sval;
if (!reg_offset.symbolic_p ())
{
tree byte_offset_tree
= wide_int_to_tree (integer_type_node,
bit_offset >> LOG2_BITS_PER_UNIT);
byte_offset_sval
= m_mgr->get_or_create_constant_svalue (byte_offset_tree);
}
else
byte_offset_sval = reg_offset.get_symbolic_byte_offset ();
const svalue *num_bytes_sval = reg->get_byte_size_sval (m_mgr);
return check_symbolic_bounds (base_reg, byte_offset_sval, num_bytes_sval,
byte_capacity, dir, sval_hint, ctxt);
}
/* Otherwise continue to check with concrete values. */
bit_range bits_outside (0, 0);
bool oob_safe = true;
/* NUM_BITS_TREE should always be interpreted as unsigned. */
bit_offset_t num_bits_unsigned = wi::to_offset (num_bits_tree);
bit_range read_bits (bit_offset, num_bits_unsigned);
/* If read_bits has a subset < 0, we do have an underwrite. */
if (read_bits.falls_short_of_p (0, &bits_outside))
{
tree diag_arg = get_representative_tree (base_reg);
switch (dir)
{
default:
gcc_unreachable ();
break;
case DIR_READ:
gcc_assert (sval_hint == nullptr);
ctxt->warn (make_unique<concrete_buffer_under_read> (*this, reg,
diag_arg,
bits_outside));
oob_safe = false;
break;
case DIR_WRITE:
ctxt->warn (make_unique<concrete_buffer_underwrite> (*this,
reg, diag_arg,
bits_outside,
sval_hint));
oob_safe = false;
break;
}
}
/* For accesses past the end, we do need a concrete capacity. No need to
do a symbolic check here because the inequality check does not reason
whether constants are greater than symbolic values. */
if (!cst_byte_capacity_tree)
return oob_safe;
bit_range buffer (0, wi::to_offset (cst_byte_capacity_tree) * BITS_PER_UNIT);
/* If READ_BITS exceeds BUFFER, we do have an overflow. */
if (read_bits.exceeds_p (buffer, &bits_outside))
{
tree bit_bound = wide_int_to_tree (size_type_node,
buffer.get_next_bit_offset ());
tree diag_arg = get_representative_tree (base_reg);
switch (dir)
{
default:
gcc_unreachable ();
break;
case DIR_READ:
gcc_assert (sval_hint == nullptr);
ctxt->warn (make_unique<concrete_buffer_over_read> (*this,
reg, diag_arg,
bits_outside,
bit_bound));
oob_safe = false;
break;
case DIR_WRITE:
ctxt->warn (make_unique<concrete_buffer_overflow> (*this,
reg, diag_arg,
bits_outside,
bit_bound,
sval_hint));
oob_safe = false;
break;
}
}
return oob_safe;
}
} // namespace ana
#endif /* #if ENABLE_ANALYZER */