From 88c49c025cff014ec1e7f4265cc964367542161e Mon Sep 17 00:00:00 2001
From: Aaron LI <aly@aaronly.me>
Date: Wed, 17 Feb 2021 20:29:36 +0800
Subject: [PATCH 6/7] Implement the fexecve(2) system call

The fexecve(2) function is equivalent to execve(2), except that the file
to be executed is determined by the file descriptor fd instead of a
pathname.

The purpose of fexecve(2) is to enable executing a file which has been
verified to be the intended file.  It is possible to actively check the
file by reading from the file descriptor and be sure that the file is
not exchanged for another between the reading and the execution.

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

This work is partially based on swildner's patch and FreeBSD's
implementation (revisions 177787, 182191, 238220).

XXX: 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     |  71 +++++++++++++++++++++---
 lib/libc/sys/open.2       |   4 +-
 sys/kern/init_sysent.c    |   1 +
 sys/kern/kern_exec.c      | 114 +++++++++++++++++++++++++++++++-------
 sys/kern/syscalls.c       |   1 +
 sys/kern/syscalls.master  |   1 +
 sys/sys/kern_syscall.h    |   3 +-
 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, 182 insertions(+), 35 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 77cabb2950a..e10f5a6f18e 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 January 23, 2021
+.Dd February 17, 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,7 +51,17 @@ 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)
@@ -196,10 +209,32 @@ 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 the 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
+As
 .Fn execve
-function overlays the current process image
+overlays the current process image
 with a new process image the successful call
 has no process to return to.
 If
@@ -208,6 +243,10 @@ does return to the calling process an error has occurred; the
 return value will be -1 and the global variable
 .Va errno
 is set to indicate the error.
+.Pp
+.Fn Fexecve
+has the same return values as
+.Fn execve .
 .Sh ERRORS
 .Fn Execve
 will fail and return to the calling process if:
@@ -262,6 +301,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
@@ -289,10 +338,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 .
+system call appeared in
+.At v7 .
+The
+.Fn fexecve
+system call first appeared in
+.Dx 5.9 .
diff --git a/lib/libc/sys/open.2 b/lib/libc/sys/open.2
index 02314ec4515..e4658d085d8 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 February 17, 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 c02866aa597..d7b65a3eae4 100644
--- a/sys/kern/kern_exec.c
+++ b/sys/kern/kern_exec.c
@@ -197,8 +197,10 @@ __read_mostly static const struct execsw **execsw;
  * process!
  */
 int
