NetBSD Problem Report #60962

From www@netbsd.org  Wed Nov  4 18:09:07 2026
Return-Path: <www@netbsd.org>
Received: from mail.netbsd.org (mail.netbsd.org [199.233.217.200])
	(using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)
	 key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256
	 client-signature RSA-PSS (2048 bits) client-digest SHA256)
	(Client CN "mail.netbsd.org", Issuer "YR1" (not verified))
	by mollari.NetBSD.org (Postfix) with ESMTPS id 6A5221A923B
	for <gnats-bugs@gnats.NetBSD.org>; Wed,  4 Nov 2026 18:09:07 +0000 (UTC)
Message-Id: <20261104180905.C93631A923C@mollari.NetBSD.org>
Date: Wed,  4 Nov 2026 18:09:05 +0000 (UTC)
From: campbell+netbsd@mumble.net
Reply-To: campbell+netbsd@mumble.net
To: gnats-bugs@NetBSD.org
Subject: fork and spawn read p_pgrp without proc_lock
X-Send-Pr-Version: www-1.0
X-From4GNATS: "campbell+netbsd@mumble.net via gnats" <gnats-admin@NetBSD.org>

>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:          needs-pullups
>Class:          sw-bug
>Submitter-Id:   net
>Arrival-Date:   Wed Nov 04 18:10:01 +0000 2026
>Closed-Date:    
>Last-Modified:  Thu Nov 05 00:15:00 +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.

>Release-Note:

>Audit-Trail:
From: "Taylor R Campbell" <riastradh@netbsd.org>
To: gnats-bugs@gnats.NetBSD.org
Cc: 
Subject: PR/60962 CVS commit: src/sys/kern
Date: Wed, 4 Nov 2026 18:18:00 +0000

 Module Name:	src
 Committed By:	riastradh
 Date:		Wed Nov  4 18:18:00 UTC 2026

 Modified Files:
 	src/sys/kern: kern_exec.c kern_fork.c

 Log Message:
 fork, posix_spawn: Avoid race with setsid/setpgid.

 PR kern/60962: fork and spawn read p_pgrp without proc_lock


 To generate a diff of this commit:
 cvs rdiff -u -r1.535 -r1.536 src/sys/kern/kern_exec.c
 cvs rdiff -u -r1.236 -r1.237 src/sys/kern/kern_fork.c

 Please note that diffs are not public domain; they are subject to the
 copyright notices on the relevant files.

State-Changed-From-To: open->needs-pullups
State-Changed-By: riastradh@NetBSD.org
State-Changed-When: Wed, 04 Nov 2026 18:32:36 +0000
State-Changed-Why:
fixed in HEAD, needs pullup-11 and pullup-10


From: "Taylor R Campbell" <riastradh@netbsd.org>
To: gnats-bugs@gnats.NetBSD.org
Cc: 
Subject: PR/60962 CVS commit: src/sys/sys
Date: Wed, 4 Nov 2026 18:41:11 +0000

 Module Name:	src
 Committed By:	riastradh
 Date:		Wed Nov  4 18:41:11 UTC 2026

 Modified Files:
 	src/sys/sys: param.h proc.h

 Log Message:
 struct proc: Move p_pgrp out of p_startcopy/endcopy region.

 It cannot be safely copied with the current memcpy logic that logic
 uses happens without holding proc_lock, so we have to copy it
 separately anyway.  Leave a comment emphasizing this aspect of the
 locking rules so we don't have this bug again.

 Looks like p_mqueue_cnt may need similar treatment.

 As a result of this kernel ABI change, welcome to 11.99.10!

 PR kern/60962: fork and spawn read p_pgrp without proc_lock


 To generate a diff of this commit:
 cvs rdiff -u -r1.748 -r1.749 src/sys/sys/param.h
 cvs rdiff -u -r1.375 -r1.376 src/sys/sys/proc.h

 Please note that diffs are not public domain; they are subject to the
 copyright notices on the relevant files.

From: Robert Elz <kre@munnari.OZ.AU>
To: gnats-bugs@netbsd.org
Cc: 
Subject: Re: kern/60962: fork and spawn read p_pgrp without proc_lock
Date: Thu, 05 Nov 2026 07:10:17 +0700

 It looks to me there's an inherent race in the situation described,
 anyway, given the 2 threads running, one doing a fork() the other
 setsid() or setpgid(), the situation described

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

 might just as easily be

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

 and I don't believe

 	4. profit!

 applies at all.   This is a simple code bug in the application.
 If it cares what sid or pgrp the child gets, it shouldn't be
 altering those things at or around the time of the fork().

 Further, I don't think the change you made really alters anything,
 or not anything significant, if anything it makes it slightly more
 likely that the race will go the way of the fork'd child getting the
 pgrp from after the change (but obviously can't guarantee that, as in
 the first of the two scenarios above, 1 might be finished before 2 starts).

 That is unless you're imagining some kind of corruption if the code writing
 to p_pgrp can somehow mangle the value read from it by the copy.
 At the time the memcpy() is done, the target process (the one truly
 vulnerable here) doesn't yet exist as far as the rest of the kernel
 can tell (not yet linked anywhere), so nothing needs to be locked
 to alter it.

 kre


>Unformatted:

NetBSD Home
NetBSD PR Database Search

(Contact us) $NetBSD: query-full-pr,v 1.51 2026/08/10 02:28:17 riastradh Exp $
$NetBSD: gnats_config.sh,v 1.10 2026/05/13 22:00:09 riastradh Exp $
Copyright © 1994-2026 The NetBSD Foundation, Inc. ALL RIGHTS RESERVED.