Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

AUXV parameter AT_EXECFN returns argv[0] instead of filename passed to execve #2133

Closed
christoh opened this issue May 20, 2017 · 11 comments
Closed

Comments

@christoh
Copy link

christoh commented May 20, 2017

  • Your Windows build number: 16199

  • What you're doing and what's happening:

I have the following multi-command (multi-call) binary "multi_command.c" (one binary that features many commands like e.g. busybox does):

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/auxv.h>
#include <libgen.h>
#include <errno.h>

int main(int argc, char** argv)
{
        char* fullname = (char*)getauxval(AT_EXECFN);
        char* invoked_as_name = basename(fullname);
        printf("This binary has been invoked as %s (Full name is %s)\n", invoked_as_name, fullname);

        if (!strcmp(invoked_as_name, "xxx"))
        {
                // xxx command goes here
                exit(0);
        }

        if (!strcmp(invoked_as_name, "yyy"))
        {
                // yyy command goes here
                exit(0);
        }

        fprintf(stderr, "%s: %s\n", invoked_as_name, strerror(ENOSYS));
        exit(ENOSYS);
}

I use AT_EXECFN in the aux vector because argv[0] is unreliable (argv[0] is just another argument like argv[1] to argv[argc-1]) . You cannot expect that every launcher sets it like a standard shell.

Now I launch this program with this "launcher.c" that sets some custom argv[0] like many programs do ( e.g. /etc/inetd.conf requires argv[0] specified explicitly)

#include <unistd.h>
#include <malloc.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>

int main(int argc, char** argv)
{
        if (argc < 2)
        {
                fprintf(stderr,"Usage: %s executable [arguments]\n", argv[0]);
                exit(EINVAL);
        }

        char** new_argv = (char**)malloc((argc) * sizeof(char**));
        new_argv[0] = "AUXV_TEST_UNSUCCESSFUL";
        new_argv[argc - 1] = NULL;

        int i = 1;
        for (;i < argc -1; i++) new_argv[i] = argv[i + 1];

        execvp(argv[1], new_argv);
        fprintf(stderr, "Could not start %s: %s\n", argv[1], strerror(errno));
}

The result on real Ubuntu 16.04 is:

[root@ubuntu1604 ~/auxv] # ll
total 16
drwxr-xr-x  2 root root     4096 May 20 13:24 ./
drwxrwx--- 62 root christoh 4096 May 20 13:23 ../
-rw-r--r--  1 root root      555 May 20 13:24 launcher.c
-rw-r--r--  1 root root      590 May 20 13:24 multi_command.c
[root@ubuntu1604 ~/auxv] # lsb_release -a && uname -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 16.04.2 LTS
Release:        16.04
Codename:       xenial
Linux ubuntu1604.example.com 4.4.0-78-generic #99-Ubuntu SMP Thu Apr 27 15:29:09 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux
[root@ubuntu1604 ~/auxv] # gcc -o launcher launcher.c && gcc -o  multi_command multi_command.c
[root@ubuntu1604 ~/auxv] # ln -srv multi_command xxx
'xxx' -> 'multi_command'
[root@ubuntu1604 ~/auxv] # sudo cp multi_command /usr/local/bin
[root@ubuntu1604 ~/auxv] # ./launcher ./multi_command
This binary has been invoked as multi_command (Full name is ./multi_command)
multi_command: Function not implemented
[root@ubuntu1604 ~/auxv] # ./launcher multi_command
This binary has been invoked as multi_command (Full name is /usr/local/bin/multi_command)
multi_command: Function not implemented
[root@ubuntu1604 ~/auxv] # ./launcher ./xxx
This binary has been invoked as xxx (Full name is ./xxx)
[root@ubuntu1604 ~/auxv] #

AT_EXECFN always contains the filename exactly as passed to the first argument of the execve syscall. The "p variants" (user mode stuff in glibc) of the exec family of functions modify the filename argument before actually syscalling execve.

WSLs implementation incorrectly uses argv[0] instead.

