Skip to content

Commit

Permalink
umtx: shm: Fix use-after-free due to multiple drops of the registry r…
Browse files Browse the repository at this point in the history
…eference

umtx_shm_unref_reg_locked() would unconditionally drop the "registry"
reference, tied to USHMF_LINKED.

This is not a problem for caller umtx_shm_object_terminated(), which
operates under the 'umtx_shm_lock' lock end-to-end, but it is for
indirect caller umtx_shm(), which drops the lock between
umtx_shm_find_reg() and the call to umtx_shm_unref_reg(true) that
deregisters the umtx shared region (from 'umtx_shm_registry';
umtx_shm_find_reg() only finds registered shared mutexes).

Thus, two concurrent user-space callers of _umtx_op() with UMTX_OP_SHM
and flags UMTX_SHM_DESTROY, both progressing past umtx_shm_find_reg()
but before umtx_shm_unref_reg(true), would then decrease twice the
reference count for the single reference standing for the shared mutex's
registration.

Reported by:    Synacktiv
Reviewed by:    kib
Approved by:    emaste (mentor)
Security:	FreeBSD-SA-24:14.umtx
Security:	CVE-2024-43102
Security:       CAP-01
Sponsored by:   The Alpha-Omega Project
Sponsored by:	The FreeBSD Foundation
Differential Revision:  https://reviews.freebsd.org/D46126

(cherry picked from commit 62f40433ab47ad4a9694a22a0313d57661502ca1)
  • Loading branch information
OlCe2 authored and brooksdavis committed Sep 6, 2024
1 parent 29c149a commit c470a5d
Showing 1 changed file with 33 additions and 18 deletions.
51 changes: 33 additions & 18 deletions sys/kern/kern_umtx.c
Original file line number Diff line number Diff line change
Expand Up @@ -4436,39 +4436,49 @@ umtx_shm_free_reg(struct umtx_shm_reg *reg)
}

static bool
umtx_shm_unref_reg_locked(struct umtx_shm_reg *reg, bool force)
umtx_shm_unref_reg_locked(struct umtx_shm_reg *reg, bool linked_ref)
{
bool res;

mtx_assert(&umtx_shm_lock, MA_OWNED);
KASSERT(reg->ushm_refcnt > 0, ("ushm_reg %p refcnt 0", reg));
reg->ushm_refcnt--;
res = reg->ushm_refcnt == 0;
if (res || force) {
if ((reg->ushm_flags & USHMF_LINKED) != 0) {
TAILQ_REMOVE(&umtx_shm_registry[reg->ushm_key.hash],
reg, ushm_reg_link);
LIST_REMOVE(reg, ushm_obj_link);
reg->ushm_flags &= ~USHMF_LINKED;
}

if (linked_ref) {
if ((reg->ushm_flags & USHMF_LINKED) == 0)
/*
* The reference tied to USHMF_LINKED has already been
* released concurrently.
*/
return (false);

TAILQ_REMOVE(&umtx_shm_registry[reg->ushm_key.hash], reg,
ushm_reg_link);
LIST_REMOVE(reg, ushm_obj_link);
reg->ushm_flags &= ~USHMF_LINKED;
}
return (res);

reg->ushm_refcnt--;
return (reg->ushm_refcnt == 0);
}

static void
umtx_shm_unref_reg(struct umtx_shm_reg *reg, bool force)
umtx_shm_unref_reg(struct umtx_shm_reg *reg, bool linked_ref)
{
vm_object_t object;
bool dofree;

if (force) {
if (linked_ref) {
/*
* Note: This may be executed multiple times on the same
* shared-memory VM object in presence of concurrent callers
* because 'umtx_shm_lock' is not held all along in umtx_shm()
* and here.
*/
object = reg->ushm_obj->shm_object;
VM_OBJECT_WLOCK(object);
vm_object_set_flag(object, OBJ_UMTXDEAD);
VM_OBJECT_WUNLOCK(object);
}
mtx_lock(&umtx_shm_lock);
dofree = umtx_shm_unref_reg_locked(reg, force);
dofree = umtx_shm_unref_reg_locked(reg, linked_ref);
mtx_unlock(&umtx_shm_lock);
if (dofree)
umtx_shm_free_reg(reg);
Expand Down Expand Up @@ -4521,7 +4531,6 @@ umtx_shm_create_reg(struct thread *td, const struct umtx_key *key,
if (!chgumtxcnt(cred->cr_ruidinfo, 1, lim_cur(td, RLIMIT_UMTXP)))
return (ENOMEM);
reg = uma_zalloc(umtx_shm_reg_zone, M_WAITOK | M_ZERO);
reg->ushm_refcnt = 1;
bcopy(key, &reg->ushm_key, sizeof(*key));
reg->ushm_obj = shm_alloc(td->td_ucred, O_RDWR, false);
reg->ushm_cred = crhold(cred);
Expand All @@ -4538,11 +4547,17 @@ umtx_shm_create_reg(struct thread *td, const struct umtx_key *key,
*res = reg1;
return (0);
}
reg->ushm_refcnt++;
TAILQ_INSERT_TAIL(&umtx_shm_registry[key->hash], reg, ushm_reg_link);
LIST_INSERT_HEAD(USHM_OBJ_UMTX(key->info.shared.object), reg,
ushm_obj_link);
reg->ushm_flags = USHMF_LINKED;
/*
* This is one reference for the registry and the list of shared
* mutexes referenced by the VM object containing the lock pointer, and
* another for the caller, which it will free after use. So, one of
* these is tied to the presence of USHMF_LINKED.
*/
reg->ushm_refcnt = 2;
mtx_unlock(&umtx_shm_lock);
*res = reg;
return (0);
Expand Down

0 comments on commit c470a5d

Please sign in to comment.