extend -Wstringop-overflow to allocated objects (PR 91582)
diff mbox series

Message ID 5b68c166-e94b-2660-04f3-e3fafe69112c@gmail.com
State New
Headers show
Series
  • extend -Wstringop-overflow to allocated objects (PR 91582)
Related show

Commit Message

Martin Sebor Nov. 8, 2019, 10:11 p.m. UTC
Unless it's used with _FORTIFY_SOURCE, -Wstringop-overflow
doesn't consider out-of-bounds accesses to objects allocated
by alloca, malloc, other functions declared with attribute
alloc_size, or even VLAs with variable bounds.  This was
a known limitation of the checks (done just before expansion)
relying on the the object size pass when they were introduced
in GCC 7.

But since its introduction in GCC 7, the warning has evolved
beyond some of the limitations of the object size pass.  Unlike
it, the warning considers non-constant offsets and stores with
non-constant sizes.  Attached is a simple enhancement that
(finally) adds the ability to also detect overflow in allocated
objects to the warning.

With the patch GCC detects the overflow in code like this:

   char* f (void)
   {
     char s[] = "12345";
     char *p = malloc (strlen (s));
     strcpy (p, s);   // warning here
     return p;
   }

but not (yet) in something like this:

   char* g (const char *s)
   {
     char *p = malloc (strlen (s));
     strcpy (p, s);   // no warning (yet)
     return p;
   }

and quite a few other examples.  Doing better requires extending
the strlen pass.  I'm working on this extension and expect to
submit a patch before stage 1 ends.

Martin

PS I was originally planning to do all the allocation checking
in the strlen pass but it occurred to me that by also enhancing
the compute_objsize function, all warnings that use it will
benefit.  Besides -Wstringop-overflow this includes a subset
of -Warray-bounds, -Wformat-overflow, and -Wrestrict.  It's
nice when a small enhancement has such a broad positive effect.

Comments

Martin Sebor Nov. 18, 2019, 6:22 p.m. UTC | #1
Ping: https://gcc.gnu.org/ml/gcc-patches/2019-11/msg00652.html

On 11/8/19 3:11 PM, Martin Sebor wrote:
> Unless it's used with _FORTIFY_SOURCE, -Wstringop-overflow
> doesn't consider out-of-bounds accesses to objects allocated
> by alloca, malloc, other functions declared with attribute
> alloc_size, or even VLAs with variable bounds.  This was
> a known limitation of the checks (done just before expansion)
> relying on the the object size pass when they were introduced
> in GCC 7.
> 
> But since its introduction in GCC 7, the warning has evolved
> beyond some of the limitations of the object size pass.  Unlike
> it, the warning considers non-constant offsets and stores with
> non-constant sizes.  Attached is a simple enhancement that
> (finally) adds the ability to also detect overflow in allocated
> objects to the warning.
> 
> With the patch GCC detects the overflow in code like this:
> 
>    char* f (void)
>    {
>      char s[] = "12345";
>      char *p = malloc (strlen (s));
>      strcpy (p, s);   // warning here
>      return p;
>    }
> 
> but not (yet) in something like this:
> 
>    char* g (const char *s)
>    {
>      char *p = malloc (strlen (s));
>      strcpy (p, s);   // no warning (yet)
>      return p;
>    }
> 
> and quite a few other examples.  Doing better requires extending
> the strlen pass.  I'm working on this extension and expect to
> submit a patch before stage 1 ends.
> 
> Martin
> 
> PS I was originally planning to do all the allocation checking
> in the strlen pass but it occurred to me that by also enhancing
> the compute_objsize function, all warnings that use it will
> benefit.  Besides -Wstringop-overflow this includes a subset
> of -Warray-bounds, -Wformat-overflow, and -Wrestrict.  It's
> nice when a small enhancement has such a broad positive effect.
Martin Sebor Nov. 25, 2019, 5:53 p.m. UTC | #2
Ping: https://gcc.gnu.org/ml/gcc-patches/2019-11/msg00652.html

This change is independent of either of the two patches below:
   https://gcc.gnu.org/ml/gcc-patches/2019-11/msg00429.html
   https://gcc.gnu.org/ml/gcc-patches/2019-11/msg00652.html

On 11/18/19 11:22 AM, Martin Sebor wrote:
> Ping: https://gcc.gnu.org/ml/gcc-patches/2019-11/msg00652.html
> 
> On 11/8/19 3:11 PM, Martin Sebor wrote:
>> Unless it's used with _FORTIFY_SOURCE, -Wstringop-overflow
>> doesn't consider out-of-bounds accesses to objects allocated
>> by alloca, malloc, other functions declared with attribute
>> alloc_size, or even VLAs with variable bounds.  This was
>> a known limitation of the checks (done just before expansion)
>> relying on the the object size pass when they were introduced
>> in GCC 7.
>>
>> But since its introduction in GCC 7, the warning has evolved
>> beyond some of the limitations of the object size pass.  Unlike
>> it, the warning considers non-constant offsets and stores with
>> non-constant sizes.  Attached is a simple enhancement that
>> (finally) adds the ability to also detect overflow in allocated
>> objects to the warning.
>>
>> With the patch GCC detects the overflow in code like this:
>>
>>    char* f (void)
>>    {
>>      char s[] = "12345";
>>      char *p = malloc (strlen (s));
>>      strcpy (p, s);   // warning here
>>      return p;
>>    }
>>
>> but not (yet) in something like this:
>>
>>    char* g (const char *s)
>>    {
>>      char *p = malloc (strlen (s));
>>      strcpy (p, s);   // no warning (yet)
>>      return p;
>>    }
>>
>> and quite a few other examples.  Doing better requires extending
>> the strlen pass.  I'm working on this extension and expect to
>> submit a patch before stage 1 ends.
>>
>> Martin
>>
>> PS I was originally planning to do all the allocation checking
>> in the strlen pass but it occurred to me that by also enhancing
>> the compute_objsize function, all warnings that use it will
>> benefit.  Besides -Wstringop-overflow this includes a subset
>> of -Warray-bounds, -Wformat-overflow, and -Wrestrict.  It's
>> nice when a small enhancement has such a broad positive effect.
>
Jeff Law Dec. 2, 2019, 5:06 p.m. UTC | #3
On 11/8/19 3:11 PM, Martin Sebor wrote:
> Unless it's used with _FORTIFY_SOURCE, -Wstringop-overflow
> doesn't consider out-of-bounds accesses to objects allocated
> by alloca, malloc, other functions declared with attribute
> alloc_size, or even VLAs with variable bounds.  This was
> a known limitation of the checks (done just before expansion)
> relying on the the object size pass when they were introduced
> in GCC 7.
> 
> But since its introduction in GCC 7, the warning has evolved
> beyond some of the limitations of the object size pass.  Unlike
> it, the warning considers non-constant offsets and stores with
> non-constant sizes.  Attached is a simple enhancement that
> (finally) adds the ability to also detect overflow in allocated
> objects to the warning.
> 
> With the patch GCC detects the overflow in code like this:
> 
>   char* f (void)
>   {
>     char s[] = "12345";
>     char *p = malloc (strlen (s));
>     strcpy (p, s);   // warning here
>     return p;
>   }
> 
> but not (yet) in something like this:
> 
>   char* g (const char *s)
>   {
>     char *p = malloc (strlen (s));
>     strcpy (p, s);   // no warning (yet)
>     return p;
>   }
> 
> and quite a few other examples.  Doing better requires extending
> the strlen pass.  I'm working on this extension and expect to
> submit a patch before stage 1 ends.
> 
> Martin
> 
> PS I was originally planning to do all the allocation checking
> in the strlen pass but it occurred to me that by also enhancing
> the compute_objsize function, all warnings that use it will
> benefit.  Besides -Wstringop-overflow this includes a subset
> of -Warray-bounds, -Wformat-overflow, and -Wrestrict.  It's
> nice when a small enhancement has such a broad positive effect.

> PR middle-end/91582 - missing heap overflow detection for strcpy
> 
> gcc/ChangeLog:
> 
>         * builtins.c (gimple_call_alloc_size): New function.
>         (compute_objsize): Add argument.  Call gimple_call_alloc_size.
>         Handle variable offsets and indices.
>         * builtins.h (gimple_call_alloc_size): Declare.
>         (compute_objsize): Add argument.
>         * tree-ssa-strlen.c (handle_store): Handle calls to allocated objects.
> 
> gcc/testsuite/ChangeLog:
> 
>         * c-c++-common/Wstringop-truncation.c: Remove xfails.
>         * gcc/testsuite/g++.dg/ext/attr-alloc_size.C: Suppress -Warray-bounds.
>         * gcc.dg/Wstringop-overflow-22.c: New test.
>         * gcc/testsuite/gcc.dg/attr-alloc_size.c: Suppress -Warray-bounds.
>         * gcc/testsuite/gcc.dg/attr-copy-2.c: Same.
>         * gcc.dg/builtin-stringop-chk-5.c: Remove xfails.
>         * gcc.dg/builtin-stringop-chk-8.c: Same.  Correct the text of expected
>         warnings.
>         * gcc.target/i386/pr82002-2a.c: Prune expected warning.
>         * gcc.target/i386/pr82002-2b.c: Same.
[ ... ]


