From wpaul@skynet.ctr.columbia.edu  Wed Nov 27 22:33:39 1996
Received: from skynet.ctr.columbia.edu (skynet.ctr.columbia.edu [128.59.64.70])
          by freefall.freebsd.org (8.7.5/8.7.3) with SMTP id WAA08829
          for <FreeBSD-gnats-submit@freebsd.org>; Wed, 27 Nov 1996 22:33:35 -0800 (PST)
Received: (from wpaul@localhost) by skynet.ctr.columbia.edu (8.6.12/8.6.9) id BAA00553; Thu, 28 Nov 1996 01:34:35 -0500
Message-Id: <199611280634.BAA00553@skynet.ctr.columbia.edu>
Date: Thu, 28 Nov 1996 01:34:35 -0500
From: Bill Paul <wpaul@skynet.ctr.columbia.edu>
Reply-To: wpaul@skynet.ctr.columbia.edu
To: FreeBSD-gnats-submit@freebsd.org
Subject: recv() with MSG_PEEK and NULL pointer wedges system
X-Send-Pr-Version: 3.2

>Number:         2114
>Category:       kern
>Synopsis:       recv() with MSG_PEEK and NULL pointer wedges system
>Confidential:   no
>Severity:       serious
>Priority:       high
>Responsible:    freebsd-bugs
>State:          closed
>Quarter:
>Keywords:
>Date-Required:
>Class:          sw-bug
>Submitter-Id:   current-users
>Arrival-Date:   Wed Nov 27 22:40:02 PST 1996
>Closed-Date:    Fri Nov 29 11:03:49 PST 1996
>Last-Modified:  Fri Nov 29 11:04:09 PST 1996
>Originator:     Bill Paul
>Release:        FreeBSD 2.1-STABLE i386
>Organization:
Columbia University, New York City
>Environment:

	2.1.0-RELEASE
	486DX2/66 with 20MB RAM
	400MB IDE disk
	ET4000 graphics
	3c503 ethernet adapter
	syscons console driver

	The test program that freezes my system has also been 
	reported to have the same effect on a machine of 2.2-RELENG
	vintage.

>Description:

	Enclosed are two simple programs: s.c and r.c, which I was
	using for testing. The r program creates an AF_UNIX socket,
	binds it, listen()s it, then accept()s on it, which makes it
	block while waiting for callers. The s program connect()s
	to the AF_UNIX socket and send a small amount of data using
	write(2).

	The r program first does a recv() on the socket with the
	MSG_PEEK flag set, mainly to see the size of the pending
	message before actually read(2)ing it. If then uses read(2)
	to read the message into a buffer.

	recv(2) expects a socket descriptor, a pointer to a buffer,
	the size of the buffer, and a flag. On a whim, I made the
	buffer pointer NULL. Doing this turned out to be a bad idea:
	once the r program hits the recv() system call, the system
	freezes, and the only way to recover is to reboot.

	Once in this frozen state, the system still accepts some
	interrupts: I can switch virtual consoles (syscons driver)
	and I can ping the system from another machine via ethernet,
	however I can't log in, and the console does not respond to
	any keypresses other than ALT-F?.

>How-To-Repeat:

Here is the source for the s program:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <err.h>
#include <sys/types.h>
#include <sys/time.h>
#include <string.h>
#include <sys/uio.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/fcntl.h>
#include <sys/stat.h>
#include <sys/ipc.h>
#include <sys/msg.h>

#ifndef lint
static const char rcsid[] = "$Id: s.c,v 1.2 1996/11/24 06:17:07 wpaul Exp $";
#endif

#define S_MKEY 12345678
#define C_MKEY 87654321
#define PERMS 0666
#define RPC_MSGTYPE 9090

struct msgbuf {
	long type;
	int data;
};

struct cmessage {
	struct cmsghdr cmsg;
	int fd;
};

int
main()
{
	struct iovec iov[1];
	struct msghdr msg;
	struct cmessage cm;
	struct sockaddr_un sun;
	int s;
	int len;
	int  readfd;
	int  key;
	struct msqid_ds qid;
	struct msgbuf message;
	int type;
	char testbuf[] = "this is a test message";

	strcpy(sun.sun_path, "/tmp/testsock");
	sun.sun_family = AF_UNIX;
	len = sun.sun_len = sizeof(sun.sun_len) + sizeof(sun.sun_family) +
			strlen(sun.sun_path) + 1;

	if  ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1)
		err(1, "socket creation failed");

	if (connect(s, (struct sockaddr *)&sun, len) == -1)
		err(1, "couldn't connect socket");

	write(s, &testbuf, strlen(testbuf));

	close(s);

	exit(0);
}


Here is the source for the r program:


#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <err.h>
#include <sys/types.h>
#include <sys/time.h>
#include <string.h>
#include <sys/uio.h>
#include <sys/fcntl.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/stat.h>
#include <sys/ipc.h>
#include <sys/msg.h>

#ifndef lint
static const char rcsid[] = "$Id: r.c,v 1.3 1996/11/24 06:17:07 wpaul Exp wpaul $";
#endif

struct cmessage {
	struct cmsghdr cmsg;
	int fd;
};

#define S_MKEY 12345678
#define C_MKEY 87654321
#define PERMS 0666

#define RPC_MSGTYPE 9090
#define S_MSGTYPE 9999999

struct msgbuf {
	long type;
	int data;
};

