[pushed] analyzer: use bit-level granularity for concrete bounds-checking [PR112792]

Message ID 20231216140544.3424839-1-dmalcolm@redhat.com
State Unresolved
Headers
Series [pushed] analyzer: use bit-level granularity for concrete bounds-checking [PR112792] |

Checks

Context Check Description
snail/gcc-patch-check warning Git am fail log

Commit Message

David Malcolm Dec. 16, 2023, 2:05 p.m. UTC
  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.

Successfully bootstrapped & regrtested on x86_64-pc-linux-gnu.
Successful run of analyzer integration tests on x86_64-pc-linux-gnu.
Pushed to trunk as r14-6622-g5f1bed2a7af828.

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               | 435 ++++++++++++++----
 gcc/analyzer/region.cc                        |  71 +++
 gcc/analyzer/region.h                         |  12 +-
 gcc/analyzer/store.cc                         | 142 +++---
 gcc/analyzer/store.h                          |  17 +-
 .../analyzer/out-of-bounds-pr112792.c         |  18 +
 6 files changed, 512 insertions(+), 183 deletions(-)
 create mode 100644 gcc/testsuite/c-c++-common/analyzer/out-of-bounds-pr112792.c
  

Patch

diff --git a/gcc/analyzer/bounds-checking.cc b/gcc/analyzer/bounds-checking.cc
index cc43ecc54683..7cbfe91515f8 100644
--- a/gcc/analyzer/bounds-checking.cc
+++ b/gcc/analyzer/bounds-checking.cc
@@ -50,12 +50,12 @@  public:
   class oob_region_creation_event_capacity : public region_creation_event_capacity
   {
   public:
-    oob_region_creation_event_capacity (tree capacity,
+    oob_region_creation_event_capacity (tree byte_capacity,
 					const event_loc_info &loc_info,
 					out_of_bounds &oob)
-      : region_creation_event_capacity (capacity,
-					loc_info),
-	m_oob (oob)
+    : region_creation_event_capacity (byte_capacity,
+				      loc_info),
+      m_oob (oob)
     {
     }
     void prepare_for_emission (checker_path *path,
@@ -67,7 +67,7 @@  public:
 							    emission_id);
       m_oob.m_region_creation_event_id = emission_id;
     }
-    private:
+  private:
     out_of_bounds &m_oob;
   };
 
@@ -97,15 +97,16 @@  public:
   }
 
   void add_region_creation_events (const region *,
-				   tree capacity,
+				   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 (capacity)
+    if (byte_capacity)
       emission_path.add_event
-	(make_unique<oob_region_creation_event_capacity> (capacity, loc_info,
+	(make_unique<oob_region_creation_event_capacity> (byte_capacity,
+							  loc_info,
 							  *this));
   }
 
@@ -205,10 +206,10 @@  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,
+			  bit_range out_of_bounds_bits,
 			  const svalue *sval_hint)
   : out_of_bounds (model, reg, diag_arg, sval_hint),
-    m_out_of_bounds_range (out_of_bounds_range)
+    m_out_of_bounds_bits (out_of_bounds_bits)
   {}
 
   bool subclass_equal_p (const pending_diagnostic &base_other) const override
@@ -216,11 +217,16 @@  public:
     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);
+	    && m_out_of_bounds_bits == other.m_out_of_bounds_bits);
+  }
+
+  bool get_out_of_bounds_bytes (byte_range *out) const
+  {
+    return m_out_of_bounds_bits.as_byte_range (out);
   }
 
 protected:
-  byte_range m_out_of_bounds_range;
+  bit_range m_out_of_bounds_bits;
 };
 
 /* Abstract subclass to complaing about concrete out-of-bounds
@@ -230,12 +236,18 @@  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 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_byte_bound (byte_bound)
-  {}
+    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
@@ -243,8 +255,8 @@  public:
     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));
+	    && pending_diagnostic::same_tree_p (m_bit_bound,
+						other.m_bit_bound));
   }
 
   void add_region_creation_events (const region *,
@@ -260,6 +272,7 @@  public:
   }
 
 protected:
+  tree m_bit_bound;
   tree m_byte_bound;
 };
 
@@ -270,9 +283,9 @@  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,
+			    bit_range range, tree bit_bound,
 			    const svalue *sval_hint)
-  : concrete_past_the_end (model, reg, diag_arg, range, byte_bound, sval_hint)
+  : concrete_past_the_end (model, reg, diag_arg, range, bit_bound, sval_hint)
   {}
 
   const char *get_kind () const final override
@@ -301,23 +314,44 @@  public:
 
     if (warned)
       {
-	if (wi::fits_uhwi_p (m_out_of_bounds_range.m_size_in_bytes))
+	if (wi::fits_uhwi_p (m_out_of_bounds_bits.m_size_in_bits))
 	  {
-	    unsigned HOST_WIDE_INT num_bad_bytes
-	      = m_out_of_bounds_range.m_size_in_bytes.to_uhwi ();
-	    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);
+	    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
-	      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);
+	      {
+		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 (),
@@ -331,10 +365,23 @@  public:
   }
 
   label_text describe_final_event (const evdesc::final_event &ev)
-  final override
+    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 ();
+    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];
@@ -345,10 +392,10 @@  public:
 	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);
+				     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);
+				   m_byte_bound);
       }
     else
       {
@@ -363,6 +410,38 @@  public:
       }
   }
 
+  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; }
 };
 
@@ -373,8 +452,8 @@  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)
+			     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
@@ -401,23 +480,44 @@  public:
 
     if (warned)
       {
-	if (wi::fits_uhwi_p (m_out_of_bounds_range.m_size_in_bytes))
+	if (wi::fits_uhwi_p (m_out_of_bounds_bits.m_size_in_bits))
 	  {
-	    unsigned HOST_WIDE_INT num_bad_bytes
-	      = m_out_of_bounds_range.m_size_in_bytes.to_uhwi ();
-	    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);
+	    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
-	      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);
+	      {
+		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 (),
@@ -431,10 +531,23 @@  public:
   }
 
   label_text describe_final_event (const evdesc::final_event &ev)
-  final override
+    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 ();
+    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];
@@ -463,6 +576,38 @@  public:
       }
   }
 
+  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; }
 };
 
@@ -473,7 +618,7 @@  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,
+			      bit_range range,
 			      const svalue *sval_hint)
   : concrete_out_of_bounds (model, reg, diag_arg, range, sval_hint)
   {}
@@ -505,10 +650,20 @@  public:
   }
 
   label_text describe_final_event (const evdesc::final_event &ev)
-  final override
+    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 ();
+    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];
@@ -518,8 +673,8 @@  public:
       {
 	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);
+				     " 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);
       }
@@ -535,6 +690,37 @@  public:
       }
   }
 
+  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; }
 };
 
@@ -545,7 +731,7 @@  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)
+			      bit_range range)
   : concrete_out_of_bounds (model, reg, diag_arg, range, NULL)
   {}
 
@@ -576,10 +762,20 @@  public:
   }
 
   label_text describe_final_event (const evdesc::final_event &ev)
-  final override
+    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 ();
+    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];
@@ -606,6 +802,36 @@  public:
       }
   }
 
+  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; }
 };
 
@@ -955,16 +1181,16 @@  region_model::check_region_bounds (const region *reg,
   region_offset reg_offset = reg->get_offset (m_mgr);
   const region *base_reg = reg_offset.get_base_region ();
 
-  /* 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))
+  /* 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.  */
-  const svalue *capacity = get_capacity (base_reg);
-  tree cst_capacity_tree = maybe_get_integer_cst_tree (capacity);
+  /* 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
@@ -973,36 +1199,39 @@  region_model::check_region_bounds (const region *reg,
 
      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;
+  bit_offset_t bit_offset;
   if (!reg_offset.symbolic_p ())
-    offset = wi::sext (reg_offset.get_bit_offset () >> LOG2_BITS_PER_UNIT,
-		       TYPE_PRECISION (size_type_node));
+    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_bytes_tree)
+  if (base_reg->symbolic_p () || reg_offset.symbolic_p () || !num_bits_tree)
     {
       const svalue* byte_offset_sval;
       if (!reg_offset.symbolic_p ())
 	{
-	  tree offset_tree = wide_int_to_tree (integer_type_node, offset);
+	  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 (offset_tree);
+	    = 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,
-			     capacity, dir, sval_hint, ctxt);
+				    byte_capacity, dir, sval_hint, ctxt);
     }
 
   /* Otherwise continue to check with concrete values.  */
