From 8787522b58d243ccbc03ff0a2fd68b05b7af4a56 Mon Sep 17 00:00:00 2001
From: Sascha Wildner <saw@online.de>
Date: Sat, 23 Jan 2021 00:41:44 +0100
Subject: [PATCH] Implement the fexecve() system call.

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

See https://pubs.opengroup.org/onlinepubs/9699919799/functions/fexecve.html
---
 include/unistd.h          |  2 --
 lib/libc/sys/Makefile.inc |  1 +
 lib/libc/sys/Symbol.map   |  3 ++
 lib/libc/sys/execve.2     | 51 ++++++++++++++++++++++++++++--
 lib/libc/sys/open.2       |  4 +--
 sys/kern/init_sysent.c    |  1 +
 sys/kern/kern_exec.c      | 65 ++++++++++++++++++++++++++++++++++-----
 sys/kern/syscalls.c       |  1 +
 sys/kern/syscalls.master  |  1 +
 sys/sys/syscall.h         |  3 +-
 sys/sys/syscall.mk        |  3 +-
 sys/sys/sysproto.h        |  6 ++++
 sys/sys/sysunion.h        |  1 +
 13 files changed, 127 insertions(+), 15 deletions(-)

diff --git a/include/unistd.h b/include/unistd.h
index 4fe9c61c95..f6b8fe6033 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 536ced4b57..c16757f61d 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 e9454ff10d..a867b78364 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 ca535ec0f9..2da72902f9 100644
--- a/lib/libc/sys/execve.2
+++ b/lib/libc/sys/execve.2
@@ -28,11 +28,12 @@
 .\"     @(#)execve.2	8.5 (Berkeley) 6/1/94
 .\" $FreeBSD: src/lib/libc/sys/execve.2,v 1.16.2.10 2001/12/22 01:21:30 jwd Exp $
 .\"
-.Dd December 21, 2007
+.Dd January 23, 2021
 .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,6 +51,15 @@ whose name is pointed to by
 .Fa path ,
 called the
 .Em new process 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 file is either an executable object file,
 or a file of data for an interpreter.
 An executable object file consists of an identifying header,
@@ -190,6 +202,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
@@ -256,6 +290,16 @@ 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
@@ -281,3 +325,6 @@ The
 .Fn execve
 function call appeared in
 .Bx 4.2 .
+The
+.Fn fexecve
+system call conforms to The Open Group Extended API Set 2 specification.
diff --git a/lib/libc/sys/open.2 b/lib/libc/sys/open.2
index 02314ec451..13ecb3fa74 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 1659fb8145..2dc0b60083 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 3a85873651..254a81d0e1 100644
--- a/sys/kern/kern_exec.c
+++ b/sys/kern/kern_exec.c
@@ -654,6 +654,53 @@ sys_execve(struct sysmsg *sysmsg, const struct execve_args *uap)
 	return (error);
 }
 
+/*
+ * fexecve() system call.
+ */
+int
+sys_fexecve(struct sysmsg *sysmsg, const struct fexecve_args *uap)
+{
+	struct file *fp;
+	struct image_args args;
+	struct nlookupdata nd;
+	struct thread *td = curthread;
+	struct vnode *vp;
+	char *freepath, *fullpath;
+	int error;
+
+	error = holdvnode(td, uap->fd, &fp);
+	if (error != 0)
+		goto done;
+	vp = fp->f_data;
+	fdrop(fp);
+	freepath = NULL;
+	fullpath = "-";
+	error = vn_fullpath(td->td_proc, vp, &fullpath, &freepath, 0);
+	if (error != 0)
+		goto done;
+	bzero(&args, sizeof(args));
+	error = nlookup_init(&nd, fullpath, UIO_SYSSPACE, NLC_FOLLOW);
+	if (error == 0) {
+		error = exec_copyin_args(&args, NULL, PATH_SYSSPACE, uap->argv,
+		    uap->envv);
+	}
+	if (freepath)
+		kfree(freepath, M_TEMP);
+	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 */
+	}
+	if (error == 0)
+		sysmsg->sysmsg_result64 = 0;
+done:
+	return (error);
+}
+
 int
 exec_map_page(struct image_params *imgp, vm_pindex_t pageno,
 	      struct lwbuf **plwb, const char **pdata)
@@ -916,13 +963,17 @@ exec_copyin_args(struct image_args *args, char *fname,
 
 	args->fname = args->buf + ARG_MAX;
 
-	/*
-	 * Copy the file name.
-	 */
-	if (segflg == PATH_SYSSPACE) {
-		error = copystr(fname, args->fname, PATH_MAX, &length);
-	} else if (segflg == PATH_USERSPACE) {
-		error = copyinstr(fname, args->fname, PATH_MAX, &length);
+	if (fname != NULL) {
+		/*
+		 * Copy the file name.
+		 */
+		if (segflg == PATH_SYSSPACE) {
+			error = copystr(fname, args->fname, PATH_MAX, &length);
+		} else if (segflg == PATH_USERSPACE) {
+			error = copyinstr(fname, args->fname, PATH_MAX, &length);
+		}
+	} else {
+		length = 0;
 	}
 
 	/*
diff --git a/sys/kern/syscalls.c b/sys/kern/syscalls.c
index e171d59604..efbc838c96 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 30f2de24b3..b1d6ec01eb 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/syscall.h b/sys/sys/syscall.h
index c622667970..25851565f3 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 10b1385da2..fb4ed95c66 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 2a74435932..cadb8ea2af 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 592de73f49..25a0938bcf 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.28.0

