COMMAND
VFS
SYSTEMS AFFECTED
OpenBSD
PROBLEM
Alexander Viro found following. Let's start with the trivial:
good old aliasing bugs.
Example 1:
dup2() vs. close(). Relevant file: kern/kern_descrip.c
sys_dup2(p, v, retval)
struct proc *p;
void *v;
register_t *retval;
{
[snip]
if ((u_int)old >= fdp->fd_nfiles || fdp->fd_ofiles[old] == NULL ||
(u_int)new >= p->p_rlimit[RLIMIT_NOFILE].rlim_cur ||
(u_int)new >= maxfiles)
return (EBADF);
OK, we've checked (among other things) that old is indeed opened.
[snip]
if (new >= fdp->fd_nfiles) {
We either expand a descriptor table
[snip]
} else {
Or reuse existing descriptor, closing file if it's opened.
(void) fdrelease(p, new);
Which is the blocking operation, BTW.
}
return (finishdup(fdp, old, new, retval));
}
[snip]
finishdup(fdp, old, new, retval)
register struct filedesc *fdp;
register int old, new;
register_t *retval;
{
register struct file *fp;
fp = fdp->fd_ofiles[old];
Got the struct sile of the file we are trying to dup...
if (fp->f_count == LONG_MAX-2)
... and dereference it. We had checked that it's non-NULL, right?
Wrong. Another thread might be sharing our descriptor table (man
rfork). IOW, fdp points to shared data structure. So we had done
the equivalent of
if (global_var) {
blocking_call();
if (global_var->f_count)
...
}
We have a nice shiny race between dup2(0,1); and close(0). And
it's a wide one. Turning that into full-blown exploit is left as
an exercise for readers.
Example 2:
pipe() vs. close() (kern/sys_pipe.c)
sys_opipe(p, v, retval)
[snip]
error = falloc(p, &rf, &fd);
if (error)
goto free2;
[snip]
retval[0] = fd;
error = falloc(p, &wf, &fd);
if (error)
goto free3;
[snip]
return (0);
free3:
ffree(rf);
fdremove(fdp, retval[0]);
free2:
[snip]
Think what happens if the second allocation fails. It is a
blocking call. During that time another thread had a nice
possibility to call close(retval[0]); since that value is very
easy to predict - it's the first available file descriptor.
close() would
* remove pointer from fdp[retval[0]]
* call ffree() on it.
Now, we come back and do _another_ ffree() on the poor beast.
Welcome to kernel panic... Code is equivalent to
global_var = p = alloc_foo();
blocking_call();
release_foo(p);
global_var = NULL;
It's obviously broken - obviously for anyone with half of clue.
One can easily provide more examples of the same crap and so can
anyone who would bother to RTFS the descriptor handling in kern/*.
Apparently that had never happened during the last 5 years or so.
Not talking about the bugs that would require anything nontrivial
to find and understand. Just follow the yello^Wpiles of sloppy C
and nearly every one will turn out to be exploitable. And no,
it's not limited to descriptor handling - same goes for
sys_pipe.c, etc.
SOLUTION
Nothing yet.