[165] in linux-security and linux-alert archive

home help back first fref pref prev next nref lref last post

Linux NFS client

daemon@ATHENA.MIT.EDU (Yossi Gottlieb)
Tue Mar 14 13:32:21 1995

From: Yossi Gottlieb <yogo@math.tau.ac.il>
To: linux-security@tarsier.cv.nrao.edu
Date: Tue, 14 Mar 1995 14:19:08 +0200 (GMT+0200)


I've recently noticed a problem with the implementation of the NFS in the
kernel. Since the NFS server doesn't keep track of open files, a huge
possibility for race conditions exists. For example, with current CLIENT
implementation, it is possible to open a file on an NFS filesystem and while
the file is open, erase it, and immediately replace it with a symlink. The 
symlink gets the same inode and most important, the same file handle of the
original file (it may not necessarily be so, and depends on the server). Any
futher writes to the file will go thru the symlink.

This is a HUGE security hole, and puts in danger both the client and the
server. BSD handles this by disallowing NFS REMOVE calls on locally-open
files. Instead, the file is renamed into something like ".nfsXXXX" (on SunOS 
anyway), which remains until the file is closed, and a real NFS REMOVE call
is sent.

A simple patch is included for having a similar behaviour on the Linux NFS
client. Note that it is hardly tested, so be careful.

Note that the patch doesn't completely solve the problem. Other clients
can still replace the open file, or the temporary '.nfs...' file (that's
the same thing, actually). 

Fixing that problem is only possible by using a special NFS server which
keeps track of all files, and verifies no two files get the same file handle 
(i.e. something like keeping a counter which gets increased per file creation,
and somehow stuffed into the file handle).

btw Perhaps the SunOS nfsd have this behaviour, anybody can confirm/deny? 
While experimenting wit SunOS, I've noticed that whenever I try to replace
an open file, any further writes to it causes a 'NFS Write Error' be sent
to syslog, even though the syscall does not indicate any problem.

yossi.



--- linux/fs/nfs/file.c.orig	Sat Mar 11 23:46:48 1995
+++ linux/fs/nfs/file.c	Tue Mar 14 13:24:36 1995
@@ -10,6 +10,7 @@
  *     and implementation by Wai S Kok elekokws@ee.nus.sg.
  *
  *  Expire cache on write to a file by Wai S Kok (Oct 1994).
+ *  Better treatment of unlink (BSD-style) by Yossi Gottlieb (Mar. 95)
  *
  *  nfs regular file handling functions
  */
@@ -33,6 +34,7 @@
 static int nfs_file_read(struct inode *, struct file *, char *, int);
 static int nfs_file_write(struct inode *, struct file *, char *, int);
 static int nfs_fsync(struct inode *, struct file *);
+static void nfs_release(struct inode *, struct file *);
 
 static struct file_operations nfs_file_operations = {
 	NULL,			/* lseek - default */
@@ -43,7 +45,7 @@
 	NULL,			/* ioctl - default */
 	nfs_mmap,		/* mmap */
 	NULL,			/* no special open is needed */
-	NULL,			/* release */
+	nfs_release,		/* release */
 	nfs_fsync,		/* fsync */
 };
 
@@ -94,6 +96,23 @@
 {
 	return 0;
 }
+
+static void nfs_release(struct inode *inode, struct file *file)
+{
+	struct inode *dir;
+
+	if (inode->u.nfs_i.del_ino) 
+		if ((dir = iget(inode->i_sb, inode->u.nfs_i.del_ino))) {
+			inode->u.nfs_i.del_ino = 0;
+			file->f_count--;
+			if (dir->i_op->unlink(dir, inode->u.nfs_i.del_name, NFS_DUMMYLEN) < 0) 
+				printk("nfs_release: dummy nfs file remove error.\n");
+			file->f_count++;
+		}
+	return;
+}
+
+	
 
 static int nfs_file_read(struct inode *inode, struct file *file, char *buf,
 			 int count)
--- linux/fs/nfs/dir.c.orig	Sat Mar 11 23:07:42 1995
+++ linux/fs/nfs/dir.c	Tue Mar 14 13:24:17 1995
@@ -2,6 +2,7 @@
  *  linux/fs/nfs/dir.c
  *
  *  Copyright (C) 1992  Rick Sladkey
