Fix a pair of races in SIGIO registration
First, funsetownlst() list looks at the first element of the list to see whether it's processing a process or a process group list. Then it acquires the global sigio lock and processes the list. However, nothing prevents the first sigio tracker from being freed by a concurrent funsetown() before the sigio lock is acquired. Fix this by acquiring the global sigio lock immediately after checking whether the list is empty. Callers of funsetownlst() ensure that new sigio trackers cannot be added concurrently. Second, fsetown() uses funsetown() to remove an existing sigio structure from a file object. However, funsetown() uses a racy check to avoid the sigio lock, so two threads may call fsetown() on the same file object, both observe that no sigio tracker is present, and enqueue two sigio trackers for the same file object. However, if the file object is destroyed, funsetown() will only remove one sigio tracker, and funsetownlst() may later trigger a use-after-free when it clears the file object reference for each entry in the list. Fix this by introducing funsetown_locked(), which avoids the racy check. Reviewed by: kib Reported by: pho Tested by: pho MFC after: 1 week Sponsored by: The FreeBSD Foundation Differential Revision: https://reviews.freebsd.org/D27157
This commit is contained in:
parent
26007fe37c
commit
f52979098d
@ -1001,6 +1001,40 @@ kern_dup(struct thread *td, u_int mode, int flags, int old, int new)
|
||||
return (error);
|
||||
}
|
||||
|
||||
static void
|
||||
sigiofree(struct sigio *sigio)
|
||||
{
|
||||
crfree(sigio->sio_ucred);
|
||||
free(sigio, M_SIGIO);
|
||||
}
|
||||
|
||||
static struct sigio *
|
||||
funsetown_locked(struct sigio *sigio)
|
||||
{
|
||||
struct proc *p;
|
||||
struct pgrp *pg;
|
||||
|
||||
SIGIO_ASSERT_LOCKED();
|
||||
|
||||
if (sigio == NULL)
|
||||
return (NULL);
|
||||
*(sigio->sio_myref) = NULL;
|
||||
if (sigio->sio_pgid < 0) {
|
||||
pg = sigio->sio_pgrp;
|
||||
PGRP_LOCK(pg);
|
||||
SLIST_REMOVE(&sigio->sio_pgrp->pg_sigiolst, sigio,
|
||||
sigio, sio_pgsigio);
|
||||
PGRP_UNLOCK(pg);
|
||||
} else {
|
||||
p = sigio->sio_proc;
|
||||
PROC_LOCK(p);
|
||||
SLIST_REMOVE(&sigio->sio_proc->p_sigiolst, sigio,
|
||||
sigio, sio_pgsigio);
|
||||
PROC_UNLOCK(p);
|
||||
}
|
||||
return (sigio);
|
||||
}
|
||||
|
||||
/*
|
||||
* If sigio is on the list associated with a process or process group,
|
||||
* disable signalling from the device, remove sigio from the list and
|
||||
@ -1011,92 +1045,82 @@ funsetown(struct sigio **sigiop)
|
||||
{
|
||||
struct sigio *sigio;
|
||||
|
||||
/* Racy check, consumers must provide synchronization. */
|
||||
if (*sigiop == NULL)
|
||||
return;
|
||||
|
||||
SIGIO_LOCK();
|
||||
sigio = *sigiop;
|
||||
if (sigio == NULL) {
|
||||
sigio = funsetown_locked(*sigiop);
|
||||
SIGIO_UNLOCK();
|
||||
return;
|
||||
}
|
||||
*(sigio->sio_myref) = NULL;
|
||||
if ((sigio)->sio_pgid < 0) {
|
||||
struct pgrp *pg = (sigio)->sio_pgrp;
|
||||
PGRP_LOCK(pg);
|
||||
SLIST_REMOVE(&sigio->sio_pgrp->pg_sigiolst, sigio,
|
||||
sigio, sio_pgsigio);
|
||||
PGRP_UNLOCK(pg);
|
||||
} else {
|
||||
struct proc *p = (sigio)->sio_proc;
|
||||
PROC_LOCK(p);
|
||||
SLIST_REMOVE(&sigio->sio_proc->p_sigiolst, sigio,
|
||||
sigio, sio_pgsigio);
|
||||
PROC_UNLOCK(p);
|
||||
}
|
||||
SIGIO_UNLOCK();
|
||||
crfree(sigio->sio_ucred);
|
||||
free(sigio, M_SIGIO);
|
||||
if (sigio != NULL)
|
||||
sigiofree(sigio);
|
||||
}
|
||||
|
||||
/*
|
||||
* Free a list of sigio structures.
|
||||
* We only need to lock the SIGIO_LOCK because we have made ourselves
|
||||
* inaccessible to callers of fsetown and therefore do not need to lock
|
||||
* the proc or pgrp struct for the list manipulation.
|
||||
* Free a list of sigio structures. The caller must ensure that new sigio
|
||||
* structures cannot be added after this point. For process groups this is
|
||||
* guaranteed using the proctree lock; for processes, the P_WEXIT flag serves
|
||||
* as an interlock.
|
||||
*/
|
||||
void
|
||||
funsetownlst(struct sigiolst *sigiolst)
|
||||
{
|
||||
struct proc *p;
|
||||
struct pgrp *pg;
|
||||
struct sigio *sigio;
|
||||
struct sigio *sigio, *tmp;
|
||||
|
||||
/* Racy check. */
|
||||
sigio = SLIST_FIRST(sigiolst);
|
||||
if (sigio == NULL)
|
||||
return;
|
||||
|
||||
p = NULL;
|
||||
pg = NULL;
|
||||
|
||||
SIGIO_LOCK();
|
||||
sigio = SLIST_FIRST(sigiolst);
|
||||
if (sigio == NULL) {
|
||||
SIGIO_UNLOCK();
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* Every entry of the list should belong
|
||||
* to a single proc or pgrp.
|
||||
* Every entry of the list should belong to a single proc or pgrp.
|
||||
*/
|
||||
if (sigio->sio_pgid < 0) {
|
||||
pg = sigio->sio_pgrp;
|
||||
PGRP_LOCK_ASSERT(pg, MA_NOTOWNED);
|
||||
sx_assert(&proctree_lock, SX_XLOCKED);
|
||||
PGRP_LOCK(pg);
|
||||
} else /* if (sigio->sio_pgid > 0) */ {
|
||||
p = sigio->sio_proc;
|
||||
PROC_LOCK_ASSERT(p, MA_NOTOWNED);
|
||||
PROC_LOCK(p);
|
||||
KASSERT((p->p_flag & P_WEXIT) != 0,
|
||||
("%s: process %p is not exiting", __func__, p));
|
||||
}
|
||||
|
||||
SIGIO_LOCK();
|
||||
while ((sigio = SLIST_FIRST(sigiolst)) != NULL) {
|
||||
*(sigio->sio_myref) = NULL;
|
||||
SLIST_FOREACH(sigio, sigiolst, sio_pgsigio) {
|
||||
*sigio->sio_myref = NULL;
|
||||
if (pg != NULL) {
|
||||
KASSERT(sigio->sio_pgid < 0,
|
||||
("Proc sigio in pgrp sigio list"));
|
||||
KASSERT(sigio->sio_pgrp == pg,
|
||||
("Bogus pgrp in sigio list"));
|
||||
PGRP_LOCK(pg);
|
||||
SLIST_REMOVE(&pg->pg_sigiolst, sigio, sigio,
|
||||
sio_pgsigio);
|
||||
PGRP_UNLOCK(pg);
|
||||
} else /* if (p != NULL) */ {
|
||||
KASSERT(sigio->sio_pgid > 0,
|
||||
("Pgrp sigio in proc sigio list"));
|
||||
KASSERT(sigio->sio_proc == p,
|
||||
("Bogus proc in sigio list"));
|
||||
PROC_LOCK(p);
|
||||
SLIST_REMOVE(&p->p_sigiolst, sigio, sigio,
|
||||
sio_pgsigio);
|
||||
}
|
||||
}
|
||||
|
||||
if (pg != NULL)
|
||||
PGRP_UNLOCK(pg);
|
||||
else
|
||||
PROC_UNLOCK(p);
|
||||
}
|
||||
SIGIO_UNLOCK();
|
||||
crfree(sigio->sio_ucred);
|
||||
free(sigio, M_SIGIO);
|
||||
SIGIO_LOCK();
|
||||
}
|
||||
SIGIO_UNLOCK();
|
||||
|
||||
SLIST_FOREACH_SAFE(sigio, sigiolst, sio_pgsigio, tmp)
|
||||
sigiofree(sigio);
|
||||
}
|
||||
|
||||
/*
|
||||
@ -1110,7 +1134,7 @@ fsetown(pid_t pgid, struct sigio **sigiop)
|
||||
{
|
||||
struct proc *proc;
|
||||
struct pgrp *pgrp;
|
||||
struct sigio *sigio;
|
||||
struct sigio *osigio, *sigio;
|
||||
int ret;
|
||||
|
||||
if (pgid == 0) {
|
||||
@ -1120,13 +1144,14 @@ fsetown(pid_t pgid, struct sigio **sigiop)
|
||||
|
||||
ret = 0;
|
||||
|
||||
/* Allocate and fill in the new sigio out of locks. */
|
||||
sigio = malloc(sizeof(struct sigio), M_SIGIO, M_WAITOK);
|
||||
sigio->sio_pgid = pgid;
|
||||
sigio->sio_ucred = crhold(curthread->td_ucred);
|
||||
sigio->sio_myref = sigiop;
|
||||
|
||||
sx_slock(&proctree_lock);
|
||||
SIGIO_LOCK();
|
||||
osigio = funsetown_locked(*sigiop);
|
||||
if (pgid > 0) {
|
||||
proc = pfind(pgid);
|
||||
if (proc == NULL) {
|
||||
@ -1142,20 +1167,21 @@ fsetown(pid_t pgid, struct sigio **sigiop)
|
||||
* restrict FSETOWN to the current process or process
|
||||
* group for maximum safety.
|
||||
*/
|
||||
PROC_UNLOCK(proc);
|
||||
if (proc->p_session != curthread->td_proc->p_session) {
|
||||
PROC_UNLOCK(proc);
|
||||
ret = EPERM;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
pgrp = NULL;
|
||||
sigio->sio_proc = proc;
|
||||
SLIST_INSERT_HEAD(&proc->p_sigiolst, sigio, sio_pgsigio);
|
||||
PROC_UNLOCK(proc);
|
||||
} else /* if (pgid < 0) */ {
|
||||
pgrp = pgfind(-pgid);
|
||||
if (pgrp == NULL) {
|
||||
ret = ESRCH;
|
||||
goto fail;
|
||||
}
|
||||
PGRP_UNLOCK(pgrp);
|
||||
|
||||
/*
|
||||
* Policy - Don't allow a process to FSETOWN a process
|
||||
@ -1166,44 +1192,28 @@ fsetown(pid_t pgid, struct sigio **sigiop)
|
||||
* group for maximum safety.
|
||||
*/
|
||||
if (pgrp->pg_session != curthread->td_proc->p_session) {
|
||||
PGRP_UNLOCK(pgrp);
|
||||
ret = EPERM;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
proc = NULL;
|
||||
}
|
||||
funsetown(sigiop);
|
||||
if (pgid > 0) {
|
||||
PROC_LOCK(proc);
|
||||
/*
|
||||
* Since funsetownlst() is called without the proctree
|
||||
* locked, we need to check for P_WEXIT.
|
||||
* XXX: is ESRCH correct?
|
||||
*/
|
||||
if ((proc->p_flag & P_WEXIT) != 0) {
|
||||
PROC_UNLOCK(proc);
|
||||
ret = ESRCH;
|
||||
goto fail;
|
||||
}
|
||||
SLIST_INSERT_HEAD(&proc->p_sigiolst, sigio, sio_pgsigio);
|
||||
sigio->sio_proc = proc;
|
||||
PROC_UNLOCK(proc);
|
||||
} else {
|
||||
PGRP_LOCK(pgrp);
|
||||
SLIST_INSERT_HEAD(&pgrp->pg_sigiolst, sigio, sio_pgsigio);
|
||||
sigio->sio_pgrp = pgrp;
|
||||
PGRP_UNLOCK(pgrp);
|
||||
}
|
||||
sx_sunlock(&proctree_lock);
|
||||
SIGIO_LOCK();
|
||||
*sigiop = sigio;
|
||||
SIGIO_UNLOCK();
|
||||
if (osigio != NULL)
|
||||
sigiofree(osigio);
|
||||
return (0);
|
||||
|
||||
fail:
|
||||
SIGIO_UNLOCK();
|
||||
sx_sunlock(&proctree_lock);
|
||||
crfree(sigio->sio_ucred);
|
||||
free(sigio, M_SIGIO);
|
||||
sigiofree(sigio);
|
||||
if (osigio != NULL)
|
||||
sigiofree(osigio);
|
||||
return (ret);
|
||||
}
|
||||
|
||||
|
@ -358,7 +358,7 @@ exit1(struct thread *td, int rval, int signo)
|
||||
|
||||
/*
|
||||
* Reset any sigio structures pointing to us as a result of
|
||||
* F_SETOWN with our pid.
|
||||
* F_SETOWN with our pid. The P_WEXIT flag interlocks with fsetown().
|
||||
*/
|
||||
funsetownlst(&p->p_sigiolst);
|
||||
|
||||
|
@ -774,7 +774,8 @@ pgdelete(struct pgrp *pgrp)
|
||||
|
||||
/*
|
||||
* Reset any sigio structures pointing to us as a result of
|
||||
* F_SETOWN with our pgid.
|
||||
* F_SETOWN with our pgid. The proctree lock ensures that
|
||||
* new sigio structures will not be added after this point.
|
||||
*/
|
||||
funsetownlst(&pgrp->pg_sigiolst);
|
||||
|
||||
|
@ -337,7 +337,7 @@ struct thread;
|
||||
#define SIGIO_TRYLOCK() mtx_trylock(&sigio_lock)
|
||||
#define SIGIO_UNLOCK() mtx_unlock(&sigio_lock)
|
||||
#define SIGIO_LOCKED() mtx_owned(&sigio_lock)
|
||||
#define SIGIO_ASSERT(type) mtx_assert(&sigio_lock, type)
|
||||
#define SIGIO_ASSERT_LOCKED(type) mtx_assert(&sigio_lock, MA_OWNED)
|
||||
|
||||
extern struct mtx sigio_lock;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user