> Index: gcc/builtins.c
> ===================================================================
> --- gcc/builtins.c      (revision 277978)
> +++ gcc/builtins.c      (working copy)
> @@ -3563,6 +3563,80 @@ check_access (tree exp, tree, tree, tree dstwrite,
>    return true;
>  }
> 
> +/* If STMT is a call to an allocation function, returns the size
> +   of the object allocated by the call.  */
> +
> +tree
> +gimple_call_alloc_size (gimple *stmt)
> +{
> +  tree size = gimple_call_arg (stmt, argidx1);
> +  tree n = argidx2 < nargs ? gimple_call_arg (stmt, argidx2) : integer_one_node;
> +
> +  /* To handle ranges do the math in wide_int and return the product
> +     of the upper bounds as a constant.  Ignore anti-ranges.  */
> +  wide_int rng1[2];
> +  if (TREE_CODE (size) == INTEGER_CST)
> +    rng1[0] = rng1[1] = wi::to_wide (size);
> +  else if (TREE_CODE (size) != SSA_NAME
> +          || get_range_info (size, rng1, rng1 + 1) != VR_RANGE)
> +    return NULL_TREE;
> +
> +  wide_int rng2[2];
> +  if (TREE_CODE (n) == INTEGER_CST)
> +    rng2[0] = rng2[1] = wi::to_wide (n);
> +  else if (TREE_CODE (n) != SSA_NAME
> +          || get_range_info (n, rng2 + 1, rng2 + 1) != VR_RANGE)
> +    return NULL_TREE;
Should that 2nd call to get_range_info be "get_range_info (n, rng2, rng2
+ 1)?  I don't think it makes any difference in practice due to the
implementation of get_range_info, but if it wasn't intentional let's get
it fixed.


> Index: gcc/builtins.h
> ===================================================================
> --- gcc/builtins.h      (revision 277978)
> +++ gcc/builtins.h      (working copy)
> @@ -134,7 +134,8 @@ extern tree fold_call_stmt (gcall *, bool);
>  extern void set_builtin_user_assembler_name (tree decl, const char *asmspec);
>  extern bool is_simple_builtin (tree);
>  extern bool is_inexpensive_builtin (tree);
> -extern tree compute_objsize (tree, int, tree * = NULL);
> +tree gimple_call_alloc_size (gimple *);
> +extern tree compute_objsize (tree, int, tree * = NULL, tree * = NULL);
> 
>  extern bool readonly_data_expr (tree exp);
>  extern bool init_target_chars (void);
Is there a reason there's no "extern" on the gimple_call_alloc_size
prototype?

I think this is fine with those nits fixed.  You'll have a minor merge
conflict with the compute_objsize changes due to recent fixes in the
same hunk of code, but I don't think it warrants reposting/resubmission.

Jeff
Martin Sebor Dec. 5, 2019, 1:37 a.m. UTC | #4
On 12/2/19 10:06 AM, Jeff Law wrote:
> On 11/8/19 3:11 PM, Martin Sebor wrote:
>> Unless it's used with _FORTIFY_SOURCE, -Wstringop-overflow
>> doesn't consider out-of-bounds accesses to objects allocated
>> by alloca, malloc, other functions declared with attribute
>> alloc_size, or even VLAs with variable bounds.  This was
>> a known limitation of the checks (done just before expansion)
>> relying on the the object size pass when they were introduced
>> in GCC 7.
>>
>> But since its introduction in GCC 7, the warning has evolved
>> beyond some of the limitations of the object size pass.  Unlike
>> it, the warning considers non-constant offsets and stores with
>> non-constant sizes.  Attached is a simple enhancement that
>> (finally) adds the ability to also detect overflow in allocated
>> objects to the warning.
>>
>> With the patch GCC detects the overflow in code like this:
>>
>>    char* f (void)
>>    {
>>      char s[] = "12345";
>>      char *p = malloc (strlen (s));
>>      strcpy (p, s);   // warning here
>>      return p;
>>    }
>>
>> but not (yet) in something like this:
>>
>>    char* g (const char *s)
>>    {
>>      char *p = malloc (strlen (s));
>>      strcpy (p, s);   // no warning (yet)
>>      return p;
>>    }
>>
>> and quite a few other examples.  Doing better requires extending
>> the strlen pass.  I'm working on this extension and expect to
>> submit a patch before stage 1 ends.
>>
>> Martin
>>
>> PS I was originally planning to do all the allocation checking
>> in the strlen pass but it occurred to me that by also enhancing
>> the compute_objsize function, all warnings that use it will
>> benefit.  Besides -Wstringop-overflow this includes a subset
>> of -Warray-bounds, -Wformat-overflow, and -Wrestrict.  It's
>> nice when a small enhancement has such a broad positive effect.
> 
>> PR middle-end/91582 - missing heap overflow detection for strcpy
>>
>> gcc/ChangeLog:
>>
>>          * builtins.c (gimple_call_alloc_size): New function.
>>          (compute_objsize): Add argument.  Call gimple_call_alloc_size.
>>          Handle variable offsets and indices.
>>          * builtins.h (gimple_call_alloc_size): Declare.
>>          (compute_objsize): Add argument.
>>          * tree-ssa-strlen.c (handle_store): Handle calls to allocated objects.
>>
>> gcc/testsuite/ChangeLog:
>>
>>          * c-c++-common/Wstringop-truncation.c: Remove xfails.
>>          * gcc/testsuite/g++.dg/ext/attr-alloc_size.C: Suppress -Warray-bounds.
>>          * gcc.dg/Wstringop-overflow-22.c: New test.
>>          * gcc/testsuite/gcc.dg/attr-alloc_size.c: Suppress -Warray-bounds.
>>          * gcc/testsuite/gcc.dg/attr-copy-2.c: Same.
>>          * gcc.dg/builtin-stringop-chk-5.c: Remove xfails.
>>          * gcc.dg/builtin-stringop-chk-8.c: Same.  Correct the text of expected
>>          warnings.
>>          * gcc.target/i386/pr82002-2a.c: Prune expected warning.
>>          * gcc.target/i386/pr82002-2b.c: Same.
> [ ... ]
> 
> 
>> Index: gcc/builtins.c
>> ===================================================================
>> --- gcc/builtins.c      (revision 277978)
>> +++ gcc/builtins.c      (working copy)
>> @@ -3563,6 +3563,80 @@ check_access (tree exp, tree, tree, tree dstwrite,
>>     return true;
>>   }
>>
>> +/* If STMT is a call to an allocation function, returns the size
>> +   of the object allocated by the call.  */
>> +
>> +tree
>> +gimple_call_alloc_size (gimple *stmt)
>> +{
>> +  tree size = gimple_call_arg (stmt, argidx1);
>> +  tree n = argidx2 < nargs ? gimple_call_arg (stmt, argidx2) : integer_one_node;
>> +
>> +  /* To handle ranges do the math in wide_int and return the product
>> +     of the upper bounds as a constant.  Ignore anti-ranges.  */
>> +  wide_int rng1[2];
>> +  if (TREE_CODE (size) == INTEGER_CST)
>> +    rng1[0] = rng1[1] = wi::to_wide (size);
>> +  else if (TREE_CODE (size) != SSA_NAME
>> +          || get_range_info (size, rng1, rng1 + 1) != VR_RANGE)
>> +    return NULL_TREE;
>> +
>> +  wide_int rng2[2];
>> +  if (TREE_CODE (n) == INTEGER_CST)
>> +    rng2[0] = rng2[1] = wi::to_wide (n);
>> +  else if (TREE_CODE (n) != SSA_NAME
>> +          || get_range_info (n, rng2 + 1, rng2 + 1) != VR_RANGE)
>> +    return NULL_TREE;
> Should that 2nd call to get_range_info be "get_range_info (n, rng2, rng2
> + 1)?  I don't think it makes any difference in practice due to the
> implementation of get_range_info, but if it wasn't intentional let's get
> it fixed.

Yes, it should be.  It's correct in my tree but didn't post
the updated revision.

> 
> 
>> Index: gcc/builtins.h
>> ===================================================================
>> --- gcc/builtins.h      (revision 277978)
>> +++ gcc/builtins.h      (working copy)
>> @@ -134,7 +134,8 @@ extern tree fold_call_stmt (gcall *, bool);
>>   extern void set_builtin_user_assembler_name (tree decl, const char *asmspec);
>>   extern bool is_simple_builtin (tree);
>>   extern bool is_inexpensive_builtin (tree);
>> -extern tree compute_objsize (tree, int, tree * = NULL);
>> +tree gimple_call_alloc_size (gimple *);
>> +extern tree compute_objsize (tree, int, tree * = NULL, tree * = NULL);
>>
>>   extern bool readonly_data_expr (tree exp);
>>   extern bool init_target_chars (void);
> Is there a reason there's no "extern" on the gimple_call_alloc_size
> prototype?

I'm sure it was copied and pasted from the definition.  It makes
no difference either way so it didn't get caught by anything.

> 
> I think this is fine with those nits fixed.  You'll have a minor merge
> conflict with the compute_objsize changes due to recent fixes in the
> same hunk of code, but I don't think it warrants reposting/resubmission.
> 

I've fixed the nits above and committed r278983 after retesting.

This is an improvement in the buffer overflow detection but there
is still the (arguably more important) second half of it:
extending the strlen pass to detect the overflow that cannot be
caught later (e.g., all stores by MEM_REFs are only handled in
strlen): https://gcc.gnu.org/ml/gcc-patches/2019-11/msg02340.html

