Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit da977b2c authored by Eric Van Hensbergen's avatar Eric Van Hensbergen Committed by Linus Torvalds
Browse files

[PATCH] 9p: fix segfault caused by race condition in meta-data operations



Running dbench multithreaded exposed a race condition where fid structures
were removed while in use.  This patch adds semaphores to meta-data operations
to protect the fid structure.  Some cleanup of error-case handling in the
inode operations is also included.

Signed-off-by: default avatarEric Van Hensbergen <ericvh@gmail.com>
Signed-off-by: default avatarAndrew Morton <akpm@osdl.org>
Signed-off-by: default avatarLinus Torvalds <torvalds@linux-foundation.org>
parent ff76e1df
Loading
Loading
Loading
Loading
+66 −3
Original line number Diff line number Diff line
@@ -25,6 +25,7 @@
#include <linux/fs.h>
#include <linux/sched.h>
#include <linux/idr.h>
#include <asm/semaphore.h>

#include "debug.h"
#include "v9fs.h"
@@ -84,6 +85,7 @@ struct v9fs_fid *v9fs_fid_create(struct v9fs_session_info *v9ses, int fid)
	new->iounit = 0;
	new->rdir_pos = 0;
	new->rdir_fcall = NULL;
	init_MUTEX(&new->lock);
	INIT_LIST_HEAD(&new->list);

	return new;
@@ -102,11 +104,11 @@ void v9fs_fid_destroy(struct v9fs_fid *fid)
}

/**
 * v9fs_fid_lookup - retrieve the right fid from a  particular dentry
 * v9fs_fid_lookup - return a locked fid from a dentry
 * @dentry: dentry to look for fid in
 * @type: intent of lookup (operation or traversal)
 *
 * find a fid in the dentry
 * find a fid in the dentry, obtain its semaphore and return a reference to it.
 * code calling lookup is responsible for releasing lock
 *
 * TODO: only match fids that have the same uid as current user
 *
@@ -124,7 +126,68 @@ struct v9fs_fid *v9fs_fid_lookup(struct dentry *dentry)

	if (!return_fid) {
		dprintk(DEBUG_ERROR, "Couldn't find a fid in dentry\n");
		return_fid = ERR_PTR(-EBADF);
	}

	if(down_interruptible(&return_fid->lock))
		return ERR_PTR(-EINTR);

	return return_fid;
}

/**
 * v9fs_fid_clone - lookup the fid for a dentry, clone a private copy and release it
 * @dentry: dentry to look for fid in
 *
 * find a fid in the dentry and then clone to a new private fid
 *
 * TODO: only match fids that have the same uid as current user
 *
 */

struct v9fs_fid *v9fs_fid_clone(struct dentry *dentry)
{
	struct v9fs_session_info *v9ses = v9fs_inode2v9ses(dentry->d_inode);
	struct v9fs_fid *base_fid, *new_fid = ERR_PTR(-EBADF);
	struct v9fs_fcall *fcall = NULL;
	int fid, err;

	base_fid = v9fs_fid_lookup(dentry);

	if(IS_ERR(base_fid))
		return base_fid;

	if(base_fid) {  /* clone fid */
		fid = v9fs_get_idpool(&v9ses->fidpool);
		if (fid < 0) {
			eprintk(KERN_WARNING, "newfid fails!\n");
			new_fid = ERR_PTR(-ENOSPC);
			goto Release_Fid;
		}

		err = v9fs_t_walk(v9ses, base_fid->fid, fid, NULL, &fcall);
		if (err < 0) {
			dprintk(DEBUG_ERROR, "clone walk didn't work\n");
			v9fs_put_idpool(fid, &v9ses->fidpool);
			new_fid = ERR_PTR(err);
			goto Free_Fcall;
		}
		new_fid = v9fs_fid_create(v9ses, fid);
		if (new_fid == NULL) {
			dprintk(DEBUG_ERROR, "out of memory\n");
			new_fid = ERR_PTR(-ENOMEM);
		}
Free_Fcall:
		kfree(fcall);
	}

Release_Fid:
	up(&base_fid->lock);
	return new_fid;
}

