Skip site navigation (1)Skip section navigation (2)
Date:      Thu, 20 Jun 2002 04:06:33 +0200 (CEST)
From:      Philipp Mergenthaler <philipp.mergenthaler@stud.uni-karlsruhe.de>
To:        FreeBSD-gnats-submit@FreeBSD.org
Subject:   kern/39556: [PATCH] lio_listio(2) is broken.
Message-ID:  <200206200206.g5K26Xjc001117@i609a.hadiko.de>

next in thread | raw e-mail | index | archive | help


>Number:         39556
>Category:       kern
>Synopsis:       [PATCH] lio_listio(2) is broken.
>Confidential:   no
>Severity:       serious
>Priority:       low
>Responsible:    freebsd-bugs
>State:          open
>Quarter:        
>Keywords:       
>Date-Required:
>Class:          sw-bug
>Submitter-Id:   current-users
>Arrival-Date:   Wed Jun 19 19:10:01 PDT 2002
>Closed-Date:
>Last-Modified:
>Originator:     Philipp Mergenthaler
>Release:        FreeBSD 5.0-CURRENT i386
>Organization:
University of Karlsruhe, Germany
>Environment:
System: FreeBSD i609a.hadiko.de 5.0-CURRENT FreeBSD 5.0-CURRENT #551: Thu Jun 20 02:57:50 CEST 2002 p@i609a.hadiko.de:/usr/src/sys/i386/compile/I609 i386

The bug is in -stable, too.

>Description:

In May 2000, this commit:

http://www.freebsd.org/cgi/cvsweb.cgi/src/sys/kern/vfs_aio.c#rev1.74

introduced kqueue() and kevent() to the aio_* syscalls.  This new code
(ab)uses the field aio_lio_opcode of struct aiocb to pass a pointer
(see kevent(2)).  The code differentiates between two cases:
- aio_lio_opcode == NULL (actually it should compare with "all bits zero")
  when _aio_aqueue() is called from e.g. aio_read() without using
  the kevent mechanism
- aio_lio_opcode != NULL; then it assumes that the kevent mechanism is
  used and a pointer is passed in this field.

However, this overlooks the case where _aio_aqueue() is called from
lio_listio() in which case aio_lio_opcode can have the value
LIO_READ or LIO_WRITE, both of which are != 0.

Therefore the code interprets LIO_READ/WRITE as a pointer, which, of course,
fails.

>How-To-Repeat:

Here is a demo program for the lio_listio() syscall.  It takes up to
10 file names as command line arguments, reads these files using
lio_listio() and prints the first 20 characters of each file.
Because of the bug the result of the individual I/O operations is EINVAL.

#include <fcntl.h>
#include <errno.h>
#include <time.h>
#include <aio.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define BUFFERSIZE 2048
#define NBUF 10

char buf[NBUF][BUFFERSIZE];
ssize_t retval;
ssize_t nbytes;
struct aiocb myaiocb[NBUF];
struct aiocb *myaiocbp[NBUF], *aiocbp, **haiocbp;

int main(int argc, char *argv[]) {
  int i;

  if (argc < 2)
    return 1;
  if ((argc-1) > NBUF)
    return 1;

  aiocbp=myaiocb;
  haiocbp=myaiocbp;
  for (i=argc-1; i; i--) {
    bzero( aiocbp, sizeof (struct aiocb));
    aiocbp->aio_lio_opcode = LIO_READ;
    aiocbp->aio_fildes = open( *(++argv), O_RDONLY);
    printf("file: %s\n", *argv);
    aiocbp->aio_offset = 0;
    aiocbp->aio_buf = malloc(BUFFERSIZE);
    aiocbp->aio_nbytes = BUFFERSIZE;
    aiocbp->aio_sigevent.sigev_notify = SIGEV_NONE;

    (*haiocbp++)=aiocbp++;
  }
  retval = lio_listio(LIO_WAIT, myaiocbp, argc-1, NULL);
  printf("lio_listio: %i\n", retval);
  if (retval) perror("lio_listio:");

  aiocbp=myaiocb;
  for(i=0; i < (argc-1); i++) {
    retval = aio_error(aiocbp);
    switch (retval) {
      case 0:
        printf("%i completed successfully; read %i bytes:\n", i, aio_return(aiocbp));
	printf("  %.20s\n", (volatile char*)aiocbp->aio_buf);
        break;
      case EINPROGRESS:
        printf("%i not yet completed!?\n", i);
        break;
      case -1:
        printf("%i ", i);
        fflush(stdout);
        perror("aio_error");
        break;
      default:
        printf("%i ", i);
        errno=retval;
        perror("request's status");
        break;
    }
    aiocbp++;
  }
  return 0;
}


>Fix:

This patch tests for opcode == LIO_READ/WRITE in addition to NULL:


Index: vfs_aio.c
===================================================================
RCS file: /ncvs/src/sys/kern/vfs_aio.c,v
retrieving revision 1.132
diff -u -r1.132 vfs_aio.c
--- vfs_aio.c	31 May 2002 11:52:30 -0000	1.132
+++ vfs_aio.c	20 Jun 2002 01:02:33 -0000
@@ -1391,9 +1391,11 @@
 		 */
 		struct kevent *kevp;
 
-		kevp = (struct kevent *)(uintptr_t)job->aio_lio_opcode;
-		if (kevp == NULL)
+		if (((struct kevent *)(uintptr_t)job->aio_lio_opcode == NULL) ||
+		    (job->aio_lio_opcode == LIO_READ) ||
+		    (job->aio_lio_opcode == LIO_WRITE))
 			goto no_kqueue;
+		kevp = (struct kevent *)(uintptr_t)job->aio_lio_opcode;
 
 		error = copyin(kevp, &kev, sizeof(kev));
 		if (error)





Actually, there is another bug: in the usual use of the aio_*()
functions, the user bzero()s the struct aiocb.  Therefore we shouldn't
check for NULL, we should check for "all bits zero".  (According to the
C standard, NULL is not guaranteed to be represented as "all bits
zero".)  Maybe something like (untested:)

char *cp;
for(cp = (char *)&(job->aio_lio_opcode);
    cp < (char *)&(job->aio_lio_opcode) + sizeof(job->aio_lio_opcode);
    cp++)
	if (*cp)
		goto no_kqueue;


But I guess in practice we can leave the comparison with NULL.
>Release-Note:
>Audit-Trail:
>Unformatted:

To Unsubscribe: send mail to majordomo@FreeBSD.org
with "unsubscribe freebsd-bugs" in the body of the message




Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?200206200206.g5K26Xjc001117>