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

            Bug ID: 120785
           Summary: No compiler warning when wrapping a packed C-style
                    array in a std::span, array memcpy instead
           Product: gcc
           Version: 13.3.0
            Status: UNCONFIRMED
          Severity: normal
          Priority: P3
         Component: c++
          Assignee: unassigned at gcc dot gnu.org
          Reporter: anthony.hayw...@ast-science.com
  Target Milestone: ---

Created attachment 61693
  --> https://gcc.gnu.org/bugzilla/attachment.cgi?id=61693&action=edit
File generated by `g++ -v -save-temps -std=c++20
packed_span_stack_overflow.cpp`

We have some packed structs that contain arrays, which we use for mapping
binary messages.  When such an array contains some aligned type such as `int`
rather than just being composed of 1-byte fields, we expect a compiler warning
or error if we try to take the address of the array.

A compiler warning or error does happen in many cases (e.g. if it's an
`std::array` rather than a C-style array) but there is no warning when it is a
C-style array and we construct a `std::span` to point at it.  Instead, gcc
generates a `memcpy` call that copies the entire array onto the stack.  This is
surprising behaviour and undesirable, given that `std::span` is supposed to be
a lightweight pointer-style non-owning reference.  Also, if the `std::span` is
a local variable, the address sanitizer does not believe that it can be read
safely (it does seem to be safe if the `span` is a function parameter).

I have attached a short and self-contained example program
`packed_span_stack_overflow.cpp` which demonstrates the stack copy.  When run,
it causes a segmentation fault (or `stack-overflow` if compiled with
`-fsanitize=address`) due to copying the over-large array from the heap to the
stack.

Here is the console output from `g++ -v -save-temps -std=c++20
packed_span_stack_overflow.cpp`:


Using built-in specs.
COLLECT_GCC=g++
COLLECT_LTO_WRAPPER=/usr/libexec/gcc/x86_64-linux-gnu/13/lto-wrapper
OFFLOAD_TARGET_NAMES=nvptx-none:amdgcn-amdhsa
OFFLOAD_TARGET_DEFAULT=1
Target: x86_64-linux-gnu
Configured with: ../src/configure -v --with-pkgversion='Ubuntu
13.3.0-6ubuntu2~24.04' --with-bugurl=file:///usr/share/doc/gcc-13/README.Bugs
--enable-languages=c,ada,c++,go,d,fortran,objc,obj-c++,m2 --prefix=/usr
--with-gcc-major-version-only --program-suffix=-13
--program-prefix=x86_64-linux-gnu- --enable-shared --enable-linker-build-id
--libexecdir=/usr/libexec --without-included-gettext --enable-threads=posix
--libdir=/usr/lib --enable-nls --enable-bootstrap --enable-clocale=gnu
--enable-libstdcxx-debug --enable-libstdcxx-time=yes
--with-default-libstdcxx-abi=new --enable-libstdcxx-backtrace
--enable-gnu-unique-object --disable-vtable-verify --enable-plugin
--enable-default-pie --with-system-zlib --enable-libphobos-checking=release
--with-target-system-zlib=auto --enable-objc-gc=auto --enable-multiarch
--disable-werror --enable-cet --with-arch-32=i686 --with-abi=m64
--with-multilib-list=m32,m64,mx32 --enable-multilib --with-tune=generic
--enable-offload-targets=nvptx-none=/build/gcc-13-fG75Ri/gcc-13-13.3.0/debian/tmp-nvptx/usr,amdgcn-amdhsa=/build/gcc-13-fG75Ri/gcc-13-13.3.0/debian/tmp-gcn/usr
--enable-offload-defaulted --without-cuda-driver --enable-checking=release
--build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu
--with-build-config=bootstrap-lto-lean --enable-link-serialization=2
Thread model: posix
Supported LTO compression algorithms: zlib zstd
gcc version 13.3.0 (Ubuntu 13.3.0-6ubuntu2~24.04) 
COLLECT_GCC_OPTIONS='-v' '-save-temps' '-std=c++20' '-shared-libgcc'
'-mtune=generic' '-march=x86-64' '-dumpdir' 'a-'
 /usr/libexec/gcc/x86_64-linux-gnu/13/cc1plus -E -quiet -v -imultiarch
x86_64-linux-gnu -D_GNU_SOURCE packed_span_stack_overflow.cpp -mtune=generic
-march=x86-64 -std=c++20 -fpch-preprocess -fasynchronous-unwind-tables
-fstack-protector-strong -Wformat -Wformat-security -fstack-clash-protection
-fcf-protection -o a-packed_span_stack_overflow.ii
ignoring duplicate directory "/usr/include/x86_64-linux-gnu/c++/13"
ignoring nonexistent directory "/usr/local/include/x86_64-linux-gnu"
ignoring nonexistent directory
"/usr/lib/gcc/x86_64-linux-gnu/13/include-fixed/x86_64-linux-gnu"
ignoring nonexistent directory "/usr/lib/gcc/x86_64-linux-gnu/13/include-fixed"
ignoring nonexistent directory
"/usr/lib/gcc/x86_64-linux-gnu/13/../../../../x86_64-linux-gnu/include"
#include "..." search starts here:
#include <...> search starts here:
 /usr/include/c++/13
 /usr/include/x86_64-linux-gnu/c++/13
 /usr/include/c++/13/backward
 /usr/lib/gcc/x86_64-linux-gnu/13/include
 /usr/local/include
 /usr/include/x86_64-linux-gnu
 /usr/include
