Commit ffeeaada authored by Al Viro's avatar Al Viro
Browse files

nfs: fix ->d_revalidate() UAF on ->d_name accesses



Pass the stable name all the way down to ->rpc_ops->lookup() instances.

Note that passing &dentry->d_name is safe in e.g. nfs_lookup() - it *is*
stable there, as it is in ->create() et.al.

dget_parent() in nfs_instantiate() should be redundant - it'd better be
stable there; if it's not, we have more trouble, since ->d_name would
also be unsafe in such case.

nfs_submount() and nfs4_submount() may or may not require fixes - if
they ever get moved on server with fhandle preserved, we are in trouble
there...

UAF window is fairly narrow here and exfiltration requires the ability
to watch the traffic.

Reviewed-by: default avatarJeff Layton <jlayton@kernel.org>
Signed-off-by: default avatarAl Viro <viro@zeniv.linux.org.uk>
parent 39f644a2
Loading
Loading
Loading
Loading
+8 −6
Original line number Diff line number Diff line
@@ -1672,7 +1672,7 @@ nfs_lookup_revalidate_delegated(struct inode *dir, struct dentry *dentry,
	return nfs_lookup_revalidate_done(dir, dentry, inode, 1);
}

static int nfs_lookup_revalidate_dentry(struct inode *dir,
static int nfs_lookup_revalidate_dentry(struct inode *dir, const struct qstr *name,
					struct dentry *dentry,
					struct inode *inode, unsigned int flags)
{
@@ -1690,7 +1690,7 @@ static int nfs_lookup_revalidate_dentry(struct inode *dir,
		goto out;

	dir_verifier = nfs_save_change_attribute(dir);
	ret = NFS_PROTO(dir)->lookup(dir, dentry, fhandle, fattr);
	ret = NFS_PROTO(dir)->lookup(dir, dentry, name, fhandle, fattr);
	if (ret < 0)
		goto out;

@@ -1775,7 +1775,7 @@ nfs_do_lookup_revalidate(struct inode *dir, const struct qstr *name,
	if (NFS_STALE(inode))
		goto out_bad;

	return nfs_lookup_revalidate_dentry(dir, dentry, inode, flags);
	return nfs_lookup_revalidate_dentry(dir, name, dentry, inode, flags);
out_valid:
	return nfs_lookup_revalidate_done(dir, dentry, inode, 1);
out_bad:
@@ -1970,7 +1970,8 @@ struct dentry *nfs_lookup(struct inode *dir, struct dentry * dentry, unsigned in

	dir_verifier = nfs_save_change_attribute(dir);
	trace_nfs_lookup_enter(dir, dentry, flags);
	error = NFS_PROTO(dir)->lookup(dir, dentry, fhandle, fattr);
	error = NFS_PROTO(dir)->lookup(dir, dentry, &dentry->d_name,
				       fhandle, fattr);
	if (error == -ENOENT) {
		if (nfs_server_capable(dir, NFS_CAP_CASE_INSENSITIVE))
			dir_verifier = inode_peek_iversion_raw(dir);
@@ -2246,7 +2247,7 @@ nfs4_lookup_revalidate(struct inode *dir, const struct qstr *name,
reval_dentry:
	if (flags & LOOKUP_RCU)
		return -ECHILD;
	return nfs_lookup_revalidate_dentry(dir, dentry, inode, flags);
	return nfs_lookup_revalidate_dentry(dir, name, dentry, inode, flags);

full_reval:
	return nfs_do_lookup_revalidate(dir, name, dentry, flags);
@@ -2305,7 +2306,8 @@ nfs_add_or_obtain(struct dentry *dentry, struct nfs_fh *fhandle,
	d_drop(dentry);

	if (fhandle->size == 0) {
		error = NFS_PROTO(dir)->lookup(dir, dentry, fhandle, fattr);
		error = NFS_PROTO(dir)->lookup(dir, dentry, &dentry->d_name,
					       fhandle, fattr);
		if (error)
			goto out_error;
	}
+1 −1
Original line number Diff line number Diff line
@@ -308,7 +308,7 @@ int nfs_submount(struct fs_context *fc, struct nfs_server *server)
	int err;

	/* Look it up again to get its attributes */
	err = server->nfs_client->rpc_ops->lookup(d_inode(parent), dentry,
	err = server->nfs_client->rpc_ops->lookup(d_inode(parent), dentry, &dentry->d_name,
						  ctx->mntfh, ctx->clone_data.fattr);
	dput(parent);
	if (err != 0)
+2 −3
Original line number Diff line number Diff line
@@ -192,7 +192,7 @@ __nfs3_proc_lookup(struct inode *dir, const char *name, size_t len,
}

static int
nfs3_proc_lookup(struct inode *dir, struct dentry *dentry,
nfs3_proc_lookup(struct inode *dir, struct dentry *dentry, const struct qstr *name,
		 struct nfs_fh *fhandle, struct nfs_fattr *fattr)
{
	unsigned short task_flags = 0;
@@ -202,8 +202,7 @@ nfs3_proc_lookup(struct inode *dir, struct dentry *dentry,
		task_flags |= RPC_TASK_TIMEOUT;

	dprintk("NFS call  lookup %pd2\n", dentry);
	return __nfs3_proc_lookup(dir, dentry->d_name.name,
				  dentry->d_name.len, fhandle, fattr,
	return __nfs3_proc_lookup(dir, name->name, name->len, fhandle, fattr,
				  task_flags);
}

+10 −10
Original line number Diff line number Diff line
@@ -4536,15 +4536,15 @@ nfs4_proc_setattr(struct dentry *dentry, struct nfs_fattr *fattr,
}

static int _nfs4_proc_lookup(struct rpc_clnt *clnt, struct inode *dir,
		struct dentry *dentry, struct nfs_fh *fhandle,
		struct nfs_fattr *fattr)
		struct dentry *dentry, const struct qstr *name,
		struct nfs_fh *fhandle, struct nfs_fattr *fattr)
{
	struct nfs_server *server = NFS_SERVER(dir);
	int		       status;
	struct nfs4_lookup_arg args = {
		.bitmask = server->attr_bitmask,
		.dir_fh = NFS_FH(dir),
		.name = &dentry->d_name,
		.name = name,
	};
	struct nfs4_lookup_res res = {
		.server = server,
@@ -4586,17 +4586,16 @@ static void nfs_fixup_secinfo_attributes(struct nfs_fattr *fattr)
}

static int nfs4_proc_lookup_common(struct rpc_clnt **clnt, struct inode *dir,
				   struct dentry *dentry, struct nfs_fh *fhandle,
				   struct nfs_fattr *fattr)
				   struct dentry *dentry, const struct qstr *name,
				   struct nfs_fh *fhandle, struct nfs_fattr *fattr)
{
	struct nfs4_exception exception = {
		.interruptible = true,
	};
	struct rpc_clnt *client = *clnt;
	const struct qstr *name = &dentry->d_name;
	int err;
	do {
		err = _nfs4_proc_lookup(client, dir, dentry, fhandle, fattr);
		err = _nfs4_proc_lookup(client, dir, dentry, name, fhandle, fattr);
		trace_nfs4_lookup(dir, name, err);
		switch (err) {
		case -NFS4ERR_BADNAME:
@@ -4631,13 +4630,13 @@ static int nfs4_proc_lookup_common(struct rpc_clnt **clnt, struct inode *dir,
	return err;
}

static int nfs4_proc_lookup(struct inode *dir, struct dentry *dentry,
static int nfs4_proc_lookup(struct inode *dir, struct dentry *dentry, const struct qstr *name,
			    struct nfs_fh *fhandle, struct nfs_fattr *fattr)
{
	int status;
	struct rpc_clnt *client = NFS_CLIENT(dir);

	status = nfs4_proc_lookup_common(&client, dir, dentry, fhandle, fattr);
	status = nfs4_proc_lookup_common(&client, dir, dentry, name, fhandle, fattr);
	if (client != NFS_CLIENT(dir)) {
		rpc_shutdown_client(client);
		nfs_fixup_secinfo_attributes(fattr);
@@ -4652,7 +4651,8 @@ nfs4_proc_lookup_mountpoint(struct inode *dir, struct dentry *dentry,
	struct rpc_clnt *client = NFS_CLIENT(dir);
	int status;

	status = nfs4_proc_lookup_common(&client, dir, dentry, fhandle, fattr);
	status = nfs4_proc_lookup_common(&client, dir, dentry, &dentry->d_name,
					 fhandle, fattr);
	if (status < 0)
		return ERR_PTR(status);
	return (client == NFS_CLIENT(dir)) ? rpc_clone_client(client) : client;
+3 −3
Original line number Diff line number Diff line
@@ -153,13 +153,13 @@ nfs_proc_setattr(struct dentry *dentry, struct nfs_fattr *fattr,
}

static int
nfs_proc_lookup(struct inode *dir, struct dentry *dentry,
nfs_proc_lookup(struct inode *dir, struct dentry *dentry, const struct qstr *name,
		struct nfs_fh *fhandle, struct nfs_fattr *fattr)
{
	struct nfs_diropargs	arg = {
		.fh		= NFS_FH(dir),
		.name		= dentry->d_name.name,
		.len		= dentry->d_name.len
		.name		= name->name,
		.len		= name->len
	};
	struct nfs_diropok	res = {
		.fh		= fhandle,
Loading