git: 5c6949e12ee6 - stable/14 - kern: disallow user scheduling/debugging/signalling of jailed procs

From: Kyle Evans <kevans_at_FreeBSD.org>
Date: Tue, 03 Feb 2026 04:38:08 UTC
The branch stable/14 has been updated by kevans:

URL: https://cgit.FreeBSD.org/src/commit/?id=5c6949e12ee6143505a200b37f2d0bbaf2611656

commit 5c6949e12ee6143505a200b37f2d0bbaf2611656
Author:     Kyle Evans <kevans@FreeBSD.org>
AuthorDate: 2026-02-03 04:37:23 +0000
Commit:     Kyle Evans <kevans@FreeBSD.org>
CommitDate: 2026-02-03 04:37:23 +0000

    kern: disallow user scheduling/debugging/signalling of jailed procs
    
    Currently, jails are generally ignored when determining whether the
    current process/thread can take action upon another, except to determine
    if the target's jail is somewhere in the source's hierarchy. Notably,
    uid 1001 in a jail (including prison0) can take action upon a process
    run by uid 1001 inside of a subordinate jail by default.
    
    While this could be considered a feature at times, it is a scenario
    that really should be deliberately crafted; there is no guarantee that
    uid 1001 in the parent jail is at all related to uid 1001 in a
    subordinate.
    
    This changes introduces three new privileges that grant a process
    this kind of insight into other jails:
    
    - PRIV_DEBUG_DIFFJAIL
    - PRIV_SCHED_DIFFJAIl
    - PRIV_SIGNAL_DIFFJAIL
    
    These can be granted independently or in conjunction with the
    accompanying *_DIFFCRED privileges, i.e.:
    
    - PRIV_DEBUG_DIFFCRED alone will let uid 1001 debug uid 1002, but
      PRIV_DEBUG_DIFFJAIL is additionally needed to let it debug uid 1002
      in a jail.
    
    - PRIV_DEBUG_DIFFJAIL alone will let uid 1001 debug uid 1001 in a jail,
      but will not allow it to debug uid 1002 in a jail.
    
    Note that security.bsd.see_jail_proc can be used for similar effects,
    but does not prevent a user from learning the pid of a jailed process
    with matching creds and signalling it or rescheduling it (e.g., cpuset).
    Debugging is restricted by visibility in all cases, so that one is less
    of a concern.
    
    This change adds a new jail(8) parameter for the parent to indicate on
    a per-jail basis if its users are open to being tampered with by the
    parent's unprivileged users: allow.unprivileged_parent_tampering.  This
    is enabled by default in 14.x, but may be disabled to honor the new
    priv(9) checks for earlier testing of the new behavior in FreeBSD 15.x.
    
    Development setups that involve regularly debugging jailed processes
    from outside the jail, will want to consider adding a default
    `allow.unprivileged_parent_tampering;` to your /etc/jail.conf before
    transitioning to 15.x.
    
    Reviewed by:    jamie
    Relnotes:       yes (added, off by default)
    
    (cherry picked from commit 8a5ceebece0311bc41180b3ca0ce7237def1e253)
    (cherry picked from commit bd21c672a868f039edb109b73757ad560252ca0f)
---
 sys/kern/kern_jail.c | 13 +++++++++++-
 sys/kern/kern_prot.c | 56 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 sys/sys/jail.h       |  7 +++++--
 sys/sys/priv.h       |  3 +++
 usr.sbin/jail/jail.8 |  8 +++++++-
 5 files changed, 83 insertions(+), 4 deletions(-)

diff --git a/sys/kern/kern_jail.c b/sys/kern/kern_jail.c
index 30b77090434e..786a4d804115 100644
--- a/sys/kern/kern_jail.c
+++ b/sys/kern/kern_jail.c
@@ -232,6 +232,9 @@ static struct bool_flags pr_flag_allow[NBBY * NBPW] = {
 	{"allow.nfsd", "allow.nonfsd", PR_ALLOW_NFSD},
 #endif
 	{"allow.routing", "allow.norouting", PR_ALLOW_ROUTING},
+	{"allow.unprivileged_parent_tampering",
+	    "allow.nounprivileged_parent_tampering",
+	    PR_ALLOW_UNPRIV_PARENT_TAMPER},
 };
 static unsigned pr_allow_all = PR_ALLOW_ALL_STATIC;
 const size_t pr_flag_allow_size = sizeof(pr_flag_allow);
@@ -239,7 +242,8 @@ const size_t pr_flag_allow_size = sizeof(pr_flag_allow);
 #define	JAIL_DEFAULT_ALLOW		(PR_ALLOW_SET_HOSTNAME | \
 					 PR_ALLOW_RESERVED_PORTS | \
 					 PR_ALLOW_UNPRIV_DEBUG | \
