analyzer: use bit-level granularity for concrete bounds-checking [PR112792]
commit5f1bed2a7af828103ca23a3546466a23e8dd2f30
authorDavid Malcolm <dmalcolm@redhat.com>
Sat, 16 Dec 2023 14:03:16 +0000 (16 09:03 -0500)
committerDavid Malcolm <dmalcolm@redhat.com>
Sat, 16 Dec 2023 14:03:16 +0000 (16 09:03 -0500)
treed2f9b1577658ce4308dee09e3b702526eabdca34
parent9a1105b770df9a9b485705398abbb74b5c487a25
analyzer: use bit-level granularity for concrete bounds-checking [PR112792]

PR analyzer/112792 reports false positives from -fanalyzer's
bounds-checking on certain packed structs containing bitfields e.g.
in the Linux kernel's drivers/dma/idxd/device.c:

union msix_perm {
  struct {
    u32 rsvd2 : 8;
    u32 pasid : 20;
  };
  u32 bits;
} __attribute__((__packed__));

The root cause is that the bounds-checking is done using byte offsets
and ranges; in the above, an access of "pasid" is treated as a 32-bit
access starting one byte inside the union, thus accessing byte offsets
1-4 when only offsets 0-3 are valid.

This patch updates the bounds-checking to use bit offsets and ranges
wherever possible - for concrete offsets and capacities.  In the above
accessing "pasid" is treated as bits 8-27 of a 32-bit region, fixing the
false positive.

Symbolic offsets and ranges are still handled at byte granularity.

gcc/analyzer/ChangeLog:
PR analyzer/112792
* bounds-checking.cc
(out_of_bounds::oob_region_creation_event_capacity): Rename
"capacity" to "byte_capacity".  Layout fix.
(out_of_bounds::::add_region_creation_events): Rename
"capacity" to "byte_capacity".
(class concrete_out_of_bounds): Rename m_out_of_bounds_range to
m_out_of_bounds_bits and convert from a byte_range to a bit_range.
(concrete_out_of_bounds::get_out_of_bounds_bytes): New.
(concrete_past_the_end::concrete_past_the_end): Rename param
"byte_bound" to "bit_bound".  Initialize m_byte_bound.
(concrete_past_the_end::subclass_equal_p): Update for renaming
of m_byte_bound to m_bit_bound.
(concrete_past_the_end::m_bit_bound): New field.
(concrete_buffer_overflow::concrete_buffer_overflow): Convert
param "range" from byte_range to bit_range.  Rename param
"byte_bound" to "bit_bound".
(concrete_buffer_overflow::emit): Update for bits vs bytes.
(concrete_buffer_overflow::describe_final_event): Split
into...
(concrete_buffer_overflow::describe_final_event_as_bytes): ...this
(concrete_buffer_overflow::describe_final_event_as_bits): ...and
this.
(concrete_buffer_over_read::concrete_buffer_over_read): Convert
param "range" from byte_range to bit_range.  Rename param
"byte_bound" to "bit_bound".
(concrete_buffer_over_read::emit): Update for bits vs bytes.
(concrete_buffer_over_read::describe_final_event): Split into...
(concrete_buffer_over_read::describe_final_event_as_bytes):
...this
(concrete_buffer_over_read::describe_final_event_as_bits): ...and
this.
(concrete_buffer_underwrite::concrete_buffer_underwrite): Convert
param "range" from byte_range to bit_range.
(concrete_buffer_underwrite::describe_final_event): Split into...
(concrete_buffer_underwrite::describe_final_event_as_bytes):
...this
(concrete_buffer_underwrite::describe_final_event_as_bits): ...and
this.
(concrete_buffer_under_read::concrete_buffer_under_read): Convert
param "range" from byte_range to bit_range.
(concrete_buffer_under_read::describe_final_event): Split into...
(concrete_buffer_under_read::describe_final_event_as_bytes):
...this
(concrete_buffer_under_read::describe_final_event_as_bits): ...and
this.
(region_model::check_region_bounds): Use bits for concrete values,
and rename locals to indicate whether we're dealing with bits or
bytes.  Specifically, replace "num_bytes_sval" with
"num_bits_sval", and get it from reg's "get_bit_size_sval".
Replace "num_bytes_tree" with "num_bits_tree".  Rename "capacity"
to "byte_capacity".  Rename "cst_capacity_tree" to
"cst_byte_capacity_tree".  Replace "offset" and
"num_bytes_unsigned" with "bit_offset" and "num_bits_unsigned"
respectively, converting from byte_offset_t to bit_offset_t.
Replace "out" and "read_bytes" with "bits_outside" and "read_bits"
respectively, converting from byte_range to bit_range.  Convert
"buffer" from byte_range to bit_range.  Replace "byte_bound" with
"bit_bound".
* region.cc (region::get_bit_size_sval): New.
(offset_region::get_bit_offset): New.
(offset_region::get_bit_size_sval): New.
(sized_region::get_bit_size_sval): New.
(bit_range_region::get_bit_size_sval): New.
* region.h (region::get_bit_size_sval): New vfunc.
(offset_region::get_bit_offset): New decl.
(offset_region::get_bit_size_sval): New decl.
(sized_region::get_bit_size_sval): New decl.
(bit_range_region::get_bit_size_sval): New decl.
* store.cc (bit_range::intersects_p): New, based on
byte_range::intersects_p.
(bit_range::exceeds_p): New, based on byte_range::exceeds_p.
(bit_range::falls_short_of_p): New, based on
byte_range::falls_short_of_p.
(byte_range::intersects_p): Delete.
(byte_range::exceeds_p): Delete.
(byte_range::falls_short_of_p): Delete.
* store.h (bit_range::intersects_p): New overload.
(bit_range::exceeds_p): New.
(bit_range::falls_short_of_p): New.
(byte_range::intersects_p): Delete.
(byte_range::exceeds_p): Delete.
(byte_range::falls_short_of_p): Delete.

gcc/testsuite/ChangeLog:
PR analyzer/112792
* c-c++-common/analyzer/out-of-bounds-pr112792.c: New test.

Signed-off-by: David Malcolm <dmalcolm@redhat.com>
gcc/analyzer/bounds-checking.cc
gcc/analyzer/region.cc
gcc/analyzer/region.h
gcc/analyzer/store.cc
gcc/analyzer/store.h
gcc/testsuite/c-c++-common/analyzer/out-of-bounds-pr112792.c [new file with mode: 0644]