[christoh@wsl-insider ~/auxv] $ ll
total 5
drwxr-xr-x 0 christoh christoh 512 May 20 13:53 ./
drwxr-xr-x 0 christoh christoh 512 May 20 12:51 ../
-rw-r--r-- 1 christoh christoh 555 May 20 12:53 launcher.c
-rw-r--r-- 1 christoh christoh 590 May 20 12:53 multi_command.c
[christoh@wsl-insider ~/auxv] $ lsb_release -a && uname -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 16.04.1 LTS
Release:        16.04
Codename:       xenial
Linux wsl-insider 4.4.0-43-Microsoft #1-Microsoft Wed Dec 31 14:42:53 PST 2014 x86_64 x86_64 x86_64 GNU/Linux
[christoh@wsl-insider ~/auxv] $ gcc -o launcher launcher.c && gcc -o  multi_command multi_command.c
[christoh@wsl-insider ~/auxv] $ ln -srv multi_command xxx
'xxx' -> 'multi_command'
[christoh@wsl-insider ~/auxv] $ sudo cp multi_command /usr/local/bin
[sudo] password for christoh:
[christoh@wsl-insider ~/auxv] $ ./launcher ./multi_command
This binary has been invoked as AUXV_TEST_UNSUCCESSFUL (Full name is AUXV_TEST_UNSUCCESSFUL)
AUXV_TEST_UNSUCCESSFUL: Function not implemented
[christoh@wsl-insider ~/auxv] $ ./launcher multi_command
This binary has been invoked as AUXV_TEST_UNSUCCESSFUL (Full name is AUXV_TEST_UNSUCCESSFUL)
AUXV_TEST_UNSUCCESSFUL: Function not implemented
[christoh@wsl-insider ~/auxv] $ ./launcher ./xxx
This binary has been invoked as AUXV_TEST_UNSUCCESSFUL (Full name is AUXV_TEST_UNSUCCESSFUL)
AUXV_TEST_UNSUCCESSFUL: Function not implemented
[christoh@wsl-insider ~/auxv] $

  • Strace of the failing command, if applicable: (If <cmd> is failing, then run strace -o strace.txt -ff <cmd>, and post the strace.txt output here)

Won't help very much because getauxval is a glibc user mode function that searches the AUXV in the current process memory without involving a kernel call. But just for completeness.

Real Ubuntu 16.04:

