mirror of
				https://github.com/torvalds/linux.git
				synced 2025-10-31 16:48:26 +02:00 
			
		
		
		
	vfs: check unlinked ancestors before mount
We check submounts before doing d_drop() on a non-empty directory dentry in NFS (have_submounts()), but we do not exclude a racing mount. Nor do we prevent mounts to be added to the disconnected subtree using relative paths after the d_drop(). This patch fixes these issues by checking for unlinked (unhashed, non-root) ancestors before proceeding with the mount. This is done with rename seqlock taken for write and with ->d_lock grabbed on each ancestor in turn, including our dentry itself. This ensures that the only one of check_submounts_and_drop() or has_unlinked_ancestor() can succeed. Signed-off-by: Miklos Szeredi <miklos@szeredi.hu> Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
This commit is contained in:
		
							parent
							
								
									848ac114e8
								
							
						
					
					
						commit
						eed8100766
					
				
					 3 changed files with 39 additions and 6 deletions
				
			
		
							
								
								
									
										33
									
								
								fs/dcache.c
									
									
									
									
									
								
							
							
						
						
									
										33
									
								
								fs/dcache.c
									
									
									
									
									
								
							|  | @ -1183,6 +1183,39 @@ int have_submounts(struct dentry *parent) | |||
| } | ||||
| EXPORT_SYMBOL(have_submounts); | ||||
| 
 | ||||
| /*
 | ||||
|  * Called by mount code to set a mountpoint and check if the mountpoint is | ||||
|  * reachable (e.g. NFS can unhash a directory dentry and then the complete | ||||
|  * subtree can become unreachable). | ||||
|  * | ||||
|  * Only one of check_submounts_and_drop() and d_set_mounted() must succeed.  For | ||||
|  * this reason take rename_lock and d_lock on dentry and ancestors. | ||||
|  */ | ||||
| int d_set_mounted(struct dentry *dentry) | ||||
| { | ||||
| 	struct dentry *p; | ||||
| 	int ret = -ENOENT; | ||||
| 	write_seqlock(&rename_lock); | ||||
| 	for (p = dentry->d_parent; !IS_ROOT(p); p = p->d_parent) { | ||||
| 		/* Need exclusion wrt. check_submounts_and_drop() */ | ||||
| 		spin_lock(&p->d_lock); | ||||
| 		if (unlikely(d_unhashed(p))) { | ||||
| 			spin_unlock(&p->d_lock); | ||||
| 			goto out; | ||||
| 		} | ||||
| 		spin_unlock(&p->d_lock); | ||||
| 	} | ||||
| 	spin_lock(&dentry->d_lock); | ||||
| 	if (!d_unlinked(dentry)) { | ||||
| 		dentry->d_flags |= DCACHE_MOUNTED; | ||||
| 		ret = 0; | ||||
| 	} | ||||
|  	spin_unlock(&dentry->d_lock); | ||||
| out: | ||||
| 	write_sequnlock(&rename_lock); | ||||
| 	return ret; | ||||
| } | ||||
| 
 | ||||
| /*
 | ||||
|  * Search the dentry child list of the specified parent, | ||||
|  * and move any unused dentries to the end of the unused | ||||
|  |  | |||
|  | @ -126,6 +126,7 @@ extern int invalidate_inodes(struct super_block *, bool); | |||
|  * dcache.c | ||||
|  */ | ||||
| extern struct dentry *__d_alloc(struct super_block *, const struct qstr *); | ||||
| extern int d_set_mounted(struct dentry *dentry); | ||||
| 
 | ||||
| /*
 | ||||
|  * read_write.c | ||||
|  |  | |||
|  | @ -611,6 +611,7 @@ static struct mountpoint *new_mountpoint(struct dentry *dentry) | |||
| { | ||||
| 	struct list_head *chain = mountpoint_hashtable + hash(NULL, dentry); | ||||
| 	struct mountpoint *mp; | ||||
| 	int ret; | ||||
| 
 | ||||
| 	list_for_each_entry(mp, chain, m_hash) { | ||||
| 		if (mp->m_dentry == dentry) { | ||||
|  | @ -626,14 +627,12 @@ static struct mountpoint *new_mountpoint(struct dentry *dentry) | |||
| 	if (!mp) | ||||
| 		return ERR_PTR(-ENOMEM); | ||||
| 
 | ||||
| 	spin_lock(&dentry->d_lock); | ||||
| 	if (d_unlinked(dentry)) { | ||||
| 		spin_unlock(&dentry->d_lock); | ||||
| 	ret = d_set_mounted(dentry); | ||||
| 	if (ret) { | ||||
| 		kfree(mp); | ||||
| 		return ERR_PTR(-ENOENT); | ||||
| 		return ERR_PTR(ret); | ||||
| 	} | ||||
| 	dentry->d_flags |= DCACHE_MOUNTED; | ||||
| 	spin_unlock(&dentry->d_lock); | ||||
| 
 | ||||
| 	mp->m_dentry = dentry; | ||||
| 	mp->m_count = 1; | ||||
| 	list_add(&mp->m_hash, chain); | ||||
|  |  | |||
		Loading…
	
		Reference in a new issue
	
	 Miklos Szeredi
						Miklos Szeredi