![]() |
|
Message-ID: <CAOWsQ2Y_9wnD_Gje=o65Oit-28z8JK_OXiKZSmFn=kpcReGGiw@mail.gmail.com> Date: Wed, 1 Oct 2025 08:49:30 -0500 From: Mike Hilgendorf <mike@...gram.dev> To: Rich Felker <dalias@...c.org> Cc: musl@...ts.openwall.com Subject: Re: `unsetenv()` does not always work when run in an `__attribute__((constructor))` function Confirmed, this was an issue in the way bash handles setenv/unsetenv/getenv and environ. Apologies for the report. The specific problem was that bash overrides the ***env functions internally, so any kind of injection that relied on them agreeing on those functions naturally wouldn't work. There's no change to __libc_start_main required. I wasn't able to observe the issue even using the third arg to main(). Sorry for the erroneous report! - Mike On Wed, Oct 1, 2025 at 7:06 AM Rich Felker <dalias@...c.org> wrote: > > On Tue, Sep 30, 2025 at 04:23:44PM -0500, Mike Hilgendorf wrote: > > I've failed to reproduce this bug except in rare cases, this is the > > smallest I could make it. > > > > Say you want to set LD_PRELOAD to run some code before main() and > > unset LD_PRELOAD within that function so it only runs for the parent > > process. You might do something like this: > > > > > > ``` > > #include <stdio.h> > > #include <stdlib.h> > > #include <errno.h> > > > > __attribute__((constructor)) > > static int preload () { > > if (unsetenv("LD_PRELOAD")) { > > printf("unsetenv errored: %d\n", errno); > > } > > if (getenv("LD_PRELOAD")) { > > printf("LD_PRELOAD was still set\n"); > > } > > } > > ``` > > compiled by running: > > > > musl-gcc preload.c -shared -o preload.so > > > > Now you want to inject this into a binary, say bash, compiled against musl libc > > > > ``` > > cd bash-5.2.37 > > export CC=musl-gcc > > ./configure > > make > > > > env -i LD_PRELOAD=path/to/preload.so ./bash > > ``` > > > > You will see that LD_PRELOAD was not unset (even in the context of the > > ctor function), and LD_PRELOAD is set in the shell. > > > > From what I can tell, LD_PRELOAD is not special, this is true of other > > environment variables. However this is not reproducible with simpler > > programs - bash 5.2 is the one where I saw this happen first, and so > > far the only program I know that has this issue. > > Are you sure it's reproducible with other programs? I suspect what's > happening is that bash is using the optional third argument envp to > main, not the actual current environment, as its source to derive the > initial environment list. > > I don't see any way the issue you're describing could happen > otherwise. > > > Let me know if I'm doing something very wrong or if there is an easier > > reproduction, from scanning bash's source I don't think they're doing > > anything strange (they initialize variables with the `char** envp` > > passed to main). > > Yes, so it's exactly what I suspected. > > > I do notice in `dynlink.c` that the stack pointer passed to the > > entrypoint by the loader is whatever was passed to the loader, not > > accounting for any envp mutations made by calling the DT_INIT_ARRAY > > functions. But I don't know if this is actually a problem or not for > > the implementation of _start. > > No application code, not even DT_INIT_ARRAY handlers, has executed at > this point. It all runs after execution is passed to the main > program's ELF entry point (_start). > > The behavior you're seeing has nothing to do with the dynamic linker. > It's line 95 of src/env/__libc_start_main.c passing a pointer to the > initial environment vector rather than any potentially-updated value > of environ. I'm not sure if this should be changed or not (there are > probably arguments for either), but bash using the nonstandard > third-arg to main rather than the standard global environ[] strikes me > as a bash bug. > > Rich
Powered by blists - more mailing lists
Confused about mailing lists and their use? Read about mailing lists on Wikipedia and check out these guidelines on proper formatting of your messages.