mirror of https://github.com/hak5/openwrt.git
149 lines
3.9 KiB
Diff
149 lines
3.9 KiB
Diff
|
Patch by: Miklos Szeredi <miklos@szeredi.hu>
|
||
|
|
||
|
Some filesystems (e.g. jffs2) lock the same resources for both readdir
|
||
|
and lookup, leading to a deadlock in ovl_cache_entry_new, which is called
|
||
|
from the filldir, and calls lookup itself.
|
||
|
|
||
|
--- 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 = 0;
|
||
|
+
|
||
|
+ mutex_lock(&dir->d_inode->i_mutex);
|
||
|
+ while (rdd->first_maybe_whiteout) {
|
||
|
+ struct dentry *dentry;
|
||
|
+ const struct cred *old_cred;
|
||
|
+ struct cred *override_cred;
|
||
|
+ struct ovl_cache_entry *p = rdd->first_maybe_whiteout;
|
||
|
+
|
||
|
+ rdd->first_maybe_whiteout = p->next_maybe_whiteout;
|
||
|
+
|
||
|
+ override_cred = prepare_creds();
|
||
|
+ if (!override_cred) {
|
||
|
+ err = -ENOMEM;
|
||
|
+ break;
|
||
|
+ }
|
||
|
+ /*
|
||
|
+ * CAP_DAC_OVERRIDE for lookup
|
||
|
+ */
|
||
|
+ cap_raise(override_cred->cap_effective, CAP_DAC_OVERRIDE);
|
||
|
+ old_cred = override_creds(override_cred);
|
||
|
+
|
||
|
+ dentry = lookup_one_len(p->name, dir, p->len);
|
||
|
+ if (!IS_ERR(dentry)) {
|
||
|
+ p->is_whiteout = ovl_is_whiteout(dentry);
|
||
|
+ dput(dentry);
|
||
|
+ }
|
||
|
+ revert_creds(old_cred);
|
||
|
+ put_cred(override_cred);
|
||
|
+ }
|
||
|
+ mutex_unlock(&dir->d_inode->i_mutex);
|
||
|
+
|
||
|
+ 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;
|