On Tue, Nov 16, 2010 at 05:23:19PM +1100, Nick Piggin wrote: > > fs: d_validate fixes > > d_validate has been broken for a long time. > > kmem_ptr_validate does not guarantee that a pointer can be dereferenced > if it can go away at any time. Even rcu_read_lock doesn't help, because > the pointer might be queued in RCU callbacks but not executed yet. > > So the parent cannot be checked, nor the name hashed. The dentry pointer > can not be touched until it can be verified under lock. Hashing simply > cannot be used. > > Instead, verify the parent/child relationship by traversing parent's > d_child list. It's slow, but only ncpfs and the destaged smbfs care > about it, at this point. That's what both callers already do when d_validate fails, so if we want to go down that route I'd suggest to just remove it, like in the patch below. Note that there's probably a lot more caching infrastrucuture in ncpfs/smbfs that becomes dead by this as well. Index: linux-2.6/drivers/staging/smbfs/cache.c =================================================================== --- linux-2.6.orig/drivers/staging/smbfs/cache.c 2010-11-16 10:49:55.915263307 +0100 +++ linux-2.6/drivers/staging/smbfs/cache.c 2010-11-16 11:12:17.091018582 +0100 @@ -73,33 +73,13 @@ smb_invalidate_dircache_entries(struct d spin_unlock(&dcache_lock); } -/* - * dget, but require that fpos and parent matches what the dentry contains. - * dentry is not known to be a valid pointer at entry. - */ struct dentry * -smb_dget_fpos(struct dentry *dentry, struct dentry *parent, unsigned long fpos) +smb_dget_fpos(struct dentry *parent, unsigned long fpos) { - struct dentry *dent = dentry; - struct list_head *next; + struct dentry *dent; - if (d_validate(dent, parent)) { - if (dent->d_name.len <= SMB_MAXNAMELEN && - (unsigned long)dent->d_fsdata == fpos) { - if (!dent->d_inode) { - dput(dent); - dent = NULL; - } - return dent; - } - dput(dent); - } - - /* If a pointer is invalid, we search the dentry. */ spin_lock(&dcache_lock); - next = parent->d_subdirs.next; - while (next != &parent->d_subdirs) { - dent = list_entry(next, struct dentry, d_u.d_child); + list_for_each_entry(dent, &parent->d_subdirs, d_u.d_child) { if ((unsigned long)dent->d_fsdata == fpos) { if (dent->d_inode) dget_locked(dent); @@ -107,7 +87,6 @@ smb_dget_fpos(struct dentry *dentry, str dent = NULL; goto out_unlock; } - next = next->next; } dent = NULL; out_unlock: Index: linux-2.6/drivers/staging/smbfs/dir.c =================================================================== --- linux-2.6.orig/drivers/staging/smbfs/dir.c 2010-11-16 10:52:14.577003425 +0100 +++ linux-2.6/drivers/staging/smbfs/dir.c 2010-11-16 11:11:29.420254992 +0100 @@ -166,8 +166,7 @@ smb_readdir(struct file *filp, void *dir struct dentry *dent; int res; - dent = smb_dget_fpos(ctl.cache->dentry[ctl.idx], - dentry, filp->f_pos); + dent = smb_dget_fpos(dentry, filp->f_pos); if (!dent) goto invalid_cache; Index: linux-2.6/drivers/staging/smbfs/proto.h =================================================================== --- linux-2.6.orig/drivers/staging/smbfs/proto.h 2010-11-16 10:52:00.746253110 +0100 +++ linux-2.6/drivers/staging/smbfs/proto.h 2010-11-16 10:52:06.439266241 +0100 @@ -43,7 +43,7 @@ extern void smb_renew_times(struct dentr /* cache.c */ extern void smb_invalid_dir_cache(struct inode *dir); extern void smb_invalidate_dircache_entries(struct dentry *parent); -extern struct dentry *smb_dget_fpos(struct dentry *dentry, struct dentry *parent, unsigned long fpos); +extern struct dentry *smb_dget_fpos(struct dentry *parent, unsigned long fpos); extern int smb_fill_cache(struct file *filp, void *dirent, filldir_t filldir, struct smb_cache_control *ctrl, struct qstr *qname, struct smb_fattr *entry); /* sock.c */ extern void smb_data_ready(struct sock *sk, int len); Index: linux-2.6/fs/ncpfs/dir.c =================================================================== --- linux-2.6.orig/fs/ncpfs/dir.c 2010-11-16 10:49:04.988254089 +0100 +++ linux-2.6/fs/ncpfs/dir.c 2010-11-16 11:03:55.184018020 +0100 @@ -367,28 +367,12 @@ finished: } static struct dentry * -ncp_dget_fpos(struct dentry *dentry, struct dentry *parent, unsigned long fpos) +ncp_dget_fpos(struct dentry *parent, unsigned long fpos) { - struct dentry *dent = dentry; - struct list_head *next; + struct dentry *dent; - if (d_validate(dent, parent)) { - if (dent->d_name.len <= NCP_MAXPATHLEN && - (unsigned long)dent->d_fsdata == fpos) { - if (!dent->d_inode) { - dput(dent); - dent = NULL; - } - return dent; - } - dput(dent); - } - - /* If a pointer is invalid, we search the dentry. */ spin_lock(&dcache_lock); - next = parent->d_subdirs.next; - while (next != &parent->d_subdirs) { - dent = list_entry(next, struct dentry, d_u.d_child); + list_for_each_entry(dent, &parent->d_subdirs, d_u.d_child) { if ((unsigned long)dent->d_fsdata == fpos) { if (dent->d_inode) dget_locked(dent); @@ -397,11 +381,9 @@ ncp_dget_fpos(struct dentry *dentry, str spin_unlock(&dcache_lock); goto out; } - next = next->next; } spin_unlock(&dcache_lock); return NULL; - out: return dent; } @@ -496,8 +478,7 @@ static int ncp_readdir(struct file *filp struct dentry *dent; int res; - dent = ncp_dget_fpos(ctl.cache->dentry[ctl.idx], - dentry, filp->f_pos); + dent = ncp_dget_fpos(dentry, filp->f_pos); if (!dent) goto invalid_cache; res = filldir(dirent, dent->d_name.name, Index: linux-2.6/fs/dcache.c =================================================================== --- linux-2.6.orig/fs/dcache.c 2010-11-16 10:57:20.960004264 +0100 +++ linux-2.6/fs/dcache.c 2010-11-16 10:57:25.790005239 +0100 @@ -1482,39 +1482,6 @@ out: return dentry; } -/** - * d_validate - verify dentry provided from insecure source - * @dentry: The dentry alleged to be valid child of @dparent - * @dparent: The parent dentry (known to be valid) - * - * An insecure source has sent us a dentry, here we verify it and dget() it. - * This is used by ncpfs in its readdir implementation. - * Zero is returned in the dentry is invalid. - */ -int d_validate(struct dentry *dentry, struct dentry *parent) -{ - struct hlist_head *head = d_hash(parent, dentry->d_name.hash); - struct hlist_node *node; - struct dentry *d; - - /* Check whether the ptr might be valid at all.. */ - if (!kmem_ptr_validate(dentry_cache, dentry)) - return 0; - if (dentry->d_parent != parent) - return 0; - - rcu_read_lock(); - hlist_for_each_entry_rcu(d, node, head, d_hash) { - if (d == dentry) { - dget(dentry); - return 1; - } - } - rcu_read_unlock(); - return 0; -} -EXPORT_SYMBOL(d_validate); - /* * When a file is deleted, we have two options: * - turn this dentry into a negative dentry Index: linux-2.6/include/linux/dcache.h =================================================================== --- linux-2.6.orig/include/linux/dcache.h 2010-11-16 10:57:14.736253110 +0100 +++ linux-2.6/include/linux/dcache.h 2010-11-16 10:57:18.474254784 +0100 @@ -305,9 +305,6 @@ extern struct dentry * d_lookup(struct d extern struct dentry * __d_lookup(struct dentry *, struct qstr *); extern struct dentry * d_hash_and_lookup(struct dentry *, struct qstr *); -/* validate "insecure" dentry pointer */ -extern int d_validate(struct dentry *, struct dentry *); - /* * helper function for dentry_operations.d_dname() members */ -- To unsubscribe from this list: send the line "unsubscribe linux-fsdevel" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html