Martin
Christophe Lyon Dec. 6, 2019, 3:44 p.m. UTC | #5
On Thu, 5 Dec 2019 at 02:37, Martin Sebor <msebor@gmail.com> wrote:
>
> On 12/2/19 10:06 AM, Jeff Law wrote:
> > On 11/8/19 3:11 PM, Martin Sebor wrote:
> >> Unless it's used with _FORTIFY_SOURCE, -Wstringop-overflow
> >> doesn't consider out-of-bounds accesses to objects allocated
> >> by alloca, malloc, other functions declared with attribute
> >> alloc_size, or even VLAs with variable bounds.  This was
> >> a known limitation of the checks (done just before expansion)
> >> relying on the the object size pass when they were introduced
> >> in GCC 7.
> >>
> >> But since its introduction in GCC 7, the warning has evolved
> >> beyond some of the limitations of the object size pass.  Unlike
> >> it, the warning considers non-constant offsets and stores with
> >> non-constant sizes.  Attached is a simple enhancement that
> >> (finally) adds the ability to also detect overflow in allocated
> >> objects to the warning.
> >>
> >> With the patch GCC detects the overflow in code like this:
> >>
> >>    char* f (void)
> >>    {
> >>      char s[] = "12345";
> >>      char *p = malloc (strlen (s));
> >>      strcpy (p, s);   // warning here
> >>      return p;
> >>    }
> >>
> >> but not (yet) in something like this:
> >>
> >>    char* g (const char *s)
> >>    {
> >>      char *p = malloc (strlen (s));
> >>      strcpy (p, s);   // no warning (yet)
> >>      return p;
> >>    }
> >>
> >> and quite a few other examples.  Doing better requires extending
> >> the strlen pass.  I'm working on this extension and expect to
> >> submit a patch before stage 1 ends.
> >>
> >> Martin
> >>
> >> PS I was originally planning to do all the allocation checking
> >> in the strlen pass but it occurred to me that by also enhancing
> >> the compute_objsize function, all warnings that use it will
> >> benefit.  Besides -Wstringop-overflow this includes a subset
> >> of -Warray-bounds, -Wformat-overflow, and -Wrestrict.  It's
> >> nice when a small enhancement has such a broad positive effect.
> >
> >> PR middle-end/91582 - missing heap overflow detection for strcpy
> >>
> >> gcc/ChangeLog:
> >>
> >>          * builtins.c (gimple_call_alloc_size): New function.
> >>          (compute_objsize): Add argument.  Call gimple_call_alloc_size.
> >>          Handle variable offsets and indices.
> >>          * builtins.h (gimple_call_alloc_size): Declare.
> >>          (compute_objsize): Add argument.
> >>          * tree-ssa-strlen.c (handle_store): Handle calls to allocated objects.
> >>
> >> gcc/testsuite/ChangeLog:
> >>
> >>          * c-c++-common/Wstringop-truncation.c: Remove xfails.
> >>          * gcc/testsuite/g++.dg/ext/attr-alloc_size.C: Suppress -Warray-bounds.
> >>          * gcc.dg/Wstringop-overflow-22.c: New test.
> >>          * gcc/testsuite/gcc.dg/attr-alloc_size.c: Suppress -Warray-bounds.
> >>          * gcc/testsuite/gcc.dg/attr-copy-2.c: Same.
> >>          * gcc.dg/builtin-stringop-chk-5.c: Remove xfails.
> >>          * gcc.dg/builtin-stringop-chk-8.c: Same.  Correct the text of expected
> >>          warnings.
> >>          * gcc.target/i386/pr82002-2a.c: Prune expected warning.
> >>          * gcc.target/i386/pr82002-2b.c: Same.
> > [ ... ]
> >
> >
> >> Index: gcc/builtins.c
> >> ===================================================================
> >> --- gcc/builtins.c      (revision 277978)
> >> +++ gcc/builtins.c      (working copy)
> >> @@ -3563,6 +3563,80 @@ check_access (tree exp, tree, tree, tree dstwrite,
> >>     return true;
> >>   }
> >>
> >> +/* If STMT is a call to an allocation function, returns the size
> >> +   of the object allocated by the call.  */
> >> +
> >> +tree
> >> +gimple_call_alloc_size (gimple *stmt)
> >> +{
> >> +  tree size = gimple_call_arg (stmt, argidx1);
> >> +  tree n = argidx2 < nargs ? gimple_call_arg (stmt, argidx2) : integer_one_node;
> >> +
> >> +  /* To handle ranges do the math in wide_int and return the product
> >> +     of the upper bounds as a constant.  Ignore anti-ranges.  */
> >> +  wide_int rng1[2];
> >> +  if (TREE_CODE (size) == INTEGER_CST)
> >> +    rng1[0] = rng1[1] = wi::to_wide (size);
> >> +  else if (TREE_CODE (size) != SSA_NAME
> >> +          || get_range_info (size, rng1, rng1 + 1) != VR_RANGE)
> >> +    return NULL_TREE;
> >> +
> >> +  wide_int rng2[2];
> >> +  if (TREE_CODE (n) == INTEGER_CST)
> >> +    rng2[0] = rng2[1] = wi::to_wide (n);
> >> +  else if (TREE_CODE (n) != SSA_NAME
> >> +          || get_range_info (n, rng2 + 1, rng2 + 1) != VR_RANGE)
> >> +    return NULL_TREE;
> > Should that 2nd call to get_range_info be "get_range_info (n, rng2, rng2
> > + 1)?  I don't think it makes any difference in practice due to the
> > implementation of get_range_info, but if it wasn't intentional let's get
> > it fixed.
>
> Yes, it should be.  It's correct in my tree but didn't post
> the updated revision.
>
> >
> >
> >> Index: gcc/builtins.h
> >> ===================================================================
> >> --- gcc/builtins.h      (revision 277978)
> >> +++ gcc/builtins.h      (working copy)
> >> @@ -134,7 +134,8 @@ extern tree fold_call_stmt (gcall *, bool);
> >>   extern void set_builtin_user_assembler_name (tree decl, const char *asmspec);
> >>   extern bool is_simple_builtin (tree);
> >>   extern bool is_inexpensive_builtin (tree);
> >> -extern tree compute_objsize (tree, int, tree * = NULL);
> >> +tree gimple_call_alloc_size (gimple *);
> >> +extern tree compute_objsize (tree, int, tree * = NULL, tree * = NULL);
> >>
> >>   extern bool readonly_data_expr (tree exp);
> >>   extern bool init_target_chars (void);
> > Is there a reason there's no "extern" on the gimple_call_alloc_size
> > prototype?
>
> I'm sure it was copied and pasted from the definition.  It makes
> no difference either way so it didn't get caught by anything.
>
> >
> > I think this is fine with those nits fixed.  You'll have a minor merge
> > conflict with the compute_objsize changes due to recent fixes in the
> > same hunk of code, but I don't think it warrants reposting/resubmission.
> >
>
> I've fixed the nits above and committed r278983 after retesting.
>

Hi Martin,

I've noticed that this patch introduces a new FAIL:
gcc.dg/Warray-bounds-56.c  (test for warnings, line 82)
when GCC is configured with:
--target arm-none-linux-gnueabihf
--with-mode arm
--with-cpu cortex-a5
--with-fpu vfpv3-d16-fp16

This test passes when using cortex-a9, a15, a57.

Christophe


