https://gcc.gnu.org/bugzilla/show_bug.cgi?id=77606

            Bug ID: 77606
           Summary: abort in __memcpy_chk on an in-bounds copy with type-2
                    builtin_object_size
           Product: gcc
           Version: 7.0
            Status: UNCONFIRMED
          Severity: normal
          Priority: P3
         Component: middle-end
          Assignee: unassigned at gcc dot gnu.org
          Reporter: msebor at gcc dot gnu.org
  Target Milestone: ---

The Object Size Checking Built-in Functions page of the manual shows an example
of using the __builtin_object_size intrinsic to implement a checking memcpy. 
The example makes use of "type-0" __builtin_object_size documented to return
the size of largest "whole variables."  This is the most permissive mode that
allows the checked functions to overflow the destination in cases where there
are a set of destination buffers known at compile time, some big enough and
some smaller than the number of bytes to write, and the smaller one ends up
being used as the destination at runtime.

The second argument to __builtin_object_size is documented to make it possible
to determine the size of the smallest object instead:

If there are multiple objects ptr can point to and all of them are known at
compile time, the returned number is the maximum of remaining byte counts in
those objects if type & 2 is 0 and minimum if nonzero. 

The test case below changes the example to use __builtin_object_size in this
less permissive mode expected to return the size of the smaller of the two
objects it may refer to at runtime.  The example shows that in this mode, the
intrinsic fails to return the size of the smaller object as documented (in this
case 40) and instead returns 0, causing the memcpy function to abort, even
though no overflow would happen.

Note that this problem doesn't seem to affect destinations that point to one of
two or more statically allocated objects, or when the destination is a VLA
defined as 'char d [1 < argc ? 40 : 80]', but only when the destination points
to a dynamically allocated object with a runtime-size.

$ cat x.c && /build/gcc-trunk-git/gcc/xgcc -B /build/gcc-trunk-git/gcc -O2
-Wall -Wextra -Wpedantic -fdump-tree-optimized=/dev/stdout x.c && ./a.out 
#define bos(dest) __builtin_object_size (dest, 2)
#define memcpy(dest, src, n) \
  __builtin___memcpy_chk (dest, src, n, bos (dest))

int main (int argc, char *argv[])
{
  char *d = __builtin_malloc (1 < argc ? 40 : 80);

  __builtin_printf ("size of d = %zu\n", bos (d));

  memcpy (d, "abcde", 5);

  __builtin_printf ("%s\n", d);
}
x.c: In function ‘main’:
x.c:5:27: warning: unused parameter ‘argv’ [-Wunused-parameter]
 int main (int argc, char *argv[])
                           ^~~~

;; Function main (main, funcdef_no=0, decl_uid=1792, cgraph_uid=0,
symbol_order=0) (executed once)

Removing basic block 3
main (int argc, char * * argv)
{
  char * d;
  long unsigned int iftmp.0_3;

  <bb 2>:
  if (argc_4(D) > 1)
    goto <bb 4>;
  else
    goto <bb 3>;

  <bb 3>:

  <bb 4>:
  # iftmp.0_3 = PHI <40(2), 80(3)>
  d_7 = __builtin_malloc (iftmp.0_3);
  __builtin_printf ("size of d = %zu\n", 0);
  __builtin___memcpy_chk (d_7, "abcde", 5, 0);
  __builtin_puts (d_7);
  return 0;

}


x.c:3:3: warning: call to __builtin___memcpy_chk will always overflow
destination buffer
   __builtin___memcpy_chk (dest, src, n, bos (dest))
   ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
x.c:11:3: note: in expansion of macro ‘memcpy’
   memcpy (d, "abcde", 5);
   ^~~~~~
size of d = 0
*** buffer overflow detected ***: ./a.out terminated

Reply via email to