diff options
Diffstat (limited to 'fs/overlayfs')
| -rw-r--r-- | fs/overlayfs/copy_up.c | 6 | ||||
| -rw-r--r-- | fs/overlayfs/dir.c | 20 | ||||
| -rw-r--r-- | fs/overlayfs/inode.c | 20 | ||||
| -rw-r--r-- | fs/overlayfs/namei.c | 33 | ||||
| -rw-r--r-- | fs/overlayfs/overlayfs.h | 4 | ||||
| -rw-r--r-- | fs/overlayfs/ovl_entry.h | 3 | ||||
| -rw-r--r-- | fs/overlayfs/readdir.c | 17 | ||||
| -rw-r--r-- | fs/overlayfs/super.c | 30 | ||||
| -rw-r--r-- | fs/overlayfs/util.c | 24 | 
9 files changed, 95 insertions, 62 deletions
| diff --git a/fs/overlayfs/copy_up.c b/fs/overlayfs/copy_up.c index aad97b30d5e6..c441f9387a1b 100644 --- a/fs/overlayfs/copy_up.c +++ b/fs/overlayfs/copy_up.c @@ -561,10 +561,8 @@ static int ovl_do_copy_up(struct ovl_copy_up_ctx *c)  		c->tmpfile = true;  		err = ovl_copy_up_locked(c);  	} else { -		err = -EIO; -		if (lock_rename(c->workdir, c->destdir) != NULL) { -			pr_err("overlayfs: failed to lock workdir+upperdir\n"); -		} else { +		err = ovl_lock_rename_workdir(c->workdir, c->destdir); +		if (!err) {  			err = ovl_copy_up_locked(c);  			unlock_rename(c->workdir, c->destdir);  		} diff --git a/fs/overlayfs/dir.c b/fs/overlayfs/dir.c index 3309b1912241..cc961a3bd3bd 100644 --- a/fs/overlayfs/dir.c +++ b/fs/overlayfs/dir.c @@ -216,26 +216,6 @@ out_unlock:  	return err;  } -static int ovl_lock_rename_workdir(struct dentry *workdir, -				   struct dentry *upperdir) -{ -	/* Workdir should not be the same as upperdir */ -	if (workdir == upperdir) -		goto err; - -	/* Workdir should not be subdir of upperdir and vice versa */ -	if (lock_rename(workdir, upperdir) != NULL) -		goto err_unlock; - -	return 0; - -err_unlock: -	unlock_rename(workdir, upperdir); -err: -	pr_err("overlayfs: failed to lock workdir+upperdir\n"); -	return -EIO; -} -  static struct dentry *ovl_clear_empty(struct dentry *dentry,  				      struct list_head *list)  { diff --git a/fs/overlayfs/inode.c b/fs/overlayfs/inode.c index a619addecafc..321511ed8c42 100644 --- a/fs/overlayfs/inode.c +++ b/fs/overlayfs/inode.c @@ -598,18 +598,30 @@ static bool ovl_verify_inode(struct inode *inode, struct dentry *lowerdentry,  	return true;  } -struct inode *ovl_get_inode(struct dentry *dentry, struct dentry *upperdentry) +struct inode *ovl_get_inode(struct dentry *dentry, struct dentry *upperdentry, +			    struct dentry *index)  {  	struct dentry *lowerdentry = ovl_dentry_lower(dentry);  	struct inode *realinode = upperdentry ? d_inode(upperdentry) : NULL;  	struct inode *inode; +	/* Already indexed or could be indexed on copy up? */ +	bool indexed = (index || (ovl_indexdir(dentry->d_sb) && !upperdentry)); + +	if (WARN_ON(upperdentry && indexed && !lowerdentry)) +		return ERR_PTR(-EIO);  	if (!realinode)  		realinode = d_inode(lowerdentry); -	if (!S_ISDIR(realinode->i_mode) && -	    (upperdentry || (lowerdentry && ovl_indexdir(dentry->d_sb)))) { -		struct inode *key = d_inode(lowerdentry ?: upperdentry); +	/* +	 * Copy up origin (lower) may exist for non-indexed upper, but we must +	 * not use lower as hash key in that case. +	 * Hash inodes that are or could be indexed by origin inode and +	 * non-indexed upper inodes that could be hard linked by upper inode. +	 */ +	if (!S_ISDIR(realinode->i_mode) && (upperdentry || indexed)) { +		struct inode *key = d_inode(indexed ? lowerdentry : +						      upperdentry);  		unsigned int nlink;  		inode = iget5_locked(dentry->d_sb, (unsigned long) key, diff --git a/fs/overlayfs/namei.c b/fs/overlayfs/namei.c index c3addd1114f1..a12dc10bf726 100644 --- a/fs/overlayfs/namei.c +++ b/fs/overlayfs/namei.c @@ -405,14 +405,13 @@ int ovl_verify_index(struct dentry *index, struct path *lowerstack,  	 * be treated as stale (i.e. after unlink of the overlay inode).  	 * We don't know the verification rules for directory and whiteout  	 * index entries, because they have not been implemented yet, so return -	 * EROFS if those entries are found to avoid corrupting an index that -	 * was created by a newer kernel. +	 * EINVAL if those entries are found to abort the mount to avoid +	 * corrupting an index that was created by a newer kernel.  	 */ -	err = -EROFS; +	err = -EINVAL;  	if (d_is_dir(index) || ovl_is_whiteout(index))  		goto fail; -	err = -EINVAL;  	if (index->d_name.len < sizeof(struct ovl_fh)*2)  		goto fail; @@ -506,6 +505,11 @@ static struct dentry *ovl_lookup_index(struct dentry *dentry,  	index = lookup_one_len_unlocked(name.name, ofs->indexdir, name.len);  	if (IS_ERR(index)) { +		err = PTR_ERR(index); +		if (err == -ENOENT) { +			index = NULL; +			goto out; +		}  		pr_warn_ratelimited("overlayfs: failed inode index lookup (ino=%lu, key=%*s, err=%i);\n"  				    "overlayfs: mount with '-o index=off' to disable inodes index.\n",  				    d_inode(origin)->i_ino, name.len, name.name, @@ -515,18 +519,9 @@ static struct dentry *ovl_lookup_index(struct dentry *dentry,  	inode = d_inode(index);  	if (d_is_negative(index)) { -		if (upper && d_inode(origin)->i_nlink > 1) { -			pr_warn_ratelimited("overlayfs: hard link with origin but no index (ino=%lu).\n", -					    d_inode(origin)->i_ino); -			goto fail; -		} - -		dput(index); -		index = NULL; +		goto out_dput;  	} else if (upper && d_inode(upper) != inode) { -		pr_warn_ratelimited("overlayfs: wrong index found (index=%pd2, ino=%lu, upper ino=%lu).\n", -				    index, inode->i_ino, d_inode(upper)->i_ino); -		goto fail; +		goto out_dput;  	} else if (ovl_dentry_weird(index) || ovl_is_whiteout(index) ||  		   ((inode->i_mode ^ d_inode(origin)->i_mode) & S_IFMT)) {  		/* @@ -546,6 +541,11 @@ out:  	kfree(name.name);  	return index; +out_dput: +	dput(index); +	index = NULL; +	goto out; +  fail:  	dput(index);  	index = ERR_PTR(-EIO); @@ -634,6 +634,7 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry,  		}  		if (d.redirect) { +			err = -ENOMEM;  			upperredirect = kstrdup(d.redirect, GFP_KERNEL);  			if (!upperredirect)  				goto out_put_upper; @@ -708,7 +709,7 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry,  		upperdentry = dget(index);  	if (upperdentry || ctr) { -		inode = ovl_get_inode(dentry, upperdentry); +		inode = ovl_get_inode(dentry, upperdentry, index);  		err = PTR_ERR(inode);  		if (IS_ERR(inode))  			goto out_free_oe; diff --git a/fs/overlayfs/overlayfs.h b/fs/overlayfs/overlayfs.h index d4e8c1a08fb0..d9a0edd4e57e 100644 --- a/fs/overlayfs/overlayfs.h +++ b/fs/overlayfs/overlayfs.h @@ -235,6 +235,7 @@ bool ovl_inuse_trylock(struct dentry *dentry);  void ovl_inuse_unlock(struct dentry *dentry);  int ovl_nlink_start(struct dentry *dentry, bool *locked);  void ovl_nlink_end(struct dentry *dentry, bool locked); +int ovl_lock_rename_workdir(struct dentry *workdir, struct dentry *upperdir);  static inline bool ovl_is_impuredir(struct dentry *dentry)  { @@ -285,7 +286,8 @@ int ovl_update_time(struct inode *inode, struct timespec *ts, int flags);  bool ovl_is_private_xattr(const char *name);  struct inode *ovl_new_inode(struct super_block *sb, umode_t mode, dev_t rdev); -struct inode *ovl_get_inode(struct dentry *dentry, struct dentry *upperdentry); +struct inode *ovl_get_inode(struct dentry *dentry, struct dentry *upperdentry, +			    struct dentry *index);  static inline void ovl_copyattr(struct inode *from, struct inode *to)  {  	to->i_uid = from->i_uid; diff --git a/fs/overlayfs/ovl_entry.h b/fs/overlayfs/ovl_entry.h index 878a750986dd..25d9b5adcd42 100644 --- a/fs/overlayfs/ovl_entry.h +++ b/fs/overlayfs/ovl_entry.h @@ -37,6 +37,9 @@ struct ovl_fs {  	bool noxattr;  	/* sb common to all layers */  	struct super_block *same_sb; +	/* Did we take the inuse lock? */ +	bool upperdir_locked; +	bool workdir_locked;  };  /* private information held for every overlayfs dentry */ diff --git a/fs/overlayfs/readdir.c b/fs/overlayfs/readdir.c index 62e9b22a2077..698b74dd750e 100644 --- a/fs/overlayfs/readdir.c +++ b/fs/overlayfs/readdir.c @@ -988,6 +988,7 @@ int ovl_indexdir_cleanup(struct dentry *dentry, struct vfsmount *mnt,  			 struct path *lowerstack, unsigned int numlower)  {  	int err; +	struct dentry *index = NULL;  	struct inode *dir = dentry->d_inode;  	struct path path = { .mnt = mnt, .dentry = dentry };  	LIST_HEAD(list); @@ -1007,8 +1008,6 @@ int ovl_indexdir_cleanup(struct dentry *dentry, struct vfsmount *mnt,  	inode_lock_nested(dir, I_MUTEX_PARENT);  	list_for_each_entry(p, &list, l_node) { -		struct dentry *index; -  		if (p->name[0] == '.') {  			if (p->len == 1)  				continue; @@ -1018,18 +1017,20 @@ int ovl_indexdir_cleanup(struct dentry *dentry, struct vfsmount *mnt,  		index = lookup_one_len(p->name, dentry, p->len);  		if (IS_ERR(index)) {  			err = PTR_ERR(index); +			index = NULL;  			break;  		}  		err = ovl_verify_index(index, lowerstack, numlower); -		if (err) { -			if (err == -EROFS) -				break; +		/* Cleanup stale and orphan index entries */ +		if (err && (err == -ESTALE || err == -ENOENT))  			err = ovl_cleanup(dir, index); -			if (err) -				break; -		} +		if (err) +			break; +  		dput(index); +		index = NULL;  	} +	dput(index);  	inode_unlock(dir);  out:  	ovl_cache_free(&list); diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c index fd5ea4facc62..f5738e96a052 100644 --- a/fs/overlayfs/super.c +++ b/fs/overlayfs/super.c @@ -174,6 +174,9 @@ static struct inode *ovl_alloc_inode(struct super_block *sb)  {  	struct ovl_inode *oi = kmem_cache_alloc(ovl_inode_cachep, GFP_KERNEL); +	if (!oi) +		return NULL; +  	oi->cache = NULL;  	oi->redirect = NULL;  	oi->version = 0; @@ -211,9 +214,10 @@ static void ovl_put_super(struct super_block *sb)  	dput(ufs->indexdir);  	dput(ufs->workdir); -	ovl_inuse_unlock(ufs->workbasedir); +	if (ufs->workdir_locked) +		ovl_inuse_unlock(ufs->workbasedir);  	dput(ufs->workbasedir); -	if (ufs->upper_mnt) +	if (ufs->upper_mnt && ufs->upperdir_locked)  		ovl_inuse_unlock(ufs->upper_mnt->mnt_root);  	mntput(ufs->upper_mnt);  	for (i = 0; i < ufs->numlower; i++) @@ -881,9 +885,13 @@ static int ovl_fill_super(struct super_block *sb, void *data, int silent)  			goto out_put_upperpath;  		err = -EBUSY; -		if (!ovl_inuse_trylock(upperpath.dentry)) { -			pr_err("overlayfs: upperdir is in-use by another mount\n"); +		if (ovl_inuse_trylock(upperpath.dentry)) { +			ufs->upperdir_locked = true; +		} else if (ufs->config.index) { +			pr_err("overlayfs: upperdir is in-use by another mount, mount with '-o index=off' to override exclusive upperdir protection.\n");  			goto out_put_upperpath; +		} else { +			pr_warn("overlayfs: upperdir is in-use by another mount, accessing files from both mounts will result in undefined behavior.\n");  		}  		err = ovl_mount_dir(ufs->config.workdir, &workpath); @@ -901,9 +909,13 @@ static int ovl_fill_super(struct super_block *sb, void *data, int silent)  		}  		err = -EBUSY; -		if (!ovl_inuse_trylock(workpath.dentry)) { -			pr_err("overlayfs: workdir is in-use by another mount\n"); +		if (ovl_inuse_trylock(workpath.dentry)) { +			ufs->workdir_locked = true; +		} else if (ufs->config.index) { +			pr_err("overlayfs: workdir is in-use by another mount, mount with '-o index=off' to override exclusive workdir protection.\n");  			goto out_put_workpath; +		} else { +			pr_warn("overlayfs: workdir is in-use by another mount, accessing files from both mounts will result in undefined behavior.\n");  		}  		ufs->workbasedir = workpath.dentry; @@ -1156,11 +1168,13 @@ out_put_lowerpath:  out_free_lowertmp:  	kfree(lowertmp);  out_unlock_workdentry: -	ovl_inuse_unlock(workpath.dentry); +	if (ufs->workdir_locked) +		ovl_inuse_unlock(workpath.dentry);  out_put_workpath:  	path_put(&workpath);  out_unlock_upperdentry: -	ovl_inuse_unlock(upperpath.dentry); +	if (ufs->upperdir_locked) +		ovl_inuse_unlock(upperpath.dentry);  out_put_upperpath:  	path_put(&upperpath);  out_free_config: diff --git a/fs/overlayfs/util.c b/fs/overlayfs/util.c index 117794582f9f..b9b239fa5cfd 100644 --- a/fs/overlayfs/util.c +++ b/fs/overlayfs/util.c @@ -430,7 +430,7 @@ void ovl_inuse_unlock(struct dentry *dentry)  	}  } -/* Called must hold OVL_I(inode)->oi_lock */ +/* Caller must hold OVL_I(inode)->lock */  static void ovl_cleanup_index(struct dentry *dentry)  {  	struct inode *dir = ovl_indexdir(dentry->d_sb)->d_inode; @@ -469,6 +469,9 @@ static void ovl_cleanup_index(struct dentry *dentry)  	err = PTR_ERR(index);  	if (!IS_ERR(index))  		err = ovl_cleanup(dir, index); +	else +		index = NULL; +  	inode_unlock(dir);  	if (err)  		goto fail; @@ -557,3 +560,22 @@ void ovl_nlink_end(struct dentry *dentry, bool locked)  		mutex_unlock(&OVL_I(d_inode(dentry))->lock);  	}  } + +int ovl_lock_rename_workdir(struct dentry *workdir, struct dentry *upperdir) +{ +	/* Workdir should not be the same as upperdir */ +	if (workdir == upperdir) +		goto err; + +	/* Workdir should not be subdir of upperdir and vice versa */ +	if (lock_rename(workdir, upperdir) != NULL) +		goto err_unlock; + +	return 0; + +err_unlock: +	unlock_rename(workdir, upperdir); +err: +	pr_err("overlayfs: failed to lock workdir+upperdir\n"); +	return -EIO; +} |