> This is an improvement in the buffer overflow detection but there
> is still the (arguably more important) second half of it:
> extending the strlen pass to detect the overflow that cannot be
> caught later (e.g., all stores by MEM_REFs are only handled in
> strlen): https://gcc.gnu.org/ml/gcc-patches/2019-11/msg02340.html
>
> Martin
Martin Sebor Dec. 6, 2019, 5:03 p.m. UTC | #6
On 12/6/19 8:44 AM, Christophe Lyon wrote:
> On Thu, 5 Dec 2019 at 02:37, Martin Sebor <msebor@gmail.com> wrote:
>>
>> On 12/2/19 10:06 AM, Jeff Law wrote:
>>> On 11/8/19 3:11 PM, Martin Sebor wrote:
>>>> Unless it's used with _FORTIFY_SOURCE, -Wstringop-overflow
>>>> doesn't consider out-of-bounds accesses to objects allocated
>>>> by alloca, malloc, other functions declared with attribute
>>>> alloc_size, or even VLAs with variable bounds.  This was
>>>> a known limitation of the checks (done just before expansion)
>>>> relying on the the object size pass when they were introduced
>>>> in GCC 7.
>>>>
>>>> But since its introduction in GCC 7, the warning has evolved
>>>> beyond some of the limitations of the object size pass.  Unlike
>>>> it, the warning considers non-constant offsets and stores with
>>>> non-constant sizes.  Attached is a simple enhancement that
>>>> (finally) adds the ability to also detect overflow in allocated
>>>> objects to the warning.
>>>>
>>>> With the patch GCC detects the overflow in code like this:
>>>>
>>>>     char* f (void)
>>>>     {
>>>>       char s[] = "12345";
>>>>       char *p = malloc (strlen (s));
>>>>       strcpy (p, s);   // warning here
>>>>       return p;
>>>>     }
>>>>
>>>> but not (yet) in something like this:
>>>>
>>>>     char* g (const char *s)
>>>>     {
>>>>       char *p = malloc (strlen (s));
>>>>       strcpy (p, s);   // no warning (yet)
>>>>       return p;
>>>>     }
>>>>
>>>> and quite a few other examples.  Doing better requires extending
>>>> the strlen pass.  I'm working on this extension and expect to
>>>> submit a patch before stage 1 ends.
>>>>
>>>> Martin
>>>>
>>>> PS I was originally planning to do all the allocation checking
>>>> in the strlen pass but it occurred to me that by also enhancing
>>>> the compute_objsize function, all warnings that use it will
>>>> benefit.  Besides -Wstringop-overflow this includes a subset
>>>> of -Warray-bounds, -Wformat-overflow, and -Wrestrict.  It's
>>>> nice when a small enhancement has such a broad positive effect.
>>>
>>>> PR middle-end/91582 - missing heap overflow detection for strcpy
>>>>
>>>> gcc/ChangeLog:
>>>>
>>>>           * builtins.c (gimple_call_alloc_size): New function.
>>>>           (compute_objsize): Add argument.  Call gimple_call_alloc_size.
>>>>           Handle variable offsets and indices.
>>>>           * builtins.h (gimple_call_alloc_size): Declare.
>>>>           (compute_objsize): Add argument.
>>>>           * tree-ssa-strlen.c (handle_store): Handle calls to allocated objects.
>>>>
>>>> gcc/testsuite/ChangeLog:
>>>>
>>>>           * c-c++-common/Wstringop-truncation.c: Remove xfails.
>>>>           * gcc/testsuite/g++.dg/ext/attr-alloc_size.C: Suppress -Warray-bounds.
>>>>           * gcc.dg/Wstringop-overflow-22.c: New test.
>>>>           * gcc/testsuite/gcc.dg/attr-alloc_size.c: Suppress -Warray-bounds.
>>>>           * gcc/testsuite/gcc.dg/attr-copy-2.c: Same.
>>>>           * gcc.dg/builtin-stringop-chk-5.c: Remove xfails.
>>>>           * gcc.dg/builtin-stringop-chk-8.c: Same.  Correct the text of expected
>>>>           warnings.
>>>>           * gcc.target/i386/pr82002-2a.c: Prune expected warning.
>>>>           * gcc.target/i386/pr82002-2b.c: Same.
>>> [ ... ]
>>>
>>>
>>>> Index: gcc/builtins.c
>>>> ===================================================================
>>>> --- gcc/builtins.c      (revision 277978)
>>>> +++ gcc/builtins.c      (working copy)
>>>> @@ -3563,6 +3563,80 @@ check_access (tree exp, tree, tree, tree dstwrite,
>>>>      return true;
>>>>    }
>>>>
>>>> +/* If STMT is a call to an allocation function, returns the size
>>>> +   of the object allocated by the call.  */
>>>> +
>>>> +tree
>>>> +gimple_call_alloc_size (gimple *stmt)
>>>> +{
>>>> +  tree size = gimple_call_arg (stmt, argidx1);
>>>> +  tree n = argidx2 < nargs ? gimple_call_arg (stmt, argidx2) : integer_one_node;
>>>> +
>>>> +  /* To handle ranges do the math in wide_int and return the product
>>>> +     of the upper bounds as a constant.  Ignore anti-ranges.  */
>>>> +  wide_int rng1[2];
>>>> +  if (TREE_CODE (size) == INTEGER_CST)
>>>> +    rng1[0] = rng1[1] = wi::to_wide (size);
>>>> +  else if (TREE_CODE (size) != SSA_NAME
>>>> +          || get_range_info (size, rng1, rng1 + 1) != VR_RANGE)
>>>> +    return NULL_TREE;
>>>> +
>>>> +  wide_int rng2[2];
>>>> +  if (TREE_CODE (n) == INTEGER_CST)
>>>> +    rng2[0] = rng2[1] = wi::to_wide (n);
>>>> +  else if (TREE_CODE (n) != SSA_NAME
>>>> +          || get_range_info (n, rng2 + 1, rng2 + 1) != VR_RANGE)
>>>> +    return NULL_TREE;
>>> Should that 2nd call to get_range_info be "get_range_info (n, rng2, rng2
>>> + 1)?  I don't think it makes any difference in practice due to the
>>> implementation of get_range_info, but if it wasn't intentional let's get
>>> it fixed.
>>
>> Yes, it should be.  It's correct in my tree but didn't post
>> the updated revision.
>>
>>>
>>>
>>>> Index: gcc/builtins.h
>>>> ===================================================================
>>>> --- gcc/builtins.h      (revision 277978)
>>>> +++ gcc/builtins.h      (working copy)
>>>> @@ -134,7 +134,8 @@ extern tree fold_call_stmt (gcall *, bool);
>>>>    extern void set_builtin_user_assembler_name (tree decl, const char *asmspec);
>>>>    extern bool is_simple_builtin (tree);
>>>>    extern bool is_inexpensive_builtin (tree);
>>>> -extern tree compute_objsize (tree, int, tree * = NULL);
>>>> +tree gimple_call_alloc_size (gimple *);
>>>> +extern tree compute_objsize (tree, int, tree * = NULL, tree * = NULL);
>>>>
>>>>    extern bool readonly_data_expr (tree exp);
>>>>    extern bool init_target_chars (void);
>>> Is there a reason there's no "extern" on the gimple_call_alloc_size
>>> prototype?
>>
>> I'm sure it was copied and pasted from the definition.  It makes
>> no difference either way so it didn't get caught by anything.
>>
>>>
>>> I think this is fine with those nits fixed.  You'll have a minor merge
>>> conflict with the compute_objsize changes due to recent fixes in the
>>> same hunk of code, but I don't think it warrants reposting/resubmission.
>>>
>>
>> I've fixed the nits above and committed r278983 after retesting.
>>
> 
> Hi Martin,
> 
> I've noticed that this patch introduces a new FAIL:
> gcc.dg/Warray-bounds-56.c  (test for warnings, line 82)
> when GCC is configured with:
> --target arm-none-linux-gnueabihf
> --with-mode arm
> --with-cpu cortex-a5
> --with-fpu vfpv3-d16-fp16
> 
> This test passes when using cortex-a9, a15, a57.

Thanks for the heads up.  It's most likely due to the same issue
as PR 92829 (seen on powerpc64*) that I started looking into
yesterday.

Martin

> 
> Christophe
> 
> 
>> This is an improvement in the buffer overflow detection but there
>> is still the (arguably more important) second half of it:
>> extending the strlen pass to detect the overflow that cannot be
>> caught later (e.g., all stores by MEM_REFs are only handled in
>> strlen): https://gcc.gnu.org/ml/gcc-patches/2019-11/msg02340.html
>>
>> Martin

Patch
diff mbox series

PR middle-end/91582 - missing heap overflow detection for strcpy

gcc/ChangeLog:

	* builtins.c (gimple_call_alloc_size): New function.
	(compute_objsize): Add argument.  Call gimple_call_alloc_size.
	Handle variable offsets and indices.
	* builtins.h (gimple_call_alloc_size): Declare.
	(compute_objsize): Add argument.
	* tree-ssa-strlen.c (handle_store): Handle calls to allocated objects.

gcc/testsuite/ChangeLog:

	* c-c++-common/Wstringop-truncation.c: Remove xfails.
	* gcc/testsuite/g++.dg/ext/attr-alloc_size.C: Suppress -Warray-bounds.
	* gcc.dg/Wstringop-overflow-22.c: New test.
	* gcc/testsuite/gcc.dg/attr-alloc_size.c: Suppress -Warray-bounds.
	* gcc/testsuite/gcc.dg/attr-copy-2.c: Same.
	* gcc.dg/builtin-stringop-chk-5.c: Remove xfails.
	* gcc.dg/builtin-stringop-chk-8.c: Same.  Correct the text of expected
	warnings.
	* gcc.target/i386/pr82002-2a.c: Prune expected warning.
	* gcc.target/i386/pr82002-2b.c: Same.

Index: gcc/builtins.c
===================================================================
--- gcc/builtins.c	(revision 277978)
+++ gcc/builtins.c	(working copy)
@@ -3563,6 +3563,80 @@  check_access (tree exp, tree, tree, tree dstwrite,
   return true;
 }
 