-  byte_range out (0, 0);
+  bit_range bits_outside (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))
+  /* 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)
@@ -1014,13 +1243,13 @@  region_model::check_region_bounds (const region *reg,
 	  gcc_assert (sval_hint == nullptr);
 	  ctxt->warn (make_unique<concrete_buffer_under_read> (*this, reg,
 							       diag_arg,
-							       out));
+							       bits_outside));
 	  oob_safe = false;
 	  break;
 	case DIR_WRITE:
 	  ctxt->warn (make_unique<concrete_buffer_underwrite> (*this,
 							       reg, diag_arg,
-							       out,
+							       bits_outside,
 							       sval_hint));
 	  oob_safe = false;
 	  break;
@@ -1030,15 +1259,15 @@  region_model::check_region_bounds (const region *reg,
   /* 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;
+  if (!cst_byte_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))
+  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 byte_bound = wide_int_to_tree (size_type_node,
-					  buffer.get_next_byte_offset ());
+      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)
@@ -1050,13 +1279,15 @@  region_model::check_region_bounds (const region *reg,
 	  gcc_assert (sval_hint == nullptr);
 	  ctxt->warn (make_unique<concrete_buffer_over_read> (*this,
 							      reg, diag_arg,
-							      out, byte_bound));
+							      bits_outside,
+							      bit_bound));
 	  oob_safe = false;
 	  break;
 	case DIR_WRITE:
 	  ctxt->warn (make_unique<concrete_buffer_overflow> (*this,
 							     reg, diag_arg,
-							     out, byte_bound,
+							     bits_outside,
+							     bit_bound,
 							     sval_hint));
 	  oob_safe = false;
 	  break;
diff --git a/gcc/analyzer/region.cc b/gcc/analyzer/region.cc
index 9b27e8febcc9..d58d064f9ff1 100644
--- a/gcc/analyzer/region.cc
+++ b/gcc/analyzer/region.cc
@@ -757,6 +757,24 @@  int_size_in_bits (const_tree type, bit_size_t *out)
     return false;
 }
 
+/* Base implementation of region::get_bit_size_sval vfunc.  */
+
+const svalue *
+region::get_bit_size_sval (region_model_manager *mgr) const
+{
+  tree type = get_type ();
+
+  /* Bail out e.g. for heap-allocated regions.  */
+  if (!type)
+    return mgr->get_or_create_unknown_svalue (size_type_node);
+
+  bit_size_t bits;
+  if (!int_size_in_bits (type, &bits))
+    return mgr->get_or_create_unknown_svalue (size_type_node);
+
+  return mgr->get_or_create_int_cst (size_type_node, bits);
+}
+
 /* If the size of this region (in bits) is known statically, write it to *OUT
    and return true.
    Otherwise return false.  */
@@ -1933,6 +1951,15 @@  offset_region::dump_to_pp (pretty_printer *pp, bool simple) const
     }
 }
 
+const svalue *
+offset_region::get_bit_offset (region_model_manager *mgr) const
+{
+  const svalue *bits_per_byte_sval
+    = mgr->get_or_create_int_cst (size_type_node, BITS_PER_UNIT);
+  return mgr->get_or_create_binop (size_type_node, MULT_EXPR,
+				   m_byte_offset, bits_per_byte_sval);
+}
+
 /* Implementation of region::get_relative_concrete_offset vfunc
    for offset_region.  */
 
@@ -1987,6 +2014,30 @@  offset_region::get_byte_size_sval (region_model_manager *mgr) const
   return region::get_byte_size_sval (mgr);
 }
 
