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:
Thomas Veerman 2013-11-04 11:42:08 +00:00 committed by Thomas Veerman
parent 7d85156ac0
commit 17c8162011

View File

@ -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,8 +681,10 @@ 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);