| From 11f3710417d026ea2f4fcf362d866342c5274185 Mon Sep 17 00:00:00 2001 |
| From: Miklos Szeredi <mszeredi@redhat.com> |
| Date: Mon, 21 Mar 2016 17:31:44 +0100 |
| Subject: ovl: verify upper dentry before unlink and rename |
| |
| From: Miklos Szeredi <mszeredi@redhat.com> |
| |
| commit 11f3710417d026ea2f4fcf362d866342c5274185 upstream. |
| |
| Unlink and rename in overlayfs checked the upper dentry for staleness by |
| verifying upper->d_parent against upperdir. However the dentry can go |
| stale also by being unhashed, for example. |
| |
| Expand the verification to actually look up the name again (under parent |
| lock) and check if it matches the upper dentry. This matches what the VFS |
| does before passing the dentry to filesytem's unlink/rename methods, which |
| excludes any inconsistency caused by overlayfs. |
| |
| Signed-off-by: Miklos Szeredi <mszeredi@redhat.com> |
| Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> |
| |
| --- |
| fs/overlayfs/dir.c | 59 ++++++++++++++++++++++++++++++++++------------------- |
| 1 file changed, 38 insertions(+), 21 deletions(-) |
| |
| --- a/fs/overlayfs/dir.c |
| +++ b/fs/overlayfs/dir.c |
| @@ -590,21 +590,25 @@ static int ovl_remove_upper(struct dentr |
| { |
| struct dentry *upperdir = ovl_dentry_upper(dentry->d_parent); |
| struct inode *dir = upperdir->d_inode; |
| - struct dentry *upper = ovl_dentry_upper(dentry); |
| + struct dentry *upper; |
| int err; |
| |
| mutex_lock_nested(&dir->i_mutex, I_MUTEX_PARENT); |
| + upper = lookup_one_len(dentry->d_name.name, upperdir, |
| + dentry->d_name.len); |
| + err = PTR_ERR(upper); |
| + if (IS_ERR(upper)) |
| + goto out_unlock; |
| + |
| err = -ESTALE; |
| - if (upper->d_parent == upperdir) { |
| - /* Don't let d_delete() think it can reset d_inode */ |
| - dget(upper); |
| + if (upper == ovl_dentry_upper(dentry)) { |
| if (is_dir) |
| err = vfs_rmdir(dir, upper); |
| else |
| err = vfs_unlink(dir, upper, NULL); |
| - dput(upper); |
| ovl_dentry_version_inc(dentry->d_parent); |
| } |
| + dput(upper); |
| |
| /* |
| * Keeping this dentry hashed would mean having to release |
| @@ -614,6 +618,7 @@ static int ovl_remove_upper(struct dentr |
| */ |
| if (!err) |
| d_drop(dentry); |
| +out_unlock: |
| mutex_unlock(&dir->i_mutex); |
| |
| return err; |
| @@ -834,29 +839,39 @@ static int ovl_rename2(struct inode *old |
| |
| trap = lock_rename(new_upperdir, old_upperdir); |
| |
| - olddentry = ovl_dentry_upper(old); |
| - newdentry = ovl_dentry_upper(new); |
| - if (newdentry) { |
| + |
| + olddentry = lookup_one_len(old->d_name.name, old_upperdir, |
| + old->d_name.len); |
| + err = PTR_ERR(olddentry); |
| + if (IS_ERR(olddentry)) |
| + goto out_unlock; |
| + |
| + err = -ESTALE; |
| + if (olddentry != ovl_dentry_upper(old)) |
| + goto out_dput_old; |
| + |
| + newdentry = lookup_one_len(new->d_name.name, new_upperdir, |
| + new->d_name.len); |
| + err = PTR_ERR(newdentry); |
| + if (IS_ERR(newdentry)) |
| + goto out_dput_old; |
| + |
| + err = -ESTALE; |
| + if (ovl_dentry_upper(new)) { |
| if (opaquedir) { |
| - newdentry = opaquedir; |
| - opaquedir = NULL; |
| + if (newdentry != opaquedir) |
| + goto out_dput; |
| } else { |
| - dget(newdentry); |
| + if (newdentry != ovl_dentry_upper(new)) |
| + goto out_dput; |
| } |
| } else { |
| new_create = true; |
| - newdentry = lookup_one_len(new->d_name.name, new_upperdir, |
| - new->d_name.len); |
| - err = PTR_ERR(newdentry); |
| - if (IS_ERR(newdentry)) |
| - goto out_unlock; |
| + if (!d_is_negative(newdentry) && |
| + (!new_opaque || !ovl_is_whiteout(newdentry))) |
| + goto out_dput; |
| } |
| |
| - err = -ESTALE; |
| - if (olddentry->d_parent != old_upperdir) |
| - goto out_dput; |
| - if (newdentry->d_parent != new_upperdir) |
| - goto out_dput; |
| if (olddentry == trap) |
| goto out_dput; |
| if (newdentry == trap) |
| @@ -919,6 +934,8 @@ static int ovl_rename2(struct inode *old |
| |
| out_dput: |
| dput(newdentry); |
| +out_dput_old: |
| + dput(olddentry); |
| out_unlock: |
| unlock_rename(new_upperdir, old_upperdir); |
| out_revert_creds: |