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