Here's a 3rd approach to solve the TOCTOU race in single_thread_set().
The issue being that the lock serializing access to `ps_single' is not
held when calling single_thread_check().

The approach below is controversial because it extends the scope of the
SCHED_LOCK().  On the other hand, the two others approaches that both
add a new lock to avoid this race ignore the fact that accesses to
`ps_single' are currently not clearly serialized w/o KERNEL_LOCK().

So the diff below improves the situation in that regard and do not add
more complexity due to the use of multiple locks.  After having looked
for a way to split the SCHED_LOCK() I believe this is the simplest
approach.

I deliberately used a *_locked() function to avoid grabbing the lock
recursively as I'm trying to get rid of the recursion, see the other
thread on tech@.

That said the uses of `ps_single' in ptrace_ctrl() are not covered by
this diff and I'd be glad to hear some comments about them.  This is
fine as long as all the code using `ps_single' runs under KERNEL_LOCK()
but since we're trying to get the single_thread_* API out of it, this
need to be addressed.

Note that this diff introduces a helper for initializing ps_single*
values in order to keep all the accesses of those fields in the same
file.

ok?

Index: kern/kern_fork.c
===================================================================
RCS file: /cvs/src/sys/kern/kern_fork.c,v
retrieving revision 1.226
diff -u -p -r1.226 kern_fork.c
--- kern/kern_fork.c    25 Oct 2020 01:55:18 -0000      1.226
+++ kern/kern_fork.c    4 Nov 2020 12:52:54 -0000
@@ -563,10 +563,7 @@ thread_fork(struct proc *curp, void *sta
         * if somebody else wants to take us to single threaded mode,
         * count ourselves in.
         */
-       if (pr->ps_single) {
-               atomic_inc_int(&pr->ps_singlecount);
-               atomic_setbits_int(&p->p_flag, P_SUSPSINGLE);
-       }
+       single_thread_init(p);
 
        /*
         * Return tid to parent thread and copy it out to userspace
Index: kern/kern_sig.c
===================================================================
RCS file: /cvs/src/sys/kern/kern_sig.c,v
retrieving revision 1.263
diff -u -p -r1.263 kern_sig.c
--- kern/kern_sig.c     16 Sep 2020 13:50:42 -0000      1.263
+++ kern/kern_sig.c     4 Nov 2020 12:38:35 -0000
@@ -1932,11 +1932,27 @@ userret(struct proc *p)
        p->p_cpu->ci_schedstate.spc_curpriority = p->p_usrpri;
 }
 
+void
+single_thread_init(struct proc *p)
+{
+       struct process *pr = p->p_p;
+       int s;
+
+       SCHED_LOCK(s);
+       if (pr->ps_single) {
+               atomic_inc_int(&pr->ps_singlecount);
+               atomic_setbits_int(&p->p_flag, P_SUSPSINGLE);
+       }
+       SCHED_UNLOCK(s);
+}
+
 int
-single_thread_check(struct proc *p, int deep)
+_single_thread_check_locked(struct proc *p, int deep)
 {
        struct process *pr = p->p_p;
 
+       SCHED_ASSERT_LOCKED();
+
        if (pr->ps_single != NULL && pr->ps_single != p) {
                do {
                        int s;
@@ -1949,14 +1965,12 @@ single_thread_check(struct proc *p, int 
                                        return (EINTR);
                        }
 
-                       SCHED_LOCK(s);
-                       if (pr->ps_single == NULL) {
-                               SCHED_UNLOCK(s);
+                       if (pr->ps_single == NULL)
                                continue;
-                       }
 
                        if (atomic_dec_int_nv(&pr->ps_singlecount) == 0)
                                wakeup(&pr->ps_singlecount);
+
                        if (pr->ps_flags & PS_SINGLEEXIT) {
                                SCHED_UNLOCK(s);
                                KERNEL_LOCK();
@@ -1967,13 +1981,24 @@ single_thread_check(struct proc *p, int 
                        /* not exiting and don't need to unwind, so suspend */
                        p->p_stat = SSTOP;
                        mi_switch();
-                       SCHED_UNLOCK(s);
                } while (pr->ps_single != NULL);
        }
 
        return (0);
 }
 
+int
+single_thread_check(struct proc *p, int deep)
+{
+       int s, error;
+
+       SCHED_LOCK(s);
+       error = _single_thread_check_locked(p, deep);
+       SCHED_UNLOCK(s);
+
+       return error;
+}
+
 /*
  * Stop other threads in the process.  The mode controls how and
  * where the other threads should stop:
@@ -1995,8 +2020,12 @@ single_thread_set(struct proc *p, enum s
        KERNEL_ASSERT_LOCKED();
        KASSERT(curproc == p);
 
-       if ((error = single_thread_check(p, deep)))
+       SCHED_LOCK(s);
+       error = _single_thread_check_locked(p, deep);
+       if (error) {
+               SCHED_UNLOCK(s);
                return error;
+       }
 
        switch (mode) {
        case SINGLE_SUSPEND:
@@ -2014,7 +2043,6 @@ single_thread_set(struct proc *p, enum s
                panic("single_thread_mode = %d", mode);
 #endif
        }
-       SCHED_LOCK(s);
        pr->ps_singlecount = 0;
        membar_producer();
        pr->ps_single = p;
Index: sys/proc.h
===================================================================
RCS file: /cvs/src/sys/sys/proc.h,v
retrieving revision 1.300
diff -u -p -r1.300 proc.h
--- sys/proc.h  16 Sep 2020 08:01:15 -0000      1.300
+++ sys/proc.h  4 Nov 2020 12:34:48 -0000
@@ -600,6 +600,7 @@ enum single_thread_mode {
        SINGLE_UNWIND,          /* other threads to unwind and stop */
        SINGLE_EXIT             /* other threads to unwind and then exit */
 };
+void   single_thread_init(struct proc *);
 int    single_thread_set(struct proc *, enum single_thread_mode, int);
 int    single_thread_wait(struct process *, int);
 void   single_thread_clear(struct proc *, int);

Reply via email to