[PATCH] elf/rtld: Fix auxiliary vector for enable_secure
Adhemerval Zanella Netto
adhemerval.zanella@linaro.org
Fri Jun 28 13:22:40 GMT 2024
On 28/06/24 10:01, Stefan Liebler wrote:
> On 27.06.24 22:40, Adhemerval Zanella Netto wrote:
>>
>>
>> On 25/06/24 06:17, Stefan Liebler wrote:
>>> Starting with commit
>>> 59974938fe1f4add843f5325f78e2a7ccd8db853
>>> elf/rtld: Count skipped environment variables for enable_secure
>>>
>>> The new testcase elf/tst-tunables-enable_secure-env segfaults on s390 (31bit).
>>> There _start parses the auxiliary vector for some additional checks.
>>>
>>> Therefore it skips over the zeros after the environment variables ...
>>> 0x7fffac20: 0x7fffbd17 0x7fffbd32 0x7fffbd69 0x00000000
>>> ------------------------------------------------^^^last environment variable
>>>
>>> ... and then it parses the auxiliary vector and stops at AT_NULL.
>>> 0x7fffac30: 0x00000000 0x00000021 0x00000000 0x00000000
>>> --------------------------------^^^AT_SYSINFO_EHDR--------------^^^AT_NULL
>>> ----------------^^^newp-----------------------------------------^^^oldp
>>> Afterwards it tries to access AT_PHDR which points to somewhere and segfaults.
>>>
>>> Due to not incorporating the skip_env variable in the computation of oldp
>>> when shuffling down the auxv in rtld.c, it just copies one entry with AT_NULL
>>> and value 0x00000021 and stops the loop. In reality we have skipped
>>> GLIBC_TUNABLES environment variable (=> skip_env=1). Thus we should copy from
>>> here:
>>> 0x7fffac40: 0x00000021 0x7ffff000 0x00000010 0x007fffff
>>> ----------------^^^fixed-oldp
>>>
>>> This patch fixes the computation of oldp when shuffling down auxiliary vector.
>>> It also adds some checks in the testcase. Those checks also fail on
>>> s390x (64bit) and x86_64 without the fix.
>>> ---
>>> elf/rtld.c | 2 +-
>>> elf/tst-tunables-enable_secure-env.c | 19 +++++++++++++++++++
>>> 2 files changed, 20 insertions(+), 1 deletion(-)
>>>
>>> diff --git a/elf/rtld.c b/elf/rtld.c
>>> index e9525ea987..883c651d48 100644
>>> --- a/elf/rtld.c
>>> +++ b/elf/rtld.c
>>> @@ -1325,7 +1325,7 @@ _dl_start_args_adjust (int skip_args, int skip_env)
>>>
>>> /* Shuffle auxv down. */
>>> ElfW(auxv_t) ax;
>>> - char *oldp = (char *) (p + 1);
>>> + char *oldp = (char *) (p + 1 + skip_env);
>>> char *newp = (char *) (sp + 1);
>>> do
>>> {
>>
>> I don't think this is correct, running as the testcase it does show the expected value:
>>
> LD_SHOW_AUXV=1 can't be used to show this issue.
>
> In process_envvars(), process_envvars_secure() is called and e.g.
> GLIBC_TUNABLES variable is removed with unsetenv(). The skipped/removed
> variables are counted in skip_env. Note that unsetenv() itself removes
> the pointer on the stack by moving the later ones to the front.
> Then in process_envvars_default(), _dl_show_auxv() is called if
> LD_SHOW_AUXV=1 is set. At this time, the auxv is fine.
>
> Lateron, in _dl_start_args_adjust(), the arguments,
> environment-variables and the auxiliary-vector are shuffled down as
> "elf/ld-linux-x86-64.so.2" "--library-path" "..." arguments are removed.
>
> argv/envp are shuffled down until the NULL-pointers are found on stack:
> /* Shuffle argv down. */
> do
> *++sp = *++p;
> while (*p != NULL);
> ...
> /* Shuffle envp down. */
> do
> *++sp = *++p;
> while (*p != NULL);
>
> Then auxv is shuffled down. The code assumes that there is one
> NULL-pointer between envp and auxv.
> /* Shuffle auxv down. */
> ElfW(auxv_t) ax;
> char *oldp = (char *) (p + 1);
> char *newp = (char *) (sp + 1);
> do
> {
> memcpy (&ax, oldp, sizeof (ax));
> memcpy (newp, &ax, sizeof (ax));
> oldp += sizeof (ax);
> newp += sizeof (ax);
> }
> while (ax.a_type != AT_NULL);
>
> But GLIBC_TUNABLES was removed with unsetenv() and thus there are
> skip_env=1 additional pointers between envp and auxv.
>
> With skip_env==1, oldp points to the original NULL-pointer between envp
> and auxv at startup of ld.so. Thus only one auxv-entry is in place - the
> AT_NULL-one. (Also for skip_env >1, oldp points to a NULL-pointer)
>
> If you want, add "_dl_show_auxv ();" after the call of
> "_dl_start_args_adjust (_dl_argv - orig_argv, skip_env);" But you won't
> get an output!
Sorry if I was not clearn, I was not relying on the _dl_show_auxv but rather
I changed tst-tunables-enable_secure-env to loop over a list of AT_ entries
with getauxval. I used LD_SHOW_AUXV=1 as an example, but I does fail if you
set any filtered variable from sysdeps/generic/unsecvars.h:
GLIBC_TUNABLES=glibc.rtld.enable_secure=1 \
LD_BIND_NOT=1 \
elf/ld-linux-x86-64.so.2 --library-path ... elf/tst-tunables-enable_secure-env --direct
AT_EXECFD: 0
AT_EXECFN: (null)
AT_PHDR: 0x0
AT_PHENT: 0
AT_PHNUM: 0
AT_PAGESZ: 0
AT_BASE: 0x0
AT_FLAGS: 0x0
AT_ENTRY: 0x0
AT_NOTELF: 0
AT_UID: 0
AT_EUID: 0
AT_GID: 0
AT_EGID: 0
AT_PLATFORM: (null)
AT_HWCAP: 2
AT_CLKTCK: 0
AT_FPUCW: 0
AT_DCACHEBSIZE: 0x0
AT_ICACHEBSIZE: 0x0
AT_UCACHEBSIZE: 0x0
AT_IGNOREPPC(null)
AT_SECURE: 0
AT_BASE_PLATFORM: (null)
AT_SYSINFO: 0x0
AT_SYSINFO_EHDR: 0x0
AT_RANDOM: 0x0
AT_HWCAP2: 0x2
AT_HWCAP3: 0x0
AT_HWCAP4: 0x0
AT_MINSIGSTKSZ: 0
AT_L1I_CACHESIZE: 0
AT_L1I_CACHEGEOMETRY: 0x0
AT_L1D_CACHESIZE: 0
AT_L1D_CACHEGEOMETRY: 0x0
AT_L2_CACHESIZE: 0
AT_L2_CACHEGEOMETRY: 0x0
AT_L3_CACHESIZE: 0
AT_L3_CACHEGEOMETRY: 0x0
>
> The called executable (elf/tst-tunables-enable_secure-env) does not get
> any auxv-entries via getauxval(). Okay, only the special handled ones:
> AT_HWCAP/AT_HWCAP2.
Well, it does if only set GLIBC_TUNABLES=glibc.rtld.enable_secure=1, but
the auxv will only contain AT_HWCAP if you set something from the filtered
environments variables. I am not sure if this is really a consistent
behavior.
>
>> $ GLIBC_TUNABLES=glibc.rtld.enable_secure=1 \
>> elf/ld-linux-x86-64.so.2 --library-path ... elf/tst-tunables-enable_secure-env --direct
>> AT_EXECFD: 0
>> AT_EXECFN: [...]/elf/tst-tunables-enable_secure-env
>> AT_PHDR: 0x7e7f608b4040
>> AT_PHENT: 56
>> AT_PHNUM: 13
>> AT_PAGESZ: 4096
>> AT_BASE: 0x0
>> AT_FLAGS: 0x0
>> AT_ENTRY: 0x7e7f608b6760
>> AT_NOTELF: 0
>> AT_UID: 1000
>> AT_EUID: 1000
>> AT_GID: 1000
>> AT_EGID: 1000
>> AT_PLATFORM: x86_64
>> AT_HWCAP: 2
>> AT_CLKTCK: 100
>> AT_FPUCW: 0
>> AT_DCACHEBSIZE: 0x0
>> AT_ICACHEBSIZE: 0x0
>> AT_UCACHEBSIZE: 0x0
>> AT_IGNOREPPC(null)
>> AT_SECURE: 0
>> AT_BASE_PLATFORM: (null)
>> AT_SYSINFO: 0x0
>> AT_SYSINFO_EHDR: 0x0
>> AT_RANDOM: 0x7ffe90102d29
>> AT_HWCAP2: 0x2
>> AT_HWCAP3: 0x0
>> AT_HWCAP4: 0x0
>> AT_MINSIGSTKSZ: 3376
>> AT_L1I_CACHESIZE: 0
>> AT_L1I_CACHEGEOMETRY: 0x0
>> AT_L1D_CACHESIZE: 0
>> AT_L1D_CACHEGEOMETRY: 0x0
>> AT_L2_CACHESIZE: 0
>> AT_L2_CACHEGEOMETRY: 0x0
>> AT_L3_CACHESIZE: 0
>> AT_L3_CACHEGEOMETRY: 0x0
>>
>> However if I add environment that should be filtered:
>>
>>
>> $ GLIBC_TUNABLES=glibc.rtld.enable_secure=1 \
>> LD_SHOW_AUXV=1 \
>> elf/ld-linux-x86-64.so.2 --library-path ... elf/tst-tunables-enable_secure-env --direct
>>
>> AT_EXECFD: 0
>> AT_EXECFN: (null)
>> AT_PHDR: 0x0
>> AT_PHENT: 0
>> AT_PHNUM: 0
>> AT_PAGESZ: 0
>> AT_BASE: 0x0
>> AT_FLAGS: 0x0
>> AT_ENTRY: 0x0
>> AT_NOTELF: 0
>> AT_UID: 0
>> AT_EUID: 0
>> AT_GID: 0
>> AT_EGID: 0
>> AT_PLATFORM: (null)
>> AT_HWCAP: 2
>> AT_CLKTCK: 0
>> AT_FPUCW: 0
>> AT_DCACHEBSIZE: 0x0
>> AT_ICACHEBSIZE: 0x0
>> AT_UCACHEBSIZE: 0x0
>> AT_IGNOREPPC(null)
>> AT_SECURE: 0
>> AT_BASE_PLATFORM: (null)
>> AT_SYSINFO: 0x0
>> AT_SYSINFO_EHDR: 0x0
>> AT_RANDOM: 0x0
>> AT_HWCAP2: 0x2
>> AT_HWCAP3: 0x0
>> AT_HWCAP4: 0x0
>> AT_MINSIGSTKSZ: 0
>> AT_L1I_CACHESIZE: 0
>> AT_L1I_CACHEGEOMETRY: 0x0
>> AT_L1D_CACHESIZE: 0
>> AT_L1D_CACHEGEOMETRY: 0x0
>> AT_L2_CACHESIZE: 0
>> AT_L2_CACHEGEOMETRY: 0x0
>> AT_L3_CACHESIZE: 0
>> AT_L3_CACHEGEOMETRY: 0x0
>>
>>> diff --git a/elf/tst-tunables-enable_secure-env.c b/elf/tst-tunables-enable_secure-env.c
>>> index 24e846f299..c78e113c21 100644
>>> --- a/elf/tst-tunables-enable_secure-env.c
>>> +++ b/elf/tst-tunables-enable_secure-env.c
>>> @@ -17,8 +17,12 @@
>>> License along with the GNU C Library; if not, see
>>> <https://www.gnu.org/licenses/>. */
>>>
>>> +#include <stdlib.h>
>>> #include <support/capture_subprocess.h>
>>> #include <support/check.h>
>>> +#ifdef __linux__
>>> + #include <sys/auxv.h>
>>> +#endif
>>>
>>> static int
>>> do_test (int argc, char *argv[])
>>> @@ -26,6 +30,21 @@ do_test (int argc, char *argv[])
>>> /* Ensure that no assertions are hit when a dynamically linked application
>>> runs. This test requires that GLIBC_TUNABLES=glibc.rtld.enable_secure=1
>>> is set. */
>>> +
>>> + /* The environment variable GLIBC_TUNABLES is skipped for secure
>>> + execution. */
>>> + TEST_VERIFY (getenv ("GLIBC_TUNABLES") == NULL);
>>> +
>>> +#ifdef __linux__
>>> + /* On linux, the auxiliary vector is located after the environment variables.
>>> + Check that some of them are available as those are also shuffled down by
>>> + ld.so. */
>>> + TEST_VERIFY (getauxval (AT_PHDR) != 0);
>>> + TEST_VERIFY (getauxval (AT_PHENT) != 0);
>>> + TEST_VERIFY (getauxval (AT_PHNUM) != 0);
>>> + TEST_VERIFY (getauxval (AT_ENTRY) != 0);
>>> +#endif
>>> +
>>> return 0;
>>> }
>>>
>
More information about the Libc-alpha
mailing list