NetBSD-Bugs archive

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index][Old Index]

kern/60962: fork and spawn read p_pgrp without proc_lock



>Number:         60962
>Category:       kern
>Synopsis:       fork and spawn read p_pgrp without proc_lock
>Confidential:   no
>Severity:       serious
>Priority:       medium
>Responsible:    kern-bug-people
>State:          open
>Class:          sw-bug
>Submitter-Id:   net
>Arrival-Date:   Wed Nov 04 18:10:01 +0000 2026
>Originator:     Taylor R Campbell
>Release:        current, 11, 10, ...
>Organization:
The ForkBSD Foundarace
>Environment:
>Description:

	As documented in proc.h:

    210 /*
    211  * Description of a process.
...
    221  * Field markings and the corresponding locks:
...
    225  * l:	proc_lock
...
    230  */
...
    233 struct proc {
...
    320 /*
    321  * The following fields are all copied upon creation in fork.
    322  */
    323 #define	p_startcopy	p_sigctx
    324 
    325 	struct sigctx 	p_sigctx;	/* p: Shared signal state */
...
    330 	struct pgrp 	*p_pgrp;	/* l: Pointer to process group */
...
    336 /*
    337  * End area that is copied on creation
    338  */
    339 #define	p_endcopy	p_xsig
...

	https://nxr.netbsd.org/xref/src/sys/sys/proc.h?r=1.375#210

	But fork (and posix_spawn) will read p_pgrp from the parent,
	implicitly via memcpy of p_startcopy through p_endcopy, without
	holding proc_lock, and leave a possibly stale pointer to it in
	the child:

    340 	/*
    341 	 * Make a proc table entry for the new process.
    342 	 * Start by zeroing the section of proc that is zero-initialized,
    343 	 * then copy the section that is copied directly from the parent.
    344 	 */
    345 	memset(&p2->p_startzero, 0,
    346 	    (unsigned) ((char *)&p2->p_endzero - (char *)&p2->p_startzero));
    347 	memcpy(&p2->p_startcopy, &p1->p_startcopy,
    348 	    (unsigned) ((char *)&p2->p_endcopy - (char *)&p2->p_startcopy));
...
    521 	/*
    522 	 * It's now safe for the scheduler and other processes to see the
    523 	 * child process.
    524 	 */
    525 	mutex_enter(&proc_lock);

	https://nxr.netbsd.org/xref/src/sys/kern/kern_fork.c?r=1.236#340

	A concurrent thread in the parent can call setsid or setpgid
	and thereby its own p_pgrp in the interim before fork takes
	proc_lock and inserts the child on the (now wrong) process
	group list:

    543 	LIST_INSERT_AFTER(p1, p2, p_pglist);

	https://nxr.netbsd.org/xref/src/sys/kern/kern_fork.c?r=1.236#543

	Reported by alnsn@.

>How-To-Repeat:

	1. fork in one thread
	2. setsid/setpgid in another thread
	3. ???
	4. profit!

>Fix:

	1. Ignore the copy of p_pgrp copied with memcpy.

	2. Copy p_pgrp from the parent to the child only under
	   proc_lock, which must be held across that copy and the
	   LIST_INSERT_AFTER in p_pglist.

	3. In a separate change, because it affects the kernel ABI,
	   move the p_pgrp member out of the p_startcopy/endcopy region
	   of struct proc, and add a note to proc.h that only members
	   which are stable after process creation or changed under
	   struct proc::p_lock can be included in that region.




Home | Main Index | Thread Index | Old Index