Bug 1866474

Summary: gcc 10.2 stores global const pointer in read-only memory area, regression from gcc 9.1
Product: [Fedora] Fedora Reporter: Gus Wirth <gwirth79>
Component: gccAssignee: Jakub Jelinek <jakub>
Status: CLOSED NOTABUG QA Contact: Fedora Extras Quality Assurance <extras-qa>
Severity: high Docs Contact:
Priority: unspecified    
Version: 32CC: aoliva, dmalcolm, fweimer, jakub, jwakely, law, mpolacek, msebor, nickc, sipoyare
Target Milestone: ---Keywords: Regression
Target Release: ---   
Hardware: x86_64   
OS: Linux   
Whiteboard:
Fixed In Version: Doc Type: If docs needed, set a value
Doc Text:
Story Points: ---
Clone Of: Environment:
Last Closed: 2020-08-05 17:20:21 UTC Type: Bug
Regression: --- Mount Type: ---
Documentation: --- CRM:
Verified Versions: Category: ---
oVirt Team: --- RHEL 7.3 requirements from Atomic Host:
Cloudforms Team: --- Target Upstream Version:
Embargoed:

Description Gus Wirth 2020-08-05 16:21:32 UTC
Description of problem:

gcc 10.2 generates code that segfaults due to placing global const pointers in read-only memory area rather than read-write as previous compilers

Version-Release number of selected component (if applicable):

gcc (GCC) 10.2.1 20200723 (Red Hat 10.2.1-1)

How reproducible:

Compile program with a global const pointer prototype to be initialized later.

Additional info:

Here is the simplest program I could come up with to demonstrate the 
problem. I call the program "maptest.c" because I'm looking at where 
things get mapped to memory by looking in /proc/<pid>/maps and seeing 
what permissions each chunk of memory has and where it is located. Using 
the debugger I can tell where in that memory space my variables are located.

Here's the program:


/* A program to try and find where a SIGSEV error occurs as
  * part of the program DOSEMU
  */

# include <stdio.h>
# include <stdlib.h>

char * const lowmem_base;

int main(int argc, char **argv)

{

   void *addr = malloc(114112);

   *(char **)(&lowmem_base) = addr;

   printf("Hello World!\n");

   free(addr);
}

I could probably delete the Hello World! to make it even smaller but 
it's an experiment.

When I compile this with gcc like this:

$ gcc -g -Wall -o maptest maptest.c

I get an ELF executable called "maptest" with debug information as expected.

When I compile the program with gcc version 9.1.2 as found in Fedora 31, 
the programs runs and exits normally. Running in the debugger shows that 
the variable lowmem_base is located in a portion of the maptest space 
that is marked rw-p. Activating the disassembler in gdb shows that the 
value of addr is in a register and moves to the memory location of 
lowmem_base.

When I compile the program with gcc version 10.2.1 as found in Fedora 
32, the program runs and segfaults on the line:

   *(char **)(&lowmem_base) = addr;

Running in the debugger shows that the variable lowmem_base is located 
in a region of maptest marked as read only.

If I copy the binary from Feodra 31 to Fedora 32 it runs fine so I don't 
think it has anything to do with any of the shared libraries.

The equivalent of this program has been working since before 2012, so I 
think that there is a problem with gcc.

If I compile the program with clang, it functions as expected without a segfault.

Comment 1 Jakub Jelinek 2020-08-05 17:20:21 UTC
The testcase is invalid.
See e.g. ISO C99, 6.7.3/5: "If an attempt is made to modify an object defined with a const-qualified type through use
of an lvalue with non-const-qualified type, the behavior is undefined."
The only reason this "worked" before GCC 10 is that older GCC versions defaulted for C to -fcommon and as the variable has no initializer,
it has been a common symbol which is always writable.  If you compile this testcase with -fno-common, even GCC 9 or 8 will segfault on it.
If you compile with C++, it will not even compile.
You can use various workarounds, like -fcommon, or add __attribute__((common)) to the var, but the right fix is to drop the const qualifier if you want to modify the variable.