+/* Implementation of region::get_bit_size_sval vfunc for offset_region.  */
+
+const svalue *
+offset_region::get_bit_size_sval (region_model_manager *mgr) const
+{
+  tree offset_cst = get_bit_offset (mgr)->maybe_get_constant ();
+  bit_size_t bit_size;
+  /* If the offset points in the middle of the region,
+     return the remaining bits.  */
+  if (get_bit_size (&bit_size) && offset_cst)
+    {
+      bit_size_t offset = wi::to_offset (offset_cst);
+      bit_range r (0, bit_size);
+      if (r.contains_p (offset))
+	{
+	  tree remaining_bit_size = wide_int_to_tree (size_type_node,
+						       bit_size - offset);
+	  return mgr->get_or_create_constant_svalue (remaining_bit_size);
+	}
+    }
+
+  return region::get_bit_size_sval (mgr);
+}
+
 /* class sized_region : public region.  */
 
 /* Implementation of region::accept vfunc for sized_region.  */
@@ -2047,6 +2098,17 @@  sized_region::get_bit_size (bit_size_t *out) const
   return true;
 }
 
+/* Implementation of region::get_bit_size_sval vfunc for sized_region.  */
+
+const svalue *
+sized_region::get_bit_size_sval (region_model_manager *mgr) const
+{
+  const svalue *bits_per_byte_sval
+    = mgr->get_or_create_int_cst (size_type_node, BITS_PER_UNIT);
+  return mgr->get_or_create_binop (size_type_node, MULT_EXPR,
+				   m_byte_size_sval, bits_per_byte_sval);
+}
+
 /* class cast_region : public region.  */
 
 /* Implementation of region::accept vfunc for cast_region.  */
@@ -2198,6 +2260,15 @@  bit_range_region::get_byte_size_sval (region_model_manager *mgr) const
   return mgr->get_or_create_int_cst (size_type_node, num_bytes);
 }
 
+/* Implementation of region::get_bit_size_sval vfunc for bit_range_region.  */
+
+const svalue *
+bit_range_region::get_bit_size_sval (region_model_manager *mgr) const
+{
+  return mgr->get_or_create_int_cst (size_type_node,
+				     m_bits.m_size_in_bits);
+}
+
 /* Implementation of region::get_relative_concrete_offset vfunc for
    bit_range_region.  */
 
diff --git a/gcc/analyzer/region.h b/gcc/analyzer/region.h
index fee161cf8630..6b542094064c 100644
--- a/gcc/analyzer/region.h
+++ b/gcc/analyzer/region.h
@@ -199,6 +199,10 @@  public:
      (which could be "unknown").  */
   virtual const svalue *get_byte_size_sval (region_model_manager *mgr) const;
 
+  /* Get a symbolic value describing the size of this region in bits
+     (which could be "unknown").  */
+  virtual const svalue *get_bit_size_sval (region_model_manager *mgr) const;
+
   /* Attempt to get the offset in bits of this region relative to its parent.
      If successful, return true and write to *OUT.
      Otherwise return false.  */
@@ -969,13 +973,15 @@  public:
   void dump_to_pp (pretty_printer *pp, bool simple) const final override;
 
   const svalue *get_byte_offset () const { return m_byte_offset; }
+  const svalue *get_bit_offset (region_model_manager *mgr) const;
 
   bool get_relative_concrete_offset (bit_offset_t *out) const final override;
   const svalue *get_relative_symbolic_offset (region_model_manager *mgr)
     const final override;
   const svalue * get_byte_size_sval (region_model_manager *mgr)
     const final override;
-
+  const svalue * get_bit_size_sval (region_model_manager *mgr)
+    const final override;
 
 private:
   const svalue *m_byte_offset;
@@ -1070,6 +1076,9 @@  public:
     return m_byte_size_sval;
   }
 
+  const svalue *
+  get_bit_size_sval (region_model_manager *) const final override;
+
 private:
   const svalue *m_byte_size_sval;
 };
@@ -1304,6 +1313,7 @@  public:
   bool get_byte_size (byte_size_t *out) const final override;
   bool get_bit_size (bit_size_t *out) const final override;
   const svalue *get_byte_size_sval (region_model_manager *mgr) const final override;
+  const svalue *get_bit_size_sval (region_model_manager *mgr) const final override;
   bool get_relative_concrete_offset (bit_offset_t *out) const final override;
   const svalue *get_relative_symbolic_offset (region_model_manager *mgr)
     const final override;
