Use LD_PRELOAD to load shared objects with the initfirst flag. Calling the getenv() function from a function with __attribute__((constructor)) returns NULL. I think this is probably because the function is being called before someone (libc?) initializes __environ.
// foo.c
#include <stdio.h>
#include <stdlib.h>
void setup(void) __attribute__((constructor));
void
setup(void)
{
fprintf(stderr, "FOO=\"%s\"\n", getenv("FOO"));
}
$ cc -o foo.so -fPIC -shared foo.c -Wl,-z,initfirst
$ LD_PRELOAD=./foo.so FOO=123 ./hello
FOO="(null)"
Removing the initfirst flag produced the expected result.
Questions:
__environ? The loader, libc, crt0, or the kernel?Environment: Ubuntu 24.04.1 LTS, amd64, gcc 13.3.0, glibc 2.39
Funtions marked with __attribute__((constructor)) take the same arguments as main, including the envp argument. Note that envp is a bad idea to use within main since it breaks if setenv is called, but here it is useful since environ has not yet been initialized.
// foo.c
#include <stdio.h>
#include <stdlib.h>
void setup(int argc, char **argv, char **envp) __attribute__((constructor));
void
setup(int argc, char **argv, char **envp)
{
fprintf(stderr, "argc: %d\n", argc);
for (int i = 0; i < argc; ++i)
{
fprintf(stderr, "argv[%d]: %p\n", i, argv[i]);
}
for (char **p = envp; *p; ++p)
{
fprintf(stderr, "env: %s\n", *p);
}
}
Since this question relies on dynamic linking, we can ignore all libc implementations that don't support that. Among those that do: