# Implementing 'execveat' for the Linuxulator.



## philmb (May 25, 2021)

I'm trying to implement 'execveat'. Here is what I have so far.

```
+int
+linux_execveat(struct thread *td, struct linux_execveat_args *args)
+{
+       struct image_args eargs;
+       char *path;
+       int error, dfd;
+
+       LINUX_CTR(execveat);
+
+       dfd = (args->dfd == LINUX_AT_FDCWD) ? AT_FDCWD : args->dfd;
+
+       if (!LUSECONVPATH(td)) {
+               printf("copyin with path.  I don't know. \n");
+               return (EINVAL);
+       }
+       LCONVPATHEXIST_AT(td, args->filename, &path, dfd);
+       printf("lconvpathexist():1;  with dfd=%d.  with path=%s.   LINUX_AT_FDCWD=%d\n", dfd, path, LINUX_AT_FDCWD);
+       printf("  flags = %d  ||   AT_SYMLINK_NOFOLLOW=%d   AT_EMPTY_PATH=%d\n",
+                       args->flags,
+                       args->flags & AT_SYMLINK_NOFOLLOW,
+                       args->flags & AT_EMPTY_PATH);
+       eargs.fd = dfd;
+       error = exec_copyin_args(&eargs, path, UIO_SYSSPACE, args->argv,
+           args->envp);
+       eargs.fd = dfd;
+       LFREEPATH(path);
+       // LCONVPATH_AT
+       // LCONVPATHEXIST
+       //
+
+       if (error == 0)
+               error = linux_common_execve(td, &eargs);
+
+       return (error);
+}
+
```


----------



## philmb (May 25, 2021)

Running the self test for Linux 4.4.14 gets me mixed results. Some execs work, some not.

```
bash-4.3# make TARGETS="exec" kselftest                                                                                
for TARGET in exec; do \
        make -C $TARGET; \
done;
make[2]: Entering directory '/usr/src/linux-4.4.14/tools/testing/selftests/exec'
make[2]: Nothing to be done for 'all'.
make[2]: Leaving directory '/usr/src/linux-4.4.14/tools/testing/selftests/exec'
for TARGET in exec; do \
        make -C $TARGET run_tests; \
done;
make[2]: Entering directory '/usr/src/linux-4.4.14/tools/testing/selftests/exec'
Check success of execveat(3, '../execveat', 0)... [FAIL]: execveat() failed, rc=-1 errno=2 (No such file or directory)
Check success of execveat(3, '../execveat', 0)... [FAIL] (child 4081 exited with 1 not 99 nor 99)
Check success of execveat(5, 'execveat', 0)... [OK]
Check success of execveat(6, 'execveat', 0)... [OK]
Check success of execveat(-100, '/usr/src/linux-4.4.1...ftests/exec/execveat', 0)... [OK]
Check success of execveat(99, '/usr/src/linux-4.4.1...ftests/exec/execveat', 0)... [OK]
Check success of execveat(8, '', 4096)... [FAIL]: execveat() failed, rc=-1 errno=2 (No such file or directory)
Check success of execveat(8, '', 4096)... [FAIL] (child 4086 exited with 1 not 99 nor 99)
Check success of execveat(17, '', 4096)... [FAIL]: execveat() failed, rc=-1 errno=2 (No such file or directory)
Check success of execveat(17, '', 4096)... [FAIL] (child 4087 exited with 1 not 99 nor 99)
Check success of execveat(9, '', 4096)... [FAIL]: execveat() failed, rc=-1 errno=2 (No such file or directory)
Check success of execveat(9, '', 4096)... [FAIL] (child 4088 exited with 1 not 99 nor 99)
Check success of execveat(14, '', 4096)... [FAIL]: execveat() failed, rc=-1 errno=2 (No such file or directory)
Check success of execveat(14, '', 4096)... [FAIL] (child 4089 exited with 1 not 99 nor 99)
Check success of execveat(14, '', 4096)... [FAIL]: execveat() failed, rc=-1 errno=2 (No such file or directory)
Check success of execveat(14, '', 4096)... [FAIL] (child 4090 exited with 1 not 99 nor 99)
Check success of execveat(15, '', 4096)... [FAIL]: execveat() failed, rc=-1 errno=2 (No such file or directory)
Check success of execveat(15, '', 4096)... [FAIL] (child 4091 exited with 1 not 99 nor 99)
Check failure of execveat(8, '', 0) with ENOENT... [OK]
Check failure of execveat(8, '(null)', 4096) with EFAULT... [OK]
Check success of execveat(5, 'execveat.symlink', 0)... [OK]
Check success of execveat(6, 'execveat.symlink', 0)... [OK]
Check success of execveat(-100, '/usr/src/linux-4.4.1...xec/execveat.symlink', 0)... [OK]
Check success of execveat(10, '', 4096)... [FAIL]: execveat() failed, rc=-1 errno=2 (No such file or directory)
Check success of execveat(10, '', 4096)... [FAIL] (child 4095 exited with 1 not 99 nor 99)
Check success of execveat(10, '', 4352)... [FAIL]: execveat() failed, rc=-1 errno=2 (No such file or directory)
Check success of execveat(10, '', 4352)... [FAIL] (child 4096 exited with 1 not 99 nor 99)
selftests: execveat [FAIL]
make[2]: Leaving directory '/usr/src/linux-4.4.14/tools/testing/selftests/exec'
```


