From bd960b63df42c65d6f636ecd3e259474c462c1df Mon Sep 17 00:00:00 2001
From: Aaron LI <aly@aaronly.me>
Date: Mon, 15 Feb 2021 17:07:31 +0800
Subject: [PATCH 2/2] Implement the fexecve(2) system call

Like execve(), but with an fd instead of a path name.

See https://pubs.opengroup.org/onlinepubs/9699919799/functions/fexecve.html

This work is partially based on swildner's patch and FreeBSD's
implementation (revision 177787).

XXX/TODO: We're missing O_EXEC support in open(2).
---
 include/unistd.h          |   2 -
 lib/libc/sys/Makefile.inc |   1 +
 lib/libc/sys/Symbol.map   |   3 +
 lib/libc/sys/execve.2     |  64 ++++++++++++++-
 lib/libc/sys/open.2       |   4 +-
 sys/kern/init_sysent.c    |   1 +
 sys/kern/kern_exec.c      | 169 +++++++++++++++++++++++++++++++-------
 sys/kern/syscalls.c       |   1 +
 sys/kern/syscalls.master  |   1 +
 sys/sys/imgact.h          |  10 +--
 sys/sys/param.h           |   3 +-
 sys/sys/syscall.h         |   3 +-
 sys/sys/syscall.mk        |   3 +-
 sys/sys/sysproto.h        |   6 ++
 sys/sys/sysunion.h        |   1 +
 15 files changed, 225 insertions(+), 47 deletions(-)

diff --git a/include/unistd.h b/include/unistd.h
index 4fe9c61c953..f6b8fe6033c 100644
--- a/include/unistd.h
+++ b/include/unistd.h
@@ -474,9 +474,7 @@ int	 truncate(const char *, off_t);
 #if __POSIX_VISIBLE >= 200809
 int	faccessat(int, const char *, int, int);
 int	fchownat(int, const char *, uid_t, gid_t, int);
-#if 0 /* XXX missing */
 int	fexecve(int, char *const [], char *const []);
-#endif
 int	linkat(int, const char *, int, const char *, int);
 ssize_t	readlinkat(int, const char * __restrict, char * __restrict, size_t);
 int	symlinkat(const char *, int, const char *);
diff --git a/lib/libc/sys/Makefile.inc b/lib/libc/sys/Makefile.inc
index 536ced4b574..c16757f61d5 100644
--- a/lib/libc/sys/Makefile.inc
+++ b/lib/libc/sys/Makefile.inc
@@ -120,6 +120,7 @@ MLINKS+=clock_gettime.2 clock_getres.2 \
 	clock_gettime.2 clock_settime.2
 MLINKS+=dup.2 dup2.2
 MLINKS+=exec_sys_register.2 exec_sys_unregister.2
+MLINKS+=execve.2 fexecve.2
 MLINKS+=extattr.2 extattr_delete_file.2 \
 	extattr.2 extattr_get_file.2 \
 	extattr.2 extattr_set_file.2