-					 PR_ALLOW_SUSER)
+					 PR_ALLOW_SUSER | \
+					 PR_ALLOW_UNPRIV_PARENT_TAMPER)
 #define	JAIL_DEFAULT_ENFORCE_STATFS	2
 #define	JAIL_DEFAULT_DEVFS_RSNUM	0
 static unsigned jail_default_allow = JAIL_DEFAULT_ALLOW;
@@ -3987,6 +3991,7 @@ prison_priv_check(struct ucred *cred, int priv)
 	case PRIV_DEBUG_DIFFCRED:
 	case PRIV_DEBUG_SUGID:
 	case PRIV_DEBUG_UNPRIV:
+	case PRIV_DEBUG_DIFFJAIL:
 
 		/*
 		 * Allow jail to set various resource limits and login
@@ -4019,8 +4024,10 @@ prison_priv_check(struct ucred *cred, int priv)
 		 */
 	case PRIV_SCHED_DIFFCRED:
 	case PRIV_SCHED_CPUSET:
+	case PRIV_SCHED_DIFFJAIL:
 	case PRIV_SIGNAL_DIFFCRED:
 	case PRIV_SIGNAL_SUGID:
+	case PRIV_SIGNAL_DIFFJAIL:
 
 		/*
 		 * Allow jailed processes to write to sysctls marked as jail
@@ -4630,6 +4637,10 @@ SYSCTL_JAIL_PARAM(_allow, read_msgbuf, CTLTYPE_INT | CTLFLAG_RW,
     "B", "Jail may read the kernel message buffer");
 SYSCTL_JAIL_PARAM(_allow, unprivileged_proc_debug, CTLTYPE_INT | CTLFLAG_RW,
     "B", "Unprivileged processes may use process debugging facilities");
+SYSCTL_JAIL_PARAM(_allow, unprivileged_parent_tampering,
+    CTLTYPE_INT | CTLFLAG_RW, "B",
+    "Unprivileged parent jail processes may tamper with same-uid processes"
+    " (signal/debug/cpuset)");
 SYSCTL_JAIL_PARAM(_allow, suser, CTLTYPE_INT | CTLFLAG_RW,
     "B", "Processes in jail with uid 0 have privilege");
 #ifdef VIMAGE
diff --git a/sys/kern/kern_prot.c b/sys/kern/kern_prot.c
index 872645da8614..246413a54903 100644
--- a/sys/kern/kern_prot.c
+++ b/sys/kern/kern_prot.c
@@ -1915,6 +1915,38 @@ cr_canseejailproc(struct ucred *u1, struct ucred *u2)
 	return (ESRCH);
 }
 
+/*
+ * Determine if u1 can tamper with the subject specified by u2, if they are in
+ * different jails and 'unprivileged_parent_tampering' jail policy allows it.
+ *
+ * May be called if u1 and u2 are in the same jail, but it is expected that the
+ * caller has already done a prison_check() prior to calling it.
+ *
+ * Returns: 0 for permitted, EPERM otherwise
+ */
+static int
+cr_can_tamper_with_subjail(struct ucred *u1, struct ucred *u2, int priv)
+{
+
+	MPASS(prison_check(u1, u2) == 0);
+	if (u1->cr_prison == u2->cr_prison)
+		return (0);
+
+	if (priv_check_cred(u1, priv) == 0)
+		return (0);
+
+	/*
+	 * Jails do not maintain a distinct UID space, so process visibility is
+	 * all that would control an unprivileged process' ability to tamper
+	 * with a process in a subjail by default if we did not have the
+	 * allow.unprivileged_parent_tampering knob to restrict it by default.
+	 */
+	if (prison_allow(u2, PR_ALLOW_UNPRIV_PARENT_TAMPER))
+		return (0);
+
+	return (EPERM);
+}
+
 /*
  * Helper for cr_cansee*() functions to abide by system-wide security.bsd.see_*
  * policies.  Determines if u1 "can see" u2 according to these policies.
@@ -2064,6 +2096,19 @@ cr_cansignal(struct ucred *cred, struct proc *proc, int signum)
 			return (error);
 	}
 
+	/*
+	 * At this point, the target may be in a different jail than the
+	 * subject -- the subject must be in a parent jail to the target,
+	 * whether it is prison0 or a subordinate of prison0 that has
+	 * children.  Additional privileges are required to allow this, as
+	 * whether the creds are truly equivalent or not must be determined on
+	 * a case-by-case basis.
+	 */
+	error = cr_can_tamper_with_subjail(cred, proc->p_ucred,
+	    PRIV_SIGNAL_DIFFJAIL);
+	if (error)
+		return (error);
+
 	return (0);
 }
 
@@ -2140,6 +2185,12 @@ p_cansched(struct thread *td, struct proc *p)
 		if (error)
 			return (error);
 	}
+
+	error = cr_can_tamper_with_subjail(td->td_ucred, p->p_ucred,
+	    PRIV_SCHED_DIFFJAIL);
+	if (error)
+		return (error);
+
 	return (0);
 }
 