int
main()
{
	struct iovec iov[1];
	struct msghdr msg;
	struct cmessage cm;
	struct sockaddr_un sun;
	int s;
	int sock;
	int len;
	fd_set readfds;
	struct stat st;
	int  readfd;
	int  writefd;
	struct msqid_ds qid;
	struct msgbuf message;
	char testbuf[512];


	if (unlink("/tmp/testsock") == -1)
		warn("couldn't remove old socket");

	strcpy(sun.sun_path, "/tmp/testsock");
	sun.sun_family = AF_UNIX;
	len = sun.sun_len = sizeof(sun.sun_len) + sizeof(sun.sun_family) +
			strlen(sun.sun_path) + 1;

	if  ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1)
		err(1, "socket creation failed");

	if (bind(s, (struct sockaddr *)&sun, len) == -1)
		err(1, "couldn't bind socket");

	listen(s, 2);

	if ((sock = accept(s, (struct sockaddr *)&sun, &len)) == -1)
		err(1, "accept failed");

	FD_ZERO(&readfds);
	FD_SET(sock, &readfds);

	select(FD_SETSIZE, &readfds, NULL, NULL, NULL);

	len = 0;
	len = recv(sock, NULL, sizeof(testbuf), MSG_PEEK);
	/*               ^^^^ bug!! */
	/* replace NULL with &testbuf, and everything works fine */

	printf("peeked length was: %d\n", len);

	len = 0;
	len = read(sock, &testbuf, sizeof(testbuf));
	printf ("read length was: %d\n", len);

	close(s);
	close(sock);

	exit(0);
}


To reproduce the problem, do the following:

% cc -o s s.c
% cc -o r r.c
% ./r &
% ./s

As soon as the r program wakes up and tries to peek at the message
sent to it by the s program, the system will freeze.

>Fix:
	
Unknown. Presumeably, the recv() system call needs to do better
argument checking.
>Release-Note:
>Audit-Trail:

From: Bruce Evans <bde@zeta.org.au>
To: FreeBSD-gnats-submit@freebsd.org, wpaul@skynet.ctr.columbia.edu
Cc:  Subject: Re: kern/2114: recv() with MSG_PEEK and NULL pointer wedges system
Date: Fri, 29 Nov 1996 00:13:32 +1100

 >	The test program that freezes my system has also been 
 >	reported to have the same effect on a machine of 2.2-RELENG
 >	vintage.
 
 One of the uiomove()s in soreceive() returns EFAULT and doesn't
 make any progress.  The return code is not checked.  This should
 be easy to fix.
 
 Bruce

From: Bill Paul <wpaul@skynet.ctr.columbia.edu>
To: bde@zeta.org.au (Bruce Evans)
Cc: FreeBSD-gnats-submit@freebsd.org, current@freebsd.org
Subject: Re: kern/2114: recv() with MSG_PEEK and NULL pointer wedges system
Date: Thu, 28 Nov 1996 14:03:29 -0500 (EST)

 Of all the gin joints in all the towns in all the world, Bruce Evans had 
 to walk into mine and say:
 
 > >	The test program that freezes my system has also been 
 > >	reported to have the same effect on a machine of 2.2-RELENG
 > >	vintage.
 > 
 > One of the uiomove()s in soreceive() returns EFAULT and doesn't
 > make any progress.  The return code is not checked.  This should
 > be easy to fix.
 > 
 > Bruce
 
 Oh, I see: you want _me_ to fix it. My, you are a trusting soul,
 aren't you. :)
 
 If my limited understanding of the problem is correct, then what's
 happening is that the failing uiomove() is happening inside a while()
 loop, and before control returns to the top of the loop where the
 error would be noticed, it gets stuck in another loop which never 
 terminates due to the uiomove() failure.
 
 Well, the obvious fix would seem to be this:
 
 
 *** uipc_socket.c.orig	Thu Nov 28 13:15:11 1996
 --- uipc_socket.c	Thu Nov 28 13:05:37 1996
 ***************
 *** 702,707 ****
 --- 702,709 ----
   			splx(s);
   			error = uiomove(mtod(m, caddr_t) + moff, (int)len, uio);
   			s = splnet();
 + 			if (error)
 + 				goto release;
   		} else
   			uio->uio_resid -= len;
   		if (len == m->m_len - moff) {
 
 
 I'm not 100% sure this is the _correct_ fix however: there are a lot
 of mbufs being slung about, and it's hard to tell if this does the right
 thing without accidentally causing a leak somewhere.
 
 In any case, this does fix the immediate problem: with this patch in
 place, my sample program gets back an EFAULT rather than freezing the
 system.
 
 Note that OpenBSD and NetBSD may well be subject to the same bug since
 this code comes direct from 4.4BSD-Lite.
 
 -Bill
 
 -- 
 =============================================================================
 -Bill Paul (212) 854-6020 | System Manager, Master of Unix-Fu Work: 
 wpaul@ctr.columbia.edu | Center for Telecommunications Research Home: 
 wpaul@skynet.ctr.columbia.edu | Columbia University, New York City
 =============================================================================
  "If you're ever in trouble, go to the CTR. Ask for Bill. He will help you."
 =============================================================================
State-Changed-From-To: open->closed 
State-Changed-By: davidg 
State-Changed-When: Fri Nov 29 11:03:49 PST 1996 
State-Changed-Why:  
Suggested patch applied. 
>Unformatted:
