Package: bash
Version: 5.0-6
Severity: normal

Dear Maintainer,

When a library is ld-preloaded into the bash process, it can not set 
environment variables without loosing the whole environment. Whenever "setenv" 
is called, all other environment variables disappear and are only set again, 
when bash's main function executes. Before that the environment is lost. 
Moreover when the corresponding variable is set by both the ld-preload library 
and the external environment, the variable set by the external environment 
overrides the one by the ld-preload library in any case.

Consider the following example:
$ cat libldpreloadenv2.C
#include <stdlib.h>
#include <stdio.h>
static struct EarlyInitializer {
  EarlyInitializer()
  {
        char *baz1 = getenv("baz");
        printf("baz1: %s\n", baz1);
        setenv("foo", "bar", 1);
        char *baz2 = getenv("baz");
        printf("baz2: %s\n", baz2);
  }
} ei;

$ g++ --shared -o libldpreloadenv2.so libldpreloadenv2.C 
$ baz=hi LD_PRELOAD=./libldpreloadenv2.so bash -c "printf 'Bash is ready\n'"
baz1: hi
baz2: (null)
Bash is ready
$ baz=hi LD_PRELOAD=./libldpreloadenv2.so bash -c "printf 'Bash is ready: %s\n' 
\"\$foo\""
baz1: hi
baz2: (null)
Bash is ready: bar
$ foo=outer baz=hi LD_PRELOAD=./libldpreloadenv2.so bash -c "printf 'Bash is 
ready: %s\n' \"\$foo\""
baz1: hi
baz2: (null)
Bash is ready: outer

two behaviors are wrong here:
1) that "baz" disappears when setting another environment variable
2) that the external environment wins in the last execution.

The problem is caused by a hacky mechanism, bash uses to set its environment 
variables for library functions
https://salsa.debian.org/debian/bash/-/blob/6a0146056618a32be35ba89e1b092eda2f2fa749/lib/sh/getenv.c#L50
However the internal structure for bash-variables (shell_variables) is not 
initialized yet.
The getenv implementation from bash has a special case for that, to ask the 
"real" environment in this case:
https://salsa.debian.org/debian/bash/-/blob/6a0146056618a32be35ba89e1b092eda2f2fa749/lib/sh/getenv.c#L72

However when setenv (or putenv) is called, the variable tables are initialized 
(empty) which seems to clear the environment
until the real bash process is started:
#0  create_variable_tables () at .././variables.c:321
#1  0x00005555555a3f4e in bind_variable (name=0x7ffff7fc9012 "foo", 
value=0x7ffff7fc900e "bar", flags=0) at .././variables.c:3230
#2  0x0000555555601da6 in setenv (name=0x7ffff7fc9012 "foo", 
value=0x7ffff7fc900e "bar", rewrite=1) at ../../.././lib/sh/getenv.c:178
#3  0x00007ffff7fc81c2 in EarlyInitializer::EarlyInitializer() () from 
./libldpreloadenv2.so

My suggestion to fix this bug would be to have a special case for putenv/setenv 
as well, that writes to the real (libc) environment,
when shell_variables is not initialized yet, just as it is implemented for 
getenv.

-- System Information:
Debian Release: bullseye/sid
  APT prefers unstable-debug
  APT policy: (500, 'unstable-debug'), (500, 'unstable'), (500, 'stable'), (1, 
'experimental-debug'), (1, 'experimental')
Architecture: amd64 (x86_64)
Foreign Architectures: i386

Kernel: Linux 5.6.0-1-amd64 (SMP w/8 CPU cores)
Kernel taint flags: TAINT_PROPRIETARY_MODULE, TAINT_OOT_MODULE, 
TAINT_UNSIGNED_MODULE
Locale: LANG=C.UTF-8, LC_CTYPE=C.UTF-8 (charmap=UTF-8), LANGUAGE=C.UTF-8 
(charmap=UTF-8)
Shell: /bin/sh linked to /bin/dash
Init: systemd (via /run/systemd/system)
LSM: AppArmor: enabled

Versions of packages bash depends on:
ii  base-files   11
ii  debianutils  4.9.1
ii  libc6        2.30-8
ii  libtinfo6    6.2-1

Versions of packages bash recommends:
ii  bash-completion  1:2.10-1

Versions of packages bash suggests:
pn  bash-doc  <none>

-- no debconf information

Reply via email to