@@ -2246,6 +2297,11 @@ p_candebug(struct thread *td, struct proc *p)
 			return (error);
 	}
 
+	error = cr_can_tamper_with_subjail(td->td_ucred, p->p_ucred,
+	    PRIV_DEBUG_DIFFJAIL);
+	if (error)
+		return (error);
+
 	/* Can't trace init when securelevel > 0. */
 	if (p == initproc) {
 		error = securelevel_gt(td->td_ucred, 0);
diff --git a/sys/sys/jail.h b/sys/sys/jail.h
index 504dedc277df..5fed00436a75 100644
--- a/sys/sys/jail.h
+++ b/sys/sys/jail.h
@@ -257,6 +257,8 @@ struct prison_racct {
 #define	PR_ALLOW_KMEM_ACCESS		0x00010000	/* reserved, not used yet */
 #define	PR_ALLOW_NFSD			0x00020000
 #define	PR_ALLOW_ROUTING		0x00040000
+/* Bits assigned in main */
+#define	PR_ALLOW_UNPRIV_PARENT_TAMPER	0x00400000
 
 /*
  * PR_ALLOW_PRISON0 are the allow flags that we apply by default to prison0,
@@ -264,14 +266,15 @@ struct prison_racct {
  * build time.  PR_ALLOW_ALL_STATIC should contain any bit above that we expect
  * to be used on the system, while PR_ALLOW_PRISON0 will be some subset of that.
  */
-#define	PR_ALLOW_ALL_STATIC		0x000787ff
+#define	PR_ALLOW_ALL_STATIC		0x004787ff
 #define	PR_ALLOW_PRISON0		(PR_ALLOW_ALL_STATIC)
 
 /*
  * PR_ALLOW_DIFFERENCES determines which flags are able to be
  * different between the parent and child jail upon creation.
  */
-#define	PR_ALLOW_DIFFERENCES		(PR_ALLOW_UNPRIV_DEBUG)
+#define	PR_ALLOW_DIFFERENCES		\
+    (PR_ALLOW_UNPRIV_DEBUG | PR_ALLOW_UNPRIV_PARENT_TAMPER)
 
 /*
  * OSD methods
diff --git a/sys/sys/priv.h b/sys/sys/priv.h
index 0dec78427933..090e2b25ce5d 100644
--- a/sys/sys/priv.h
+++ b/sys/sys/priv.h
@@ -115,6 +115,7 @@
 #define	PRIV_DEBUG_SUGID	81	/* Exempt debugging setuid proc. */
 #define	PRIV_DEBUG_UNPRIV	82	/* Exempt unprivileged debug limit. */
 #define	PRIV_DEBUG_DENIED	83	/* Exempt P2_NOTRACE. */
+#define	PRIV_DEBUG_DIFFJAIL	84	/* Exempt debugging other jails. */
 
 /*
  * Dtrace privileges.
@@ -193,6 +194,7 @@
 #define	PRIV_SCHED_CPUSET	206	/* Can manipulate cpusets. */
 #define	PRIV_SCHED_CPUSET_INTR	207	/* Can adjust IRQ to CPU binding. */
 #define	PRIV_SCHED_IDPRIO	208	/* Can set idle time scheduling. */
+#define	PRIV_SCHED_DIFFJAIL	209	/* Exempt scheduling other jails. */
 
 /*
  * POSIX semaphore privileges.
@@ -204,6 +206,7 @@
  */
 #define	PRIV_SIGNAL_DIFFCRED	230	/* Exempt signalling other users. */
 #define	PRIV_SIGNAL_SUGID	231	/* Non-conserv signal setuid proc. */
+#define	PRIV_SIGNAL_DIFFJAIL	232	/* Exempt signalling other jails. */
 
 /*
  * Sysctl privileges.
diff --git a/usr.sbin/jail/jail.8 b/usr.sbin/jail/jail.8
index c5a88bbff4b7..d4b6addfa5ec 100644
--- a/usr.sbin/jail/jail.8
+++ b/usr.sbin/jail/jail.8
@@ -23,7 +23,7 @@
 .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 .\" SUCH DAMAGE.
 .\"
-.Dd May 11, 2025
+.Dd August 7, 2025
 .Dt JAIL 8
 .Os
 .Sh NAME
@@ -669,6 +669,12 @@ in the
 file outside of the jails.
 .It Va allow.reserved_ports
 The jail root may bind to ports lower than 1024.
+.It Va allow.unprivileged_parent_tampering
+Unprivileged processes in the jail's parent may tamper with processes of the
+same UID in the jail.
+This includes the ability to signal, debug, and
+.Xr cpuset 1
+processes that belong to the jail.
 .It Va allow.unprivileged_proc_debug
 Unprivileged processes in the jail may use debugging facilities.
 .It Va allow.suser