3

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:

  1. Is there a way to read environment variables under these conditions? (It's fine if it only works on Linux, amd64, gcc, and glibc)
  2. Who initializes __environ? The loader, libc, crt0, or the kernel?

Environment: Ubuntu 24.04.1 LTS, amd64, gcc 13.3.0, glibc 2.39

2

1 Answer 1

5

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:

  • broken: MUSL
  • works: GLIBC
  • works: all BSDs (not tested)
  • not investigated: Mac OS X, Solaris, Cygwin, etc.
Sign up to request clarification or add additional context in comments.

1 Comment

Thanks to this excellent answer, I learned that arguments are passed to each function in .init_array, so I checked the glibc source code and found that three arguments were indeed passed. I couldn't find any mention of this issue in the gcc or glibc documentation.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.