End of search list.
COLLECT_GCC_OPTIONS='-v' '-save-temps' '-std=c++20' '-shared-libgcc'
'-mtune=generic' '-march=x86-64' '-dumpdir' 'a-'
 /usr/libexec/gcc/x86_64-linux-gnu/13/cc1plus -fpreprocessed
a-packed_span_stack_overflow.ii -quiet -dumpdir a- -dumpbase
packed_span_stack_overflow.cpp -dumpbase-ext .cpp -mtune=generic -march=x86-64
-std=c++20 -version -fasynchronous-unwind-tables -fstack-protector-strong
-Wformat -Wformat-security -fstack-clash-protection -fcf-protection -o
a-packed_span_stack_overflow.s
GNU C++20 (Ubuntu 13.3.0-6ubuntu2~24.04) version 13.3.0 (x86_64-linux-gnu)
        compiled by GNU C version 13.3.0, GMP version 6.3.0, MPFR version
4.2.1, MPC version 1.3.1, isl version isl-0.26-GMP

GGC heuristics: --param ggc-min-expand=100 --param ggc-min-heapsize=131072
Compiler executable checksum: c81c05345ce537099dafd5580045814a
COLLECT_GCC_OPTIONS='-v' '-save-temps' '-std=c++20' '-shared-libgcc'
'-mtune=generic' '-march=x86-64' '-dumpdir' 'a-'
 as -v --64 -o a-packed_span_stack_overflow.o a-packed_span_stack_overflow.s
GNU assembler version 2.42 (x86_64-linux-gnu) using BFD version (GNU Binutils
for Ubuntu) 2.42
COMPILER_PATH=/usr/libexec/gcc/x86_64-linux-gnu/13/:/usr/libexec/gcc/x86_64-linux-gnu/13/:/usr/libexec/gcc/x86_64-linux-gnu/:/usr/lib/gcc/x86_64-linux-gnu/13/:/usr/lib/gcc/x86_64-linux-gnu/
LIBRARY_PATH=/usr/lib/gcc/x86_64-linux-gnu/13/:/usr/lib/gcc/x86_64-linux-gnu/13/../../../x86_64-linux-gnu/:/usr/lib/gcc/x86_64-linux-gnu/13/../../../../lib/:/lib/x86_64-linux-gnu/:/lib/../lib/:/usr/lib/x86_64-linux-gnu/:/usr/lib/../lib/:/usr/lib/gcc/x86_64-linux-gnu/13/../../../:/lib/:/usr/lib/
COLLECT_GCC_OPTIONS='-v' '-save-temps' '-std=c++20' '-shared-libgcc'
'-mtune=generic' '-march=x86-64' '-dumpdir' 'a.'
 /usr/libexec/gcc/x86_64-linux-gnu/13/collect2 -plugin
/usr/libexec/gcc/x86_64-linux-gnu/13/liblto_plugin.so
-plugin-opt=/usr/libexec/gcc/x86_64-linux-gnu/13/lto-wrapper
-plugin-opt=-fresolution=a.res -plugin-opt=-pass-through=-lgcc_s
-plugin-opt=-pass-through=-lgcc -plugin-opt=-pass-through=-lc
-plugin-opt=-pass-through=-lgcc_s -plugin-opt=-pass-through=-lgcc --build-id
--eh-frame-hdr -m elf_x86_64 --hash-style=gnu --as-needed -dynamic-linker
/lib64/ld-linux-x86-64.so.2 -pie -z now -z relro
/usr/lib/gcc/x86_64-linux-gnu/13/../../../x86_64-linux-gnu/Scrt1.o
/usr/lib/gcc/x86_64-linux-gnu/13/../../../x86_64-linux-gnu/crti.o
/usr/lib/gcc/x86_64-linux-gnu/13/crtbeginS.o -L/usr/lib/gcc/x86_64-linux-gnu/13
-L/usr/lib/gcc/x86_64-linux-gnu/13/../../../x86_64-linux-gnu
-L/usr/lib/gcc/x86_64-linux-gnu/13/../../../../lib -L/lib/x86_64-linux-gnu
-L/lib/../lib -L/usr/lib/x86_64-linux-gnu -L/usr/lib/../lib
-L/usr/lib/gcc/x86_64-linux-gnu/13/../../.. a-packed_span_stack_overflow.o
-lstdc++ -lm -lgcc_s -lgcc -lc -lgcc_s -lgcc
/usr/lib/gcc/x86_64-linux-gnu/13/crtendS.o
/usr/lib/gcc/x86_64-linux-gnu/13/../../../x86_64-linux-gnu/crtn.o
COLLECT_GCC_OPTIONS='-v' '-save-temps' '-std=c++20' '-shared-libgcc'
'-mtune=generic' '-march=x86-64' '-dumpdir' 'a.'



If you reduce the array size on source line 6 from 7,000,000 to 700,000 then
the array fits on the stack, but compiling the modified program with
`-fsanitize-address` and running it leads to a `stack-use-after-scope` error,
showing that the unwanted copy of the array on the stack is not considered safe
to access.  In some environments, I've been able to read freed memory this way,
suggesting that the sanitizer is right to be worried.

I believe that gcc should raise a compiler error on this program, so that the
surprise memcpy is avoided.

This occurs on gcc 13.3 but I've also tested with 15.1 and the result is the
same.

Reply via email to