+/* If STMT is a call to an allocation function, returns the size
+   of the object allocated by the call.  */
+
+tree
+gimple_call_alloc_size (gimple *stmt)
+{
+  if (!stmt)
+    return NULL_TREE;
+
+  tree allocfntype;
+  if (tree fndecl = gimple_call_fndecl (stmt))
+    allocfntype = TREE_TYPE (fndecl);
+  else
+    allocfntype = gimple_call_fntype (stmt);
+
+  if (!allocfntype)
+    return NULL_TREE;
+
+  unsigned argidx1 = UINT_MAX, argidx2 = UINT_MAX;
+  tree at = lookup_attribute ("alloc_size", TYPE_ATTRIBUTES (allocfntype));
+  if (!at)
+    {
+      if (!gimple_call_builtin_p (stmt, BUILT_IN_ALLOCA_WITH_ALIGN))
+	return NULL_TREE;
+
+      argidx1 = 0;
+    }
+
+  unsigned nargs = gimple_call_num_args (stmt);
+
+  if (argidx1 == UINT_MAX)
+    {
+      tree atval = TREE_VALUE (at);
+      if (!atval)
+	return NULL_TREE;
+
+      argidx1 = TREE_INT_CST_LOW (TREE_VALUE (atval)) - 1;
+      if (nargs <= argidx1)
+	return NULL_TREE;
+
+      atval = TREE_CHAIN (atval);
+      if (atval)
+	{
+	  argidx2 = TREE_INT_CST_LOW (TREE_VALUE (atval)) - 1;
+	  if  (nargs <= argidx2)
+	    return NULL_TREE;
+	}
+    }
+
+  tree size = gimple_call_arg (stmt, argidx1);
+  tree n = argidx2 < nargs ? gimple_call_arg (stmt, argidx2) : integer_one_node;
+
+  /* To handle ranges do the math in wide_int and return the product
+     of the upper bounds as a constant.  Ignore anti-ranges.  */
+  wide_int rng1[2];
+  if (TREE_CODE (size) == INTEGER_CST)
+    rng1[0] = rng1[1] = wi::to_wide (size);
+  else if (TREE_CODE (size) != SSA_NAME
+	   || get_range_info (size, rng1, rng1 + 1) != VR_RANGE)
+    return NULL_TREE;
+
+  wide_int rng2[2];
+  if (TREE_CODE (n) == INTEGER_CST)
+    rng2[0] = rng2[1] = wi::to_wide (n);
+  else if (TREE_CODE (n) != SSA_NAME
+	   || get_range_info (n, rng2 + 1, rng2 + 1) != VR_RANGE)
+    return NULL_TREE;
+
+  const int prec = TYPE_PRECISION (sizetype);
+  rng1[1] = wide_int::from (rng1[1], prec, UNSIGNED);
+  rng2[1] = wide_int::from (rng2[1], prec, UNSIGNED);
+  return wide_int_to_tree (sizetype, rng1[1] * rng2[1]);
+}
+
 /* Helper to compute the size of the object referenced by the DEST
    expression which must have pointer type, using Object Size type
    OSTYPE (only the least significant 2 bits are used).  Return
@@ -3571,17 +3645,23 @@  check_access (tree exp, tree, tree, tree dstwrite,
    a non-constant offset in some range the returned value represents
    the largest size given the smallest non-negative offset in the
    range.  If nonnull, set *PDECL to the decl of the referenced
-   subobject if it can be determined, or to null otherwise.
+   subobject if it can be determined, or to null otherwise.  Likewise,
+   when POFF is nonnull *POFF is set to the offset into *PDECL.
    The function is intended for diagnostics and should not be used
    to influence code generation or optimization.  */
 
 tree
-compute_objsize (tree dest, int ostype, tree *pdecl /* = NULL */)
+compute_objsize (tree dest, int ostype, tree *pdecl /* = NULL */,
+		 tree *poff /* = NULL */)
 {
-  tree dummy = NULL_TREE;
+  tree dummy_decl = NULL_TREE;
   if (!pdecl)
-    pdecl = &dummy;
+    pdecl = &dummy_decl;
 
+  tree dummy_off = size_zero_node;
+  if (!poff)
+    poff = &dummy_off;
+
   unsigned HOST_WIDE_INT size;
 
   /* Only the two least significant bits are meaningful.  */
@@ -3593,6 +3673,13 @@  tree
   if (TREE_CODE (dest) == SSA_NAME)
     {
       gimple *stmt = SSA_NAME_DEF_STMT (dest);
+      if (is_gimple_call (stmt))
+	{
+	  /* If STMT is a call to an allocation function get the size
+	     from its argument(s).  */
+	  return gimple_call_alloc_size (stmt);
+	}
+
       if (!is_gimple_assign (stmt))
 	return NULL_TREE;
 
@@ -3608,7 +3695,7 @@  tree
 	  tree off = gimple_assign_rhs2 (stmt);
 	  if (TREE_CODE (off) == INTEGER_CST)
 	    {
-	      if (tree size = compute_objsize (dest, ostype, pdecl))
+	      if (tree size = compute_objsize (dest, ostype, pdecl, poff))
 		{
 		  wide_int wioff = wi::to_wide (off);
 		  wide_int wisiz = wi::to_wide (size);
@@ -3619,10 +3706,16 @@  tree
 		  if (wi::sign_mask (wioff))
 		    ;
 		  else if (wi::ltu_p (wioff, wisiz))
-		    return wide_int_to_tree (TREE_TYPE (size),
-					     wi::sub (wisiz, wioff));
+		    {
+		      *poff = size_binop (PLUS_EXPR, *poff, off);
+		      return wide_int_to_tree (TREE_TYPE (size),
+					       wi::sub (wisiz, wioff));
+		    }
 		  else
-		    return size_zero_node;
+		    {
+		      *poff = size_binop (PLUS_EXPR, *poff, off);
+		      return size_zero_node;
+		    }
 		}
 	    }
 	  else if (TREE_CODE (off) == SSA_NAME
@@ -3644,10 +3737,18 @@  tree
 			  || wi::sign_mask (max))
 			;
 		      else if (wi::ltu_p (min, wisiz))
-			return wide_int_to_tree (TREE_TYPE (size),
-						 wi::sub (wisiz, min));
+			{
+			  *poff = size_binop (PLUS_EXPR, *poff,
+					      wide_int_to_tree (sizetype, min));
+			  return wide_int_to_tree (TREE_TYPE (size),
+						   wi::sub (wisiz, min));
+			}
 		      else
-			return size_zero_node;
+			{
+			  *poff = size_binop (PLUS_EXPR, *poff,
+					      wide_int_to_tree (sizetype, min));
+			  return size_zero_node;
+			}
 		    }
 		}
 	    }
@@ -3666,19 +3767,17 @@  tree
     {
       tree ref = TREE_OPERAND (dest, 0);
       tree off = TREE_OPERAND (dest, 1);
-      if (tree size = compute_objsize (ref, ostype, pdecl))
+      if (tree size = compute_objsize (ref, ostype, pdecl, poff))
 	{
-	  /* If the declaration of the destination object is known
-	     to have zero size, return zero.  */
-	  if (integer_zerop (size))
-	    return integer_zero_node;
+	  /* A valid offset into a declared object cannot be negative.  */
+	  if (tree_int_cst_sgn (*poff) < 0)
+	    return size_zero_node;
 
-	  if (TREE_CODE (off) != INTEGER_CST
-	      || TREE_CODE (size) != INTEGER_CST)
-	    return NULL_TREE;
-
+	  /* Adjust SIZE either up or down by the sum of *POFF and OFF
+	     above.  */
 	  if (TREE_CODE (dest) == ARRAY_REF)
 	    {
+	      /* Convert the array index into a byte offset.  */
 	      tree eltype = TREE_TYPE (dest);
 	      tree tpsize = TYPE_SIZE_UNIT (eltype);
 	      if (tpsize && TREE_CODE (tpsize) == INTEGER_CST)
@@ -3687,9 +3786,74 @@  tree
 		return NULL_TREE;
 	    }
 
-	  if (tree_int_cst_lt (off, size))
-	    return fold_build2 (MINUS_EXPR, size_type_node, size, off);
-	  return integer_zero_node;
+	  wide_int offrng[2];
+	  if (TREE_CODE (off) == INTEGER_CST)
+	    offrng[0] = offrng[1] = wi::to_wide (off);
+	  else if (TREE_CODE (off) == SSA_NAME)
+	    {
+	      wide_int min, max;
+	      enum value_range_kind rng
+		= get_range_info (off, offrng, offrng + 1);
+	      if (rng != VR_RANGE)
+		return NULL_TREE;
+	    }
+	  else
+	    return NULL_TREE;
+
+	  /* Convert to the same precision to keep wide_int from "helpfuly"
+	     crashing whenever it sees other argumments.  */
+	  offrng[0] = wide_int::from (offrng[0], ADDR_MAX_BITSIZE, SIGNED);
+	  offrng[1] = wide_int::from (offrng[1], ADDR_MAX_BITSIZE, SIGNED);
+
+	  tree dstoff = *poff;
+	  if (integer_zerop (*poff))
+	    *poff = off;
+	  else if (!integer_zerop (off))
+	    {
+	      *poff = fold_convert (ptrdiff_type_node, *poff);
+	      off = fold_convert (ptrdiff_type_node, off);
+	      *poff = size_binop (PLUS_EXPR, *poff, off);
+	    }
+
+	  if (wi::sign_mask (offrng[0]) >= 0)
+	    {
+	      if (TREE_CODE (size) != INTEGER_CST)
+		return NULL_TREE;
+
+	      /* Return the difference between the size and the offset
+		 or zero if the offset is greater.  */
+	      wide_int wisize = wi::to_wide (size, ADDR_MAX_BITSIZE);
+	      if (wi::ltu_p (wisize, offrng[0]))
+		return size_zero_node;
+
+	      return wide_int_to_tree (sizetype, wisize - offrng[0]);
+	    }
+
+	  wide_int dstoffrng[2];
+	  if (TREE_CODE (dstoff) == INTEGER_CST)
+	    dstoffrng[0] = dstoffrng[1] = wi::to_wide (dstoff);
+	  else if (TREE_CODE (dstoff) == SSA_NAME)
+	    {
+	      enum value_range_kind rng
+		= get_range_info (dstoff, dstoffrng, dstoffrng + 1);
+	      if (rng != VR_RANGE)
+		return NULL_TREE;
+	    }
+	  else
+	    return NULL_TREE;
+
+	  dstoffrng[0] = wide_int::from (dstoffrng[0], ADDR_MAX_BITSIZE, SIGNED);
+	  dstoffrng[1] = wide_int::from (dstoffrng[1], ADDR_MAX_BITSIZE, SIGNED);
+
+	  wide_int declsize = wi::to_wide (size);
+	  if (wi::sign_mask (dstoffrng[0]) > 0)
+	    declsize += dstoffrng[0];
+
+	  offrng[1] += dstoffrng[1];
+	  if (wi::sign_mask (offrng[1]) < 0)
+	    return size_zero_node;
+
+	  return wide_int_to_tree (sizetype, declsize);
 	}
 
       return NULL_TREE;
@@ -3701,14 +3865,13 @@  tree
       return component_ref_size (dest);
     }
 
