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)) {