-kern_execve(struct nlookupdata *nd, struct image_args *args)
+kern_execve(struct nlookupdata *nd, struct file *fp,
+	    struct image_args *args)
 {
+	static const char *proctitle = "(execve)";
 	register_t *stack_base;
 	struct thread *td = curthread;
 	struct lwp *lp = td->td_lwp;
@@ -210,6 +212,7 @@ kern_execve(struct nlookupdata *nd, struct image_args *args)
 	struct image_params image_params, *imgp;
 	struct filedesc *fds;
 	struct nchandle *nch;
+	struct nlookupdata nd_interpreter;
 	struct vattr_lite lva;
 	int error, len, i;
 	int (*img_first) (struct image_params *);
@@ -252,19 +255,27 @@ 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.
-	 */
-	nch = &nd->nl_nch;
-	nd->nl_flags |= NLC_SHAREDLOCK;
-	if ((error = nlookup(nd)) != 0)
-		goto failed;
+	if (nd || imgp->interpreted) {
+		/*
+		 * Translate the file name to a vnode.  Unlock the cache
+		 * entry to improve parallelism for programs exec'd in
+		 * parallel.
+		 */
+		nch = &nd->nl_nch;
+		nd->nl_flags |= NLC_SHAREDLOCK;
+		if ((error = nlookup(nd)) != 0)
+			goto failed;
 
-	error = cache_vget(nch, nd->nl_cred, LK_SHARED, &imgp->vp);
-	KKASSERT(nd->nl_flags & NLC_NCPISLOCKED);
-	nd->nl_flags &= ~NLC_NCPISLOCKED;
-	cache_unlock(nch);
+		error = cache_vget(nch, nd->nl_cred, LK_SHARED,
+				   &imgp->vp);
+		KKASSERT(nd->nl_flags & NLC_NCPISLOCKED);
+		nd->nl_flags &= ~NLC_NCPISLOCKED;
+		cache_unlock(nch);
+	} else {
+		nch = &fp->f_nchandle;
+		imgp->vp = fp->f_data;
+		error = vget(imgp->vp, LK_SHARED);
+	}
 	if (error) {
 		imgp->vp = NULL;
 		goto failed;
@@ -333,14 +344,14 @@ kern_execve(struct nlookupdata *nd, struct image_args *args)
 	 */
 	if (imgp->interpreted) {
 		exec_unmap_first_page(imgp);
-		nlookup_done(nd);
 		vrele(imgp->vp);
 		imgp->vp = NULL;
-		error = nlookup_init(nd, imgp->interpreter_name, UIO_SYSSPACE,
-					NLC_FOLLOW);
+		error = nlookup_init(&nd_interpreter, imgp->interpreter_name,
+				     UIO_SYSSPACE, NLC_FOLLOW);
 		if (error)
 			goto failed;
 
+		nd = &nd_interpreter;
 		goto interpret;
 	}
 
@@ -430,8 +441,13 @@ kern_execve(struct nlookupdata *nd, struct image_args *args)
 	execsigs(p);
 
 	/* name this process */
-	len = min(nch->ncp->nc_nlen, MAXCOMLEN);
-	bcopy(nch->ncp->nc_name, p->p_comm, len);
+	if (nch->ncp) {
+		len = min(nch->ncp->nc_nlen, MAXCOMLEN);
+		bcopy(nch->ncp->nc_name, p->p_comm, len);
+	} else {
+		len = sizeof(proctitle) - 1;
+		bcopy(proctitle, p->p_comm, len);
+	}
 	p->p_comm[len] = 0;
 	bcopy(p->p_comm, lp->lwp_thread->td_comm, MAXCOMLEN+1);
 
@@ -587,6 +603,8 @@ kern_execve(struct nlookupdata *nd, struct image_args *args)
 		vrele(imgp->vp);
 	if (imgp->freepath)
 		kfree(imgp->freepath, M_TEMP);
+	if (imgp->interpreted)
+		nlookup_done(&nd_interpreter);
 
 	if (error == 0) {
 		++mycpu->gd_cnt.v_exec;
@@ -634,10 +652,10 @@ 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);
+					 uap->argv, uap->envv);
 	}
 	if (error == 0)
-		error = kern_execve(&nd, &args);
+		error = kern_execve(&nd, NULL, &args);
 	nlookup_done(&nd);
 	exec_free_args(&args);
 
@@ -658,6 +676,62 @@ 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 image_args args;
+	struct thread *td = curthread;
+	struct file *fp;
+	char fname[32]; /* "/dev/fd/xxx" */
+	int error;
+
+	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;
+	}
+
+	/*
+	 * 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);
+	bzero(&args, sizeof(args));
+	error = exec_copyin_args(&args, fname, PATH_SYSSPACE,
+				 uap->argv, uap->envv);
+	if (error == 0)
+		error = kern_execve(NULL, fp, &args);
+	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;
+
+done:
+	fdrop(fp);
+	return (error);
+}
+
 int
 exec_map_page(struct image_params *imgp, vm_pindex_t pageno,
 	      struct lwbuf **plwb, const char **pdata)
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/kern_syscall.h b/sys/sys/kern_syscall.h
index 048f9213124..cd21a53e85a 100644
--- a/sys/sys/kern_syscall.h
+++ b/sys/sys/kern_syscall.h
@@ -73,7 +73,8 @@ int kern_fstat(int fd, struct stat *st);
 /*
  * Prototypes for syscalls in kern/kern_exec.c
  */
-int kern_execve(struct nlookupdata *nd, struct image_args *args);
+int kern_execve(struct nlookupdata *nd, struct file *fp,
+		struct image_args *args);
 
 /*
  * Prototypes for syscalls in kern/kern_exit.c
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

