ovl: untangle copy up call chain
Subsystems (2)
Amir Goldstein
maintainerPATCH v2 1/1 ovl: untangle copy up call chain
In an attempt to dedup ~100 LOC, we ended up creating a tangled call chain, whose branches merge and diverge in several points according to the immutable c->tmpfile copy up mode.
This call chain was hard to analyse for locking correctness because the locking requirements for the c->tmpfile flow were very different from the locking requirements for the !c->tmpfile flow (i.e. directory vs. regulare file copy up).
Split the copy up helpers of the c->tmpfile flow from those of the !c->tmpfile (i.e. workdir) flow and remove the c->tmpfile mode from copy up context.
---
Miklos,
I've managed to mess up error handling in 3 out of 6 helpers in v1. Better take a close look that I did not miss more...
Thanks,
Amir.
fs/overlayfs/copy_up.c | 155 ++++++++++++++++++++++++++++++----------- 1 file changed, 114 insertions(+), 41 deletions(-)
fs/overlayfs/copy_up.c +114 -41
@@ -395,7 +395,6 @@ struct ovl_copy_up_ctx {struct dentry *destdir;struct qstr destname;struct dentry *workdir; -bool tmpfile;bool origin;bool indexed;bool metacopy;
@@ -440,8 +439,14 @@ static int ovl_link_up(struct ovl_copy_up_ctx *c)return err; } -static int ovl_install_temp (struct ovl_copy_up_ctx *c,struct dentry *temp, -struct dentry **newdentry) +/* +* Move temp file from workdir into place on upper dir. +* Used when copying up directories and when upper fs doesn't support O_TMPFILE. +* +* Caller must hold ovl_lock_rename_workdir(). +*/ +static int ovl_rename_temp (struct ovl_copy_up_ctx *c,struct dentry *temp, +struct dentry **newdentry) {int err;struct dentry *upper;
@@ -451,19 +456,40 @@ static int ovl_install_temp(struct ovl_copy_up_ctx *c, struct dentry *temp,if (IS_ERR (upper))return PTR_ERR (upper); -if (c->tmpfile) - err =ovl_do_link (temp, udir, upper); -else - err =ovl_do_rename (d_inode (c->workdir), temp, udir, upper,0 ); + err =ovl_do_rename (d_inode (c->workdir), temp, udir, upper,0 ); +if (!err) + *newdentry =dget (temp); +dput (upper); +return err; +} + +/* Link O_TMPFILE into place on upper dir */ +static int ovl_link_tmpfile (struct ovl_copy_up_ctx *c,struct dentry *temp, +struct dentry **newdentry) +{ +int err; +struct dentry *upper =NULL ; +struct inode *udir =d_inode (c->destdir); + +inode_lock_nested (udir,I_MUTEX_PARENT ); + + upper =lookup_one_len (c->destname.name, c->destdir, c->destname.len); + err =PTR_ERR (upper); +if (IS_ERR (upper)) + goto out; + + err =ovl_do_link (temp, udir, upper);if (!err) - *newdentry =dget (c->tmpfile ? upper : temp); + *newdentry =dget (upper);dput (upper); +out: +inode_unlock (udir);return err; } -static struct dentry *ovl_get_tmpfile (struct ovl_copy_up_ctx *c) +static struct dentry *ovl_get_workdir_temp (struct ovl_copy_up_ctx *c) {int err;struct dentry *temp;
@@ -484,10 +510,32 @@ static struct dentry *ovl_get_tmpfile(struct ovl_copy_up_ctx *c)if (new_creds) old_creds =override_creds (new_creds); -if (c->tmpfile) - temp =ovl_do_tmpfile (c->workdir, c->stat.mode); -else - temp =ovl_create_temp (c->workdir, &cattr); + temp =ovl_create_temp (c->workdir, &cattr); +out: +if (new_creds) { +revert_creds (old_creds); +put_cred (new_creds); + } + +return temp; +} + +static struct dentry *ovl_get_tmpfile (struct ovl_copy_up_ctx *c) +{ +int err; +struct dentry *temp; +const struct cred *old_creds =NULL ; +struct cred *new_creds =NULL ; + + err =security_inode_copy_up (c->dentry, &new_creds); + temp =ERR_PTR (err); +if (err <0 ) + goto out; + +if (new_creds) + old_creds =override_creds (new_creds); + + temp =ovl_do_tmpfile (c->destdir, c->stat.mode); out:if (new_creds) {revert_creds (old_creds);
@@ -548,37 +596,39 @@ static int ovl_copy_up_inode(struct ovl_copy_up_ctx *c, struct dentry *temp)return err; } -static int ovl_copy_up_locked (struct ovl_copy_up_ctx *c) +/* +* Copyup using workdir to prepare temp file. +* Used when copying up directories and when upper fs doesn't support O_TMPFILE. +*/ +static int ovl_copy_up_workdir (struct ovl_copy_up_ctx *c) { -struct inode *udir = c->destdir->d_inode;struct inode *inode;struct dentry *newdentry =NULL ;struct dentry *temp;int err; - temp =ovl_get_tmpfile (c); + err =ovl_lock_rename_workdir (c->workdir, c->destdir); +if (err) +return err; + + temp =ovl_get_workdir_temp (c); + err =PTR_ERR (temp);if (IS_ERR (temp)) -return PTR_ERR (temp); + goto out; err =ovl_copy_up_inode (c, temp);if (err) - goto out; + goto out_cleanup;if (S_ISDIR (c->stat.mode) && c->indexed) { err =ovl_create_index (c->dentry, c->lowerpath.dentry, temp);if (err) - goto out; + goto out_cleanup; } -if (c->tmpfile) { -inode_lock_nested (udir,I_MUTEX_PARENT ); - err =ovl_install_temp (c, temp, &newdentry); -inode_unlock (udir); - }else { - err =ovl_install_temp (c, temp, &newdentry); - } + err =ovl_rename_temp (c, temp, &newdentry);if (err) - goto out; + goto out_cleanup;if (!c->metacopy)ovl_set_upperdata (d_inode (c->dentry));
@@ -587,10 +637,41 @@ static int ovl_copy_up_locked(struct ovl_copy_up_ctx *c)if (S_ISDIR (inode->i_mode))ovl_set_flag (OVL_WHITEOUTS , inode); -out: -if (err && !c->tmpfile) +out_cleanup: +if (err)ovl_cleanup (d_inode (c->workdir), temp);dput (temp); +out: +unlock_rename (c->workdir, c->destdir); +return err; + +} + +/* Copyup using O_TMPFILE which does not require cross dir locking */ +static int ovl_copy_up_tmpfile (struct ovl_copy_up_ctx *c) +{ +struct dentry *newdentry =NULL ; +struct dentry *temp; +int err; + + temp =ovl_get_tmpfile (c); +if (IS_ERR (temp)) +return PTR_ERR (temp); + + err =ovl_copy_up_inode (c, temp); +if (err) + goto out; + + err =ovl_link_tmpfile (c, temp, &newdentry); +if (err) + goto out; + +if (!c->metacopy) +ovl_set_upperdata (d_inode (c->dentry)); +ovl_inode_update (d_inode (c->dentry), newdentry); + +out: +dput (temp);return err; }
@@ -646,18 +727,10 @@ static int ovl_do_copy_up(struct ovl_copy_up_ctx *c) }/* Should we copyup with O_TMPFILE or with workdir? */ -if (S_ISREG (c->stat.mode) && ofs->tmpfile) { - c->tmpfile = true; - err =ovl_copy_up_locked (c); - }else { - err =ovl_lock_rename_workdir (c->workdir, c->destdir); -if (!err) { - err =ovl_copy_up_locked (c); -unlock_rename (c->workdir, c->destdir); - } - } - - +if (S_ISREG (c->stat.mode) && ofs->tmpfile) + err =ovl_copy_up_tmpfile (c); +else + err =ovl_copy_up_workdir (c);if (err) goto out;
Signature · 1 line
2.17.1
Miklos Szeredi
maintainer[Cc Vivek for a question inline]
Quoted 106 lines · Amir Goldstein
In an attempt to dedup ~100 LOC, we ended up creating a tangled call chain, whose branches merge and diverge in several points according to the immutable
c->tmpfilecopy up mode.This call chain was hard to analyse for locking correctness because the locking requirements for the
c->tmpfileflow were very different from the locking requirements for the !c->tmpfile flow (i.e. directory vs. regulare file copy up).Split the copy up helpers of the
c->tmpfileflow from those of the !c->tmpfile (i.e. workdir) flow and remove thec->tmpfilemode from copy up context.Suggested-by Al Viro <[email protected]>Signed-off-by Amir Goldstein <[email protected]>---
Miklos,
I've managed to mess up error handling in 3 out of 6 helpers in v1. Better take a close look that I did not miss more...
Thanks,
Amir.fs/overlayfs/copy_up.c | 155 ++++++++++++++++++++++++++++++----------- 1 file changed, 114 insertions(+), 41 deletions(-)fs/overlayfs/copy_up.c +35
-9@@ -395,7 +395,6 @@ struct ovl_copy_up_ctx {struct dentry *destdir;struct qstr destname;struct dentry *workdir; -bool tmpfile;bool origin;bool indexed;bool metacopy;@@ -440,8 +439,14 @@ static int ovl_link_up(struct ovl_copy_up_ctx *c)return err; } -static int ovl_install_temp (struct ovl_copy_up_ctx *c,struct dentry *temp, -struct dentry **newdentry) +/* +* Move temp file from workdir into place on upper dir. +* Used when copying up directories and when upper fs doesn't support O_TMPFILE. +* +* Caller must hold ovl_lock_rename_workdir(). +*/ +static int ovl_rename_temp (struct ovl_copy_up_ctx *c,struct dentry *temp, +struct dentry **newdentry) {int err;struct dentry *upper;@@ -451,19 +456,40 @@ static int ovl_install_temp(struct ovl_copy_up_ctx *c, struct dentry *temp,if (IS_ERR (upper))return PTR_ERR (upper); -if (c->tmpfile) - err =ovl_do_link (temp, udir, upper); -else - err =ovl_do_rename (d_inode (c->workdir), temp, udir, upper,0 ); + err =ovl_do_rename (d_inode (c->workdir), temp, udir, upper,0 ); +if (!err) + *newdentry =dget (temp); +dput (upper); +return err; +} + +/* Link O_TMPFILE into place on upper dir */ +static int ovl_link_tmpfile (struct ovl_copy_up_ctx *c,struct dentry *temp, +struct dentry **newdentry) +{ +int err; +struct dentry *upper =NULL ; +struct inode *udir =d_inode (c->destdir); + +inode_lock_nested (udir,I_MUTEX_PARENT ); + + upper =lookup_one_len (c->destname.name, c->destdir, c->destname.len); + err =PTR_ERR (upper); +if (IS_ERR (upper)) + goto out; + + err =ovl_do_link (temp, udir, upper);if (!err) - *newdentry =dget (c->tmpfile ? upper : temp); + *newdentry =dget (upper);dput (upper); +out: +inode_unlock (udir);return err; } -static struct dentry *ovl_get_tmpfile (struct ovl_copy_up_ctx *c) +static struct dentry *ovl_get_workdir_temp (struct ovl_copy_up_ctx *c) {int err;struct dentry *temp;@@ -484,10 +510,32 @@ static struct dentry *ovl_get_tmpfile(struct ovl_copy_up_ctx *c)if (new_creds)
fs/overlayfs/copy_up.c static struct dentry *ovl_get_tmpfile(struct ovl_copy_up_ctx *c) · line 516
Quoted 10 lines
fs/overlayfs/copy_up.c +4
-4old_creds =override_creds (new_creds); -if (c->tmpfile) - temp =ovl_do_tmpfile (c->workdir, c->stat.mode); -else - temp =ovl_create_temp (c->workdir, &cattr); + temp =ovl_create_temp (c->workdir, &cattr); +out: +if (new_creds) { +revert_creds (old_creds);
Not new in this patch, but it looks like this will Oops if old_creds is NULL, which happens if security_inode_copy_up() returns an error.
Trivial to fix, but I'm not sure we need the put_cred(new_creds) in the failed security_inode_copy_up() case. Vivek, do you remember the reason for this error cleanup?
Quoted 12 lines
fs/overlayfs/copy_up.c +12
-0+put_cred (new_creds); + } + +return temp; +} + +static struct dentry *ovl_get_tmpfile (struct ovl_copy_up_ctx *c) +{ +int err; +struct dentry *temp; +const struct cred *old_creds =NULL ; +struct cred *new_creds =NULL ;
fs/overlayfs/copy_up.c static struct dentry *ovl_get_tmpfile(struct ovl_copy_up_ctx *c) · line 538
Quoted 10 lines
fs/overlayfs/copy_up.c +10
-0+ + err =security_inode_copy_up (c->dentry, &new_creds); + temp =ERR_PTR (err); +if (err <0 ) + goto out; + +if (new_creds) + old_creds =override_creds (new_creds); + + temp =ovl_do_tmpfile (c->destdir, c->stat.mode);
This changes c->workdir to c->destdir. Shouldn't normally matter, but...
I changed this back, and we can discuss the merits of this change in the context of a separate patch.
Quoted 58 lines
fs/overlayfs/copy_up.c +16
-14out:if (new_creds) {revert_creds (old_creds);@@ -548,37 +596,39 @@ static int ovl_copy_up_inode(struct ovl_copy_up_ctx *c, struct dentry *temp)return err; } -static int ovl_copy_up_locked (struct ovl_copy_up_ctx *c) +/* +* Copyup using workdir to prepare temp file. +* Used when copying up directories and when upper fs doesn't support O_TMPFILE. +*/ +static int ovl_copy_up_workdir (struct ovl_copy_up_ctx *c) { -struct inode *udir = c->destdir->d_inode;struct inode *inode;struct dentry *newdentry =NULL ;struct dentry *temp;int err; - temp =ovl_get_tmpfile (c); + err =ovl_lock_rename_workdir (c->workdir, c->destdir); +if (err) +return err; + + temp =ovl_get_workdir_temp (c); + err =PTR_ERR (temp);if (IS_ERR (temp)) -return PTR_ERR (temp); + goto out; err =ovl_copy_up_inode (c, temp);if (err) - goto out; + goto out_cleanup;if (S_ISDIR (c->stat.mode) && c->indexed) { err =ovl_create_index (c->dentry, c->lowerpath.dentry, temp);if (err) - goto out; + goto out_cleanup; } -if (c->tmpfile) { -inode_lock_nested (udir,I_MUTEX_PARENT ); - err =ovl_install_temp (c, temp, &newdentry); -inode_unlock (udir); - }else { - err =ovl_install_temp (c, temp, &newdentry); - } + err =ovl_rename_temp (c, temp, &newdentry);if (err) - goto out; + goto out_cleanup;if (!c->metacopy)ovl_set_upperdata (d_inode (c->dentry));
fs/overlayfs/copy_up.c static int ovl_copy_up_locked(struct ovl_copy_up_ctx *c) · line 642
Quoted 8 lines
fs/overlayfs/copy_up.c +2
-2@@ -587,10 +637,41 @@ static int ovl_copy_up_locked(struct ovl_copy_up_ctx *c)if (S_ISDIR (inode->i_mode))ovl_set_flag (OVL_WHITEOUTS , inode); -out: -if (err && !c->tmpfile) +out_cleanup: +if (err)ovl_cleanup (d_inode (c->workdir), temp);
This is a bit awkward: ovl_cleanup() will be called IFF we'd jumped to out_cleanup. So I moved this error cleanup till after the return.
Quoted 61 lines
fs/overlayfs/copy_up.c +35
-13dput (temp); +out: +unlock_rename (c->workdir, c->destdir); +return err; + +} + +/* Copyup using O_TMPFILE which does not require cross dir locking */ +static int ovl_copy_up_tmpfile (struct ovl_copy_up_ctx *c) +{ +struct dentry *newdentry =NULL ; +struct dentry *temp; +int err; + + temp =ovl_get_tmpfile (c); +if (IS_ERR (temp)) +return PTR_ERR (temp); + + err =ovl_copy_up_inode (c, temp); +if (err) + goto out; + + err =ovl_link_tmpfile (c, temp, &newdentry); +if (err) + goto out; + +if (!c->metacopy) +ovl_set_upperdata (d_inode (c->dentry)); +ovl_inode_update (d_inode (c->dentry), newdentry); + +out: +dput (temp);return err; }@@ -646,18 +727,10 @@ static int ovl_do_copy_up(struct ovl_copy_up_ctx *c) }/* Should we copyup with O_TMPFILE or with workdir? */ -if (S_ISREG (c->stat.mode) && ofs->tmpfile) { - c->tmpfile = true; - err =ovl_copy_up_locked (c); - }else { - err =ovl_lock_rename_workdir (c->workdir, c->destdir); -if (!err) { - err =ovl_copy_up_locked (c); -unlock_rename (c->workdir, c->destdir); - } - } - - +if (S_ISREG (c->stat.mode) && ofs->tmpfile) + err =ovl_copy_up_tmpfile (c); +else + err =ovl_copy_up_workdir (c);if (err) goto out; --2.17.1
Amir Goldstein
maintainerQuoted 19 lines
Quoted 6 lines
fs/overlayfs/copy_up.c +6
-0+static struct dentry *ovl_get_tmpfile (struct ovl_copy_up_ctx *c) +{ +int err; +struct dentry *temp; +const struct cred *old_creds =NULL ; +struct cred *new_creds =NULL ;fs/overlayfs/copy_up.c static struct dentry *ovl_get_tmpfile(struct ovl_copy_up_ctx *c) · line 538
Quoted 10 lines
fs/overlayfs/copy_up.c +10
-0+ + err =security_inode_copy_up (c->dentry, &new_creds); + temp =ERR_PTR (err); +if (err <0 ) + goto out; + +if (new_creds) + old_creds =override_creds (new_creds); + + temp =ovl_do_tmpfile (c->destdir, c->stat.mode);This changes
c->workdirtoc->destdir. Shouldn't normally matter, but...
Quoted 3 lines
I changed this back, and we can discuss the merits of this change in the context of a separate patch.
Indeed. I wrote about this change the cover of the first posting, but it got lost
in cover of 2nd posting. I thought that using c->workdir was due to an oversight I didn't realize that it had merits or that it mattered at all.
I don't mind either way.
Other changes sound good.
Thanks,
Amir.
Miklos Szeredi
maintainerQuoted 11 lines · Miklos Szeredi
fs/overlayfs/copy_up.c static struct dentry *ovl_get_tmpfile(struct ovl_copy_up_ctx *c) · line 516
Quoted 4 lines
fs/overlayfs/copy_up.c +4
-0+ temp =ovl_create_temp (c->workdir, &cattr); +out: +if (new_creds) { +revert_creds (old_creds);Not new in this patch, but it looks like this will Oops if
old_credsis NULL, which happens ifsecurity_inode_copy_up()returns an error.Trivial to fix, but I'm not sure we need the
put_cred(new_creds)in the failedsecurity_inode_copy_up()case. Vivek, do you remember the reason for this error cleanup?
Answering myself: this bug was introduced by a cleanup patch by me:
7d90b853f932 ("ovl: extract helper to get temp file in copy up")
and so the put_cred() isn't needed in this case at all.
Will do a separate fix.
Thanks,
Miklos
On Thu, Oct 25, 2018 at 02:44:15PM +0200, Miklos Szeredi wrote:
[..]
Quoted 9 lines
Quoted 9 lines
fs/overlayfs/copy_up.c +1
-1-static struct dentry *ovl_get_tmpfile (struct ovl_copy_up_ctx *c) +static struct dentry *ovl_get_workdir_temp (struct ovl_copy_up_ctx *c) {int err;struct dentry *temp;@@ -484,10 +510,32 @@ static struct dentry *ovl_get_tmpfile(struct ovl_copy_up_ctx *c)if (new_creds) old_creds =override_creds (new_creds);
Quoted 12 lines
fs/overlayfs/copy_up.c static struct dentry *ovl_get_tmpfile(struct ovl_copy_up_ctx *c) · line 516
Quoted 8 lines
fs/overlayfs/copy_up.c +4
-4-if (c->tmpfile) - temp =ovl_do_tmpfile (c->workdir, c->stat.mode); -else - temp =ovl_create_temp (c->workdir, &cattr); + temp =ovl_create_temp (c->workdir, &cattr); +out: +if (new_creds) { +revert_creds (old_creds);Not new in this patch, but it looks like this will Oops if
old_credsis NULL, which happens ifsecurity_inode_copy_up()returns an error.
If security_inode_copy_up() fails, then new_creds will be null too. And in that case we will never call revert_creds(old_creds)?
IOW, new_creds should be returned by security_inode_copy_up() only if it succeeds. Otherwise new_creds should be left untouched by hook.
Quoted 3 lines
Trivial to fix, but I'm not sure we need the
put_cred(new_creds)in the failedsecurity_inode_copy_up()case. Vivek, do you remember the reason for this error cleanup?
Same, In case of failure, new_creds should be NULL and we will never call put_cred(new_creds).
So I can't see the bug. What am I missing?
Thanks
Vivek