| /* Bounds-checking of reads and writes to memory regions. |
| Copyright (C) 2019-2023 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-metadata.h" |
| #include "diagnostic-diagram.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 capacity, |
| const event_loc_info &loc_info, |
| out_of_bounds &oob) |
| : region_creation_event_capacity (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 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 (capacity) |
| emission_path.add_event |
| (make_unique<oob_region_creation_event_capacity> (capacity, loc_info, |
| *this)); |
| } |
| |
| 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 (location_t loc, logger *logger) const |
| { |
| maybe_describe_array_bounds (loc); |
| maybe_show_diagram (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->m_diagrams.m_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")); |
| diagnostic_emit_diagram (global_dc, 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, |
| byte_range out_of_bounds_range, |
| const svalue *sval_hint) |
| : out_of_bounds (model, reg, diag_arg, sval_hint), |
| m_out_of_bounds_range (out_of_bounds_range) |
| {} |
| |
| 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_range == other.m_out_of_bounds_range); |
| } |
| |
| protected: |
| byte_range m_out_of_bounds_range; |
| }; |
| |
| /* 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, byte_range range, |
| tree byte_bound, |
| const svalue *sval_hint) |
| : concrete_out_of_bounds (model, reg, diag_arg, range, sval_hint), |
| m_byte_bound (byte_bound) |
| {} |
| |
| 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_byte_bound, |
| other.m_byte_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)); |
| } |
| |
| protected: |
| 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, |
| byte_range range, tree byte_bound, |
| const svalue *sval_hint) |
| : concrete_past_the_end (model, reg, diag_arg, range, byte_bound, sval_hint) |
| {} |
| |
| const char *get_kind () const final override |
| { |
| return "concrete_buffer_overflow"; |
| } |
| |
| bool emit (rich_location *rich_loc, |
| logger *logger) final override |
| { |
| diagnostic_metadata m; |
| bool warned; |
| switch (get_memory_space ()) |
| { |
| default: |
| m.add_cwe (787); |
| warned = warning_meta (rich_loc, m, get_controlling_option (), |
| "buffer overflow"); |
| break; |
| case MEMSPACE_STACK: |
| m.add_cwe (121); |
| warned = warning_meta (rich_loc, m, get_controlling_option (), |
| "stack-based buffer overflow"); |
| break; |
| case MEMSPACE_HEAP: |
| m.add_cwe (122); |
| warned = warning_meta (rich_loc, m, get_controlling_option (), |
| "heap-based buffer overflow"); |
| break; |
| } |
| |
| if (warned) |
| { |
| if (wi::fits_uhwi_p (m_out_of_bounds_range.m_size_in_bytes)) |
| { |
| unsigned HOST_WIDE_INT num_bad_bytes |
| = m_out_of_bounds_range.m_size_in_bytes.to_uhwi (); |
| if (m_diag_arg) |
| inform_n (rich_loc->get_loc (), |
| 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 (rich_loc->get_loc (), |
| 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 (rich_loc->get_loc (), |
| "write to beyond the end of %qE", |
| m_diag_arg); |
| |
| maybe_show_notes (rich_loc->get_loc (), logger); |
| } |
| |
| return warned; |
| } |
| |
| label_text describe_final_event (const evdesc::final_event &ev) |
| final override |
| { |
| byte_size_t start = m_out_of_bounds_range.get_start_byte_offset (); |
| byte_size_t end = m_out_of_bounds_range.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); |
| } |
| } |
| |
| 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, |
| byte_range range, tree byte_bound) |
| : concrete_past_the_end (model, reg, diag_arg, range, byte_bound, NULL) |
| {} |
| |
| const char *get_kind () const final override |
| { |
| return "concrete_buffer_over_read"; |
| } |
| |
| bool emit (rich_location *rich_loc, logger *logger) final override |
| { |
| diagnostic_metadata m; |
| bool warned; |
| m.add_cwe (126); |
| switch (get_memory_space ()) |
| { |
| default: |
| warned = warning_meta (rich_loc, m, get_controlling_option (), |
| "buffer over-read"); |
| break; |
| case MEMSPACE_STACK: |
| warned = warning_meta (rich_loc, m, get_controlling_option (), |
| "stack-based buffer over-read"); |
| break; |
| case MEMSPACE_HEAP: |
| warned = warning_meta (rich_loc, m, get_controlling_option (), |
| "heap-based buffer over-read"); |
| break; |
| } |
| |
| if (warned) |
| { |
| if (wi::fits_uhwi_p (m_out_of_bounds_range.m_size_in_bytes)) |
| { |
| unsigned HOST_WIDE_INT num_bad_bytes |
| = m_out_of_bounds_range.m_size_in_bytes.to_uhwi (); |
| if (m_diag_arg) |
| inform_n (rich_loc->get_loc (), |
| 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 (rich_loc->get_loc (), |
| 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 (rich_loc->get_loc (), |
| "read from after the end of %qE", |
| m_diag_arg); |
| |
| maybe_show_notes (rich_loc->get_loc (), logger); |
| } |
| |
| return warned; |
| } |
| |
| label_text describe_final_event (const evdesc::final_event &ev) |
| final override |
| { |
| byte_size_t start = m_out_of_bounds_range.get_start_byte_offset (); |
| byte_size_t end = m_out_of_bounds_range.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); |
| } |
| } |
| |
| 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, |
| byte_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 (rich_location *rich_loc, logger *logger) final override |
| { |
| diagnostic_metadata m; |
| bool warned; |
| m.add_cwe (124); |
| switch (get_memory_space ()) |
| { |
| default: |
| warned = warning_meta (rich_loc, m, get_controlling_option (), |
| "buffer underwrite"); |
| break; |
| case MEMSPACE_STACK: |
| warned = warning_meta (rich_loc, m, get_controlling_option (), |
| "stack-based buffer underwrite"); |
| break; |
| case MEMSPACE_HEAP: |
| warned = warning_meta (rich_loc, m, get_controlling_option (), |
| "heap-based buffer underwrite"); |
| break; |
| } |
| if (warned) |
| maybe_show_notes (rich_loc->get_loc (), logger); |
| return warned; |
| } |
| |
| label_text describe_final_event (const evdesc::final_event &ev) |
| final override |
| { |
| byte_size_t start = m_out_of_bounds_range.get_start_byte_offset (); |
| byte_size_t end = m_out_of_bounds_range.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);; |
| } |
| } |
| |
| 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, |
| byte_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 (rich_location *rich_loc, logger *logger) final override |
| { |
| diagnostic_metadata m; |
| bool warned; |
| m.add_cwe (127); |
| switch (get_memory_space ()) |
| { |
| default: |
| warned = warning_meta (rich_loc, m, get_controlling_option (), |
| "buffer under-read"); |
| break; |
| case MEMSPACE_STACK: |
| warned = warning_meta (rich_loc, m, get_controlling_option (), |
| "stack-based buffer under-read"); |
| break; |
| case MEMSPACE_HEAP: |
| warned = warning_meta (rich_loc, m, get_controlling_option (), |
| "heap-based buffer under-read"); |
| break; |
| } |
| if (warned) |
| maybe_show_notes (rich_loc->get_loc (), logger); |
| return warned; |
| } |
| |
| label_text describe_final_event (const evdesc::final_event &ev) |
| final override |
| { |
| byte_size_t start = m_out_of_bounds_range.get_start_byte_offset (); |
| byte_size_t end = m_out_of_bounds_range.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);; |
| } |
| } |
| |
| 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)); |
| } |
| |
| 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 (rich_location *rich_loc, logger *logger) final override |
| { |
| diagnostic_metadata m; |
| bool warned; |
| switch (get_memory_space ()) |
| { |
| default: |
| m.add_cwe (787); |
| warned = warning_meta (rich_loc, m, get_controlling_option (), |
| "buffer overflow"); |
| break; |
| case MEMSPACE_STACK: |
| m.add_cwe (121); |
| warned = warning_meta (rich_loc, m, get_controlling_option (), |
| "stack-based buffer overflow"); |
| break; |
| case MEMSPACE_HEAP: |
| m.add_cwe (122); |
| warned = warning_meta (rich_loc, m, get_controlling_option (), |
| "heap-based buffer overflow"); |
| break; |
| } |
| if (warned) |
| maybe_show_notes (rich_loc->get_loc (), logger); |
| 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 (rich_location *rich_loc, logger *logger) final override |
| { |
| diagnostic_metadata m; |
| m.add_cwe (126); |
| bool warned; |
| switch (get_memory_space ()) |
| { |
| default: |
| m.add_cwe (787); |
| warned = warning_meta (rich_loc, m, get_controlling_option (), |
| "buffer over-read"); |
| break; |
| case MEMSPACE_STACK: |
| m.add_cwe (121); |
| warned = warning_meta (rich_loc, m, get_controlling_option (), |
| "stack-based buffer over-read"); |
| break; |
| case MEMSPACE_HEAP: |
| m.add_cwe (122); |
| warned = warning_meta (rich_loc, m, get_controlling_option (), |
| "heap-based buffer over-read"); |
| break; |
| } |
| if (warned) |
| maybe_show_notes (rich_loc->get_loc (), logger); |
| 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; } |
| }; |
| |
| /* 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 (num_bytes_sval->get_type (), PLUS_EXPR, |
| sym_byte_offset, num_bytes_sval); |
| |
| 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 (); |
| |
| /* Bail out on symbolic regions. |
| (e.g. because the analyzer did not see previous offsets on the latter, |
| it might think that a negative access is before the buffer). */ |
| if (base_reg->symbolic_p ()) |
| return true; |
| |
| /* Find out how many bytes were accessed. */ |
| const svalue *num_bytes_sval = reg->get_byte_size_sval (m_mgr); |
| tree num_bytes_tree = maybe_get_integer_cst_tree (num_bytes_sval); |
| /* Bail out if 0 bytes are accessed. */ |
| if (num_bytes_tree && zerop (num_bytes_tree)) |
| return true; |
| |
| /* Get the capacity of the buffer. */ |
| const svalue *capacity = get_capacity (base_reg); |
| tree cst_capacity_tree = maybe_get_integer_cst_tree (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. */ |
| byte_offset_t offset; |
| if (!reg_offset.symbolic_p ()) |
| offset = wi::sext (reg_offset.get_bit_offset () >> LOG2_BITS_PER_UNIT, |
| TYPE_PRECISION (size_type_node)); |
| |
| /* If either the offset or the number of bytes accessed are symbolic, |
| we have to reason about symbolic values. */ |
| if (reg_offset.symbolic_p () || !num_bytes_tree) |
| { |
| const svalue* byte_offset_sval; |
| if (!reg_offset.symbolic_p ()) |
| { |
| tree offset_tree = wide_int_to_tree (integer_type_node, offset); |
| byte_offset_sval |
| = m_mgr->get_or_create_constant_svalue (offset_tree); |
| } |
| else |
| byte_offset_sval = reg_offset.get_symbolic_byte_offset (); |
| return check_symbolic_bounds (base_reg, byte_offset_sval, num_bytes_sval, |
| capacity, dir, sval_hint, ctxt); |
| } |
| |
| /* Otherwise continue to check with concrete values. */ |
| byte_range out (0, 0); |
| bool oob_safe = true; |
| /* NUM_BYTES_TREE should always be interpreted as unsigned. */ |
| byte_offset_t num_bytes_unsigned = wi::to_offset (num_bytes_tree); |
| byte_range read_bytes (offset, num_bytes_unsigned); |
| /* If read_bytes has a subset < 0, we do have an underwrite. */ |
| if (read_bytes.falls_short_of_p (0, &out)) |
| { |
| 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, |
| out)); |
| oob_safe = false; |
| break; |
| case DIR_WRITE: |
| ctxt->warn (make_unique<concrete_buffer_underwrite> (*this, |
| reg, diag_arg, |
| out, |
| 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_capacity_tree) |
| return oob_safe; |
| |
| byte_range buffer (0, wi::to_offset (cst_capacity_tree)); |
| /* If READ_BYTES exceeds BUFFER, we do have an overflow. */ |
| if (read_bytes.exceeds_p (buffer, &out)) |
| { |
| tree byte_bound = wide_int_to_tree (size_type_node, |
| buffer.get_next_byte_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, |
| out, byte_bound)); |
| oob_safe = false; |
| break; |
| case DIR_WRITE: |
| ctxt->warn (make_unique<concrete_buffer_overflow> (*this, |
| reg, diag_arg, |
| out, byte_bound, |
| sval_hint)); |
| oob_safe = false; |
| break; |
| } |
| } |
| return oob_safe; |
| } |
| |
| } // namespace ana |
| |
| #endif /* #if ENABLE_ANALYZER */ |