----------



## philmb (May 25, 2021)

The calls look like this:

```
lconvpathexist():1;  with dfd=3.  with path=../execveat.   LINUX_AT_FDCWD=-100
  flags = 0  ||   AT_SYMLINK_NOFOLLOW=0   AT_EMPTY_PATH=0
lconvpathexist():1;  with dfd=5.  with path=execveat.   LINUX_AT_FDCWD=-100
  flags = 0  ||   AT_SYMLINK_NOFOLLOW=0   AT_EMPTY_PATH=0
lconvpathexist():1;  with dfd=6.  with path=execveat.   LINUX_AT_FDCWD=-100
  flags = 0  ||   AT_SYMLINK_NOFOLLOW=0   AT_EMPTY_PATH=0
lconvpathexist():1;  with dfd=-100.  with path=/usr/src/linux-4.4.14/tools/testing/selftests/exec/execveat.   LINUX_AT_
FDCWD=-100
  flags = 0  ||   AT_SYMLINK_NOFOLLOW=0   AT_EMPTY_PATH=0
lconvpathexist():1;  with dfd=99.  with path=/usr/src/linux-4.4.14/tools/testing/selftests/exec/execveat.   LINUX_AT_FD
CWD=-100
  flags = 0  ||   AT_SYMLINK_NOFOLLOW=0   AT_EMPTY_PATH=0
lconvpathexist():1;  with dfd=8.  with path=.   LINUX_AT_FDCWD=-100
  flags = 4096  ||   AT_SYMLINK_NOFOLLOW=0   AT_EMPTY_PATH=0
lconvpathexist():1;  with dfd=17.  with path=.   LINUX_AT_FDCWD=-100
  flags = 4096  ||   AT_SYMLINK_NOFOLLOW=0   AT_EMPTY_PATH=0
lconvpathexist():1;  with dfd=9.  with path=.   LINUX_AT_FDCWD=-100
  flags = 4096  ||   AT_SYMLINK_NOFOLLOW=0   AT_EMPTY_PATH=0
lconvpathexist():1;  with dfd=14.  with path=.   LINUX_AT_FDCWD=-100
  flags = 4096  ||   AT_SYMLINK_NOFOLLOW=0   AT_EMPTY_PATH=0
lconvpathexist():1;  with dfd=14.  with path=.   LINUX_AT_FDCWD=-100
  flags = 4096  ||   AT_SYMLINK_NOFOLLOW=0   AT_EMPTY_PATH=0
lconvpathexist():1;  with dfd=15.  with path=.   LINUX_AT_FDCWD=-100
  flags = 4096  ||   AT_SYMLINK_NOFOLLOW=0   AT_EMPTY_PATH=0
lconvpathexist():1;  with dfd=8.  with path=.   LINUX_AT_FDCWD=-100
  flags = 0  ||   AT_SYMLINK_NOFOLLOW=0   AT_EMPTY_PATH=0
lconvpathexist():1;  with dfd=5.  with path=execveat.symlink.   LINUX_AT_FDCWD=-100
  flags = 0  ||   AT_SYMLINK_NOFOLLOW=0   AT_EMPTY_PATH=0
lconvpathexist():1;  with dfd=6.  with path=execveat.symlink.   LINUX_AT_FDCWD=-100
  flags = 0  ||   AT_SYMLINK_NOFOLLOW=0   AT_EMPTY_PATH=0
lconvpathexist():1;  with dfd=-100.  with path=/usr/src/linux-4.4.14/tools/testing/selftests/exec/execveat.symlink.   L
INUX_AT_FDCWD=-100
  flags = 0  ||   AT_SYMLINK_NOFOLLOW=0   AT_EMPTY_PATH=0
lconvpathexist():1;  with dfd=10.  with path=.   LINUX_AT_FDCWD=-100
  flags = 4096  ||   AT_SYMLINK_NOFOLLOW=0   AT_EMPTY_PATH=0
lconvpathexist():1;  with dfd=10.  with path=.   LINUX_AT_FDCWD=-100
  flags = 4352  ||   AT_SYMLINK_NOFOLLOW=0   AT_EMPTY_PATH=0
lconvpathexist():1;  with dfd=5.  with path=execveat.symlink.   LINUX_AT_FDCWD=-100
  flags = 256  ||   AT_SYMLINK_NOFOLLOW=0   AT_EMPTY_PATH=0
```


