[net.bugs.4bsd] 4.1+ pipe bug

mjb@brunix.UUCP (05/13/83)

>>> 4.1a, 4.1c, 4.2 BSD ONLY (NOT 4.1) <<<

Description
-----------
Writing 64K or more to a pipe in a single write() statement will ultimately
cause the system to crash (with "panic: sbflush 2").

How to recreate
---------------
Well I wouldn't advise running it, but the following C program will do it:

main()
{
	int pipefd[2];
	char data[64*1024];

	pipe(pipefd);
	write(pipefd[1], data, 64*1024);

	/* Shouldn't get here, right? WRONG! */
	/* (it should hang because the pipe's not that big, */
	/* and no one is reading it) */

	close(pipefd[1]);
	while (read(pipefd[0], data, 1) > 0)
		/* do nothing */;
	close(pipefd[0]);

	/* SURPRISE! your system just crashed. */
}

Analysis
--------
There is a bug in the socket sending routine (sosend()) whereby it doesn't
ever do partial writes to the socket. So when you tell it to write 64KB, by
golly, it just jams the data in the pipe without regard for the arbitrary
pipe size limit of 4KB. This in itself is not that bad (after all, we have
6MB of memory!), but, alas, the size of the data queued in the socket is kept
in a short int. So 64KB of buffers have been allocated, but the count has
wrapped to 0. The read statement doesn't do anything. But on closing the
'read' half of the pipe, the system crashes because it can't figure out how
to de-allocate the 64KB worth of memory buffers.

To Fix
------
Well, you can tell your users not to do big writes to pipes, or you can
install the following fix to /sys/sys/socket.c (it's been running here for
over a month with no problems). This is 4.1a code, look for the BRUNIX ifdefs.

/*	socket.c	4.43	82/08/31	*/
	.
	.
	.
sosend(so, asa)
	register struct socket *so;
	struct sockaddr *asa;
{
	struct mbuf *top = 0;
	register struct mbuf *m, **mp = &top;
	register u_int len;
	int error = 0, space, s;
#ifdef BRUNIX
	struct sockbuf sendtempbuf;
#endif
		.
		.
		.
	space = sbspace(&so->so_snd);
	if (space <= 0 || sosendallatonce(so) && space < u.u_count) {
		if (so->so_state & SS_NBIO)
			snderr(EWOULDBLOCK);
		sbunlock(&so->so_snd);
		sbwait(&so->so_snd);
		splx(s);
		goto restart;
	}
	splx(s);
#ifdef BRUNIX
	sendtempbuf = so->so_snd;
#endif
	while (u.u_count && space > 0) {
		MGET(m, 1);
			.
			.
			.
		iomove(mtod(m, caddr_t), len, B_WRITE);
		m->m_len = len;
		*mp = m;
		mp = &m->m_next;
#ifdef BRUNIX
		sballoc(&sendtempbuf, m);
		space = sbspace(&sendtempbuf);
#else !BRUNIX
		space = sbspace(&so->so_snd);
#endif
	}
	goto again;

release:
	.
	.
	.
}

I'm Glad You Asked That
-----------------------
Who would want to write more than 64KB to a pipe at a time? Graphics people
shuffling bitmaps, that's who!

Mike Braca, Brown CS, ..!{decvax,ihnp4}!brunix!mjb, mjb.brown@udel-relay