-  if (TREE_CODE (dest) != ADDR_EXPR)
-    return NULL_TREE;
+  if (TREE_CODE (dest) == ADDR_EXPR)
+    dest = TREE_OPERAND (dest, 0);
 
-  tree ref = TREE_OPERAND (dest, 0);
-  if (DECL_P (ref))
+  if (DECL_P (dest))
     {
-      *pdecl = ref;
-      return DECL_SIZE_UNIT (ref);
+      *pdecl = dest;
+      return DECL_SIZE_UNIT (dest);
     }
 
   tree type = TREE_TYPE (dest);
@@ -3718,7 +3881,7 @@  tree
   type = TYPE_MAIN_VARIANT (type);
 
   if (TREE_CODE (type) == ARRAY_TYPE
-      && !array_at_struct_end_p (ref))
+      && !array_at_struct_end_p (dest))
     {
       if (tree size = TYPE_SIZE_UNIT (type))
 	return TREE_CODE (size) == INTEGER_CST ? size : NULL_TREE;
Index: gcc/builtins.h
===================================================================
--- gcc/builtins.h	(revision 277978)
+++ gcc/builtins.h	(working copy)
@@ -134,7 +134,8 @@  extern tree fold_call_stmt (gcall *, bool);
 extern void set_builtin_user_assembler_name (tree decl, const char *asmspec);
 extern bool is_simple_builtin (tree);
 extern bool is_inexpensive_builtin (tree);
-extern tree compute_objsize (tree, int, tree * = NULL);
+tree gimple_call_alloc_size (gimple *);
+extern tree compute_objsize (tree, int, tree * = NULL, tree * = NULL);
 
 extern bool readonly_data_expr (tree exp);
 extern bool init_target_chars (void);
Index: gcc/testsuite/c-c++-common/Wstringop-truncation.c
===================================================================
--- gcc/testsuite/c-c++-common/Wstringop-truncation.c	(revision 277978)
+++ gcc/testsuite/c-c++-common/Wstringop-truncation.c	(working copy)
@@ -300,8 +300,7 @@  void test_strncpy_array (Dest *pd, int i, const ch
   CPY (pd->a5, s, 5);               /* { dg-warning "specified bound 5 equals destination size" } */
   CPY (pd->a5, s, sizeof pd->a5);   /* { dg-warning "specified bound 5 equals destination size" } */
 
-  /* The following is not yet handled.  */
-  CPY (pd->a5 + i, s, sizeof pd->a5);   /* { dg-warning "specified bound 5 equals destination size" "member array" { xfail *-*-* } } */
+  CPY (pd->a5 + i, s, sizeof pd->a5);   /* { dg-warning "specified bound 5 equals destination size" "member array" } */
 
   /* Verify that a copy that nul-terminates is not diagnosed.  */
   CPY (pd->a5, "1234", sizeof pd->a5);
@@ -425,7 +424,7 @@  void test_strncpy_alloc (const char* s)
   size_t n = 7;
   char *d = (char *)__builtin_malloc (n);
 
-  CPY (d, s, n);                    /* { dg-warning "specified bound 7 equals destination size" "bug 79016" { xfail *-*-* } } */
+  CPY (d, s, n);                    /* { dg-warning "specified bound 7 equals destination size" } */
 
   Dest *pd = (Dest *)__builtin_malloc (sizeof *pd * n);
   CPY (pd->a5, s, 5);               /* { dg-warning "specified bound 5 equals destination size" } */
Index: gcc/testsuite/g++.dg/ext/attr-alloc_size.C
===================================================================
--- gcc/testsuite/g++.dg/ext/attr-alloc_size.C	(revision 277978)
+++ gcc/testsuite/g++.dg/ext/attr-alloc_size.C	(working copy)
@@ -1,6 +1,6 @@ 
 /* PR c++/87541 - ICE using a constant decl as an attribute alloc_size argument
    { dg-do compile }
-   { dg-options "-O2 -Wall" } */
+   { dg-options "-O2 -Wall -Wno-array-bounds" } */
 
 #define ALLOC_SIZE(N)   __attribute__ ((alloc_size (N)))
 
Index: gcc/testsuite/gcc.dg/Wstringop-overflow-22.c
===================================================================
--- gcc/testsuite/gcc.dg/Wstringop-overflow-22.c	(nonexistent)
+++ gcc/testsuite/gcc.dg/Wstringop-overflow-22.c	(working copy)
@@ -0,0 +1,285 @@ 
+/* PR middle-end/91582 - missing heap overflow detection for strcpy
+   { dg-do compile }
+   { dg-options "-O2 -Wall -Wno-array-bounds -ftrack-macro-expansion=0" } */
+
+#include "range.h"
+
+#define ATTR(...)   __attribute__ ((__VA_ARGS__))
+#define NOIPA       ATTR (noipa)
+
+extern void* alloca (size_t);
+extern void* calloc (size_t, size_t);
+extern void* malloc (size_t);
+
+extern ATTR (alloc_size (1), malloc) void*
+  alloc1 (size_t, int);
+extern ATTR (alloc_size (2), malloc) void*
+  alloc2 (int, size_t);
+extern ATTR (alloc_size (2, 4), malloc) void*
+  alloc2_4 (int, size_t, int, size_t);
+
+extern char* strcpy (char*, const char*);
+
+void sink (void*);
+
+#define S36 "0123456789abcdefghijklmnopqrstuvwxyz"
+#define S(N) (S36 + sizeof S36 - N - 1)
+
+#define T(src, alloc) do {			\
+    char *s = src;				\
+    char *d = alloc;				\
+    strcpy (d, s);				\
+    sink (d);					\
+  } while (0)
+
+
+NOIPA void test_strcpy_alloca (void)
+{
+  size_t r_0_1 = UR (0, 1);
+  size_t r_1_2 = UR (1, 2);
+  size_t r_2_3 = UR (2, 3);
+
+  T (S (0), alloca (r_0_1));
+  T (S (1), alloca (r_0_1));      // { dg-warning "\\\[-Wstringop-overflow" }
+
+  T (S (0), alloca (r_1_2));
+  T (S (1), alloca (r_1_2));
+  T (S (2), alloca (r_1_2));      // { dg-warning "\\\[-Wstringop-overflow" }
+
+  T (S (0), alloca (r_2_3));
+  T (S (2), alloca (r_2_3));
+  T (S (3), alloca (r_2_3));      // { dg-warning "\\\[-Wstringop-overflow" }
+  T (S (9), alloca (r_2_3));      // { dg-warning "\\\[-Wstringop-overflow" }
+}
+
+NOIPA void test_strcpy_calloc (void)
+{
+  size_t r_1_2 = UR (1, 2);
+  size_t r_2_3 = UR (2, 3);
+
+  T (S (0), calloc (r_1_2, 1));
+  T (S (1), calloc (r_1_2, 1));
+  T (S (2), calloc (r_1_2, 1));   // { dg-warning "\\\[-Wstringop-overflow" }
+
+  T (S (2), calloc (r_2_3, 1));
+  T (S (3), calloc (r_2_3, 1));   // { dg-warning "\\\[-Wstringop-overflow" }
+
+  T (S (0), calloc (1, r_1_2));
+  T (S (1), calloc (1, r_1_2));
+  T (S (2), calloc (1, r_1_2));   // { dg-warning "\\\[-Wstringop-overflow" }
+
+  T (S (2), calloc (1, r_2_3));
+  T (S (3), calloc (1, r_2_3));   // { dg-warning "\\\[-Wstringop-overflow" }
+
+  T (S (0), calloc (r_1_2, 2));
+  T (S (1), calloc (r_1_2, 2));
+  T (S (2), calloc (r_1_2, 2));
+  T (S (3), calloc (r_1_2, 2));
+  T (S (4), calloc (r_1_2, 2));   // { dg-warning "\\\[-Wstringop-overflow" }
+
+  T (S (0), calloc (r_2_3, 2));
+  T (S (1), calloc (r_2_3, 2));
+  T (S (2), calloc (r_2_3, 2));
+  T (S (5), calloc (r_2_3, 2));
+  T (S (6), calloc (r_2_3, 2));   // { dg-warning "\\\[-Wstringop-overflow" }
+
+  T (S (0), calloc (r_1_2, 2));
+  T (S (1), calloc (r_1_2, 2));
+  T (S (2), calloc (r_1_2, 2));
+  T (S (3), calloc (r_1_2, 2));
+  T (S (4), calloc (r_1_2, 2));   // { dg-warning "\\\[-Wstringop-overflow" }
+
+  T (S (0), calloc (r_2_3, 2));
+  T (S (1), calloc (r_2_3, 2));
+  T (S (2), calloc (r_2_3, 2));
+  T (S (5), calloc (r_2_3, 2));
+  T (S (6), calloc (r_2_3, 2));   // { dg-warning "\\\[-Wstringop-overflow" }
+
+  T (S (0), calloc (r_1_2, r_2_3));
+  T (S (1), calloc (r_1_2, r_2_3));
+  T (S (2), calloc (r_1_2, r_2_3));
+  T (S (3), calloc (r_1_2, r_2_3));
+  T (S (4), calloc (r_1_2, r_2_3));
+  T (S (5), calloc (r_1_2, r_2_3));
+  T (S (6), calloc (r_1_2, r_2_3));   // { dg-warning "\\\[-Wstringop-overflow" }
+  T (S (9), calloc (r_1_2, r_2_3));   // { dg-warning "\\\[-Wstringop-overflow" }
+}
+
+
+NOIPA void test_strcpy_malloc (void)
+{
+  size_t r_0_1 = UR (0, 1);
+  size_t r_1_2 = UR (1, 2);
+  size_t r_2_3 = UR (2, 3);
+
+  T (S (0), malloc (r_0_1));
+  T (S (1), malloc (r_0_1));      // { dg-warning "\\\[-Wstringop-overflow" }
+
+  T (S (0), malloc (r_1_2));
+  T (S (1), malloc (r_1_2));
+  T (S (2), malloc (r_1_2));      // { dg-warning "\\\[-Wstringop-overflow" }
+
+  T (S (0), malloc (r_2_3));
+  T (S (2), malloc (r_2_3));
+  T (S (3), malloc (r_2_3));      // { dg-warning "\\\[-Wstringop-overflow" }
+  T (S (9), malloc (r_2_3));      // { dg-warning "\\\[-Wstringop-overflow" }
+}
+
+
+NOIPA void test_strcpy_alloc1 (void)
+{
+  size_t r_0_1 = UR (0, 1);
+  size_t r_1_2 = UR (1, 2);
+  size_t r_2_3 = UR (2, 3);
+
+#define alloc1(n) alloc1 (n, 1)
+
+  T (S (0), alloc1 (r_0_1));
+  T (S (1), alloc1 (r_0_1));      // { dg-warning "\\\[-Wstringop-overflow" }
+
+  T (S (0), alloc1 (r_1_2));
+  T (S (1), alloc1 (r_1_2));
+  T (S (2), alloc1 (r_1_2));      // { dg-warning "\\\[-Wstringop-overflow" }
+
+  T (S (0), alloc1 (r_2_3));
+  T (S (2), alloc1 (r_2_3));
+  T (S (3), alloc1 (r_2_3));      // { dg-warning "\\\[-Wstringop-overflow" }
+  T (S (9), alloc1 (r_2_3));      // { dg-warning "\\\[-Wstringop-overflow" }
+}
+
+NOIPA void test_strcpy_alloc2 (void)
+{
+  size_t r_0_1 = UR (0, 1);
+  size_t r_1_2 = UR (1, 2);
+  size_t r_2_3 = UR (2, 3);
+
+#define alloc2(n) alloc2 (1, n)
+
+  T (S (0), alloc1 (r_0_1));
+  T (S (1), alloc1 (r_0_1));      // { dg-warning "\\\[-Wstringop-overflow" }
+
+  T (S (0), alloc1 (r_1_2));
+  T (S (1), alloc1 (r_1_2));
+  T (S (2), alloc1 (r_1_2));      // { dg-warning "\\\[-Wstringop-overflow" }
+
+  T (S (0), alloc1 (r_2_3));
+  T (S (2), alloc1 (r_2_3));
+  T (S (3), alloc1 (r_2_3));      // { dg-warning "\\\[-Wstringop-overflow" }
+  T (S (9), alloc1 (r_2_3));      // { dg-warning "\\\[-Wstringop-overflow" }
+}
+
+
+NOIPA void test_strcpy_alloc2_4 (void)
+{
+  size_t r_1_2 = UR (1, 2);
+  size_t r_2_3 = UR (2, 3);
+
+#define alloc2_4(n1, n2) alloc2_4 (1, n1, 2, n2)
+
+  T (S (0), alloc2_4 (r_1_2, 1));
+  T (S (1), alloc2_4 (r_1_2, 1));
+  T (S (2), alloc2_4 (r_1_2, 1));   // { dg-warning "\\\[-Wstringop-overflow" }
+
+  T (S (2), alloc2_4 (r_2_3, 1));
+  T (S (3), alloc2_4 (r_2_3, 1));   // { dg-warning "\\\[-Wstringop-overflow" }
+
+  T (S (0), alloc2_4 (1, r_1_2));
+  T (S (1), alloc2_4 (1, r_1_2));
+  T (S (2), alloc2_4 (1, r_1_2));   // { dg-warning "\\\[-Wstringop-overflow" }
+
+  T (S (2), alloc2_4 (1, r_2_3));
+  T (S (3), alloc2_4 (1, r_2_3));   // { dg-warning "\\\[-Wstringop-overflow" }
+
+  T (S (0), alloc2_4 (r_1_2, 2));
+  T (S (1), alloc2_4 (r_1_2, 2));
+  T (S (2), alloc2_4 (r_1_2, 2));
+  T (S (3), alloc2_4 (r_1_2, 2));
+  T (S (4), alloc2_4 (r_1_2, 2));   // { dg-warning "\\\[-Wstringop-overflow" }
+
+  T (S (0), alloc2_4 (r_2_3, 2));
+  T (S (1), alloc2_4 (r_2_3, 2));
+  T (S (2), alloc2_4 (r_2_3, 2));
+  T (S (5), alloc2_4 (r_2_3, 2));
+  T (S (6), alloc2_4 (r_2_3, 2));   // { dg-warning "\\\[-Wstringop-overflow" }
+
+  T (S (0), alloc2_4 (r_1_2, 2));
+  T (S (1), alloc2_4 (r_1_2, 2));
+  T (S (2), alloc2_4 (r_1_2, 2));
+  T (S (3), alloc2_4 (r_1_2, 2));
+  T (S (4), alloc2_4 (r_1_2, 2));   // { dg-warning "\\\[-Wstringop-overflow" }
+
+  T (S (0), alloc2_4 (r_2_3, 2));
+  T (S (1), alloc2_4 (r_2_3, 2));
+  T (S (2), alloc2_4 (r_2_3, 2));
+  T (S (5), alloc2_4 (r_2_3, 2));
+  T (S (6), alloc2_4 (r_2_3, 2));   // { dg-warning "\\\[-Wstringop-overflow" }
+
+  T (S (0), alloc2_4 (r_1_2, r_2_3));
+  T (S (1), alloc2_4 (r_1_2, r_2_3));
+  T (S (2), alloc2_4 (r_1_2, r_2_3));
+  T (S (3), alloc2_4 (r_1_2, r_2_3));
+  T (S (4), alloc2_4 (r_1_2, r_2_3));
+  T (S (5), alloc2_4 (r_1_2, r_2_3));
+  T (S (6), alloc2_4 (r_1_2, r_2_3));   // { dg-warning "\\\[-Wstringop-overflow" }
+  T (S (9), alloc2_4 (r_1_2, r_2_3));   // { dg-warning "\\\[-Wstringop-overflow" }
+}
+
+#undef T
+#define T(T, src, n) do {			\
+    char *s = src;				\
+    T vla[n];					\
+    char *d = (char*)vla;			\
+    strcpy (d, s);				\
+    sink (vla);					\
+  } while (0)
+
+NOIPA void test_strcpy_vla (void)
+{
+  size_t r_0_1 = UR (0, 1);
+  size_t r_1_2 = UR (1, 2);
+  size_t r_2_3 = UR (2, 3);
+
+  T (char, S (0), r_0_1);
+  T (char, S (1), r_0_1);       // { dg-warning "\\\[-Wstringop-overflow" }
+
+  T (char, S (0), r_1_2);
+  T (char, S (1), r_1_2);
+  T (char, S (2), r_1_2);       // { dg-warning "\\\[-Wstringop-overflow" }
+
+  T (char, S (0), r_2_3);
+  T (char, S (2), r_2_3);
+  T (char, S (3), r_2_3);       // { dg-warning "\\\[-Wstringop-overflow" }
+  T (char, S (9), r_2_3);       // { dg-warning "\\\[-Wstringop-overflow" }
+
+#ifdef __INT16_TYPE__
+  typedef __INT16_TYPE__ int16_t;
+
+  T (int16_t, S (0), r_1_2);
+  T (int16_t, S (2), r_1_2);
+  T (int16_t, S (3), r_1_2);
+  T (int16_t, S (4), r_1_2);    // { dg-warning "\\\[-Wstringop-overflow" }
+  T (int16_t, S (5), r_1_2);    // { dg-warning "\\\[-Wstringop-overflow" }
+  T (int16_t, S (9), r_1_2);    // { dg-warning "\\\[-Wstringop-overflow" }
+
+  T (int16_t, S (0), r_2_3);
+  T (int16_t, S (2), r_2_3);
+  T (int16_t, S (3), r_2_3);
+  T (int16_t, S (4), r_2_3);
+  T (int16_t, S (5), r_2_3);
+  T (int16_t, S (6), r_2_3);    // { dg-warning "\\\[-Wstringop-overflow" }
+#endif
+
+#ifdef __INT32_TYPE__
+  typedef __INT32_TYPE__ int32_t;
+
+  T (int32_t, S ( 0), r_2_3);
+  T (int32_t, S ( 2), r_2_3);
+  T (int32_t, S ( 3), r_2_3);
+  T (int32_t, S ( 4), r_2_3);
+  T (int32_t, S ( 5), r_2_3);
+  T (int32_t, S ( 6), r_2_3);
+  T (int32_t, S (11), r_2_3);
+  T (int32_t, S (12), r_2_3);    // { dg-warning "\\\[-Wstringop-overflow" }
+  T (int32_t, S (36), r_2_3);    // { dg-warning "\\\[-Wstringop-overflow" }
+#endif
+}
Index: gcc/testsuite/gcc.dg/attr-alloc_size.c
===================================================================
--- gcc/testsuite/gcc.dg/attr-alloc_size.c	(revision 277978)
+++ gcc/testsuite/gcc.dg/attr-alloc_size.c	(working copy)
@@ -1,5 +1,5 @@ 
 /* { dg-do compile } */
