| From cdb672795876d7bc1870aed9a2d7cb59f43d1d96 Mon Sep 17 00:00:00 2001 |
| From: Miklos Szeredi <mszeredi@suse.cz> |
| Date: Mon, 22 Jun 2015 13:53:48 +0200 |
| Subject: ovl: lookup whiteouts outside iterate_dir() |
| |
| From: Miklos Szeredi <mszeredi@suse.cz> |
| |
| commit cdb672795876d7bc1870aed9a2d7cb59f43d1d96 upstream. |
| |
| If jffs2 can deadlock on overlayfs readdir because it takes the same lock |
| on ->iterate() as in ->lookup(). |
| |
| Fix by moving whiteout checking outside iterate_dir(). Optimized by |
| collecting potential whiteouts (DT_CHR) in a temporary list and if |
| non-empty iterating throug these and checking for a 0/0 chardev. |
| |
| Signed-off-by: Miklos Szeredi <mszeredi@suse.cz> |
| Fixes: 49c21e1cacd7 ("ovl: check whiteout while reading directory") |
| Reported-by: Roman Yeryomin <leroi.lists@gmail.com> |
| Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> |
| |
| --- |
| fs/overlayfs/readdir.c | 77 +++++++++++++++++++++++++++++++------------------ |
| 1 file changed, 49 insertions(+), 28 deletions(-) |
| |
| --- a/fs/overlayfs/readdir.c |
| +++ b/fs/overlayfs/readdir.c |
| @@ -23,6 +23,7 @@ struct ovl_cache_entry { |
| u64 ino; |
| struct list_head l_node; |
| struct rb_node node; |
| + struct ovl_cache_entry *next_maybe_whiteout; |
| bool is_whiteout; |
| char name[]; |
| }; |
| @@ -39,7 +40,7 @@ struct ovl_readdir_data { |
| struct rb_root root; |
| struct list_head *list; |
| struct list_head middle; |
| - struct dentry *dir; |
| + struct ovl_cache_entry *first_maybe_whiteout; |
| int count; |
| int err; |
| }; |
| @@ -79,7 +80,7 @@ static struct ovl_cache_entry *ovl_cache |
| return NULL; |
| } |
| |
| -static struct ovl_cache_entry *ovl_cache_entry_new(struct dentry *dir, |
| +static struct ovl_cache_entry *ovl_cache_entry_new(struct ovl_readdir_data *rdd, |
| const char *name, int len, |
| u64 ino, unsigned int d_type) |
| { |
| @@ -98,29 +99,8 @@ static struct ovl_cache_entry *ovl_cache |
| p->is_whiteout = false; |
| |
| if (d_type == DT_CHR) { |
| - struct dentry *dentry; |
| - const struct cred *old_cred; |
| - struct cred *override_cred; |
| - |
| - override_cred = prepare_creds(); |
| - if (!override_cred) { |
| - kfree(p); |
| - return NULL; |
| - } |
| - |
| - /* |
| - * CAP_DAC_OVERRIDE for lookup |
| - */ |
| - cap_raise(override_cred->cap_effective, CAP_DAC_OVERRIDE); |
| - old_cred = override_creds(override_cred); |
| - |
| - dentry = lookup_one_len(name, dir, len); |
| - if (!IS_ERR(dentry)) { |
| - p->is_whiteout = ovl_is_whiteout(dentry); |
| - dput(dentry); |
| - } |
| - revert_creds(old_cred); |
| - put_cred(override_cred); |
| + p->next_maybe_whiteout = rdd->first_maybe_whiteout; |
| + rdd->first_maybe_whiteout = p; |
| } |
| return p; |
| } |
| @@ -148,7 +128,7 @@ static int ovl_cache_entry_add_rb(struct |
| return 0; |
| } |
| |
| - p = ovl_cache_entry_new(rdd->dir, name, len, ino, d_type); |
| + p = ovl_cache_entry_new(rdd, name, len, ino, d_type); |
| if (p == NULL) |
| return -ENOMEM; |
| |
| @@ -169,7 +149,7 @@ static int ovl_fill_lower(struct ovl_rea |
| if (p) { |
| list_move_tail(&p->l_node, &rdd->middle); |
| } else { |
| - p = ovl_cache_entry_new(rdd->dir, name, namelen, ino, d_type); |
| + p = ovl_cache_entry_new(rdd, name, namelen, ino, d_type); |
| if (p == NULL) |
| rdd->err = -ENOMEM; |
| else |
| @@ -219,6 +199,43 @@ static int ovl_fill_merge(struct dir_con |
| return ovl_fill_lower(rdd, name, namelen, offset, ino, d_type); |
| } |
| |
| +static int ovl_check_whiteouts(struct dentry *dir, struct ovl_readdir_data *rdd) |
| +{ |
| + int err; |
| + struct ovl_cache_entry *p; |
| + struct dentry *dentry; |
| + const struct cred *old_cred; |
| + struct cred *override_cred; |
| + |
| + override_cred = prepare_creds(); |
| + if (!override_cred) |
| + return -ENOMEM; |
| + |
| + /* |
| + * CAP_DAC_OVERRIDE for lookup |
| + */ |
| + cap_raise(override_cred->cap_effective, CAP_DAC_OVERRIDE); |
| + old_cred = override_creds(override_cred); |
| + |
| + err = mutex_lock_killable(&dir->d_inode->i_mutex); |
| + if (!err) { |
| + while (rdd->first_maybe_whiteout) { |
| + p = rdd->first_maybe_whiteout; |
| + rdd->first_maybe_whiteout = p->next_maybe_whiteout; |
| + dentry = lookup_one_len(p->name, dir, p->len); |
| + if (!IS_ERR(dentry)) { |
| + p->is_whiteout = ovl_is_whiteout(dentry); |
| + dput(dentry); |
| + } |
| + } |
| + mutex_unlock(&dir->d_inode->i_mutex); |
| + } |
| + revert_creds(old_cred); |
| + put_cred(override_cred); |
| + |
| + return err; |
| +} |
| + |
| static inline int ovl_dir_read(struct path *realpath, |
| struct ovl_readdir_data *rdd) |
| { |
| @@ -229,7 +246,7 @@ static inline int ovl_dir_read(struct pa |
| if (IS_ERR(realfile)) |
| return PTR_ERR(realfile); |
| |
| - rdd->dir = realpath->dentry; |
| + rdd->first_maybe_whiteout = NULL; |
| rdd->ctx.pos = 0; |
| do { |
| rdd->count = 0; |
| @@ -238,6 +255,10 @@ static inline int ovl_dir_read(struct pa |
| if (err >= 0) |
| err = rdd->err; |
| } while (!err && rdd->count); |
| + |
| + if (!err && rdd->first_maybe_whiteout) |
| + err = ovl_check_whiteouts(realpath->dentry, rdd); |
| + |
| fput(realfile); |
| |
| return err; |