Bug 703919

Summary: gcov crashes if program alters its own environment
Product: [Fedora] Fedora Reporter: Eric Blake <eblake>
Component: gccAssignee: Jakub Jelinek <jakub>
Status: CLOSED NOTABUG QA Contact: Fedora Extras Quality Assurance <extras-qa>
Severity: unspecified Docs Contact:
Priority: unspecified    
Version: 14CC: jakub
Target Milestone: ---Keywords: Reopened
Target Release: ---   
Hardware: Unspecified   
OS: Unspecified   
Whiteboard:
Fixed In Version: Doc Type: Bug Fix
Doc Text:
Story Points: ---
Clone Of: Environment:
Last Closed: 2011-05-17 15:41:09 UTC Type: ---
Regression: --- Mount Type: ---
Documentation: --- CRM:
Verified Versions: Category: ---
oVirt Team: --- RHEL 7.3 requirements from Atomic Host:
Cloudforms Team: --- Target Upstream Version:
Embargoed:

Description Eric Blake 2011-05-11 16:40:34 UTC
Description of problem:
The libvirt testsuite had a program that altered its own environment via assignment to environ.  When running this program under gcov, the program dumped core, even though the program had successfully reached exit().  Why?  Because gcov called getenv("GCOV_PREFIX") after the program's array for environ had gone out of scope.

For gcov to be useful, it should not let itself be affected by the operations of the program that it is tracing.  I think that gcov should cache all environment variables that it intends on reading up front, before starting main(), rather than re-reading them on every use case, so as to be immune to anything the traced program might do to its own environment.

Version-Release number of selected component (if applicable):
gcc-4.5.1-4.fc14.x86_64

How reproducible:
it was happening all the time for me with libvirt on the program tests/commandtest, until commit ba13a37 fixed the test to give environ a better scope.  I couldn't reproduce a crash on a simpler test case, but even then, gdb can show the problem

Steps to Reproduce:
Method 1: how I found the problem
1. git clone git://libvirt.org/libvirt.git
2. cd libvirt
3. git checkout ba13a37^
4. ./autobuild.sh

Method 2:
1. $ cat > bar.c <<\EOF
extern char **environ;
int main (void)
{
  const char *newenv[] = {
    "PATH=/usr/bin:/bin",
    0,
  };
  environ = (char **)newenv;
  return 0;
}
EOF
2. $ gcc -O2 -o bar -Wall bar.c -fprofile-arcs -ftest-coverage
3. $ gdb ./bar
4. (gdb) b main
5. (gdb) r
6. (gdb) b getenv
7. (gdb) c
8. (gdb) bt

Actual results:
Method 1: the libvirt testsuite fails, with a core dump in commandtest
Method 2: gcov calls getenv, after the contents of environ have gone out of scope, which has undefined behavior (even though if the program were not compiled for gcov, then there is no use of environ that late)
#0  getenv (name=0x4023e7 "GCOV_PREFIX") at getenv.c:36
#1  0x0000000000401370 in gcov_exit ()
#2  0x0000003d15e369f1 in __run_exit_handlers (status=0) at exit.c:78
#3  exit (status=0) at exit.c:100
#4  0x0000003d15e1ee64 in __libc_start_main (main=0x400c80 <main>, argc=1, 
    ubp_av=0x7fffffffe1c8, init=<value optimized out>, 
    fini=<value optimized out>, rtld_fini=<value optimized out>, 
    stack_end=0x7fffffffe1b8) at libc-start.c:258
#5  0x0000000000400bb9 in _start ()


Expected results:
Method 1: the libvirt test should succeed
Method 2: at step 7, the getenv() breakpoint should not hit after main() has started

Additional info:
https://www.redhat.com/archives/libvir-list/2011-May/msg00679.html

Comment 1 Jakub Jelinek 2011-05-17 09:15:40 UTC
This is clearly a bug in the testcase you are using, it is normal that there are various atexit handlers and disallowing getenv in all of them is a bug.

Comment 2 Eric Blake 2011-05-17 14:23:27 UTC
I agree that the code that originally exposed this problem was due to a bug in the program for preventing getenv() from working in an atexit handler, and that buggy program in libvirt has since been fixed to lengthen the lifetime of the new value assigned to environ.

However, I disagree with your assessment that this does not represent a real bug in gcov code - suppose I change the example in some other manner, without causing getenv() to fail in an atexit handler, but instead by changing the environment so that successive profile calls read different env var contents and corrupt the state expected by gcov.  That is, I think gcov is putting itself at risk by allowing user changes to environ to affect gcov's behavior; gcov would be safer by reading the environment once, up front, and using those cached values at all other points in the program where gcov is currently querying the environment again.

Comment 3 Jakub Jelinek 2011-05-17 15:41:09 UTC
libgcov is calling getenv solely during the atexit handler, unless you fork, thus it works just fine if the app changes the env vars, as long as getenv can be called.