diff --git a/gcc/analyzer/store.cc b/gcc/analyzer/store.cc
index be1802e1e5a8..32cb1d968b8a 100644
--- a/gcc/analyzer/store.cc
+++ b/gcc/analyzer/store.cc
@@ -285,6 +285,77 @@  bit_range::intersects_p (const bit_range &other,
     return false;
 }
 
+/* Return true if THIS and OTHER intersect and write the number
+   of bits both buffers overlap to *OUT_NUM_OVERLAP_BITS.
+
+   Otherwise return false.  */
+
+bool
+bit_range::intersects_p (const bit_range &other,
+			 bit_size_t *out_num_overlap_bits) const
+{
+  if (get_start_bit_offset () < other.get_next_bit_offset ()
+      && other.get_start_bit_offset () < get_next_bit_offset ())
+    {
+      bit_offset_t overlap_start = MAX (get_start_bit_offset (),
+					 other.get_start_bit_offset ());
+      bit_offset_t overlap_next = MIN (get_next_bit_offset (),
+					other.get_next_bit_offset ());
+      gcc_assert (overlap_next > overlap_start);
+      *out_num_overlap_bits = overlap_next - overlap_start;
+      return true;
+    }
+  else
+    return false;
+}
+
+/* Return true if THIS exceeds OTHER and write the overhanging
+   bit range to OUT_OVERHANGING_BIT_RANGE.  */
+
+bool
+bit_range::exceeds_p (const bit_range &other,
+		      bit_range *out_overhanging_bit_range) const
+{
+  gcc_assert (!empty_p ());
+
+  if (other.get_next_bit_offset () < get_next_bit_offset ())
+    {
+      /* THIS definitely exceeds OTHER.  */
+      bit_offset_t start = MAX (get_start_bit_offset (),
+				 other.get_next_bit_offset ());
+      bit_offset_t size = get_next_bit_offset () - start;
+      gcc_assert (size > 0);
+      out_overhanging_bit_range->m_start_bit_offset = start;
+      out_overhanging_bit_range->m_size_in_bits = size;
+      return true;
+    }
+  else
+    return false;
+}
+
+/* Return true if THIS falls short of OFFSET and write the
+   bit range fallen short to OUT_FALL_SHORT_BITS.  */
+
+bool
+bit_range::falls_short_of_p (bit_offset_t offset,
+			     bit_range *out_fall_short_bits) const
+{
+  gcc_assert (!empty_p ());
+
+  if (get_start_bit_offset () < offset)
+    {
+      /* THIS falls short of OFFSET.  */
+      bit_offset_t start = get_start_bit_offset ();
+      bit_offset_t size = MIN (offset, get_next_bit_offset ()) - start;
+      gcc_assert (size > 0);
+      out_fall_short_bits->m_start_bit_offset = start;
+      out_fall_short_bits->m_size_in_bits = size;
+      return true;
+    }
+  else
+    return false;
+}
+
 int
 bit_range::cmp (const bit_range &br1, const bit_range &br2)
 {
@@ -431,77 +502,6 @@  byte_range::contains_p (const byte_range &other, byte_range *out) const
     return false;
 }
 
