https://sourceware.org/bugzilla/show_bug.cgi?id=34562
Bug ID: 34562
Summary: gprofng: libcollector derives auxv address by walking
environ and crashes (SIGSEGV) when environ is
relocated to the heap (e.g., via setenv in ELF
constructor)
Product: binutils
Version: 2.46.1
Status: UNCONFIRMED
Severity: critical
Priority: P2
Component: gprofng
Assignee: unassigned at sourceware dot org
Reporter: rogerio at cadence dot com
Target Milestone: ---
Overview from Cloude
During profiling of applications using `gprofng collect app`, the target
program intermittently crashes with a **SIGSEGV** inside `_dl_init` before the
application's `main` even executes.
This issue occurs because `libgp-collector.so` attempts to locate the auxiliary
vector (`auxv`) by walking past the `NULL` terminator of `environ` on the
assumption that `auxv` immediately follows it on the initial stack. However, if
any library or ELF constructor calls `setenv()` or otherwise relocates
`environ` to a heap-allocated memory chunk (via `malloc`) prior to
`libgp-collector` initializing, this walk traverses arbitrary heap data
instead.
If the adjacent heap contains bytes matching `0x21` (which is `AT_SYSINFO_EHDR`
but also a common `malloc` chunk header size for a 0x20-byte chunk), the
collector misidentifies it as the vDSO header pointer and attempts to
dereference the subsequent QWORD, resulting in an immediate segmentation fault.
---
Root Cause Analysis & Faulting Code
In `binutils-v2.46.1/.../lib64/gprofng/libgp-collector.so`, inside
`__collector_ext_mmap_install` (where the `process_vsyscall_page` logic is
inlined):
1. Locating the auxv pointer incorrectly:
51f18: mov 0xfefb1(%rip),%rax # <environ@GLIBC_2.2.5>
51f1f: mov (%rax),%rax ; rax = environ
51f22: test %rax,%rax
51f25: je ...
51f30: add $0x8,%rax ; Walk forward
51f34: cmpq $0x0,-0x8(%rax) ; Until the previous slot is NULL
51f39: jne 51f30
51f3b: mov (%rax),%rdx ; First qword past NULL, assumed to be a_type
51f3e: mov %rax,0x1148ab(%rip) # <auxv> <-- Cached as the auxv array pointer
2. Dereferencing without validation:
51a29: mov 0x10(%rax),%rdx ; next a_type
51a2d: add $0x10,%rax ; sizeof(Elf64_auxv_t)
51a31: test %rdx,%rdx
51a34: je ... ; a_type == AT_NULL -> exit loop
51a3a: cmp $0x21,%rdx ; a_type == AT_SYSINFO_EHDR ?
51a3e: jne 51a29
51a20: mov 0x8(%rax),%r14 ; r14 = a_val (assumed Elf64_Ehdr *)
...
51ab8: movzwl 0x3c(%r14),%esi ; ehdr->e_shnum <-- SIGSEGV HERE
There is zero validation of `r14` (no ELF magic validation, no memory mapping
check) before reading fields like `e_shnum` and `e_shoff`.
---
Minimal Reproducer
The following reproducer compiles a constructor (`moveenv.c`) that mimics
glibc's `setenv()` behavior by relocating `environ` to the heap and placing a
fake `{0x21, <invalid-pointer>}` sequence right after the `NULL` terminator.
hello.c
#include <stdio.h>
int main(void)
{
double s = 0;
for (long i = 1; i < 40000000; i++) s += 1.0 / i;
printf("%f\n", s);
return 0;
}
moveenv.c
#include <stdlib.h>
extern char **environ;
__attribute__((constructor(101))) static void move_environ(void)
{
int n = 0;
while (environ[n]) n++;
unsigned long *blk = (unsigned long *) malloc((n + 8) * sizeof(char *));
for (int i = 0; i < n; i++)
((char **) blk)[i] = environ[i];
((char **) blk)[n] = NULL;
blk[n + 1] = 33; /* a_type = AT_SYSINFO_EHDR */
blk[n + 2] = 0x6d75645f736e6463UL; /* a_val = ASCII "cdns_dum" (invalid
ptr) */
blk[n + 3] = 0; /* a_type = AT_NULL */
environ = (char **) blk;
}
Execution Commands:
gcc -O2 -g -o hello hello.c
gcc -O2 -g -shared -fPIC -o libmoveenv.so moveenv.c
# 1. Baseline runs correctly:
gprofng collect app -F off -o base.er ./hello
# 2. Preloading the heap-relocated environment trigger crashes:
LD_PRELOAD=$PWD/libmoveenv.so gprofng collect app -F off -o bug.er ./hello
```
Resulting Crash Backtrace:
=> 0x7ffff7eceab8 <__collector_ext_mmap_install+920>: movzwl 0x3c(%r14),%esi
si_addr = 0x0
r14 = 0x6d75645f736e6463 ("cdns_dum")
rdx = 0x21 (False AT_SYSINFO_EHDR match)
#0 __collector_ext_mmap_install () from libgp-collector.so
#1 __collector_open_experiment () from libgp-collector.so
#2 collector_init () from libgp-collector.so
#3 call_init (l=..., argc=1, argv=0x7fffffffcc48, env=0x7fffffffcc58) at
dl-init.c:72
---
Suggested Fixes by AI:
To resolve this issue robustly, `libcollector` should avoid traversing the
stack from `environ` entirely:
1. Use Upstream APIs**: Utilize `getauxval(AT_SYSINFO_EHDR)` (supported in
glibc 2.16+).
2. Fallback to Procfs**: If `getauxval` is unavailable, open and parse
`/proc/self/auxv`, which is guaranteed to be accurate regardless of
heap-relocated environment arrays.
3. Add Validation**: Before dereferencing any address found as `a_val` for
`AT_SYSINFO_EHDR`, perform basic validation:
* Verify that `a_val` is non-zero.
* Verify that the memory address range is readable.
* Verify that the header starts with the expected ELF magic bytes
(`\x7fELF`).
--
You are receiving this mail because:
You are on the CC list for the bug.