Synopsis: Close-on-exec, SUID and ptrace(2)
NetBSD versions: 1.5, 1.5.1, 1.5.2
Thanks to: Christos Zoulas and Havard Eidnes
Reported in NetBSD Security Advisory: NetBSD-SA2002-001

Index: sys/kern/kern_exec.c
===================================================================
RCS file: /cvsroot/syssrc/sys/kern/kern_exec.c,v
retrieving revision 1.100.2.4
diff -u -r1.100.2.4 kern_exec.c
--- sys/kern/kern_exec.c	2001/07/19 13:36:19	1.100.2.4
+++ sys/kern/kern_exec.c	2002/01/14 14:29:09
@@ -98,6 +98,15 @@
 	struct nameidata *ndp;
 	size_t resid;
 
+	/*
+	 * Lock the process and set the P_INEXEC flag to indicate that
+	 * it should be left alone until we're done here.  This is
+	 * necessary to avoid race conditions - e.g. in ptrace() -
+	 * that might allow a local user to illicitly obtain elevated
+	 * privileges.
+	 */
+	p->p_flag |= P_INEXEC;
+
 	ndp = epp->ep_ndp;
 	ndp->ni_cnd.cn_nameiop = LOOKUP;
 	ndp->ni_cnd.cn_flags = FOLLOW | LOCKLEAF | SAVENAME;
@@ -498,9 +507,11 @@
 		ktremul(p->p_tracep, p, p->p_emul->e_name);
 #endif
 
+	p->p_flag &= ~P_INEXEC;
 	return (EJUSTRETURN);
 
 bad:
+	p->p_flag &= ~P_INEXEC;
 	/* free the vmspace-creation commands, and release their references */
 	kill_vmcmds(&pack.ep_vmcmds);
 	/* kill any opened file descriptor, if necessary */
@@ -516,10 +527,12 @@
 	uvm_km_free_wakeup(exec_map, (vaddr_t) argp, NCARGS);
 
 freehdr:
+	p->p_flag &= ~P_INEXEC;
 	FREE(pack.ep_hdr, M_EXEC);
 	return error;
 
 exec_abort:
+	p->p_flag &= ~P_INEXEC;
 	/*
 	 * the old process doesn't exist anymore.  exit gracefully.
 	 * get rid of the (new) address space we have created, if any, get rid
Index: sys/sys/proc.h
===================================================================
RCS file: /cvsroot/syssrc/sys/sys/proc.h,v
retrieving revision 1.74.2.2
diff -u -r1.74.2.2 proc.h
--- sys/sys/proc.h	2000/04/30 20:12:04	1.74.2.2
+++ sys/sys/proc.h	2002/01/14 14:29:20
@@ -210,24 +210,25 @@
 #define	SZOMB	5		/* Awaiting collection by parent. */
 
 /* These flags are kept in p_flag. */