----------



## philmb (May 26, 2021)

I made some progress. Now the call properly executes open files.


```
int
linux_execveat(struct thread *td, struct linux_execveat_args *args)
{
        struct image_args eargs;
        char *path;
        int error, dfd;

        LINUX_CTR(execveat);

        dfd = (args->dfd == LINUX_AT_FDCWD) ? AT_FDCWD : args->dfd;
        if (args->flags & ~(LINUX_AT_SYMLINK_FOLLOW | LINUX_AT_EMPTY_PATH))
                return (EINVAL);

        if (args->flags & LINUX_AT_EMPTY_PATH) {
                /*
                 * Special case: operate on the file 'dfd'.
                 * Requires the linux '/proc' mount.
                 */
                path = malloc(MAXPATHLEN, M_TEMP, M_WAITOK);
                sprintf(path, "/proc/self/fd/%d", dfd);
                error = exec_copyin_args(&eargs, path, UIO_SYSSPACE, args->argv,
                                args->envp);
                free(path, M_TEMP);
                if (error == 0)
                        error = linux_common_execve(td, &eargs);
                AUDIT_SYSCALL_EXIT(error == EJUSTRETURN ? 0 : error, td);
                return (error);
        }

        /* Otherwise try to exec the old fashioned way. */
        if (!LUSECONVPATH(td)) {
                error = exec_copyin_args(&eargs, args->filename, UIO_USERSPACE,
                    args->argv, args->envp);
        } else {
                LCONVPATHEXIST_AT(td, args->filename, &path, dfd);
                error = exec_copyin_args(&eargs, path, UIO_SYSSPACE, args->argv,
                                args->envp);
                LFREEPATH(path);
        }
        if (error == 0)
                error = linux_common_execve(td, &eargs);
        AUDIT_SYSCALL_EXIT(error == EJUSTRETURN ? 0 : error, td);
        return (error);
}
```


----------



## philmb (May 26, 2021)

Made some more progress. Now I have path lookups.



