Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions fs/vfs/vfs_syscalls.cc
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,16 @@ sys_read(struct file *fp, const struct iovec *iov, size_t niov,
if ((fp->f_flags & FREAD) == 0)
return EBADF;

// Reject an out-of-range iovec count with EINVAL *before* touching iov: a
// caller-controlled niov > UIO_MAXIOV would otherwise (a) walk iov[] for
// up to niov entries in the summation loop below (OOB read) and (b) hit
// the assert() further down, which in a _KERNEL build is compiled in and
// calls abort() -> whole-VM panic. It also caps the VLA below to a sane
// size. (niov is size_t; guard both the >MAXIOV and, defensively, the
// count itself.)
if (niov > UIO_MAXIOV)
return EINVAL;

size_t bytes = 0;
auto iovp = iov;
for (unsigned i = 0; i < niov; i++) {
Expand Down Expand Up @@ -289,6 +299,10 @@ sys_write(struct file *fp, const struct iovec *iov, size_t niov,
if ((fp->f_flags & FWRITE) == 0)
return EBADF;

// See sys_read: reject niov > UIO_MAXIOV up front (OOB-read + abort() DoS).
if (niov > UIO_MAXIOV)
return EINVAL;

size_t bytes = 0;
auto iovp = iov;
for (unsigned i = 0; i < niov; i++) {
Expand Down
2 changes: 1 addition & 1 deletion modules/tests/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ ext-only-tests := tst-readdir.so tst-concurrent-read.so tst-fs-link.so

specific-fs-tests := $($(fs_type)-only-tests)

tests := tst-pthread.so misc-ramdisk.so tst-vblk.so tst-mq-smoke.so tst-bsd-evh.so \
tests := tst-iovcnt-guard.so tst-pthread.so misc-ramdisk.so tst-vblk.so tst-mq-smoke.so tst-bsd-evh.so \
misc-bsd-callout.so tst-bsd-kthread.so tst-bsd-taskqueue.so \
tst-fpu.so tst-preempt.so tst-tracepoint.so tst-hub.so \
misc-console.so misc-leak.so misc-readbench.so misc-mmap-anon-perf.so \
Expand Down
44 changes: 44 additions & 0 deletions tests/tst-iovcnt-guard.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* Copyright (C) 2026 Greg Burd
*
* This work is open source software, licensed under the terms of the
* BSD license as described in the LICENSE file in the top-level directory.
*/

// Regression: a huge iovcnt to readv/writev must return -1/EINVAL, not panic
// the kernel (previously an assert(niov <= UIO_MAXIOV) -> abort()).
#include <sys/uio.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>

int main()
{
int fd = open("/tmp/iovcnt-test", O_RDWR | O_CREAT | O_TRUNC, 0644);
if (fd < 0) { perror("open"); return 1; }
// A normal small write to give the file content.
struct iovec one = { (void*)"hi", 2 };
if (writev(fd, &one, 1) != 2) { perror("writev(1)"); return 1; }
lseek(fd, 0, SEEK_SET);

// Oversized iovcnt: must be rejected with EINVAL, not abort the VM.
struct iovec v = { 0, 0 };
errno = 0;
ssize_t r = readv(fd, &v, 0x40000000);
if (!(r == -1 && errno == EINVAL)) {
fprintf(stderr, "FAIL: readv(huge iovcnt) returned %zd errno=%d (want -1/EINVAL)\n", r, errno);
return 1;
}
errno = 0;
r = writev(fd, &v, 0x40000000);
if (!(r == -1 && errno == EINVAL)) {
fprintf(stderr, "FAIL: writev(huge iovcnt) returned %zd errno=%d (want -1/EINVAL)\n", r, errno);
return 1;
}
close(fd);
unlink("/tmp/iovcnt-test");
fprintf(stderr, "iovcnt-guard test PASSED\n");
return 0;
}