-#define	P_ADVLOCK	0x00001	/* Process may hold a POSIX advisory lock. */
-#define	P_CONTROLT	0x00002	/* Has a controlling terminal. */
-#define	P_INMEM		0x00004	/* Loaded into memory. */
-#define	P_NOCLDSTOP	0x00008	/* No SIGCHLD when children stop. */
-#define	P_PPWAIT	0x00010	/* Parent is waiting for child to exec/exit. */
-#define	P_PROFIL	0x00020	/* Has started profiling. */
-#define	P_SELECT	0x00040	/* Selecting; wakeup/waiting danger. */
-#define	P_SINTR		0x00080	/* Sleep is interruptible. */
-#define	P_SUGID		0x00100	/* Had set id privileges since last exec. */
-#define	P_SYSTEM	0x00200	/* System proc: no sigs, stats or swapping. */
-#define	P_TIMEOUT	0x00400	/* Timing out during sleep. */
-#define	P_TRACED	0x00800	/* Debugged process being traced. */
-#define	P_WAITED	0x01000	/* Debugging process has waited for child. */
-#define	P_WEXIT		0x02000	/* Working on exiting. */
-#define	P_EXEC		0x04000	/* Process called exec. */
-#define	P_OWEUPC	0x08000	/* Owe process an addupc() call at next ast. */
-#define	P_FSTRACE	0x10000	/* Debugger process being traced by procfs */
-#define	P_NOCLDWAIT	0x20000	/* No zombies if child dies */
+#define	P_ADVLOCK	0x000001 /* Process may hold a POSIX advisory lock. */
+#define	P_CONTROLT	0x000002 /* Has a controlling terminal. */
+#define	P_INMEM		0x000004 /* Loaded into memory. */
+#define	P_NOCLDSTOP	0x000008 /* No SIGCHLD when children stop. */
+#define	P_PPWAIT	0x000010 /* Parent is waiting for child to exec/exit. */
+#define	P_PROFIL	0x000020 /* Has started profiling. */
+#define	P_SELECT	0x000040 /* Selecting; wakeup/waiting danger. */
+#define	P_SINTR		0x000080 /* Sleep is interruptible. */
+#define	P_SUGID		0x000100 /* Had set id privileges since last exec. */
+#define	P_SYSTEM	0x000200 /* System proc: no sigs, stats or swapping. */
+#define	P_TIMEOUT	0x000400 /* Timing out during sleep. */
+#define	P_TRACED	0x000800 /* Debugged process being traced. */
+#define	P_WAITED	0x001000 /* Debugging process has waited for child. */
+#define	P_WEXIT		0x002000 /* Working on exiting. */
+#define	P_EXEC		0x004000 /* Process called exec. */
+#define	P_OWEUPC	0x008000 /* Owe process an addupc() call at next ast. */
+#define	P_FSTRACE	0x010000 /* Debugger process being traced by procfs */
+#define	P_NOCLDWAIT	0x020000 /* No zombies if child dies */
+#define P_INEXEC	0x100000 /* Process is exec'ing and cannot be traced */
 
 /*
  * These flags are kept in schedflags.  schedflags may be modified
Index: sys/miscfs/procfs/procfs_vnops.c
===================================================================
RCS file: /cvsroot/syssrc/sys/miscfs/procfs/procfs_vnops.c,v
retrieving revision 1.61.2.1
diff -u -r1.61.2.1 procfs_vnops.c
--- sys/miscfs/procfs/procfs_vnops.c	1999/08/28 23:28:16	1.61.2.1
+++ sys/miscfs/procfs/procfs_vnops.c	2002/01/14 14:29:33
@@ -233,7 +233,7 @@
 			return (EBUSY);
 
 		if ((error = procfs_checkioperm(p1, p2)) != 0)
-			return (EPERM);
+			return (error);
 
 		if (ap->a_mode & FWRITE)
 			pfs->pfs_flags = ap->a_mode & (FWRITE|O_EXCL);
Index: sys/miscfs/procfs/procfs_regs.c
===================================================================
RCS file: /cvsroot/syssrc/sys/miscfs/procfs/procfs_regs.c,v
retrieving revision 1.14
retrieving revision 1.15
diff -c -p -r1.14 -r1.15
*** sys/miscfs/procfs/procfs_regs.c	2001/12/05 00:58:05	1.14
--- sys/miscfs/procfs/procfs_regs.c	2002/01/12 18:51:56	1.15
*************** procfs_doregs(curp, p, pfs, uio)
*** 65,71 ****
  	int kl;
  
  	if ((error = procfs_checkioperm(curp, p)) != 0)
! 		return (EPERM);
  
  	kl = sizeof(r);
  	kv = (char *) &r;
--- 65,71 ----
  	int kl;
  
  	if ((error = procfs_checkioperm(curp, p)) != 0)
! 		return error;
  
  	kl = sizeof(r);
  	kv = (char *) &r;
Index: sys/miscfs/procfs/procfs_mem.c
===================================================================
RCS file: /cvsroot/syssrc/sys/miscfs/procfs/procfs_mem.c,v
retrieving revision 1.29
retrieving revision 1.30
diff -c -p -r1.29 -r1.30
*** sys/miscfs/procfs/procfs_mem.c	2001/11/10 13:33:43	1.29
--- sys/miscfs/procfs/procfs_mem.c	2002/01/12 18:51:31	1.30
*************** procfs_checkioperm(p, t)
*** 122,128 ****
  	/*
  	 * You cannot attach to a processes mem/regs if:
  	 *
! 	 *	(1) it's not owned by you, or is set-id on exec
  	 *	    (unless you're root), or...
  	 */
  	if ((t->p_cred->p_ruid != p->p_cred->p_ruid ||
--- 122,134 ----
  	/*
  	 * You cannot attach to a processes mem/regs if:
  	 *
! 	 *	(1) It is currently exec'ing
! 	 */
! 	if (ISSET(t->p_flag, P_INEXEC))
! 		return (EAGAIN);
! 
! 	/*
! 	 *	(2) it's not owned by you, or is set-id on exec
  	 *	    (unless you're root), or...
  	 */
  	if ((t->p_cred->p_ruid != p->p_cred->p_ruid ||
*************** procfs_checkioperm(p, t)
*** 131,137 ****
  		return (error);
  
  	/*
! 	 *	(2) ...it's init, which controls the security level
  	 *	    of the entire system, and the system was not
  	 *	    compiled with permanetly insecure mode turned on.
  	 */
--- 137,143 ----
  		return (error);
  
  	/*
! 	 *	(3) ...it's init, which controls the security level
  	 *	    of the entire system, and the system was not
  	 *	    compiled with permanetly insecure mode turned on.
  	 */
*************** procfs_checkioperm(p, t)
*** 139,150 ****
  		return (EPERM);
  
  	/*
! 	 * (3) the tracer is chrooted, and its root directory is
! 	 * not at or above the root directory of the tracee
  	 */
- 
  	if (!proc_isunder(t, p))
! 		return EPERM;
  	
  	return (0);
  }
--- 145,155 ----
  		return (EPERM);
  
  	/*
! 	 *	(4) the tracer is chrooted, and its root directory is
! 	 * 	    not at or above the root directory of the tracee
  	 */
  	if (!proc_isunder(t, p))
! 		return (EPERM);
  	
  	return (0);
  }
Index: sys/miscfs/procfs/procfs_ctl.c
===================================================================
RCS file: /cvsroot/syssrc/sys/miscfs/procfs/procfs_ctl.c,v
retrieving revision 1.21
retrieving revision 1.22
diff -c -p -r1.21 -r1.22
*** sys/miscfs/procfs/procfs_ctl.c	2001/12/05 00:58:05	1.21
--- sys/miscfs/procfs/procfs_ctl.c	2002/01/11 22:02:56	1.22
*************** procfs_control(curp, p, op, sig)
*** 108,117 ****
  	int s, error;
  
  	/*
  	 * Attach - attaches the target process for debugging
  	 * by the calling process.
  	 */
- 	switch (op) {
  	case PROCFS_CTL_ATTACH:
  		/* 
  		 * You can't attach to a process if:
--- 108,123 ----
  	int s, error;
  
  	/*
+ 	 * You cannot do anything to the process if it is currently exec'ing
+ 	 */
+ 	if (ISSET(p->p_flag, P_INEXEC))
+ 		return (EAGAIN);
+ 
+ 	switch (op) {
+ 	/*
  	 * Attach - attaches the target process for debugging
  	 * by the calling process.
  	 */
  	case PROCFS_CTL_ATTACH:
  		/* 
  		 * You can't attach to a process if:
Index: sys/kern/sys_process.c
===================================================================
RCS file: /cvsroot/syssrc/sys/kern/sys_process.c,v
retrieving revision 1.71
retrieving revision 1.72
diff -c -p -r1.71 -r1.72
*** sys/kern/sys_process.c	2001/12/05 00:58:05	1.71
--- sys/kern/sys_process.c	2002/01/11 21:16:28	1.72
*************** sys_ptrace(p, v, retval)
*** 106,111 ****
--- 106,115 ----
  		if ((t = pfind(SCARG(uap, pid))) == NULL)
  			return (ESRCH);
  	}
+ 
+ 	/* Can't trace a process that's currently exec'ing. */
+ 	if ((t->p_flag & P_INEXEC) != 0)
+ 		return EAGAIN;
  
  	/* Make sure we can operate on it. */
  	switch (SCARG(uap, req)) {