lio_listio: validate aio_lio_opcode

Previously, we would accept any kind of LIO_* opcode, including ones
that were intended for in-kernel use only like LIO_SYNC (which is not
defined in userland).  The situation became more serious with
022ca2fc7f.  After that revision, setting
aio_lio_opcode to LIO_WRITEV or LIO_READV would trigger an assertion.

Note that POSIX does not specify what should happen if aio_lio_opcode is
invalid.

MFC-with:	022ca2fc7f
Reviewed by:	jhb, tmunro, 0mp
Differential Revision:	<https://reviews.freebsd.org/D28078
This commit is contained in:
Alan Somers 2021-01-09 20:23:05 -07:00
parent 292808246d
commit ff1a307801
4 changed files with 74 additions and 7 deletions

View File

@ -24,7 +24,7 @@
.\"
.\" $FreeBSD$
.\"
.Dd January 2, 2021
.Dd January 11, 2021
.Dt AIO_RETURN 2
.Os
.Sh NAME
@ -75,6 +75,12 @@ The
.Fa iocb
argument
does not reference a completed asynchronous I/O request.
.It Bq Er EINVAL
The I/O operation was submitted with
.Fn lio_listio ,
and the value of the
.Fa aio_lio_opcode
is invalid.
.El
.Sh SEE ALSO
.Xr aio_cancel 2 ,

View File

@ -1556,16 +1556,26 @@ aio_aqueue(struct thread *td, struct aiocb *ujob, struct aioliojob *lj,
goto err2;
}
/* Get the opcode. */
if (type == LIO_NOP) {
switch (job->uaiocb.aio_lio_opcode) {
case LIO_WRITE:
case LIO_NOP:
case LIO_READ:
opcode = job->uaiocb.aio_lio_opcode;
break;
default:
error = EINVAL;
goto err2;
}
} else
opcode = job->uaiocb.aio_lio_opcode = type;
ksiginfo_init(&job->ksi);
/* Save userspace address of the job info. */
job->ujob = ujob;
/* Get the opcode. */
if (type != LIO_NOP)
job->uaiocb.aio_lio_opcode = type;
opcode = job->uaiocb.aio_lio_opcode;
/*
* Validate the opcode and fetch the file object for the specified
* file descriptor.

View File

@ -43,7 +43,7 @@
#define LIO_NOP 0x0
#define LIO_WRITE 0x1
#define LIO_READ 0x2
#ifdef _KERNEL
#if defined(_KERNEL) || defined(_WANT_ALL_LIO_OPCODES)
#define LIO_SYNC 0x3
#define LIO_MLOCK 0x4
#define LIO_WRITEV 0x5

View File

@ -26,8 +26,11 @@
* $FreeBSD$
*/
#define _WANT_ALL_LIO_OPCODES
#include <sys/param.h>
#include <sys/event.h>
#include <sys/uio.h>
#include <aio.h>
#include <fcntl.h>
@ -198,6 +201,53 @@ ATF_TC_BODY(lio_listio_empty_nowait_thread, tc)
ATF_REQUIRE_EQ(0, sem_destroy(&completions));
}
/*
* Only select opcodes are allowed with lio_listio
*/
ATF_TC_WITHOUT_HEAD(lio_listio_invalid_opcode);
ATF_TC_BODY(lio_listio_invalid_opcode, tc)
{
struct aiocb sync_cb, mlock_cb, writev_cb, readv_cb;
struct aiocb *list[] = {&sync_cb, &mlock_cb, &writev_cb, &readv_cb};
struct iovec iov;
int fd;
fd = open("testfile", O_CREAT | O_RDWR);
ATF_REQUIRE_MSG(fd >= 0, "open: %s", strerror(errno));
bzero(&sync_cb, sizeof(sync_cb));
sync_cb.aio_fildes = fd;
sync_cb.aio_lio_opcode = LIO_SYNC;
bzero(&mlock_cb, sizeof(mlock_cb));
mlock_cb.aio_lio_opcode = LIO_MLOCK;
iov.iov_base = NULL;
iov.iov_len = 0;
bzero(&readv_cb, sizeof(readv_cb));
readv_cb.aio_fildes = fd;
readv_cb.aio_lio_opcode = LIO_READV;
readv_cb.aio_iov = &iov;
readv_cb.aio_iovcnt = 1;
bzero(&writev_cb, sizeof(writev_cb));
writev_cb.aio_fildes = fd;
writev_cb.aio_lio_opcode = LIO_WRITEV;
writev_cb.aio_iov = &iov;
writev_cb.aio_iovcnt = 1;
ATF_CHECK_ERRNO(EIO, lio_listio(LIO_WAIT, list, nitems(list), NULL));
ATF_CHECK_EQ(EINVAL, aio_error(&sync_cb));
ATF_CHECK_ERRNO(EINVAL, aio_return(&sync_cb) < 0);
ATF_CHECK_EQ(EINVAL, aio_error(&mlock_cb));
ATF_CHECK_ERRNO(EINVAL, aio_return(&mlock_cb) < 0);
ATF_CHECK_EQ(EINVAL, aio_error(&readv_cb));
ATF_CHECK_ERRNO(EINVAL, aio_return(&readv_cb) < 0);
ATF_CHECK_EQ(EINVAL, aio_error(&writev_cb));
ATF_CHECK_ERRNO(EINVAL, aio_return(&writev_cb) < 0);
}
ATF_TP_ADD_TCS(tp)
{
@ -207,6 +257,7 @@ ATF_TP_ADD_TCS(tp)
ATF_TP_ADD_TC(tp, lio_listio_empty_nowait_signal);
ATF_TP_ADD_TC(tp, lio_listio_empty_nowait_thread);
ATF_TP_ADD_TC(tp, lio_listio_empty_wait);
ATF_TP_ADD_TC(tp, lio_listio_invalid_opcode);
return (atf_no_error());
}