-/* Return true if THIS and OTHER intersect and write the number
-   of bytes both buffers overlap to *OUT_NUM_OVERLAP_BYTES.
-
-   Otherwise return false.  */
-
-bool
-byte_range::intersects_p (const byte_range &other,
-			  byte_size_t *out_num_overlap_bytes) const
-{
-  if (get_start_byte_offset () < other.get_next_byte_offset ()
-      && other.get_start_byte_offset () < get_next_byte_offset ())
-    {
-      byte_offset_t overlap_start = MAX (get_start_byte_offset (),
-					 other.get_start_byte_offset ());
-      byte_offset_t overlap_next = MIN (get_next_byte_offset (),
-					other.get_next_byte_offset ());
-      gcc_assert (overlap_next > overlap_start);
-      *out_num_overlap_bytes = overlap_next - overlap_start;
-      return true;
-    }
-  else
-    return false;
-}
-
-/* Return true if THIS exceeds OTHER and write the overhanging
-   byte range to OUT_OVERHANGING_BYTE_RANGE.  */
-
-bool
-byte_range::exceeds_p (const byte_range &other,
-		       byte_range *out_overhanging_byte_range) const
-{
-  gcc_assert (!empty_p ());
-
-  if (other.get_next_byte_offset () < get_next_byte_offset ())
-    {
-      /* THIS definitely exceeds OTHER.  */
-      byte_offset_t start = MAX (get_start_byte_offset (),
-				 other.get_next_byte_offset ());
-      byte_offset_t size = get_next_byte_offset () - start;
-      gcc_assert (size > 0);
-      out_overhanging_byte_range->m_start_byte_offset = start;
-      out_overhanging_byte_range->m_size_in_bytes = size;
-      return true;
-    }
-  else
-    return false;
-}
-
-/* Return true if THIS falls short of OFFSET and write the
-   byte range fallen short to OUT_FALL_SHORT_BYTES.  */
-
-bool
-byte_range::falls_short_of_p (byte_offset_t offset,
-			      byte_range *out_fall_short_bytes) const
-{
-  gcc_assert (!empty_p ());
-
-  if (get_start_byte_offset () < offset)
-    {
-      /* THIS falls short of OFFSET.  */
-      byte_offset_t start = get_start_byte_offset ();
-      byte_offset_t size = MIN (offset, get_next_byte_offset ()) - start;
-      gcc_assert (size > 0);
-      out_fall_short_bytes->m_start_byte_offset = start;
-      out_fall_short_bytes->m_size_in_bytes = size;
-      return true;
-    }
-  else
-    return false;
-}
-
 /* qsort comparator for byte ranges.  */
 
 int
diff --git a/gcc/analyzer/store.h b/gcc/analyzer/store.h
index d75d69d0b7f4..da5c8b6ffaec 100644
--- a/gcc/analyzer/store.h
+++ b/gcc/analyzer/store.h
@@ -275,10 +275,18 @@  struct bit_range
     return (get_start_bit_offset () < other.get_next_bit_offset ()
 	    && other.get_start_bit_offset () < get_next_bit_offset ());
   }
+  bool intersects_p (const bit_range &other,
+		     bit_size_t *out_num_overlap_bits) const;
   bool intersects_p (const bit_range &other,
 		     bit_range *out_this,
 		     bit_range *out_other) const;
 
+  bool exceeds_p (const bit_range &other,
+		  bit_range *out_overhanging_bit_range) const;
+
+  bool falls_short_of_p (bit_offset_t offset,
+			 bit_range *out_fall_short_bits) const;
+
   static int cmp (const bit_range &br1, const bit_range &br2);
 
   bit_range operator- (bit_offset_t offset) const;
@@ -321,15 +329,6 @@  struct byte_range
 	    && m_size_in_bytes == other.m_size_in_bytes);
   }
 
-  bool intersects_p (const byte_range &other,
-		     byte_size_t *out_num_overlap_bytes) const;
-
-  bool exceeds_p (const byte_range &other,
-		  byte_range *out_overhanging_byte_range) const;
-
-  bool falls_short_of_p (byte_offset_t offset,
-			 byte_range *out_fall_short_bytes) const;
-
   byte_offset_t get_start_byte_offset () const
   {
     return m_start_byte_offset;
diff --git a/gcc/testsuite/c-c++-common/analyzer/out-of-bounds-pr112792.c b/gcc/testsuite/c-c++-common/analyzer/out-of-bounds-pr112792.c
new file mode 100644
index 000000000000..0bc7413ce5dd
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/analyzer/out-of-bounds-pr112792.c
@@ -0,0 +1,18 @@ 
+/* PR analyzer/112792 ("-Wanalyzer-out-of-bounds false positives seen on
+   Linux kernel with certain unions").  */
+
+typedef unsigned int u32;
+
+union msix_perm {
+  struct {
+    u32 rsvd2 : 8;
+    u32 pasid : 20;
+  };
+  u32 bits;
+} __attribute__((__packed__));
+
+union msix_perm mperm;
+
+void idxd_device_set_perm_entry(u32 pasid) {
+  mperm.pasid = pasid; /* { dg-bogus "buffer-overflow" } */
+}