[root@ubuntu1604 ~/auxv] # strace ./launcher ./xxx
execve("./launcher", ["./launcher", "./xxx"], [/* 23 vars */]) = 0
brk(NULL)                               = 0x2389000
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f74725e9000
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=155890, ...}) = 0
mmap(NULL, 155890, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f74725c2000
close(3)                                = 0
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
open("/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0P\t\2\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=1864888, ...}) = 0
mmap(NULL, 3967392, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f7471ffd000
mprotect(0x7f74721bc000, 2097152, PROT_NONE) = 0
mmap(0x7f74723bc000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1bf000) = 0x7f74723bc000
mmap(0x7f74723c2000, 14752, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f74723c2000
close(3)                                = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f74725c1000
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f74725c0000
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f74725bf000
arch_prctl(ARCH_SET_FS, 0x7f74725c0700) = 0
mprotect(0x7f74723bc000, 16384, PROT_READ) = 0
mprotect(0x600000, 4096, PROT_READ)     = 0
mprotect(0x7f74725eb000, 4096, PROT_READ) = 0
munmap(0x7f74725c2000, 155890)          = 0
brk(NULL)                               = 0x2389000
brk(0x23aa000)                          = 0x23aa000
execve("./xxx", ["AUXV_TEST_UNSUCCESSFUL"], [/* 23 vars */]) = 0
brk(NULL)                               = 0x2205000
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fd3c47d3000
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=155890, ...}) = 0
mmap(NULL, 155890, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7fd3c47ac000
close(3)                                = 0
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
open("/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0P\t\2\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=1864888, ...}) = 0
mmap(NULL, 3967392, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7fd3c41e7000
mprotect(0x7fd3c43a6000, 2097152, PROT_NONE) = 0
mmap(0x7fd3c45a6000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1bf000) = 0x7fd3c45a6000
mmap(0x7fd3c45ac000, 14752, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7fd3c45ac000
close(3)                                = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fd3c47ab000
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fd3c47aa000
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fd3c47a9000
arch_prctl(ARCH_SET_FS, 0x7fd3c47aa700) = 0
mprotect(0x7fd3c45a6000, 16384, PROT_READ) = 0
mprotect(0x600000, 4096, PROT_READ)     = 0
mprotect(0x7fd3c47d5000, 4096, PROT_READ) = 0
munmap(0x7fd3c47ac000, 155890)          = 0
fstat(1, {st_mode=S_IFCHR|0600, st_rdev=makedev(136, 8), ...}) = 0
brk(NULL)                               = 0x2205000
brk(0x2226000)                          = 0x2226000
write(1, "This binary has been invoked as "..., 57This binary has been invoked as xxx (Full name is ./xxx)
) = 57
exit_group(0)                           = ?
+++ exited with 0 +++
[root@ubuntu1604 ~/auxv] #

WSL:

[christoh@wsl-insider ~/auxv] $ strace ./launcher ./xxx
execve("./launcher", ["./launcher", "./xxx"], [/* 16 vars */]) = 0
brk(NULL)                               = 0x1d90000
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7ffc3ccd0000
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=25926, ...}) = 0
mmap(NULL, 25926, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7ffc3ccc9000
close(3)                                = 0
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
open("/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0P\t\2\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=1864888, ...}) = 0
mmap(NULL, 3967392, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7ffc3c630000
mprotect(0x7ffc3c7ef000, 2097152, PROT_NONE) = 0
mmap(0x7ffc3c9ef000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1bf000) = 0x7ffc3c9ef000
mmap(0x7ffc3c9f5000, 14752, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7ffc3c9f5000
close(3)                                = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7ffc3ccc0000
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7ffc3ccb0000
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7ffc3cca0000
arch_prctl(ARCH_SET_FS, 0x7ffc3ccb0700) = 0
mprotect(0x7ffc3c9ef000, 16384, PROT_READ) = 0
mprotect(0x600000, 4096, PROT_READ)     = 0
mprotect(0x7ffc3cc25000, 4096, PROT_READ) = 0
munmap(0x7ffc3ccc9000, 25926)           = 0
brk(NULL)                               = 0x1d90000
brk(0x1db1000)                          = 0x1db1000
execve("./xxx", ["AUXV_TEST_UNSUCCESSFUL"], [/* 16 vars */]) = 0
brk(NULL)                               = 0x12ad000
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f4d357e0000
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=25926, ...}) = 0
mmap(NULL, 25926, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f4d357e2000
close(3)                                = 0
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
open("/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0P\t\2\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=1864888, ...}) = 0
mmap(NULL, 3967392, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f4d35030000
mprotect(0x7f4d351ef000, 2097152, PROT_NONE) = 0
mmap(0x7f4d353ef000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1bf000) = 0x7f4d353ef000
mmap(0x7f4d353f5000, 14752, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f4d353f5000
close(3)                                = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f4d357d0000
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f4d357c0000
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f4d357b0000
arch_prctl(ARCH_SET_FS, 0x7f4d357c0700) = 0
mprotect(0x7f4d353ef000, 16384, PROT_READ) = 0
mprotect(0x600000, 4096, PROT_READ)     = 0
mprotect(0x7f4d35625000, 4096, PROT_READ) = 0
munmap(0x7f4d357e2000, 25926)           = 0
fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 0), ...}) = 0
brk(NULL)                               = 0x12ad000
brk(0x12ce000)                          = 0x12ce000
write(1, "This binary has been invoked as "..., 93This binary has been invoked as AUXV_TEST_UNSUCCESSFUL (Full name is AUXV_TEST_UNSUCCESSFUL)
) = 93
write(2, "AUXV_TEST_UNSUCCESSFUL: Function"..., 49AUXV_TEST_UNSUCCESSFUL: Function not implemented
) = 49
exit_group(38)                          = ?
+++ exited with 38 +++
[christoh@wsl-insider ~/auxv] $
@christoh christoh changed the title AUXV parameter AT_EXECFN returns argv[0] instead of filename passed to exec family of functions AUXV parameter AT_EXECFN returns argv[0] instead of filename passed to execve May 20, 2017
@christoh
Copy link
Author

christoh commented May 20, 2017

As it is weekend, I have some time to do more tests.

I modified my "launcher.c" a little bit to pass an empty argv array (argv[0] == NULL)

#include <unistd.h>
#include <malloc.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>

int main(int argc, char** argv)
{
        if (argc<2)
        {
                fprintf(stderr,"Usage: %s executable [arguments]\n", argv[0]);
                exit(EINVAL);
        }

        char** new_argv=(char**)malloc((argc) * sizeof(char*));
        new_argv[0] = NULL;//"AUXV_TEST_UNSUCCESSFUL";
        new_argv[argc - 1] = NULL;

        int i = 1;
        for (;i < argc -1; i++) new_argv[i] = argv[i + 1];

        execv(argv[1], new_argv);
        fprintf(stderr, "Could not start %s: %s\n", argv[1], strerror(errno));
}

I also simplified multi_command.c to focus on the essentials

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/auxv.h>
#include <libgen.h>
#include <errno.h>

int main(int argc, char** argv)
{
        char* fullname = (char*)getauxval(AT_EXECFN);
        char* invoked_as_name = basename(fullname);

        printf("AT_EXECFN: %s\n", fullname);
        printf("argv[0]  : %s\n", argv[0]);
}

WSL:

[christoh@wsl-insider ~/auxv] $ ./launcher ./multi_command
AT_EXECFN: SHELL=/bin/bash
argv[0]  : (null)

;-)

Real Ubuntu 16.04:

[root@ubuntu1604 ~/auxv] # ./launcher ./multi_command
AT_EXECFN: ./multi_command
argv[0]  : (null)

Seems that WSL's AT_EXECFN points to the process enviroment if argv[0] is NULL as printenv returns this

[christoh@wsl-insider ~/auxv] $ printenv
SHELL=/bin/bash
TERM=xterm
USER=christoh
MAIL=/var/mail/christoh
PATH=/usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games
PWD=/home/christoh/auxv
PS1=\[\e]0;\u@\h: \w\a\]${debian_chroot:+($debian_chroot)}[\[\033[32m\]\u@\h\[\033[00m\] \[\033[01;34m\]\w\[\033[00m\]] \$
SHLVL=1
HOME=/home/christoh
LOGNAME=christoh
OLDPWD=/home/christoh
_=/usr/bin/printenv

Interesting strategy to use envp[0] if argv[0] is NULL.

@sunjoong
Copy link

@christoh - I don't understand this iusse and background, but... saw what might be interesting.

This is same what you show;

sunjoong@SUNJOONG-DESKTOP ~/temp $ ./launcher ./multi_command
AT_EXECFN: SHELL=/bin/bash
argv[0]  : (null)
sunjoong@SUNJOONG-DESKTOP ~/temp $

But... this is... more strange;

sunjoong@SUNJOONG-DESKTOP ~/temp $ source /etc/profile
sunjoong@SUNJOONG-DESKTOP ~/temp $ ./launcher ./multi_command
AT_EXECFN: MANPATH=/usr/local/share/man:/usr/share/man:/usr/share/gcc-data/x86_64-pc-linux-gnu/5.4.0/man:/usr/share/binutils-data/x86_64-pc-linux-gnu/2.26.1/man
argv[0]  : (null)
sunjoong@SUNJOONG-DESKTOP ~/temp $ LD_SHOW_AUXV=1 ./launcher ./multi_command
AT_SYSINFO_EHDR: 0x7fffc3161000
AT_HWCAP:        1f8bfbff
AT_PAGESZ:       4096
AT_CLKTCK:       100
AT_PHDR:         0x400040
AT_PHENT:        56
AT_PHNUM:        9
AT_BASE:         0x7fb51d000000
AT_FLAGS:        0x0
AT_ENTRY:        0x4005c0
AT_UID:          1000
AT_EUID:         1000
AT_GID:          1000
AT_EGID:         1000
AT_SECURE:       0
AT_RANDOM:       0x7fffc2b73230
AT_EXECFN:       ./launcher
AT_PLATFORM:     x86_64
AT_SYSINFO_EHDR: 0x7fffe91e0000
AT_HWCAP:        1f8bfbff
AT_PAGESZ:       4096
AT_CLKTCK:       100
AT_PHDR:         0x400040
AT_PHENT:        56
AT_PHNUM:        9
AT_BASE:         0x7f80bdc00000
AT_FLAGS:        0x0
AT_ENTRY:        0x4004c0
AT_UID:          1000
AT_EUID:         1000
AT_GID:          1000
AT_EGID:         1000
AT_SECURE:       0
AT_RANDOM:       0x7fffe90d8930
AT_EXECFN:       LD_SHOW_AUXV=1
AT_PLATFORM:     x86_64
AT_EXECFN: LD_SHOW_AUXV=1
argv[0]  : (null)
sunjoong@SUNJOONG-DESKTOP ~/temp $

@christoh
Copy link
Author

christoh commented May 20, 2017

@sunjoong
It's just what I found out. If argv[0] is NULL, it uses envp[0] instead which is the first line of the process environment.

Before running ./launcher ./multi_command run printenv

If my findings are correct, AT_EXECFN always gives you the first line of what printenv says.

If you run source /etc/profile, your order of enviroment variables may change and thus AT_EXECFN returns something different.

The same applies if you precede a command with LD_SHOW_AUXV=1. This will then be the topmost entry in your environment and thus is returned by getauxval(AT_EXECFN)

While your output differs from mine, it strongly supports my theory that envp[0] is used if argv[0] is NULL

@christoh
Copy link
Author

christoh commented May 20, 2017

The next question that comes up is: What happens if envp[0] is also NULL.

launcher.c

#include <unistd.h>
#include <malloc.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>

int main(int argc, char** argv)
{
        if (argc<2)
        {
                fprintf(stderr,"Usage: %s executable [arguments]\n", argv[0]);
                exit(EINVAL);
        }

        char** new_argv=(char**)malloc((argc) * sizeof(char*));
        new_argv[0] = NULL;//"AUXV_TEST_UNSUCCESSFUL";
        char* envp0 = NULL;
        new_argv[argc - 1] = NULL;

        int i = 1;
        for (;i < argc -1; i++) new_argv[i] = argv[i + 1];

        execve(argv[1], new_argv, &envp0);
        fprintf(stderr, "Could not start %s: %s\n", argv[1], strerror(errno));
}

multi_command.c

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/auxv.h>
#include <libgen.h>
#include <errno.h>

int main(int argc, char** argv)
{
        char* fullname = (char*)getauxval(AT_EXECFN);
        char* platform = (char*)getauxval(AT_PLATFORM);

        printf("AT_EXECFN:   %s\n", fullname);
        printf("AT_PLATFORM: %s\n", platform);
        printf("argv[0]  : %s\n", argv[0]);
}

Output:

[christoh@wsl-insider ~/auxv] $ ./launcher ./multi_command
AT_EXECFN:   x86_64
AT_PLATFORM: x86_64
argv[0]  : (null)
[christoh@wsl-insider ~/auxv] $

If both argv[0] and envp[0] are NULL, AT_EXECFN actually becomes an alias to AT_PLATFORM. At least it doesn't destroy AT_PLATFORM.

Same applies if envp itself is NULL.

It seems that in the Elf64_auxv_t struct WSL updates a_type but leaves a_un.a_val unchanged from the previous value if a NULL pointer is encountered while filling the AUXV. This might be another bug unrelated to the original one.

@sunjoong
Copy link

@christoh - Some differences between your previous code and present.....

In present;

sunjoong@SUNJOONG-DESKTOP ~/temp $ LD_SHOW_AUXV=1 ./launcher ./multi_command
AT_SYSINFO_EHDR: 0x7fffe4dc8000
AT_HWCAP:        1f8bfbff
AT_PAGESZ:       4096
AT_CLKTCK:       100
AT_PHDR:         0x400040
AT_PHENT:        56
AT_PHNUM:        9
AT_BASE:         0x7f6d5a000000
AT_FLAGS:        0x0
AT_ENTRY:        0x400630
AT_UID:          1000
AT_EUID:         1000
AT_GID:          1000
AT_EGID:         1000
AT_SECURE:       0
AT_RANDOM:       0x7fffe4790660
AT_EXECFN:       ./launcher
AT_PLATFORM:     x86_64
AT_EXECFN:   x86_64
AT_PLATFORM: x86_64
argv[0]  : (null)
sunjoong@SUNJOONG-DESKTOP ~/temp $

In previous;

sunjoong@SUNJOONG-DESKTOP ~/temp0 $ !L
LD_SHOW_AUXV=1 ./launcher ./multi_command
AT_SYSINFO_EHDR: 0x7ffffe68f000
AT_HWCAP:        1f8bfbff
AT_PAGESZ:       4096
AT_CLKTCK:       100
AT_PHDR:         0x400040
AT_PHENT:        56
AT_PHNUM:        9
AT_BASE:         0x7fdbdd200000
AT_FLAGS:        0x0
AT_ENTRY:        0x4005c0
AT_UID:          1000
AT_EUID:         1000
AT_GID:          1000
AT_EGID:         1000
AT_SECURE:       0
AT_RANDOM:       0x7ffffe5b6e50
AT_EXECFN:       ./launcher
AT_PLATFORM:     x86_64
AT_SYSINFO_EHDR: 0x7ffff991e000
AT_HWCAP:        1f8bfbff
AT_PAGESZ:       4096
AT_CLKTCK:       100
AT_PHDR:         0x400040
AT_PHENT:        56
AT_PHNUM:        9
AT_BASE:         0x7fe2f8c00000
AT_FLAGS:        0x0
AT_ENTRY:        0x4004c0
AT_UID:          1000
AT_EUID:         1000
AT_GID:          1000
AT_EGID:         1000
AT_SECURE:       0
AT_RANDOM:       0x7ffff92277d0
AT_EXECFN:       LD_SHOW_AUXV=1
AT_PLATFORM:     x86_64
AT_EXECFN: LD_SHOW_AUXV=1
argv[0]  : (null)
sunjoong@SUNJOONG-DESKTOP ~/temp0 $

There were two AT_EXECFN, ./launcher and LD_SHOW_AUXV=1, in previous, but only one in present.

@christoh
Copy link
Author

@sunjoong
Yes of course. The new launcher sets envp[0] to NULL which means that all environment variables are actually cleared (empty). That will also delete the LD_SHOW_AUXV=1 when the process switches from launcher to multi_command. So it is displayed only once.

@sunjoong
Copy link

@christoh - Thanks :)

@benhillis benhillis self-assigned this Jul 14, 2017
@benhillis benhillis added the bug label Jul 14, 2017
@benhillis
Copy link
Member

I'll take a look at this while I'm looking at #2031 since it's the same code that sets all this up.

@benhillis
Copy link
Member

I've identified the causes of all of the issues above and will be fixing this soon. To summarize:

  1. We were not correctly handling empty command lines or environment variables arrays when setting up the initial usermode address space of a threadgroup.
  2. AT_EXECFN was incorrectly set to a pointer to the memory that held argv[0]. I verified on native Linux that there is a separate copy of the filename stored just after the environment block that is also copied into user memory during exec.

@benhillis
Copy link
Member

Fixed in 16257.

@benhillis benhillis removed their assignment Aug 3, 2017
@christoh
Copy link
Author

christoh commented Aug 3, 2017

Tested with 16257. Everything is as expected and the same as under native Linux

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants