diff options
| author | Miklos Szeredi <mszeredi@suse.cz> | 2015-06-22 07:53:48 -0400 |
|---|---|---|
| committer | Miklos Szeredi <mszeredi@suse.cz> | 2015-06-22 07:53:48 -0400 |
| commit | cdb672795876d7bc1870aed9a2d7cb59f43d1d96 (patch) | |
| tree | 83f520fa3fc7ccf117e821e6707c9de25b9e989b /fs/overlayfs | |
| parent | 7c03b5d45b8eebf0111125053d8fe887cc262ba6 (diff) | |
ovl: lookup whiteouts outside iterate_dir()
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>
Diffstat (limited to 'fs/overlayfs')
| -rw-r--r-- | fs/overlayfs/readdir.c | 77 |
1 files changed, 49 insertions, 28 deletions
diff --git a/fs/overlayfs/readdir.c b/fs/overlayfs/readdir.c index 907870e81a72..70e9af551600 100644 --- a/fs/overlayfs/readdir.c +++ b/fs/overlayfs/readdir.c | |||
| @@ -23,6 +23,7 @@ struct ovl_cache_entry { | |||
| 23 | u64 ino; | 23 | u64 ino; |
| 24 | struct list_head l_node; | 24 | struct list_head l_node; |
| 25 | struct rb_node node; | 25 | struct rb_node node; |
| 26 | struct ovl_cache_entry *next_maybe_whiteout; | ||
| 26 | bool is_whiteout; | 27 | bool is_whiteout; |
| 27 | char name[]; | 28 | char name[]; |
| 28 | }; | 29 | }; |
| @@ -39,7 +40,7 @@ struct ovl_readdir_data { | |||
| 39 | struct rb_root root; | 40 | struct rb_root root; |
| 40 | struct list_head *list; | 41 | struct list_head *list; |
| 41 | struct list_head middle; | 42 | struct list_head middle; |
| 42 | struct dentry *dir; | 43 | struct ovl_cache_entry *first_maybe_whiteout; |
| 43 | int count; | 44 | int count; |
| 44 | int err; | 45 | int err; |
| 45 | }; | 46 | }; |
| @@ -79,7 +80,7 @@ static struct ovl_cache_entry *ovl_cache_entry_find(struct rb_root *root, | |||
| 79 | return NULL; | 80 | return NULL; |
| 80 | } | 81 | } |
| 81 | 82 | ||
| 82 | static struct ovl_cache_entry *ovl_cache_entry_new(struct dentry *dir, | 83 | static struct ovl_cache_entry *ovl_cache_entry_new(struct ovl_readdir_data *rdd, |
| 83 | const char *name, int len, | 84 | const char *name, int len, |
| 84 | u64 ino, unsigned int d_type) | 85 | u64 ino, unsigned int d_type) |
| 85 | { | 86 | { |
| @@ -98,29 +99,8 @@ static struct ovl_cache_entry *ovl_cache_entry_new(struct dentry *dir, | |||
| 98 | p->is_whiteout = false; | 99 | p->is_whiteout = false; |
| 99 | 100 | ||
| 100 | if (d_type == DT_CHR) { | 101 | if (d_type == DT_CHR) { |
| 101 | struct dentry *dentry; | 102 | p->next_maybe_whiteout = rdd->first_maybe_whiteout; |
| 102 | const struct cred *old_cred; | 103 | rdd->first_maybe_whiteout = p; |
| 103 | struct cred *override_cred; | ||
| 104 | |||
| 105 | override_cred = prepare_creds(); | ||
| 106 | if (!override_cred) { | ||
| 107 | kfree(p); | ||
| 108 | return NULL; | ||
| 109 | } | ||
| 110 | |||
| 111 | /* | ||
| 112 | * CAP_DAC_OVERRIDE for lookup | ||
| 113 | */ | ||
| 114 | cap_raise(override_cred->cap_effective, CAP_DAC_OVERRIDE); | ||
| 115 | old_cred = override_creds(override_cred); | ||
| 116 | |||
| 117 | dentry = lookup_one_len(name, dir, len); | ||
| 118 | if (!IS_ERR(dentry)) { | ||
| 119 | p->is_whiteout = ovl_is_whiteout(dentry); | ||
| 120 | dput(dentry); | ||
| 121 | } | ||
| 122 | revert_creds(old_cred); | ||
| 123 | put_cred(override_cred); | ||
| 124 | } | 104 | } |
| 125 | return p; | 105 | return p; |
| 126 | } | 106 | } |
| @@ -148,7 +128,7 @@ static int ovl_cache_entry_add_rb(struct ovl_readdir_data *rdd, | |||
| 148 | return 0; | 128 | return 0; |
| 149 | } | 129 | } |
| 150 | 130 | ||
| 151 | p = ovl_cache_entry_new(rdd->dir, name, len, ino, d_type); | 131 | p = ovl_cache_entry_new(rdd, name, len, ino, d_type); |
| 152 | if (p == NULL) | 132 | if (p == NULL) |
| 153 | return -ENOMEM; | 133 | return -ENOMEM; |
| 154 | 134 | ||
| @@ -169,7 +149,7 @@ static int ovl_fill_lower(struct ovl_readdir_data *rdd, | |||
| 169 | if (p) { | 149 | if (p) { |
| 170 | list_move_tail(&p->l_node, &rdd->middle); | 150 | list_move_tail(&p->l_node, &rdd->middle); |
| 171 | } else { | 151 | } else { |
| 172 | p = ovl_cache_entry_new(rdd->dir, name, namelen, ino, d_type); | 152 | p = ovl_cache_entry_new(rdd, name, namelen, ino, d_type); |
| 173 | if (p == NULL) | 153 | if (p == NULL) |
| 174 | rdd->err = -ENOMEM; | 154 | rdd->err = -ENOMEM; |
| 175 | else | 155 | else |
| @@ -219,6 +199,43 @@ static int ovl_fill_merge(struct dir_context *ctx, const char *name, | |||
| 219 | return ovl_fill_lower(rdd, name, namelen, offset, ino, d_type); | 199 | return ovl_fill_lower(rdd, name, namelen, offset, ino, d_type); |
| 220 | } | 200 | } |
| 221 | 201 | ||
| 202 | static int ovl_check_whiteouts(struct dentry *dir, struct ovl_readdir_data *rdd) | ||
| 203 | { | ||
| 204 | int err; | ||
| 205 | struct ovl_cache_entry *p; | ||
| 206 | struct dentry *dentry; | ||
| 207 | const struct cred *old_cred; | ||
| 208 | struct cred *override_cred; | ||
| 209 | |||
| 210 | override_cred = prepare_creds(); | ||
| 211 | if (!override_cred) | ||
| 212 | return -ENOMEM; | ||
| 213 | |||
| 214 | /* | ||
| 215 | * CAP_DAC_OVERRIDE for lookup | ||
| 216 | */ | ||
| 217 | cap_raise(override_cred->cap_effective, CAP_DAC_OVERRIDE); | ||
| 218 | old_cred = override_creds(override_cred); | ||
| 219 | |||
| 220 | err = mutex_lock_killable(&dir->d_inode->i_mutex); | ||
| 221 | if (!err) { | ||
| 222 | while (rdd->first_maybe_whiteout) { | ||
| 223 | p = rdd->first_maybe_whiteout; | ||
| 224 | rdd->first_maybe_whiteout = p->next_maybe_whiteout; | ||
| 225 | dentry = lookup_one_len(p->name, dir, p->len); | ||
| 226 | if (!IS_ERR(dentry)) { | ||
| 227 | p->is_whiteout = ovl_is_whiteout(dentry); | ||
| 228 | dput(dentry); | ||
| 229 | } | ||
| 230 | } | ||
| 231 | mutex_unlock(&dir->d_inode->i_mutex); | ||
| 232 | } | ||
| 233 | revert_creds(old_cred); | ||
| 234 | put_cred(override_cred); | ||
| 235 | |||
| 236 | return err; | ||
| 237 | } | ||
| 238 | |||
| 222 | static inline int ovl_dir_read(struct path *realpath, | 239 | static inline int ovl_dir_read(struct path *realpath, |
| 223 | struct ovl_readdir_data *rdd) | 240 | struct ovl_readdir_data *rdd) |
| 224 | { | 241 | { |
| @@ -229,7 +246,7 @@ static inline int ovl_dir_read(struct path *realpath, | |||
| 229 | if (IS_ERR(realfile)) | 246 | if (IS_ERR(realfile)) |
| 230 | return PTR_ERR(realfile); | 247 | return PTR_ERR(realfile); |
| 231 | 248 | ||
| 232 | rdd->dir = realpath->dentry; | 249 | rdd->first_maybe_whiteout = NULL; |
| 233 | rdd->ctx.pos = 0; | 250 | rdd->ctx.pos = 0; |
| 234 | do { | 251 | do { |
| 235 | rdd->count = 0; | 252 | rdd->count = 0; |
| @@ -238,6 +255,10 @@ static inline int ovl_dir_read(struct path *realpath, | |||
| 238 | if (err >= 0) | 255 | if (err >= 0) |
| 239 | err = rdd->err; | 256 | err = rdd->err; |
| 240 | } while (!err && rdd->count); | 257 | } while (!err && rdd->count); |
| 258 | |||
| 259 | if (!err && rdd->first_maybe_whiteout) | ||
| 260 | err = ovl_check_whiteouts(realpath->dentry, rdd); | ||
| 261 | |||
| 241 | fput(realfile); | 262 | fput(realfile); |
| 242 | 263 | ||
| 243 | return err; | 264 | return err; |
