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

Commit 2f9092e1 authored by David Woodhouse's avatar David Woodhouse Committed by Al Viro
Browse files

Fix i_mutex vs. readdir handling in nfsd



Commit 14f7dd63 ("Copy XFS readdir hack into nfsd code") introduced a
bug to generic code which had been extant for a long time in the XFS
version -- it started to call through into lookup_one_len() and hence
into the file systems' ->lookup() methods without i_mutex held on the
directory.

This patch fixes it by locking the directory's i_mutex again before
calling the filldir functions. The original deadlocks which commit
14f7dd63 was designed to avoid are still avoided, because they were due
to fs-internal locking, not i_mutex.

While we're at it, fix the return type of nfsd_buffered_readdir() which
should be a __be32 not an int -- it's an NFS errno, not a Linux errno.
And return nfserrno(-ENOMEM) when allocation fails, not just -ENOMEM.
Sparse would have caught that, if it wasn't so busy bitching about
__cold__.

Commit 05f4f678 ("nfsd4: don't do lookup within readdir in recovery
code") introduced a similar problem with calling lookup_one_len()
without i_mutex, which this patch also addresses. To fix that, it was
necessary to fix the called functions so that they expect i_mutex to be
held; that part was done by J. Bruce Fields.

Signed-off-by: default avatarDavid Woodhouse <David.Woodhouse@intel.com>
Umm-I-can-live-with-that-by: default avatarAl Viro <viro@zeniv.linux.org.uk>
Reported-by: default avatarJ. R. Okajima <hooanon05@yahoo.co.jp>
Tested-by: default avatarJ. Bruce Fields <bfields@citi.umich.edu>
LKML-Reference: <8036.1237474444@jrobl>
Cc: stable@kernel.org
Signed-off-by: default avatarAl Viro <viro@zeniv.linux.org.uk>
parent 1ba0c7db
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -1248,6 +1248,8 @@ struct dentry *lookup_one_len(const char *name, struct dentry *base, int len)
	int err;
	struct qstr this;

	WARN_ON_ONCE(!mutex_is_locked(&base->d_inode->i_mutex));

	err = __lookup_one_len(name, &this, base, len);
	if (err)
		return ERR_PTR(err);
+9 −37
Original line number Diff line number Diff line
@@ -229,21 +229,23 @@ nfsd4_list_rec_dir(struct dentry *dir, recdir_func *f)
		goto out;
	status = vfs_readdir(filp, nfsd4_build_namelist, &names);
	fput(filp);
	mutex_lock(&dir->d_inode->i_mutex);
	while (!list_empty(&names)) {
		entry = list_entry(names.next, struct name_list, list);

		dentry = lookup_one_len(entry->name, dir, HEXDIR_LEN-1);
		if (IS_ERR(dentry)) {
			status = PTR_ERR(dentry);
			goto out;
			break;
		}
		status = f(dir, dentry);
		dput(dentry);
		if (status)
			goto out;
			break;
		list_del(&entry->list);
		kfree(entry);
	}
	mutex_unlock(&dir->d_inode->i_mutex);
out:
	while (!list_empty(&names)) {
		entry = list_entry(names.next, struct name_list, list);
@@ -254,36 +256,6 @@ nfsd4_list_rec_dir(struct dentry *dir, recdir_func *f)
	return status;
}

static int
nfsd4_remove_clid_file(struct dentry *dir, struct dentry *dentry)
{
	int status;

	if (!S_ISREG(dir->d_inode->i_mode)) {
		printk("nfsd4: non-file found in client recovery directory\n");
		return -EINVAL;
	}
	mutex_lock_nested(&dir->d_inode->i_mutex, I_MUTEX_PARENT);
	status = vfs_unlink(dir->d_inode, dentry);
	mutex_unlock(&dir->d_inode->i_mutex);
	return status;
}

static int
nfsd4_clear_clid_dir(struct dentry *dir, struct dentry *dentry)
{
	int status;

	/* For now this directory should already be empty, but we empty it of
	 * any regular files anyway, just in case the directory was created by
	 * a kernel from the future.... */
	nfsd4_list_rec_dir(dentry, nfsd4_remove_clid_file);
	mutex_lock_nested(&dir->d_inode->i_mutex, I_MUTEX_PARENT);
	status = vfs_rmdir(dir->d_inode, dentry);
	mutex_unlock(&dir->d_inode->i_mutex);
	return status;
}

static int
nfsd4_unlink_clid_dir(char *name, int namlen)
{
@@ -294,18 +266,18 @@ nfsd4_unlink_clid_dir(char *name, int namlen)

	mutex_lock(&rec_dir.dentry->d_inode->i_mutex);
	dentry = lookup_one_len(name, rec_dir.dentry, namlen);
	mutex_unlock(&rec_dir.dentry->d_inode->i_mutex);
	if (IS_ERR(dentry)) {
		status = PTR_ERR(dentry);
		return status;
		goto out_unlock;
	}
	status = -ENOENT;
	if (!dentry->d_inode)
		goto out;

	status = nfsd4_clear_clid_dir(rec_dir.dentry, dentry);
	status = vfs_rmdir(rec_dir.dentry->d_inode, dentry);
out:
	dput(dentry);
out_unlock:
	mutex_unlock(&rec_dir.dentry->d_inode->i_mutex);
	return status;
}

@@ -348,7 +320,7 @@ purge_old(struct dentry *parent, struct dentry *child)
	if (nfs4_has_reclaimed_state(child->d_name.name, false))
		return 0;

	status = nfsd4_clear_clid_dir(parent, child);
	status = vfs_rmdir(parent->d_inode, child);
	if (status)
		printk("failed to remove client recovery directory %s\n",
				child->d_name.name);
+19 −6
Original line number Diff line number Diff line
@@ -1890,7 +1890,7 @@ static int nfsd_buffered_filldir(void *__buf, const char *name, int namlen,
	return 0;
}

static int nfsd_buffered_readdir(struct file *file, filldir_t func,
static __be32 nfsd_buffered_readdir(struct file *file, filldir_t func,
				    struct readdir_cd *cdp, loff_t *offsetp)
{
	struct readdir_data buf;
@@ -1901,11 +1901,12 @@ static int nfsd_buffered_readdir(struct file *file, filldir_t func,

	buf.dirent = (void *)__get_free_page(GFP_KERNEL);
	if (!buf.dirent)
		return -ENOMEM;
		return nfserrno(-ENOMEM);

	offset = *offsetp;

	while (1) {
		struct inode *dir_inode = file->f_path.dentry->d_inode;
		unsigned int reclen;

		cdp->err = nfserr_eof; /* will be cleared on successful read */
@@ -1924,26 +1925,38 @@ static int nfsd_buffered_readdir(struct file *file, filldir_t func,
		if (!size)
			break;

		/*
		 * Various filldir functions may end up calling back into
		 * lookup_one_len() and the file system's ->lookup() method.
		 * These expect i_mutex to be held, as it would within readdir.
		 */
		host_err = mutex_lock_killable(&dir_inode->i_mutex);
		if (host_err)
			break;

		de = (struct buffered_dirent *)buf.dirent;
		while (size > 0) {
			offset = de->offset;

			if (func(cdp, de->name, de->namlen, de->offset,
				 de->ino, de->d_type))
				goto done;
				break;

			if (cdp->err != nfs_ok)
				goto done;
				break;

			reclen = ALIGN(sizeof(*de) + de->namlen,
				       sizeof(u64));
			size -= reclen;
			de = (struct buffered_dirent *)((char *)de + reclen);
		}
		mutex_unlock(&dir_inode->i_mutex);
		if (size > 0) /* We bailed out early */
			break;

		offset = vfs_llseek(file, 0, SEEK_CUR);
	}

 done:
	free_page((unsigned long)(buf.dirent));

	if (host_err)