git: b13a7008282a - main - posixshm: Fix a TOCTOU race in the FIOSSHMLPGCNF handler

From: Mark Johnston <markj_at_FreeBSD.org>
Date: Tue, 25 Aug 2026 15:59:25 UTC
The branch main has been updated by markj:

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

commit b13a7008282aaa34e7021ac7b961600dbbc28bf3
Author:     Mark Johnston <markj@FreeBSD.org>
AuthorDate: 2026-08-24 18:15:38 +0000
Commit:     Mark Johnston <markj@FreeBSD.org>
CommitDate: 2026-08-25 15:46:24 +0000

    posixshm: Fix a TOCTOU race in the FIOSSHMLPGCNF handler
    
    The check for whether shm_lp_psind was assigned was unlocked.  This race
    can be exploited to create an object with psind==2 but with only
    pagesizes[1] worth of pages populated.  This in turn can be used to
    escalate privileges.
    
    Fix this by acquiring the rangelock earlier.  In shm_mmap_large(),
    assert that we hold the rangelock.  In shm_write(), annotate an unlocked
    load of shm_lp_psind.
    
    Approved by:    so
    Security:       FreeBSD-SA-26:63.posixshm
    Security:       CVE-2026-58094
    Reported by:    tsune of GMO Cybersecurity by Ierae, Inc. working with TrendAI Zero Day Initiative
    Reviewed by:    kib
    Sponsored by:   The FreeBSD Foundation
    Differential Revision:  https://reviews.freebsd.org/D59104
---
 sys/kern/uipc_shm.c | 27 +++++++++++++++++----------
 sys/sys/mman.h      |  2 +-
 2 files changed, 18 insertions(+), 11 deletions(-)

diff --git a/sys/kern/uipc_shm.c b/sys/kern/uipc_shm.c
index 04b6f4f6e07a..067a628fdab0 100644
--- a/sys/kern/uipc_shm.c
+++ b/sys/kern/uipc_shm.c
@@ -512,8 +512,6 @@ shm_write(struct file *fp, struct uio *uio, struct ucred *active_cred,
 	if (error)
 		return (error);
 #endif
-	if (shm_largepage(shmfd) && shmfd->shm_lp_psind == 0)
-		return (EINVAL);
 	foffset_lock_uio(fp, uio, flags);
 	if (uio->uio_resid > OFF_MAX - uio->uio_offset) {
 		/*
@@ -535,7 +533,9 @@ shm_write(struct file *fp, struct uio *uio, struct ucred *active_cred,
 	else
 		rl_cookie = shm_rangelock_wlock(shmfd, uio->uio_offset,
 		    MAX(newsize, uio->uio_offset));
-	if ((shmfd->shm_seals & F_SEAL_WRITE) != 0) {
+	if (shm_largepage(shmfd) && shmfd->shm_lp_psind == 0) {
+		error = EINVAL;
+	} else if ((shmfd->shm_seals & F_SEAL_WRITE) != 0) {
 		error = EPERM;
 	} else {
 		error = 0;
@@ -592,18 +592,23 @@ shm_ioctl(struct file *fp, u_long com, void *data, struct ucred *active_cred,
 		if (!shm_largepage(shmfd))
 			return (ENOTTY);
 		conf = data;
+		rl_cookie = shm_rangelock_wlock(shmfd, 0, OFF_MAX);
 		if (shmfd->shm_lp_psind != 0 &&
-		    conf->psind != shmfd->shm_lp_psind)
+		    conf->psind != shmfd->shm_lp_psind) {
+			shm_rangelock_unlock(shmfd, rl_cookie);
 			return (EINVAL);
+		}
 		if (conf->psind <= 0 || conf->psind >= MAXPAGESIZES ||
-		    pagesizes[conf->psind] == 0)
+		    pagesizes[conf->psind] == 0) {
+			shm_rangelock_unlock(shmfd, rl_cookie);
 			return (EINVAL);
+		}
 		if (conf->alloc_policy != SHM_LARGEPAGE_ALLOC_DEFAULT &&
 		    conf->alloc_policy != SHM_LARGEPAGE_ALLOC_NOWAIT &&
-		    conf->alloc_policy != SHM_LARGEPAGE_ALLOC_HARD)
+		    conf->alloc_policy != SHM_LARGEPAGE_ALLOC_HARD) {
+			shm_rangelock_unlock(shmfd, rl_cookie);
 			return (EINVAL);
-
-		rl_cookie = shm_rangelock_wlock(shmfd, 0, OFF_MAX);
+		}
 		shmfd->shm_lp_psind = conf->psind;
 		shmfd->shm_lp_alloc_policy = conf->alloc_policy;
 		shmfd->shm_object->un_pager.phys.data_val = conf->psind;
@@ -1571,7 +1576,7 @@ out:
 static int
 shm_mmap_large(struct shmfd *shmfd, vm_map_t map, vm_offset_t *addr,
     vm_size_t size, vm_prot_t prot, vm_prot_t max_prot, int flags,
-    vm_ooffset_t foff, struct thread *td)
+    vm_ooffset_t foff, struct thread *td, void *rl_cookie)
 {
 	struct vmspace *vms;
 	vm_map_entry_t next_entry, prev_entry;
@@ -1579,6 +1584,8 @@ shm_mmap_large(struct shmfd *shmfd, vm_map_t map, vm_offset_t *addr,
 	int docow, error, rv, try;
 	bool curmap;
 
+	rangelock_cookie_assert(rl_cookie, RA_LOCKED);
+
 	if (shmfd->shm_lp_psind == 0)
 		return (EINVAL);
 
@@ -1754,7 +1761,7 @@ shm_mmap(struct file *fp, vm_map_t map, vm_offset_t *addr, vm_size_t objsize,
 	if (shm_largepage(shmfd)) {
 		writecnt = false;
 		error = shm_mmap_large(shmfd, map, addr, objsize, prot,
-		    maxprot, flags, foff, td);
+		    maxprot, flags, foff, td, rl_cookie);
 	} else {
 		if (writecnt) {
 			vm_pager_update_writecount(shmfd->shm_object, 0,
diff --git a/sys/sys/mman.h b/sys/sys/mman.h
index ba478bc71c7c..e15cc4380b5b 100644
--- a/sys/sys/mman.h
+++ b/sys/sys/mman.h
@@ -293,7 +293,7 @@ struct shmfd {
 	int		shm_flags;
 	int		shm_seals;
 
-	/* largepage config */
+	/* largepage config, synchronized by the rangelock */
 	int		shm_lp_psind;
 	int		shm_lp_alloc_policy;
 };