void v9fs_fid_clunk(struct v9fs_session_info *v9ses, struct v9fs_fid *fid)
{
	v9fs_t_clunk(v9ses, fid->fid);
	v9fs_fid_destroy(fid);
}
+5 −0
Original line number Diff line number Diff line
@@ -30,6 +30,8 @@ struct v9fs_fid {
	struct list_head list;	 /* list of fids associated with a dentry */
	struct list_head active; /* XXX - debug */

	struct semaphore lock;

	u32 fid;
	unsigned char fidopen;	  /* set when fid is opened */
	unsigned char fidclunked; /* set when fid has already been clunked */
@@ -55,3 +57,6 @@ struct v9fs_fid *v9fs_fid_get_created(struct dentry *);
void v9fs_fid_destroy(struct v9fs_fid *fid);
struct v9fs_fid *v9fs_fid_create(struct v9fs_session_info *, int fid);
int v9fs_fid_insert(struct v9fs_fid *fid, struct dentry *dentry);
struct v9fs_fid *v9fs_fid_clone(struct dentry *dentry);
void v9fs_fid_clunk(struct v9fs_session_info *v9ses, struct v9fs_fid *fid);
+7 −40
Original line number Diff line number Diff line
@@ -55,53 +55,22 @@ int v9fs_file_open(struct inode *inode, struct file *file)
	struct v9fs_fid *vfid;
	struct v9fs_fcall *fcall = NULL;
	int omode;
	int fid = V9FS_NOFID;
	int err;

	dprintk(DEBUG_VFS, "inode: %p file: %p \n", inode, file);

	vfid = v9fs_fid_lookup(file->f_path.dentry);
	if (!vfid) {
		dprintk(DEBUG_ERROR, "Couldn't resolve fid from dentry\n");
		return -EBADF;
	}

	fid = v9fs_get_idpool(&v9ses->fidpool);
	if (fid < 0) {
		eprintk(KERN_WARNING, "newfid fails!\n");
		return -ENOSPC;
	}
	vfid = v9fs_fid_clone(file->f_path.dentry);
	if (IS_ERR(vfid))
		return PTR_ERR(vfid);

	err = v9fs_t_walk(v9ses, vfid->fid, fid, NULL, &fcall);
	if (err < 0) {
		dprintk(DEBUG_ERROR, "rewalk didn't work\n");
		if (fcall && fcall->id == RWALK)
			goto clunk_fid;
		else {
			v9fs_put_idpool(fid, &v9ses->fidpool);
			goto free_fcall;
		}
	}
	kfree(fcall);

	/* TODO: do special things for O_EXCL, O_NOFOLLOW, O_SYNC */
	/* translate open mode appropriately */
	omode = v9fs_uflags2omode(file->f_flags);
	err = v9fs_t_open(v9ses, fid, omode, &fcall);
	err = v9fs_t_open(v9ses, vfid->fid, omode, &fcall);
	if (err < 0) {
		PRINT_FCALL_ERROR("open failed", fcall);
		goto clunk_fid;
	}

	vfid = kmalloc(sizeof(struct v9fs_fid), GFP_KERNEL);
	if (vfid == NULL) {
		dprintk(DEBUG_ERROR, "out of memory\n");
		err = -ENOMEM;
		goto clunk_fid;
		goto Clunk_Fid;
	}

	file->private_data = vfid;
	vfid->fid = fid;
	vfid->fidopen = 1;
	vfid->fidclunked = 0;
	vfid->iounit = fcall->params.ropen.iounit;
@@ -112,10 +81,8 @@ int v9fs_file_open(struct inode *inode, struct file *file)

	return 0;

clunk_fid:
	v9fs_t_clunk(v9ses, fid);

free_fcall:
Clunk_Fid:
	v9fs_fid_clunk(v9ses, vfid);
	kfree(fcall);

	return err;
+118 −86
Original line number Diff line number Diff line
@@ -416,12 +416,8 @@ static int v9fs_remove(struct inode *dir, struct dentry *file, int rmdir)
	sb = file_inode->i_sb;
	v9ses = v9fs_inode2v9ses(file_inode);
	v9fid = v9fs_fid_lookup(file);

	if (!v9fid) {
		dprintk(DEBUG_ERROR,
			"no v9fs_fid\n");
		return -EBADF;
	}
	if(IS_ERR(v9fid))
		return PTR_ERR(v9fid);

	fid = v9fid->fid;
	if (fid < 0) {
@@ -433,11 +429,13 @@ static int v9fs_remove(struct inode *dir, struct dentry *file, int rmdir)
	result = v9fs_t_remove(v9ses, fid, &fcall);
	if (result < 0) {
		PRINT_FCALL_ERROR("remove fails", fcall);
		goto Error;
	}

	v9fs_put_idpool(fid, &v9ses->fidpool);
	v9fs_fid_destroy(v9fid);

Error:
	kfree(fcall);
	return result;
}
@@ -473,9 +471,13 @@ v9fs_vfs_create(struct inode *dir, struct dentry *dentry, int mode,
	inode = NULL;
	vfid = NULL;
	v9ses = v9fs_inode2v9ses(dir);
	dfid = v9fs_fid_lookup(dentry->d_parent);
	perm = unixmode2p9mode(v9ses, mode);
	dfid = v9fs_fid_clone(dentry->d_parent);
	if(IS_ERR(dfid)) {
		err = PTR_ERR(dfid);
		goto error;
	}

	perm = unixmode2p9mode(v9ses, mode);
	if (nd && nd->flags & LOOKUP_OPEN)
		flags = nd->intent.open.flags - 1;
	else
@@ -485,9 +487,10 @@ v9fs_vfs_create(struct inode *dir, struct dentry *dentry, int mode,
		perm, v9fs_uflags2omode(flags), NULL, &fid, &qid, &iounit);

	if (err)
		goto error;
		goto clunk_dfid;

	vfid = v9fs_clone_walk(v9ses, dfid->fid, dentry);
	v9fs_fid_clunk(v9ses, dfid);
	if (IS_ERR(vfid)) {
		err = PTR_ERR(vfid);
		vfid = NULL;
@@ -525,6 +528,9 @@ v9fs_vfs_create(struct inode *dir, struct dentry *dentry, int mode,

	return 0;

clunk_dfid:
	v9fs_fid_clunk(v9ses, dfid);

error:
	if (vfid)
		v9fs_fid_destroy(vfid);
@@ -551,7 +557,12 @@ static int v9fs_vfs_mkdir(struct inode *dir, struct dentry *dentry, int mode)
	inode = NULL;
	vfid = NULL;
	v9ses = v9fs_inode2v9ses(dir);
	dfid = v9fs_fid_lookup(dentry->d_parent);
	dfid = v9fs_fid_clone(dentry->d_parent);
	if(IS_ERR(dfid)) {
		err = PTR_ERR(dfid);
		goto error;
	}

	perm = unixmode2p9mode(v9ses, mode | S_IFDIR);

	err = v9fs_create(v9ses, dfid->fid, (char *) dentry->d_name.name,
@@ -559,37 +570,36 @@ static int v9fs_vfs_mkdir(struct inode *dir, struct dentry *dentry, int mode)

	if (err) {
		dprintk(DEBUG_ERROR, "create error %d\n", err);
		goto error;
	}

	err = v9fs_t_clunk(v9ses, fid);
	if (err) {
		dprintk(DEBUG_ERROR, "clunk error %d\n", err);
		goto error;
		goto clean_up_dfid;
	}

	vfid = v9fs_clone_walk(v9ses, dfid->fid, dentry);
	if (IS_ERR(vfid)) {
		err = PTR_ERR(vfid);
		vfid = NULL;
		goto error;
		goto clean_up_dfid;
	}

	v9fs_fid_clunk(v9ses, dfid);
	inode = v9fs_inode_from_fid(v9ses, vfid->fid, dir->i_sb);
	if (IS_ERR(inode)) {
		err = PTR_ERR(inode);
		inode = NULL;
		goto error;
		goto clean_up_fids;
	}

	dentry->d_op = &v9fs_dentry_operations;
	d_instantiate(dentry, inode);
	return 0;

error:
clean_up_fids:
	if (vfid)
		v9fs_fid_destroy(vfid);

clean_up_dfid:
	v9fs_fid_clunk(v9ses, dfid);

error:
	return err;
}

@@ -622,28 +632,23 @@ static struct dentry *v9fs_vfs_lookup(struct inode *dir, struct dentry *dentry,
	dentry->d_op = &v9fs_dentry_operations;
	dirfid = v9fs_fid_lookup(dentry->d_parent);

	if (!dirfid) {
		dprintk(DEBUG_ERROR, "no dirfid\n");
		return ERR_PTR(-EINVAL);
	}
	if(IS_ERR(dirfid))
		return ERR_PTR(PTR_ERR(dirfid));

	dirfidnum = dirfid->fid;

	if (dirfidnum < 0) {
		dprintk(DEBUG_ERROR, "no dirfid for inode %p, #%lu\n",
			dir, dir->i_ino);
		return ERR_PTR(-EBADF);
	}

	newfid = v9fs_get_idpool(&v9ses->fidpool);
	if (newfid < 0) {
		eprintk(KERN_WARNING, "newfid fails!\n");
		return ERR_PTR(-ENOSPC);
		result = -ENOSPC;
		goto Release_Dirfid;
	}

	result = v9fs_t_walk(v9ses, dirfidnum, newfid,
		(char *)dentry->d_name.name, &fcall);

	up(&dirfid->lock);

	if (result < 0) {
		if (fcall && fcall->id == RWALK)
			v9fs_t_clunk(v9ses, newfid);
@@ -701,8 +706,12 @@ static struct dentry *v9fs_vfs_lookup(struct inode *dir, struct dentry *dentry,

	return NULL;

Release_Dirfid:
	up(&dirfid->lock);

FreeFcall:
	kfree(fcall);

	return ERR_PTR(result);
}

@@ -746,10 +755,8 @@ v9fs_vfs_rename(struct inode *old_dir, struct dentry *old_dentry,
	struct inode *old_inode = old_dentry->d_inode;
	struct v9fs_session_info *v9ses = v9fs_inode2v9ses(old_inode);
	struct v9fs_fid *oldfid = v9fs_fid_lookup(old_dentry);
	struct v9fs_fid *olddirfid =
	    v9fs_fid_lookup(old_dentry->d_parent);
	struct v9fs_fid *newdirfid =
	    v9fs_fid_lookup(new_dentry->d_parent);
	struct v9fs_fid *olddirfid;
	struct v9fs_fid *newdirfid;
	struct v9fs_wstat wstat;
	struct v9fs_fcall *fcall = NULL;
	int fid = -1;
@@ -759,16 +766,26 @@ v9fs_vfs_rename(struct inode *old_dir, struct dentry *old_dentry,

	dprintk(DEBUG_VFS, "\n");

	if ((!oldfid) || (!olddirfid) || (!newdirfid)) {
		dprintk(DEBUG_ERROR, "problem with arguments\n");
		return -EBADF;
	if(IS_ERR(oldfid))
		return PTR_ERR(oldfid);

	olddirfid = v9fs_fid_clone(old_dentry->d_parent);
	if(IS_ERR(olddirfid)) {
		retval = PTR_ERR(olddirfid);
		goto Release_lock;
	}

	newdirfid = v9fs_fid_clone(new_dentry->d_parent);
	if(IS_ERR(newdirfid)) {
		retval = PTR_ERR(newdirfid);
		goto Clunk_olddir;
	}

	/* 9P can only handle file rename in the same directory */
	if (memcmp(&olddirfid->qid, &newdirfid->qid, sizeof(newdirfid->qid))) {
		dprintk(DEBUG_ERROR, "old dir and new dir are different\n");
		retval = -EXDEV;
		goto FreeFcallnBail;
		goto Clunk_newdir;
	}

	fid = oldfid->fid;
@@ -779,7 +796,7 @@ v9fs_vfs_rename(struct inode *old_dir, struct dentry *old_dentry,
		dprintk(DEBUG_ERROR, "no fid for old file #%lu\n",
			old_inode->i_ino);
		retval = -EBADF;
		goto FreeFcallnBail;
		goto Clunk_newdir;
	}

	v9fs_blank_wstat(&wstat);
@@ -788,11 +805,20 @@ v9fs_vfs_rename(struct inode *old_dir, struct dentry *old_dentry,

	retval = v9fs_t_wstat(v9ses, fid, &wstat, &fcall);

      FreeFcallnBail:
	if (retval < 0)
		PRINT_FCALL_ERROR("wstat error", fcall);

	kfree(fcall);

Clunk_newdir:
	v9fs_fid_clunk(v9ses, newdirfid);

Clunk_olddir:
	v9fs_fid_clunk(v9ses, olddirfid);

Release_lock:
	up(&oldfid->lock);

	return retval;
}

@@ -810,15 +836,12 @@ v9fs_vfs_getattr(struct vfsmount *mnt, struct dentry *dentry,
{
	struct v9fs_fcall *fcall = NULL;
	struct v9fs_session_info *v9ses = v9fs_inode2v9ses(dentry->d_inode);
	struct v9fs_fid *fid = v9fs_fid_lookup(dentry);
	struct v9fs_fid *fid = v9fs_fid_clone(dentry);
	int err = -EPERM;

	dprintk(DEBUG_VFS, "dentry: %p\n", dentry);
	if (!fid) {
		dprintk(DEBUG_ERROR,
			"couldn't find fid associated with dentry\n");
		return -EBADF;
	}
	if(IS_ERR(fid))
		return PTR_ERR(fid);

	err = v9fs_t_stat(v9ses, fid->fid, &fcall);

@@ -831,6 +854,7 @@ v9fs_vfs_getattr(struct vfsmount *mnt, struct dentry *dentry,
	}

	kfree(fcall);
	v9fs_fid_clunk(v9ses, fid);
	return err;
}

@@ -844,18 +868,14 @@ v9fs_vfs_getattr(struct vfsmount *mnt, struct dentry *dentry,
static int v9fs_vfs_setattr(struct dentry *dentry, struct iattr *iattr)
{
	struct v9fs_session_info *v9ses = v9fs_inode2v9ses(dentry->d_inode);
	struct v9fs_fid *fid = v9fs_fid_lookup(dentry);
	struct v9fs_fid *fid = v9fs_fid_clone(dentry);
	struct v9fs_fcall *fcall = NULL;
	struct v9fs_wstat wstat;
	int res = -EPERM;

	dprintk(DEBUG_VFS, "\n");

	if (!fid) {
		dprintk(DEBUG_ERROR,
			"Couldn't find fid associated with dentry\n");
		return -EBADF;
	}
	if(IS_ERR(fid))
		return PTR_ERR(fid);

	v9fs_blank_wstat(&wstat);
	if (iattr->ia_valid & ATTR_MODE)
@@ -887,6 +907,7 @@ static int v9fs_vfs_setattr(struct dentry *dentry, struct iattr *iattr)
	if (res >= 0)
		res = inode_setattr(dentry->d_inode, iattr);

	v9fs_fid_clunk(v9ses, fid);
	return res;
}

@@ -987,18 +1008,15 @@ static int v9fs_readlink(struct dentry *dentry, char *buffer, int buflen)

	struct v9fs_fcall *fcall = NULL;
	struct v9fs_session_info *v9ses = v9fs_inode2v9ses(dentry->d_inode);
	struct v9fs_fid *fid = v9fs_fid_lookup(dentry);
	struct v9fs_fid *fid = v9fs_fid_clone(dentry);

	if (!fid) {
		dprintk(DEBUG_ERROR, "could not resolve fid from dentry\n");
		retval = -EBADF;
		goto FreeFcall;
	}
	if(IS_ERR(fid))
		return PTR_ERR(fid);

	if (!v9ses->extended) {
		retval = -EBADF;
		dprintk(DEBUG_ERROR, "not extended\n");
		goto FreeFcall;
		goto ClunkFid;
	}

	dprintk(DEBUG_VFS, " %s\n", dentry->d_name.name);
@@ -1009,8 +1027,10 @@ static int v9fs_readlink(struct dentry *dentry, char *buffer, int buflen)
		goto FreeFcall;
	}

	if (!fcall)
		return -EIO;
	if (!fcall) {
		retval = -EIO;
		goto ClunkFid;
	}

	if (!(fcall->params.rstat.stat.mode & V9FS_DMSYMLINK)) {
		retval = -EINVAL;
@@ -1031,6 +1051,9 @@ static int v9fs_readlink(struct dentry *dentry, char *buffer, int buflen)
FreeFcall:
	kfree(fcall);

ClunkFid:
	v9fs_fid_clunk(v9ses, fid);

	return retval;
}

@@ -1123,52 +1146,58 @@ static int v9fs_vfs_mkspecial(struct inode *dir, struct dentry *dentry,
	int err;
	u32 fid, perm;
	struct v9fs_session_info *v9ses;
	struct v9fs_fid *dfid, *vfid;
	struct inode *inode;
	struct v9fs_fid *dfid, *vfid = NULL;
	struct inode *inode = NULL;

	inode = NULL;
	vfid = NULL;
	v9ses = v9fs_inode2v9ses(dir);
	dfid = v9fs_fid_lookup(dentry->d_parent);
	perm = unixmode2p9mode(v9ses, mode);

	if (!v9ses->extended) {
		dprintk(DEBUG_ERROR, "not extended\n");
		return -EPERM;
	}

	dfid = v9fs_fid_clone(dentry->d_parent);
	if(IS_ERR(dfid)) {
		err = PTR_ERR(dfid);
		goto error;
	}

	perm = unixmode2p9mode(v9ses, mode);

	err = v9fs_create(v9ses, dfid->fid, (char *) dentry->d_name.name,
		perm, V9FS_OREAD, (char *) extension, &fid, NULL, NULL);

	if (err)
		goto error;
		goto clunk_dfid;

	err = v9fs_t_clunk(v9ses, fid);
	if (err)
		goto error;
		goto clunk_dfid;

	vfid = v9fs_clone_walk(v9ses, dfid->fid, dentry);
	if (IS_ERR(vfid)) {
		err = PTR_ERR(vfid);
		vfid = NULL;
		goto error;
		goto clunk_dfid;
	}

	inode = v9fs_inode_from_fid(v9ses, vfid->fid, dir->i_sb);
	if (IS_ERR(inode)) {
		err = PTR_ERR(inode);
		inode = NULL;
		goto error;
		goto free_vfid;
	}

	dentry->d_op = &v9fs_dentry_operations;
	d_instantiate(dentry, inode);
	return 0;

error:
	if (vfid)
free_vfid:
	v9fs_fid_destroy(vfid);

clunk_dfid:
	v9fs_fid_clunk(v9ses, dfid);

error:
	return err;

}
@@ -1209,26 +1238,29 @@ v9fs_vfs_link(struct dentry *old_dentry, struct inode *dir,
	      struct dentry *dentry)
{
	int retval;
	struct v9fs_session_info *v9ses = v9fs_inode2v9ses(dir);
	struct v9fs_fid *oldfid;
	char *name;

	dprintk(DEBUG_VFS, " %lu,%s,%s\n", dir->i_ino, dentry->d_name.name,
		old_dentry->d_name.name);

	oldfid = v9fs_fid_lookup(old_dentry);
	if (!oldfid) {
		dprintk(DEBUG_ERROR, "can't find oldfid\n");
		return -EPERM;
	}
	oldfid = v9fs_fid_clone(old_dentry);
	if(IS_ERR(oldfid))
		return PTR_ERR(oldfid);

	name = __getname();
	if (unlikely(!name))
		return -ENOMEM;
	if (unlikely(!name)) {
		retval = -ENOMEM;
		goto clunk_fid;
	}

	sprintf(name, "%d\n", oldfid->fid);
	retval = v9fs_vfs_mkspecial(dir, dentry, V9FS_DMLINK, name);
	__putname(name);

clunk_fid:
	v9fs_fid_clunk(v9ses, oldfid);
	return retval;
}