+ *  Better treatment of unlink (BSD-style) by Yossi Gottlieb (Mar. 95)
  *
  *  nfs directory handling functions
  */
@@ -446,9 +447,46 @@
 	return error;
 }
 
-static int nfs_unlink(struct inode *dir, const char *name, int len)
+/* This handles the rename of the deleted file into a .nfsXXXX. 
+ */ 
+static char hextoasc[] = "0123456789abcdef";
+int nfs_autorename(struct inode *dir, const char *name, int len, char *newname)
 {
+	pid_t pid = current->pid;
+	struct inode *fi;
 	int error;
+	char tmpname[NFS_DUMMYLEN];
+
+	memcpy(tmpname, ".nfsAXXXX.linux", NFS_DUMMYLEN);
+	tmpname[8] = hextoasc[pid & 0xf];
+	tmpname[7] = hextoasc[(pid >> 4) & 0xf];
+	tmpname[6] = hextoasc[(pid >> 8) & 0xf];
+	tmpname[5] = hextoasc[(pid >> 12) & 0xf];
+
+	dir->i_count++; 	/* nfs_lookup does 1 iput() per call */
+ 	while (!dir->i_op->lookup(dir, tmpname, NFS_DUMMYLEN, &fi)) {
+		iput(fi);
+		tmpname[4]++;
+		if (tmpname[4] > 'z') 
+			return -EINVAL;
+		dir->i_count++;
+	}
+	iput(fi);
+
+	dir->i_count += 2;	/* nfs_rename does iput() for each dir inode */
+	if (!(error = dir->i_op->rename(dir, name, len, dir, tmpname, NFS_DUMMYLEN)))
+		memcpy(newname, tmpname, NFS_DUMMYLEN);
+	return error;
+}
+	
+	
+
+
+static int nfs_unlink(struct inode *dir, const char *name, int len)
+{
+	int error, i;
+	struct inode *fi;
+	struct file *f;
 
 	if (!dir || !S_ISDIR(dir->i_mode)) {
 		printk("nfs_unlink: inode is NULL or not a directory\n");
@@ -459,6 +497,27 @@
 		iput(dir);
 		return -ENAMETOOLONG;
 	}
+
+	/* before issuing an NFS remove call, we make sure the inode is
+  	 * not currently in use. in that case, we would only rename the
+ 	 * file, and unlink it once the final close is made.
+	 */
+	dir->i_count++;		/* lookup does iput()... */
+	if (dir->i_op->lookup(dir, name, len, &fi) < 0) {
+		iput(dir);
+		return -ENOENT;
+	}
+	for (f = first_file, i=0; i < nr_files; i++, f = f->f_next) 
+		if (f->f_count > 0 && f->f_inode->i_ino == fi->i_ino) {
+			if (!(error = nfs_autorename(dir, name, len, fi->u.nfs_i.del_name))) {
+				fi->u.nfs_i.del_ino = dir->i_ino;
+			}
+			iput(fi);
+			iput(dir);
+			return error;
+		}
+	iput(fi);
+
 	error = nfs_proc_remove(NFS_SERVER(dir), NFS_FH(dir), name);
 	if (!error)
 		nfs_lookup_cache_remove(dir, NULL, name);
--- linux/include/linux/nfs.h.orig	Tue Mar 14 13:22:03 1995
+++ linux/include/linux/nfs.h	Tue Mar 14 13:22:14 1995
@@ -9,6 +9,7 @@
 #define NFS_FHSIZE 32
 #define NFS_COOKIESIZE 4
 #define NFS_FIFO_DEV (-1)
+#define NFS_DUMMYLEN 15
 #define NFSMODE_FMT 0170000
 #define NFSMODE_DIR 0040000
 #define NFSMODE_CHR 0020000
--- linux/include/linux/nfs_fs_i.h.orig	Sat Mar 11 23:03:12 1995
+++ linux/include/linux/nfs_fs_i.h	Tue Mar 14 13:22:36 1995
@@ -8,6 +8,8 @@
  */
 struct nfs_inode_info {
 	struct nfs_fh fhandle;
+	int del_ino;
+	char del_name[NFS_DUMMYLEN];
 };
 
 #endif

home help back first fref pref prev next nref lref last post