Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

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: Jeff Layton <jlayton@kernel.org>
Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>

+25 -24
+8 -6
fs/nfs/dir.c
··· 1672 1672 return nfs_lookup_revalidate_done(dir, dentry, inode, 1); 1673 1673 } 1674 1674 1675 - static int nfs_lookup_revalidate_dentry(struct inode *dir, 1675 + static int nfs_lookup_revalidate_dentry(struct inode *dir, const struct qstr *name, 1676 1676 struct dentry *dentry, 1677 1677 struct inode *inode, unsigned int flags) 1678 1678 { ··· 1690 1690 goto out; 1691 1691 1692 1692 dir_verifier = nfs_save_change_attribute(dir); 1693 - ret = NFS_PROTO(dir)->lookup(dir, dentry, fhandle, fattr); 1693 + ret = NFS_PROTO(dir)->lookup(dir, dentry, name, fhandle, fattr); 1694 1694 if (ret < 0) 1695 1695 goto out; 1696 1696 ··· 1775 1775 if (NFS_STALE(inode)) 1776 1776 goto out_bad; 1777 1777 1778 - return nfs_lookup_revalidate_dentry(dir, dentry, inode, flags); 1778 + return nfs_lookup_revalidate_dentry(dir, name, dentry, inode, flags); 1779 1779 out_valid: 1780 1780 return nfs_lookup_revalidate_done(dir, dentry, inode, 1); 1781 1781 out_bad: ··· 1970 1970 1971 1971 dir_verifier = nfs_save_change_attribute(dir); 1972 1972 trace_nfs_lookup_enter(dir, dentry, flags); 1973 - error = NFS_PROTO(dir)->lookup(dir, dentry, fhandle, fattr); 1973 + error = NFS_PROTO(dir)->lookup(dir, dentry, &dentry->d_name, 1974 + fhandle, fattr); 1974 1975 if (error == -ENOENT) { 1975 1976 if (nfs_server_capable(dir, NFS_CAP_CASE_INSENSITIVE)) 1976 1977 dir_verifier = inode_peek_iversion_raw(dir); ··· 2247 2246 reval_dentry: 2248 2247 if (flags & LOOKUP_RCU) 2249 2248 return -ECHILD; 2250 - return nfs_lookup_revalidate_dentry(dir, dentry, inode, flags); 2249 + return nfs_lookup_revalidate_dentry(dir, name, dentry, inode, flags); 2251 2250 2252 2251 full_reval: 2253 2252 return nfs_do_lookup_revalidate(dir, name, dentry, flags); ··· 2306 2305 d_drop(dentry); 2307 2306 2308 2307 if (fhandle->size == 0) { 2309 - error = NFS_PROTO(dir)->lookup(dir, dentry, fhandle, fattr); 2308 + error = NFS_PROTO(dir)->lookup(dir, dentry, &dentry->d_name, 2309 + fhandle, fattr); 2310 2310 if (error) 2311 2311 goto out_error; 2312 2312 }
+1 -1
fs/nfs/namespace.c
··· 308 308 int err; 309 309 310 310 /* Look it up again to get its attributes */ 311 - err = server->nfs_client->rpc_ops->lookup(d_inode(parent), dentry, 311 + err = server->nfs_client->rpc_ops->lookup(d_inode(parent), dentry, &dentry->d_name, 312 312 ctx->mntfh, ctx->clone_data.fattr); 313 313 dput(parent); 314 314 if (err != 0)
+2 -3
fs/nfs/nfs3proc.c
··· 192 192 } 193 193 194 194 static int 195 - nfs3_proc_lookup(struct inode *dir, struct dentry *dentry, 195 + nfs3_proc_lookup(struct inode *dir, struct dentry *dentry, const struct qstr *name, 196 196 struct nfs_fh *fhandle, struct nfs_fattr *fattr) 197 197 { 198 198 unsigned short task_flags = 0; ··· 202 202 task_flags |= RPC_TASK_TIMEOUT; 203 203 204 204 dprintk("NFS call lookup %pd2\n", dentry); 205 - return __nfs3_proc_lookup(dir, dentry->d_name.name, 206 - dentry->d_name.len, fhandle, fattr, 205 + return __nfs3_proc_lookup(dir, name->name, name->len, fhandle, fattr, 207 206 task_flags); 208 207 } 209 208
+10 -10
fs/nfs/nfs4proc.c
··· 4536 4536 } 4537 4537 4538 4538 static int _nfs4_proc_lookup(struct rpc_clnt *clnt, struct inode *dir, 4539 - struct dentry *dentry, struct nfs_fh *fhandle, 4540 - struct nfs_fattr *fattr) 4539 + struct dentry *dentry, const struct qstr *name, 4540 + struct nfs_fh *fhandle, struct nfs_fattr *fattr) 4541 4541 { 4542 4542 struct nfs_server *server = NFS_SERVER(dir); 4543 4543 int status; 4544 4544 struct nfs4_lookup_arg args = { 4545 4545 .bitmask = server->attr_bitmask, 4546 4546 .dir_fh = NFS_FH(dir), 4547 - .name = &dentry->d_name, 4547 + .name = name, 4548 4548 }; 4549 4549 struct nfs4_lookup_res res = { 4550 4550 .server = server, ··· 4586 4586 } 4587 4587 4588 4588 static int nfs4_proc_lookup_common(struct rpc_clnt **clnt, struct inode *dir, 4589 - struct dentry *dentry, struct nfs_fh *fhandle, 4590 - struct nfs_fattr *fattr) 4589 + struct dentry *dentry, const struct qstr *name, 4590 + struct nfs_fh *fhandle, struct nfs_fattr *fattr) 4591 4591 { 4592 4592 struct nfs4_exception exception = { 4593 4593 .interruptible = true, 4594 4594 }; 4595 4595 struct rpc_clnt *client = *clnt; 4596 - const struct qstr *name = &dentry->d_name; 4597 4596 int err; 4598 4597 do { 4599 - err = _nfs4_proc_lookup(client, dir, dentry, fhandle, fattr); 4598 + err = _nfs4_proc_lookup(client, dir, dentry, name, fhandle, fattr); 4600 4599 trace_nfs4_lookup(dir, name, err); 4601 4600 switch (err) { 4602 4601 case -NFS4ERR_BADNAME: ··· 4630 4631 return err; 4631 4632 } 4632 4633 4633 - static int nfs4_proc_lookup(struct inode *dir, struct dentry *dentry, 4634 + static int nfs4_proc_lookup(struct inode *dir, struct dentry *dentry, const struct qstr *name, 4634 4635 struct nfs_fh *fhandle, struct nfs_fattr *fattr) 4635 4636 { 4636 4637 int status; 4637 4638 struct rpc_clnt *client = NFS_CLIENT(dir); 4638 4639 4639 - status = nfs4_proc_lookup_common(&client, dir, dentry, fhandle, fattr); 4640 + status = nfs4_proc_lookup_common(&client, dir, dentry, name, fhandle, fattr); 4640 4641 if (client != NFS_CLIENT(dir)) { 4641 4642 rpc_shutdown_client(client); 4642 4643 nfs_fixup_secinfo_attributes(fattr); ··· 4651 4652 struct rpc_clnt *client = NFS_CLIENT(dir); 4652 4653 int status; 4653 4654 4654 - status = nfs4_proc_lookup_common(&client, dir, dentry, fhandle, fattr); 4655 + status = nfs4_proc_lookup_common(&client, dir, dentry, &dentry->d_name, 4656 + fhandle, fattr); 4655 4657 if (status < 0) 4656 4658 return ERR_PTR(status); 4657 4659 return (client == NFS_CLIENT(dir)) ? rpc_clone_client(client) : client;
+3 -3
fs/nfs/proc.c
··· 153 153 } 154 154 155 155 static int 156 - nfs_proc_lookup(struct inode *dir, struct dentry *dentry, 156 + nfs_proc_lookup(struct inode *dir, struct dentry *dentry, const struct qstr *name, 157 157 struct nfs_fh *fhandle, struct nfs_fattr *fattr) 158 158 { 159 159 struct nfs_diropargs arg = { 160 160 .fh = NFS_FH(dir), 161 - .name = dentry->d_name.name, 162 - .len = dentry->d_name.len 161 + .name = name->name, 162 + .len = name->len 163 163 }; 164 164 struct nfs_diropok res = { 165 165 .fh = fhandle,
+1 -1
include/linux/nfs_xdr.h
··· 1785 1785 struct nfs_fattr *, struct inode *); 1786 1786 int (*setattr) (struct dentry *, struct nfs_fattr *, 1787 1787 struct iattr *); 1788 - int (*lookup) (struct inode *, struct dentry *, 1788 + int (*lookup) (struct inode *, struct dentry *, const struct qstr *, 1789 1789 struct nfs_fh *, struct nfs_fattr *); 1790 1790 int (*lookupp) (struct inode *, struct nfs_fh *, 1791 1791 struct nfs_fattr *);