-/* { dg-options "-O2 -Wall -ftrack-macro-expansion=0" } */
+/* { dg-options "-O2 -Wall -Wno-array-bounds -ftrack-macro-expansion=0" } */
 
 extern void abort (void);
 
Index: gcc/testsuite/gcc.dg/attr-copy-2.c
===================================================================
--- gcc/testsuite/gcc.dg/attr-copy-2.c	(revision 277978)
+++ gcc/testsuite/gcc.dg/attr-copy-2.c	(working copy)
@@ -2,7 +2,7 @@ 
    Exercise attribute copy for functions.
    { dg-do compile }
    { dg-require-alias "" }
-   { dg-options "-O2 -Wall" } */
+   { dg-options "-O2 -Wall -Wno-array-bounds" } */
 
 #define Assert(expr)   typedef char AssertExpr[2 * !!(expr) - 1]
 
Index: gcc/testsuite/gcc.dg/builtin-stringop-chk-5.c
===================================================================
--- gcc/testsuite/gcc.dg/builtin-stringop-chk-5.c	(revision 277978)
+++ gcc/testsuite/gcc.dg/builtin-stringop-chk-5.c	(working copy)
@@ -1,4 +1,4 @@ 
-/* Test exercising -Wrawmem-overflow and -Wstringop-overflow warnings.  */
+/* Test exercising -Wstringop-overflow warnings.  */
 /* { dg-do compile } */
 /* { dg-options "-O2 -Wstringop-overflow=1" } */
 