diff --git a/lib/libc/sys/Symbol.map b/lib/libc/sys/Symbol.map
index e9454ff10d5..a867b78364f 100644
--- a/lib/libc/sys/Symbol.map
+++ b/lib/libc/sys/Symbol.map
@@ -333,6 +333,7 @@ DF504.0 {
 
 DF510.0 {
     clock_nanosleep;
+    fexecve;
 };
 
 DFprivate_1.0 {
@@ -405,6 +406,7 @@ DFprivate_1.0 {
     __sys_fchown;
     __sys_fchownat;
     __sys_fcntl;
+    __sys_fexecve;
     __sys_fhopen;
     __sys_fhstat;
     __sys_fhstatfs;
@@ -709,6 +711,7 @@ DFprivate_1.0 {
     _fchown;
     _fchownat;
     _fcntl;
+    _fexecve;
     _fhopen;
     _fhstat;
     _fhstatfs;
diff --git a/lib/libc/sys/execve.2 b/lib/libc/sys/execve.2
index 62a2c39716c..ba1685c5076 100644
--- a/lib/libc/sys/execve.2
+++ b/lib/libc/sys/execve.2
@@ -32,7 +32,8 @@
 .Dt EXECVE 2
 .Os
 .Sh NAME
-.Nm execve
+.Nm execve ,
+.Nm fexecve
 .Nd execute a file
 .Sh LIBRARY
 .Lb libc
@@ -40,6 +41,8 @@
 .In unistd.h
 .Ft int
 .Fn execve "const char *path" "char *const argv[]" "char *const envp[]"
+.Ft int
+.Fn fexecve "int fd" "char *const argv[]" "char *const envp[]"
 .Sh DESCRIPTION
 .Fn Execve
 transforms the calling process into a new process.
@@ -48,7 +51,18 @@ whose name is pointed to by
 .Fa path ,
 called the
 .Em new process file .
-This file is either an executable object file,
+The
+.Fn fexecve
+system call is equivalent to
+.Fn execve
+except that the file to be executed is determined by the file
+descriptor
+.Fa fd
+instead of a
+.Fa path .
+This
+.Em new process file
+is either an executable object file,
 or a file of data for an interpreter.
 An executable object file consists of an identifying header,
 followed by pages of data representing the initial program (text)
@@ -197,6 +211,28 @@ and
 .Fa argv
 points to the array of character pointers
 to the arguments themselves.
+.Pp
+The
+.Fn fexecve
+function ignores the file offset of
+.Fa fd .
+Since execute permission is checked by
+.Fn fexecve ,
+the file descriptor
+.Fa fd
+need not have been opened with the
+.Dv O_EXEC
+flag.
+However, if the file to be executed denies read permission for the process
+preparing to do the exec, the only way to provide the
+.Fa fd
+to
+.Fn fexecve
+is to use the
+.Dv O_EXEC
+flag when opening
+.Fa fd .
+Note that the file to be executed can not be open for writing.
 .Sh RETURN VALUES
 As the
 .Fn execve
@@ -263,13 +299,25 @@ to an illegal address.
 .It Bq Er EIO
 An I/O error occurred while reading from the file system.
 .El
+.Pp
+In addition, the
+.Fn fexecve
+system call will fail and return to the calling process if:
+.Bl -tag -width Er
+.It Bq Er EBADF
+The
+.Fa fd
+argument is not a valid file descriptor open for executing.
+.El
 .Sh CAVEATS
 If a program is
 .Em setuid
 to a non-super-user, but is executed when
 the real
 .Em uid
-is ``root'', then the program has some of the powers
+is
+.Dq root ,
+then the program has some of the powers
 of a super-user as well.
 .Sh SEE ALSO
 .Xr ktrace 1 ,
@@ -288,10 +336,18 @@ The
 .Fn execve
 system call conforms to
 .St -p1003.1-2004 .
+The
+.Fn fexecve
+system call conforms to
+.St -p1003.1-2008 .
 .Pp
 The support for executing interpreted programs is an extension.
 .Sh HISTORY
 The
 .Fn execve
 function call appeared in
-.Bx 4.2 .
+.At v7 .
+The
+.Fn fexecve
+function first appeared in
+.Dx 5.9 .
diff --git a/lib/libc/sys/open.2 b/lib/libc/sys/open.2
index 02314ec4515..13ecb3fa741 100644
--- a/lib/libc/sys/open.2
+++ b/lib/libc/sys/open.2
@@ -27,9 +27,8 @@
 .\"
 .\"     @(#)open.2	8.2 (Berkeley) 11/16/93
 .\" $FreeBSD: src/lib/libc/sys/open.2,v 1.11.2.9 2001/12/14 18:34:01 ru Exp $
-.\" $DragonFly: src/lib/libc/sys/open.2,v 1.3 2005/07/29 23:16:04 hsu Exp $
 .\"
-.Dd July 31, 2012
+.Dd January 23, 2021
 .Dt OPEN 2
 .Os
 .Sh NAME
@@ -368,6 +367,7 @@ and
 .Xr chmod 2 ,
 .Xr close 2 ,
 .Xr dup 2 ,
+.Xr fexecve 2 ,
 .Xr getdtablesize 2 ,
 .Xr lseek 2 ,
 .Xr read 2 ,
diff --git a/sys/kern/init_sysent.c b/sys/kern/init_sysent.c
index 1659fb8145a..2dc0b600834 100644
--- a/sys/kern/init_sysent.c
+++ b/sys/kern/init_sysent.c
@@ -566,4 +566,5 @@ struct sysent sysent[] = {
 	{ AS(lwp_getname_args), (sy_call_t *)sys_lwp_getname },	/* 549 = lwp_getname */
 	{ AS(getrandom_args), (sy_call_t *)sys_getrandom },	/* 550 = getrandom */
 	{ AS(__realpath_args), (sy_call_t *)sys___realpath },	/* 551 = __realpath */
+	{ AS(fexecve_args), (sy_call_t *)sys_fexecve },	/* 552 = fexecve */
 };
diff --git a/sys/kern/kern_exec.c b/sys/kern/kern_exec.c
index 3a85873651c..20e863bdb18 100644
--- a/sys/kern/kern_exec.c
+++ b/sys/kern/kern_exec.c
@@ -77,7 +77,15 @@
 MALLOC_DEFINE(M_PARGS, "proc-args", "Process arguments");
 MALLOC_DEFINE(M_EXECARGS, "exec-args", "Exec arguments");
 
-static register_t *exec_copyout_strings (struct image_params *);
+enum exec_path_segflg {
+	PATH_SYSSPACE,
+	PATH_USERSPACE,
+};
+
+static register_t *exec_copyout_strings(struct image_params *);
+static int	exec_copyin_args(struct image_args *, char *,
+				 enum exec_path_segflg, int, char **, char **);
+static void	exec_free_args(struct image_args *);
 
 /* XXX This should be vm_size_t. */
 __read_mostly static u_long ps_strings = PS_STRINGS;
@@ -191,9 +199,12 @@ __read_mostly static const struct execsw **execsw;
 int
 kern_execve(struct nlookupdata *nd, struct image_args *args)
 {
+	static const char *fexecve_proctitle = "(fexecve)";
+	bool is_fexecve;
 	struct thread *td = curthread;
 	struct lwp *lp = td->td_lwp;
 	struct proc *p = td->td_proc;
+	struct file *fp;
 	struct vnode *ovp;
 	register_t *stack_base;
 	struct pargs *pa;
@@ -212,6 +223,7 @@ kern_execve(struct nlookupdata *nd, struct image_args *args)
 	KKASSERT(p);
 	lwkt_gettoken(&p->p_token);
 	imgp = &image_params;
+	fp = NULL;
 
 	/*
 	 * NOTE: P_INEXEC is handled by exec_new_vmspace() now.  We make
@@ -242,19 +254,33 @@ kern_execve(struct nlookupdata *nd, struct image_args *args)
 
 interpret:
 
-	/*
-	 * Translate the file name to a vnode.  Unlock the cache entry to
-	 * improve parallelism for programs exec'd in parallel.
-	 */
-	nd->nl_flags |= NLC_SHAREDLOCK;
-	if ((error = nlookup(nd)) != 0)
-		goto exec_fail;
-	error = cache_vget(&nd->nl_nch, nd->nl_cred, LK_SHARED, &imgp->vp);
-	KKASSERT(nd->nl_flags & NLC_NCPISLOCKED);
-	nd->nl_flags &= ~NLC_NCPISLOCKED;
-	cache_unlock(&nd->nl_nch);
-	if (error)
-		goto exec_fail;
+	is_fexecve = (args->fd >= 0 && imgp->interpreted == 0);
+	if (is_fexecve) {
+		/*
+		 * Translate the file descriptor to a vnode.
+		 */
+		if ((error = holdvnode(td, args->fd, &fp)) != 0)
+			goto exec_fail;
+		imgp->vp = fp->f_data;
+		if ((error = vget(imgp->vp, LK_SHARED)) != 0)
+			goto exec_fail;
+	} else {
+		/*
+		 * Translate the file name to a vnode.  Unlock the cache
+		 * entry to improve parallelism for programs exec'd in
+		 * parallel.
+		 */
+		nd->nl_flags |= NLC_SHAREDLOCK;
+		if ((error = nlookup(nd)) != 0)
+			goto exec_fail;
+		error = cache_vget(&nd->nl_nch, nd->nl_cred, LK_SHARED,
+				   &imgp->vp);
+		KKASSERT(nd->nl_flags & NLC_NCPISLOCKED);
+		nd->nl_flags &= ~NLC_NCPISLOCKED;
+		cache_unlock(&nd->nl_nch);
+		if (error)
+			goto exec_fail;
+	}
 
 	/*
 	 * Check file permissions (also 'opens' file).
@@ -281,7 +307,7 @@ kern_execve(struct nlookupdata *nd, struct image_args *args)
 
 	/*
 	 *	If the current process has a special image activator it
-	 *	wants to try first, call it.   For example, emulating shell 
+	 *	wants to try first, call it.  For example, emulating shell
 	 *	scripts differently.
 	 */
 	error = -1;
@@ -334,13 +360,12 @@ kern_execve(struct nlookupdata *nd, struct image_args *args)
 	 * Do the best to calculate the full path to the image file
 	 */
 	if (imgp->auxargs != NULL &&
-	   ((args->fname != NULL && args->fname[0] == '/') ||
-	    vn_fullpath(imgp->proc,
-			imgp->vp,
-			&imgp->execpath,
-			&imgp->freepath,
-			0) != 0))
+	    ((args->fname != NULL && args->fname[0] == '/') ||
+	     vn_fullpath(imgp->proc, imgp->vp, &imgp->execpath,
+			 &imgp->freepath, 0) != 0))
+	{
 		imgp->execpath = args->fname;
+	}
 
 	/*
 	 * Copy out strings (args and env) and initialize stack base
@@ -418,9 +443,14 @@ kern_execve(struct nlookupdata *nd, struct image_args *args)
 	/* reset caught signals */
 	execsigs(p);
 
-	/* name this process - nameiexec(p, ndp) */
-	len = min(nd->nl_nch.ncp->nc_nlen, MAXCOMLEN);
-	bcopy(nd->nl_nch.ncp->nc_name, p->p_comm, len);
+	/* name this process */
+	if (is_fexecve) {
+		len = sizeof(fexecve_proctitle) - 1;
+		bcopy(fexecve_proctitle, p->p_comm, len);
+	} else {
+		len = min(nd->nl_nch.ncp->nc_nlen, MAXCOMLEN);
+		bcopy(nd->nl_nch.ncp->nc_name, p->p_comm, len);
+	}
 	p->p_comm[len] = 0;
 	bcopy(p->p_comm, lp->lwp_thread->td_comm, MAXCOMLEN+1);
 
@@ -583,6 +613,9 @@ kern_execve(struct nlookupdata *nd, struct image_args *args)
 	if (imgp->freepath)
 		kfree(imgp->freepath, M_TEMP);
 
+	if (fp)
+		fdrop(fp);
+
 	if (error == 0) {
 		++mycpu->gd_cnt.v_exec;
 		lwkt_reltoken(&p->p_token);
@@ -630,10 +663,79 @@ sys_execve(struct sysmsg *sysmsg, const struct execve_args *uap)
 	error = nlookup_init(&nd, uap->fname, UIO_USERSPACE, NLC_FOLLOW);
 	if (error == 0) {
 		error = exec_copyin_args(&args, uap->fname, PATH_USERSPACE,
-					uap->argv, uap->envv);
+					 -1, uap->argv, uap->envv);
+	}
+	if (error == 0)
+		error = kern_execve(&nd, &args);
+	nlookup_done(&nd);
+	exec_free_args(&args);
+
+	if (error < 0) {
+		/* We hit a lethal error condition.  Let's die now. */
+		exit1(W_EXITCODE(0, SIGABRT));
+		/* NOTREACHED */
+	}
+
+	/*
+	 * The syscall result is returned in registers to the new program.
+	 * Linux will register %edx as an atexit function and we must be
+	 * sure to set it to 0.  XXX
+	 */
+	if (error == 0)
+		sysmsg->sysmsg_result64 = 0;
+
+	return (error);
+}
+
+/*
+ * fexecve() system call.
+ */
+int
+sys_fexecve(struct sysmsg *sysmsg, const struct fexecve_args *uap)
+{
+	struct nlookupdata nd;
+	struct image_args args;
+	struct thread *td = curthread;
+	struct vnode *vp;
+	struct file *fp;
+	char *fullpath, *freepath;
+	char fname[32]; /* "/dev/fd/xxx" */
+	int error;
+
+	fullpath = freepath = NULL;
+	bzero(&args, sizeof(args));
+
+	if ((error = holdvnode(td, uap->fd, &fp)) != 0)
+		return (error);
+
+	/*
+	 * Require a descriptor opened only with O_RDONLY or O_EXEC.
+	 * XXX: missing O_EXEC support
+	 */
+	if ((fp->f_flag & FWRITE) != 0 || (fp->f_flag & FREAD) == 0) {
+		error = EBADF;
+		goto done;
+	}
+
+	vp = fp->f_data;
+	error = vn_fullpath(td->td_proc, vp, &fullpath, &freepath, 0);
+	if (error)
+		goto done;
+
+	error = nlookup_init(&nd, fullpath, UIO_SYSSPACE, NLC_FOLLOW);
+	if (error == 0) {
+		/*
+		 * The 'fname' argument is required when executing an
+		 * interpreted program because the interpreter must know
+		 * the script path.  Supply it with '/dev/fd/xxx'.
+		 */
+		ksnprintf(fname, sizeof(fname), "/dev/fd/%d", uap->fd);
+		error = exec_copyin_args(&args, fname, PATH_SYSSPACE,
+					 uap->fd, uap->argv, uap->envv);
 	}
 	if (error == 0)
 		error = kern_execve(&nd, &args);
+
 	nlookup_done(&nd);
 	exec_free_args(&args);
 
@@ -651,6 +753,10 @@ sys_execve(struct sysmsg *sysmsg, const struct execve_args *uap)
 	if (error == 0)
 		sysmsg->sysmsg_result64 = 0;
 
+done:
+	if (freepath != NULL)
+		kfree(freepath, M_TEMP);
+	fdrop(fp);
 	return (error);
 }
 
@@ -901,7 +1007,8 @@ exec_new_vmspace(struct image_params *imgp, struct vmspace *vmcopy)
  */
 int
 exec_copyin_args(struct image_args *args, char *fname,
-		enum exec_path_segflg segflg, char **argv, char **envv)
+		 enum exec_path_segflg segflg, int fd,
+		 char **argv, char **envv)
 {
 	char	*argp, *envp;
 	int	error = 0;
@@ -910,20 +1017,23 @@ exec_copyin_args(struct image_args *args, char *fname,
 	args->buf = objcache_get(exec_objcache, M_WAITOK);
 	if (args->buf == NULL)
 		return (ENOMEM);
+
 	args->begin_argv = args->buf;
 	args->endp = args->begin_argv;
 	args->space = ARG_MAX;
 
 	args->fname = args->buf + ARG_MAX;
+	args->fd = fd;
 
 	/*
 	 * Copy the file name.
 	 */
-	if (segflg == PATH_SYSSPACE) {
+	if (segflg == PATH_SYSSPACE)
 		error = copystr(fname, args->fname, PATH_MAX, &length);
-	} else if (segflg == PATH_USERSPACE) {
+	else
 		error = copyinstr(fname, args->fname, PATH_MAX, &length);
-	}
+	if (error)
+		return (error);
 
 	/*
 	 * Extract argument strings.  argv may not be NULL.  The argv
@@ -993,6 +1103,7 @@ exec_copyin_args(struct image_args *args, char *fname,
 			args->envc++;
 		}
 	}
+
 	return (error);
 }
 
diff --git a/sys/kern/syscalls.c b/sys/kern/syscalls.c
index e171d596041..efbc838c96a 100644
--- a/sys/kern/syscalls.c
+++ b/sys/kern/syscalls.c
@@ -558,4 +558,5 @@ const char *syscallnames[] = {
 	"lwp_getname",			/* 549 = lwp_getname */
 	"getrandom",			/* 550 = getrandom */
 	"__realpath",			/* 551 = __realpath */
+	"fexecve",			/* 552 = fexecve */
 };
diff --git a/sys/kern/syscalls.master b/sys/kern/syscalls.master
index 30f2de24b3d..b1d6ec01eb1 100644
--- a/sys/kern/syscalls.master
+++ b/sys/kern/syscalls.master
@@ -735,3 +735,4 @@
 549	STD	{ int lwp_getname(lwpid_t tid, char *name, size_t len); }
 550	STD	{ ssize_t getrandom(void *buf, size_t len, unsigned flags); }
 551	STD	{ ssize_t __realpath(const char *path, char *buf, size_t len); }
+552	STD	{ int fexecve(int fd, char **argv, char **envv); }
diff --git a/sys/sys/imgact.h b/sys/sys/imgact.h
index 1cd5cdc59fb..acd8ea0ba12 100644
--- a/sys/sys/imgact.h
+++ b/sys/sys/imgact.h
@@ -27,7 +27,6 @@
  * SUCH DAMAGE.
  *
  * $FreeBSD: src/sys/sys/imgact.h,v 1.22.2.2 2001/12/22 01:21:44 jwd Exp $
- * $DragonFly: src/sys/sys/imgact.h,v 1.6 2004/04/11 00:10:34 dillon Exp $
  */
 
 #ifndef _SYS_IMGACT_H_
@@ -43,7 +42,8 @@ struct image_args {
 	char *begin_argv;	/* beginning of argv in buf */
 	char *begin_envv;	/* beginning of envv in buf */
 	char *endp;		/* current `end' pointer of arg & env strings */
-	char *fname;		/* beginning of file name */
+	char *fname;		/* file name of the executable */
+	int fd;			/* file descriptor of the executable */
 	int space;		/* space left in arg & env buffer */
 	int argc;		/* count of argument strings */
 	int envc;		/* count of environment strings */
@@ -70,16 +70,12 @@ struct image_params {
 };
 
 #ifdef _KERNEL
-enum	exec_path_segflg {PATH_SYSSPACE, PATH_USERSPACE};
-
 struct vmspace;
+
 int	exec_resident_imgact (struct image_params *);
 int	exec_check_permissions (struct image_params *, struct mount *);
 int	exec_new_vmspace (struct image_params *, struct vmspace *vmres);
 int	exec_shell_imgact (struct image_params *);
-int	exec_copyin_args(struct image_args *, char *, enum exec_path_segflg,
-	char **, char **);
-void	exec_free_args(struct image_args *);
 #endif
 
 #endif /* !_SYS_IMGACT_H_ */
diff --git a/sys/sys/param.h b/sys/sys/param.h
index 4d1ff905acf..c5e2c2056a5 100644
--- a/sys/sys/param.h
+++ b/sys/sys/param.h
@@ -224,9 +224,10 @@
  * 500906 - add struct ip_mreqn for IPPROTO_IP/IP_MULTICAST_IF,
  *          IP_ADD_MEMBERSHIP and IP_DROP_MEMBERSHIP.
  * 500907 - add clock_nanosleep()
+ * 500908 - add fexecve()
  */
 #undef __DragonFly_version
-#define __DragonFly_version 500907	/* propagated to newvers */
+#define __DragonFly_version 500908	/* propagated to newvers */
 
 #include <sys/_null.h>
 
diff --git a/sys/sys/syscall.h b/sys/sys/syscall.h
index c6226679703..25851565f3e 100644
--- a/sys/sys/syscall.h
+++ b/sys/sys/syscall.h
@@ -387,4 +387,5 @@
 #define	SYS_lwp_getname	549
 #define	SYS_getrandom	550
 #define	SYS___realpath	551
-#define	SYS_MAXSYSCALL	552
+#define	SYS_fexecve	552
+#define	SYS_MAXSYSCALL	553
diff --git a/sys/sys/syscall.mk b/sys/sys/syscall.mk
index 10b1385da26..fb4ed95c666 100644
--- a/sys/sys/syscall.mk
+++ b/sys/sys/syscall.mk
@@ -314,4 +314,5 @@ MIASM =  \
 	wait6.o \
 	lwp_getname.o \
 	getrandom.o \
-	__realpath.o
+	__realpath.o \
+	fexecve.o
diff --git a/sys/sys/sysproto.h b/sys/sys/sysproto.h
index 2a744359328..cadb8ea2afd 100644
--- a/sys/sys/sysproto.h
+++ b/sys/sys/sysproto.h
@@ -1431,6 +1431,11 @@ struct	__realpath_args {
 	char *	buf;	char buf_[PAD_(char *)];
 	size_t	len;	char len_[PAD_(size_t)];
 };
+struct	fexecve_args {
+	int	fd;	char fd_[PAD_(int)];
+	char **	argv;	char argv_[PAD_(char **)];
+	char **	envv;	char envv_[PAD_(char **)];
+};
 
 #undef PAD_
 
@@ -1756,6 +1761,7 @@ int	sys_wait6 (struct sysmsg *sysmsg, const struct wait6_args *);
 int	sys_lwp_getname (struct sysmsg *sysmsg, const struct lwp_getname_args *);
 int	sys_getrandom (struct sysmsg *sysmsg, const struct getrandom_args *);
 int	sys___realpath (struct sysmsg *sysmsg, const struct __realpath_args *);
+int	sys_fexecve (struct sysmsg *sysmsg, const struct fexecve_args *);
 
 #endif /* _KERNEL */
 
diff --git a/sys/sys/sysunion.h b/sys/sys/sysunion.h
index 592de73f496..25a0938bcf4 100644
--- a/sys/sys/sysunion.h
+++ b/sys/sys/sysunion.h
@@ -315,4 +315,5 @@ union sysunion {
 	struct	lwp_getname_args lwp_getname;
 	struct	getrandom_args getrandom;
 	struct	__realpath_args __realpath;
+	struct	fexecve_args fexecve;
 };
-- 
2.30.0

