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

Index: sys/miscfs/procfs/procfs_vnops.c
===================================================================
RCS file: /cvsroot/syssrc/sys/miscfs/procfs/procfs_vnops.c,v
retrieving revision 1.70.4.1
retrieving revision 1.70.4.2
diff -c -p -r1.70.4.1 -r1.70.4.2
*** sys/miscfs/procfs/procfs_vnops.c	2001/03/30 21:50:16	1.70.4.1
--- sys/miscfs/procfs/procfs_vnops.c	2002/01/14 10:59:32	1.70.4.2
*************** procfs_open(v)
*** 253,259 ****
  			return (EBUSY);
  
  		if ((error = procfs_checkioperm(p1, p2)) != 0)
! 			return (EPERM);
  
  		if (ap->a_mode & FWRITE)
  			pfs->pfs_flags = ap->a_mode & (FWRITE|O_EXCL);
--- 253,259 ----
  			return (EBUSY);
  
  		if ((error = procfs_checkioperm(p1, p2)) != 0)
! 			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.11.28.1
retrieving revision 1.11.28.2
diff -c -p -r1.11.28.1 -r1.11.28.2
*** sys/miscfs/procfs/procfs_regs.c	2001/03/30 21:48:56	1.11.28.1
--- sys/miscfs/procfs/procfs_regs.c	2002/01/14 10:55:34	1.11.28.2
*************** procfs_doregs(curp, p, pfs, uio)
*** 63,69 ****
  	int kl;
  
  	if ((error = procfs_checkioperm(curp, p)) != 0)
! 		return (EPERM);
  
  	kl = sizeof(r);
  	kv = (char *) &r;
--- 63,69 ----
  	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.23
retrieving revision 1.23.2.1
diff -c -p -r1.23 -r1.23.2.1
*** sys/miscfs/procfs/procfs_mem.c	1999/03/25 04:45:57	1.23
--- sys/miscfs/procfs/procfs_mem.c	2002/01/14 15:20:24	1.23.2.1
*************** procfs_checkioperm(p, t)
*** 140,146 ****
  	/*
  	 * 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 ||
--- 140,152 ----
  	/*
  	 * 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)
*** 149,155 ****
  		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.
  	 */
--- 155,161 ----
  		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)
*** 157,168 ****
  		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);
  }
--- 163,173 ----
  		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.17
retrieving revision 1.17.12.1
diff -c -p -r1.17 -r1.17.12.1
*** sys/miscfs/procfs/procfs_ctl.c	1999/07/22 18:13:38	1.17
--- sys/miscfs/procfs/procfs_ctl.c	2002/01/12 01:09:56	1.17.12.1
*************** procfs_control(curp, p, op, sig)
*** 106,115 ****
  	int 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:
--- 106,121 ----
  	int 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/sys/proc.h
===================================================================
RCS file: /cvsroot/syssrc/sys/sys/proc.h,v
retrieving revision 1.98.2.2
retrieving revision 1.98.2.3
diff -c -p -r1.98.2.2 -r1.98.2.3
*** sys/sys/proc.h	2000/09/06 08:41:41	1.98.2.2
--- sys/sys/proc.h	2002/01/12 01:02:20	1.98.2.3
*************** struct	proc {
*** 242,266 ****
  #define	P_ZOMBIE(p)	((p)->p_stat == SZOMB || (p)->p_stat == SDEAD)
  
  /* 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_32		0x40000	/* 32-bit process -- only used on 64-bit kernels */
  
  /*
   * Macro to compute the exit signal to be delivered.
--- 242,267 ----
  #define	P_ZOMBIE(p)	((p)->p_stat == SZOMB || (p)->p_stat == SDEAD)
  
  /* These flags are kept in p_flag. */
! #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_32		0x040000 /* 32-bit process -- only used on 64-bit kernels */
! #define P_INEXEC	0x100000 /* Process is exec'ing and cannot be traced */
  
  /*
   * Macro to compute the exit signal to be delivered.
Index: sys/kern/sys_process.c
===================================================================
RCS file: /cvsroot/syssrc/sys/kern/sys_process.c,v
retrieving revision 1.61.18.2
retrieving revision 1.61.18.3
diff -c -p -r1.61.18.2 -r1.61.18.3
*** sys/kern/sys_process.c	2001/03/30 21:47:02	1.61.18.2
--- sys/kern/sys_process.c	2002/01/12 01:03:07	1.61.18.3
*************** sys_ptrace(p, v, retval)
*** 104,109 ****
--- 104,113 ----
  		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)) {
Index: sys/kern/kern_exec.c
===================================================================
RCS file: /cvsroot/syssrc/sys/kern/kern_exec.c,v
retrieving revision 1.110.4.5
retrieving revision 1.110.4.7
diff -c -p -r1.110.4.5 -r1.110.4.7
*** sys/kern/kern_exec.c	2001/06/16 20:19:30	1.110.4.5
--- sys/kern/kern_exec.c	2002/01/14 10:49:30	1.110.4.7
*************** sys_execve(p, v, retval)
*** 233,238 ****
--- 233,247 ----
  	extern struct emul emul_netbsd;
  
  	/*
+ 	 * 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;
+ 
+ 	/*
  	 * figure out the maximum size of an exec header, if necessary.
  	 * XXX should be able to keep LKM code from modifying exec switch
  	 * when we're still using it, but...
*************** sys_execve(p, v, retval)
*** 548,556 ****
--- 557,567 ----
  		ktremul(p);
  #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 */
*************** bad:
*** 566,575 ****
--- 577,588 ----
  	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