@@ -49,7 +49,7 @@  void test_memop_warn_local (const void *src)
   memcpy (a, src, n);   /* { dg-warning "writing between 8 and 32 bytes into a region of size 4 overflows the destination" } */
   escape (a, src);
 
-  /* At -Wrawmem-overflow=1 the destination is considered to be
+  /* At -Wstringop-overflow=1 the destination is considered to be
      the whole array and its size is therefore sizeof a.  */
   memcpy (&a[0], src, n);   /* { dg-warning "writing between 8 and 32 bytes into a region of size 4 overflows the destination" } */
   escape (a, src);
@@ -110,12 +110,12 @@  void test_memop_warn_alloc (const void *src)
 
   struct A *a = __builtin_malloc (sizeof *a * 2);
 
-  memcpy (a, src, n);   /* { dg-warning "writing between 8 and 32 bytes into a region of size 4 overflows the destination" "memcpy into allocated" { xfail *-*-*} } */
+  memcpy (a, src, n);   /* { dg-warning "writing between 8 and 32 bytes into a region of size 4 overflows the destination" "memcpy into allocated" } */
   escape (a, src);
 
-  /* At -Wrawmem-overflow=1 the destination is considered to be
+  /* At -Wstringop-overflow=1 the destination is considered to be
      the whole array and its size is therefore sizeof a.  */
-  memcpy (&a[0], src, n);   /* { dg-warning "writing between 8 and 32 bytes into a region of size 4 overflows the destination" "memcpy into allocated" { xfail *-*-*} } */
+  memcpy (&a[0], src, n);   /* { dg-warning "writing between 8 and 32 bytes into a region of size 4 overflows the destination" "memcpy into allocated" } */
   escape (a, src);
 
   /* Verify the same as above but by writing into the first mmeber
@@ -127,7 +127,7 @@  void test_memop_warn_alloc (const void *src)
 
   struct B *b = __builtin_malloc (sizeof *b * 2);
 
-  memcpy (&b[0], src, n);   /* { dg-warning "writing between 12 and 32 bytes into a region of size 8 overflows the destination" "memcpy into allocated" { xfail *-*-*} } */
+  memcpy (&b[0], src, n);   /* { dg-warning "writing between 12 and 32 bytes into a region of size 8 overflows the destination" "memcpy into allocated" } */
   escape (b);
 
   /* The following idiom of clearing multiple members of a struct is
Index: gcc/testsuite/gcc.dg/builtin-stringop-chk-8.c
===================================================================
--- gcc/testsuite/gcc.dg/builtin-stringop-chk-8.c	(revision 277978)
+++ gcc/testsuite/gcc.dg/builtin-stringop-chk-8.c	(working copy)
@@ -102,9 +102,9 @@  void test_memop_warn_alloc (void *p)
 
   struct A *a = __builtin_malloc (sizeof *a * 2);
 
-  memcpy (p, a, n);   /* { dg-warning "reading between 8 and 32 bytes from region of size 4" "memcpy from allocated" { xfail *-*-*} } */
+  memcpy (p, a, n);   /* { dg-warning "reading between 8 and 32 bytes from a region of size 4" "memcpy from allocated" } */
 
-  memcpy (p, &a[0], n);   /* { dg-warning "reading between 8 and 32 bytes from a region of size 4" "memcpy from allocated" { xfail *-*-*} } */
+  memcpy (p, &a[0], n);   /* { dg-warning "reading between 8 and 32 bytes from a region of size 4" "memcpy from allocated" } */
 
   memcpy (p, &a[0].a, n);   /* { dg-warning "reading between 8 and 32 bytes from a region of size 4" "memcpy from allocated" { xfail *-*-*} } */
 
@@ -112,13 +112,13 @@  void test_memop_warn_alloc (void *p)
 
   struct B *b = __builtin_malloc (sizeof *b * 2);
 
-  memcpy (p, &b[0], n);   /* { dg-warning "reading between 12 and 32 bytes from a region of size 8" "memcpy from allocated" { xfail *-*-*} } */
+  memcpy (p, &b[0], n);   /* { dg-warning "reading between 12 and 32 bytes from a region of size 8" "memcpy from allocated" } */
 
   /* Verify memchr/memcmp.  */
   n = sizeof *b * 2 + 1;
 
-  memchr (b, 1, n);   /* { dg-warning "reading 5 bytes from a region of size 4" "memcmp from allocated" { xfail *-*-* } } */
-  memcmp (p, b, n);   /* { dg-warning "reading 5 bytes from a region of size 4" "memcmp from allocated" { xfail *-*-* } } */
+  memchr (b, 1, n);   /* { dg-warning "reading 9 bytes from a region of size 8" "memcmp from allocated" } */
+  memcmp (p, b, n);   /* { dg-warning "reading 9 bytes from a region of size 8" "memcmp from allocated" } */
 }
 
 
Index: gcc/testsuite/gcc.target/i386/pr82002-2a.c
===================================================================
--- gcc/testsuite/gcc.target/i386/pr82002-2a.c	(revision 277978)
+++ gcc/testsuite/gcc.target/i386/pr82002-2a.c	(working copy)
@@ -10,3 +10,5 @@  b ()
   a (c);
   a (c);
 }
+
+/* { dg-prune-output "\\\[-Wstringop-overflow" }  */
Index: gcc/testsuite/gcc.target/i386/pr82002-2b.c
===================================================================
--- gcc/testsuite/gcc.target/i386/pr82002-2b.c	(revision 277978)
+++ gcc/testsuite/gcc.target/i386/pr82002-2b.c	(working copy)
@@ -10,3 +10,5 @@  b ()
   a (c);
   a (c);
 }
+
+/* { dg-prune-output "\\\[-Wstringop-overflow" }  */
Index: gcc/tree-ssa-strlen.c
===================================================================
--- gcc/tree-ssa-strlen.c	(revision 277978)
+++ gcc/tree-ssa-strlen.c	(working copy)
@@ -4384,8 +4384,22 @@  handle_store (gimple_stmt_iterator *gsi, bool *zer
 			       stmt, lenrange[2], dstsize))
 		  {
 		    if (decl)
-		      inform (DECL_SOURCE_LOCATION (decl),
-			      "destination object declared here");
+		      {
+			if (TREE_CODE (decl) == SSA_NAME)
+			  {
+			    gimple *stmt = SSA_NAME_DEF_STMT (decl);
+			    if (is_gimple_call (stmt))
+			      {
+				tree allocfn = gimple_call_fndecl (stmt);
+				inform (gimple_location (stmt),
+					"destination region allocated by %qD "
+					"here", allocfn);
+			      }
+			  }
+			else
+			  inform (DECL_SOURCE_LOCATION (decl),
+				  "destination object declared here");
+		      }
 		    gimple_set_no_warning (stmt, true);
 		  }
 	      }