```
int
linux_common_execveat(struct thread *td, int dfd, int flags,
                char *upath, char **uargp, char **uenvp)
{
        struct image_args eargs;
        char *path;
        int error;
        struct nameidata nd;
        char *retbuf, *freebuf;
        int follow;

        LINUX_CTR(execveat);

        dfd = (dfd == LINUX_AT_FDCWD) ? AT_FDCWD : dfd;
        if (flags & ~(LINUX_AT_SYMLINK_FOLLOW | LINUX_AT_EMPTY_PATH))
                return (EINVAL);

        if (flags & LINUX_AT_EMPTY_PATH) {
                /*
                 * Special case: operate on the file 'dfd'.
                 * Requires the linux '/proc' mount.
                 */
                path = malloc(MAXPATHLEN, M_TEMP, M_WAITOK);
                sprintf(path, "/proc/self/fd/%d", dfd);
#if (defined(__amd64__) && defined(COMPAT_LINUX32))
                error = freebsd32_exec_copyin_args(&eargs, path, UIO_SYSSPACE,
                        (uint32_t *)uargp, (uint32_t *)uenvp);
#else
                error = exec_copyin_args(&eargs, path, UIO_SYSSPACE,
                        uargp, uenvp);
#endif
                free(path, M_TEMP);
                if (error == 0)
                        error = linux_do_execve(td, &eargs);
                AUDIT_SYSCALL_EXIT(error == EJUSTRETURN ? 0 : error, td);
                return (error);
        }

        /*
         * Otherwise try to exec the old fashioned way.
         * But first we need to find the path for the
         * relative file that is passed in. Relative to
         * dfd.
         */

        follow = (flags & LINUX_AT_SYMLINK_FOLLOW) ? FOLLOW : NOFOLLOW;
        NDINIT_ATRIGHTS(&nd, LOOKUP, follow | SAVENAME | NOCAPCHECK | AUDITVNODE1,
                        UIO_USERSPACE, upath, dfd, &cap_fstat_rights, td);
        if ((error = namei(&nd)) != 0) {
                path = malloc(MAXPATHLEN, M_TEMP, M_WAITOK);
                copyinstr(upath, path, MAXPATHLEN, NULL);
                printf("namei failure %d at path %s   dfd %d\n", error, path, dfd);
                free(path, M_TEMP);
                return (error);
        }
        error = vn_fullpath(nd.ni_vp, &retbuf, &freebuf);
        if (error != 0) {
                NDFREE(&nd, 0);
                return (error);
        }

        path = retbuf;

#if (defined(__amd64__) && defined(COMPAT_LINUX32))
        error = freebsd32_exec_copyin_args(&eargs, path, UIO_SYSSPACE,
                (uint32_t *)uargp, (uint32_t *)uenvp);
#else
        error = exec_copyin_args(&eargs, path, UIO_SYSSPACE,
                uargp, uenvp);
#endif

        NDFREE(&nd, 0);
        free(freebuf, M_TEMP);

        if (error == 0)
                error = linux_do_execve(td, &eargs);
        AUDIT_SYSCALL_EXIT(error == EJUSTRETURN ? 0 : error, td);
        return (error);
}
```

Unfortunately there's still a few failures in the linux test suite "exec" but most of the basic functionality seems to work. I might just submit like it.


----------



## zirias@ (May 26, 2021)

I wonder what's the usage for such a weird syscall 

But, anyways, the Linux manpage has the following to say:

```
In addition to the reasons explained in openat(2), the execveat()
       system call is also needed to allow fexecve(3) to be implemented
       on systems that do not have the /proc filesystem mounted.
```

So, your way to implement `AT_EMPTY_PATH` kind of spoils _that_ goal  Won't hurt emulation otherwise of course…


----------



## philmb (May 27, 2021)

Zirias said:


> I wonder what's the usage for such a weird syscall
> 
> But, anyways, the Linux manpage has the following to say:
> 
> ...


I think with f005b0df278d66aa8a1e1d43d85bad005564eeb1 I can just use the flag AT_EMPTY_PATH to namei() instead of opening /proc/self/fd/n.


----------

