VFS: fix select race condition
select_cancel_all could prematurely clean a select table slot that was still initializing in do_select. This can happen when two select calls are active and operating on the same file(s). One select is still initializing in do_select, the other receives a postponed result (select_reply2). The handler for the postponed result would cancel all selects that were operating on the same file it got a result for. Consequently, the initializing select suddenly has no valid file pointers in its slot anymore, and causes null dereferences. Change-Id: I647de9889a0548c4d232309cd5cd27165ef6ebbf
This commit is contained in:
parent
7d85156ac0
commit
17c8162011
|
|
@ -26,6 +26,10 @@
|
||||||
#define FROM_PROC 0
|
#define FROM_PROC 0
|
||||||
#define TO_PROC 1
|
#define TO_PROC 1
|
||||||
|
|
||||||
|
#define SEL_BLOCK 001 /* block until timeout or reslt */
|
||||||
|
#define SEL_INIT 002 /* initializing select call */
|
||||||
|
#define SEL_IS_CANCELED 004 /* select has been canceled */
|
||||||
|
|
||||||
static struct selectentry {
|
static struct selectentry {
|
||||||
struct fproc *requestor; /* slot is free iff this is NULL */
|
struct fproc *requestor; /* slot is free iff this is NULL */
|
||||||
endpoint_t req_endpt;
|
endpoint_t req_endpt;
|
||||||
|
|
@ -36,9 +40,9 @@ static struct selectentry {
|
||||||
int type[OPEN_MAX];
|
int type[OPEN_MAX];
|
||||||
int nfds, nreadyfds;
|
int nfds, nreadyfds;
|
||||||
int error;
|
int error;
|
||||||
char block;
|
|
||||||
clock_t expiry;
|
clock_t expiry;
|
||||||
timer_t timer; /* if expiry > 0 */
|
timer_t timer; /* if expiry > 0 */
|
||||||
|
int flags;
|
||||||
} selecttab[MAXSELECTS];
|
} selecttab[MAXSELECTS];
|
||||||
|
|
||||||
static int copy_fdsets(struct selectentry *se, int nfds, int
|
static int copy_fdsets(struct selectentry *se, int nfds, int
|
||||||
|
|
@ -114,6 +118,7 @@ int do_select(message *UNUSED(m_out))
|
||||||
se = &selecttab[s];
|
se = &selecttab[s];
|
||||||
wipe_select(se); /* Clear results of previous usage */
|
wipe_select(se); /* Clear results of previous usage */
|
||||||
se->requestor = fp;
|
se->requestor = fp;
|
||||||
|
se->flags |= SEL_INIT;
|
||||||
se->req_endpt = who_e;
|
se->req_endpt = who_e;
|
||||||
se->vir_readfds = (fd_set *) job_m_in.SEL_READFDS;
|
se->vir_readfds = (fd_set *) job_m_in.SEL_READFDS;
|
||||||
se->vir_writefds = (fd_set *) job_m_in.SEL_WRITEFDS;
|
se->vir_writefds = (fd_set *) job_m_in.SEL_WRITEFDS;
|
||||||
|
|
@ -146,11 +151,11 @@ int do_select(message *UNUSED(m_out))
|
||||||
* specified time interval.
|
* specified time interval.
|
||||||
*/
|
*/
|
||||||
if (!do_timeout) /* No timeout value set */
|
if (!do_timeout) /* No timeout value set */
|
||||||
se->block = 1;
|
se->flags |= SEL_BLOCK;
|
||||||
else if (do_timeout && (timeout.tv_sec > 0 || timeout.tv_usec > 0))
|
else if (do_timeout && (timeout.tv_sec > 0 || timeout.tv_usec > 0))
|
||||||
se->block = 1;
|
se->flags |= SEL_BLOCK;
|
||||||
else /* timeout set as (0,0) - this effects a poll */
|
else /* timeout set as (0,0) - this effects a poll */
|
||||||
se->block = 0;
|
se->flags &= ~SEL_BLOCK;
|
||||||
se->expiry = 0; /* no timer set (yet) */
|
se->expiry = 0; /* no timer set (yet) */
|
||||||
|
|
||||||
/* Verify that file descriptors are okay to select on */
|
/* Verify that file descriptors are okay to select on */
|
||||||
|
|
@ -213,6 +218,8 @@ int do_select(message *UNUSED(m_out))
|
||||||
int ops, r;
|
int ops, r;
|
||||||
struct filp *f;
|
struct filp *f;
|
||||||
|
|
||||||
|
if (se->flags & SEL_IS_CANCELED) break;
|
||||||
|
|
||||||
/* Again, check for involuntarily selected fd's */
|
/* Again, check for involuntarily selected fd's */
|
||||||
if (!(ops = tab2ops(fd, se)))
|
if (!(ops = tab2ops(fd, se)))
|
||||||
continue; /* No operations set; nothing to do for this fd */
|
continue; /* No operations set; nothing to do for this fd */
|
||||||
|
|
@ -236,7 +243,7 @@ int do_select(message *UNUSED(m_out))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((se->nreadyfds > 0 || !se->block) && !is_deferred(se)) {
|
if ((se->nreadyfds > 0 || !(se->flags & SEL_BLOCK)) && !is_deferred(se)) {
|
||||||
/* fd's were found that were ready to go right away, and/or
|
/* fd's were found that were ready to go right away, and/or
|
||||||
* we were instructed not to block at all. Must return
|
* we were instructed not to block at all. Must return
|
||||||
* immediately.
|
* immediately.
|
||||||
|
|
@ -277,6 +284,7 @@ int do_select(message *UNUSED(m_out))
|
||||||
}
|
}
|
||||||
|
|
||||||
/* process now blocked */
|
/* process now blocked */
|
||||||
|
se->flags &= ~SEL_INIT;
|
||||||
suspend(FP_BLOCKED_ON_SELECT);
|
suspend(FP_BLOCKED_ON_SELECT);
|
||||||
return(SUSPEND);
|
return(SUSPEND);
|
||||||
}
|
}
|
||||||
|
|
@ -594,6 +602,8 @@ static void select_cancel_all(struct selectentry *se)
|
||||||
int fd;
|
int fd;
|
||||||
struct filp *f;
|
struct filp *f;
|
||||||
|
|
||||||
|
se->flags |= SEL_IS_CANCELED;
|
||||||
|
|
||||||
for (fd = 0; fd < se->nfds; fd++) {
|
for (fd = 0; fd < se->nfds; fd++) {
|
||||||
if ((f = se->filps[fd]) == NULL) continue;
|
if ((f = se->filps[fd]) == NULL) continue;
|
||||||
se->filps[fd] = NULL;
|
se->filps[fd] = NULL;
|
||||||
|
|
@ -605,7 +615,7 @@ static void select_cancel_all(struct selectentry *se)
|
||||||
se->expiry = 0;
|
se->expiry = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
se->requestor = NULL;
|
if (!(se->flags & SEL_INIT)) se->requestor = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*===========================================================================*
|
/*===========================================================================*
|
||||||
|
|
@ -671,9 +681,11 @@ void init_select(void)
|
||||||
{
|
{
|
||||||
int s;
|
int s;
|
||||||
|
|
||||||
for (s = 0; s < MAXSELECTS; s++)
|
for (s = 0; s < MAXSELECTS; s++) {
|
||||||
|
selecttab[s].requestor = NULL;
|
||||||
init_timer(&selecttab[s].timer);
|
init_timer(&selecttab[s].timer);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/*===========================================================================*
|
/*===========================================================================*
|
||||||
|
|
@ -981,11 +993,11 @@ int *ops;
|
||||||
type = se->type[fd];
|
type = se->type[fd];
|
||||||
f = se->filps[fd];
|
f = se->filps[fd];
|
||||||
select_lock_filp(f, *ops);
|
select_lock_filp(f, *ops);
|
||||||
r = fdtypes[type].select_request(f, ops, se->block);
|
r = fdtypes[type].select_request(f, ops, se->flags & SEL_BLOCK);
|
||||||
unlock_filp(f);
|
unlock_filp(f);
|
||||||
if (r != OK && r != SUSPEND) {
|
if (r != OK && r != SUSPEND) {
|
||||||
se->error = EINTR;
|
se->error = EINTR;
|
||||||
se->block = 0; /* Stop blocking to return asap */
|
se->flags &= ~SEL_BLOCK; /* Stop blocking to return asap */
|
||||||
if (!is_deferred(se)) select_cancel_all(se);
|
if (!is_deferred(se)) select_cancel_all(se);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1027,7 +1039,7 @@ struct selectentry *se;
|
||||||
/* Tell process about select results (if any) unless there are still results
|
/* Tell process about select results (if any) unless there are still results
|
||||||
* pending. */
|
* pending. */
|
||||||
|
|
||||||
if ((se->nreadyfds > 0 || !se->block) && !is_deferred(se))
|
if ((se->nreadyfds > 0 || !(se->flags & SEL_BLOCK)) && !is_deferred(se))
|
||||||
select_return(se);
|
select_return(se);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1039,7 +1051,7 @@ static void wipe_select(struct selectentry *se)
|
||||||
se->nfds = 0;
|
se->nfds = 0;
|
||||||
se->nreadyfds = 0;
|
se->nreadyfds = 0;
|
||||||
se->error = OK;
|
se->error = OK;
|
||||||
se->block = 0;
|
se->flags = 0;
|
||||||
memset(se->filps, 0, sizeof(se->filps));
|
memset(se->filps, 0, sizeof(se->filps));
|
||||||
|
|
||||||
FD_ZERO(&se->readfds);
|
FD_ZERO(&se->readfds);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user