generic: add linux 4.1 support
[openwrt.git] / target / linux / generic / patches-4.1 / 140-overlayfs_readdir_locking_fix.patch
1 Patch by: Miklos Szeredi <miklos@szeredi.hu>
2
3 Some filesystems (e.g. jffs2) lock the same resources for both readdir
4 and lookup, leading to a deadlock in ovl_cache_entry_new, which is called
5 from the filldir, and calls lookup itself.
6
7 --- a/fs/overlayfs/readdir.c
8 +++ b/fs/overlayfs/readdir.c
9 @@ -23,6 +23,7 @@ struct ovl_cache_entry {
10         u64 ino;
11         struct list_head l_node;
12         struct rb_node node;
13 +       struct ovl_cache_entry *next_maybe_whiteout;
14         bool is_whiteout;
15         char name[];
16  };
17 @@ -39,7 +40,7 @@ struct ovl_readdir_data {
18         struct rb_root root;
19         struct list_head *list;
20         struct list_head middle;
21 -       struct dentry *dir;
22 +       struct ovl_cache_entry *first_maybe_whiteout;
23         int count;
24         int err;
25  };
26 @@ -79,7 +80,7 @@ static struct ovl_cache_entry *ovl_cache
27         return NULL;
28  }
29  
30 -static struct ovl_cache_entry *ovl_cache_entry_new(struct dentry *dir,
31 +static struct ovl_cache_entry *ovl_cache_entry_new(struct ovl_readdir_data *rdd,
32                                                    const char *name, int len,
33                                                    u64 ino, unsigned int d_type)
34  {
35 @@ -98,29 +99,8 @@ static struct ovl_cache_entry *ovl_cache
36         p->is_whiteout = false;
37  
38         if (d_type == DT_CHR) {
39 -               struct dentry *dentry;
40 -               const struct cred *old_cred;
41 -               struct cred *override_cred;
42 -
43 -               override_cred = prepare_creds();
44 -               if (!override_cred) {
45 -                       kfree(p);
46 -                       return NULL;
47 -               }
48 -
49 -               /*
50 -                * CAP_DAC_OVERRIDE for lookup
51 -                */
52 -               cap_raise(override_cred->cap_effective, CAP_DAC_OVERRIDE);
53 -               old_cred = override_creds(override_cred);
54 -
55 -               dentry = lookup_one_len(name, dir, len);
56 -               if (!IS_ERR(dentry)) {
57 -                       p->is_whiteout = ovl_is_whiteout(dentry);
58 -                       dput(dentry);
59 -               }
60 -               revert_creds(old_cred);
61 -               put_cred(override_cred);
62 +               p->next_maybe_whiteout = rdd->first_maybe_whiteout;
63 +               rdd->first_maybe_whiteout = p;
64         }
65         return p;
66  }
67 @@ -148,7 +128,7 @@ static int ovl_cache_entry_add_rb(struct
68                         return 0;
69         }
70  
71 -       p = ovl_cache_entry_new(rdd->dir, name, len, ino, d_type);
72 +       p = ovl_cache_entry_new(rdd, name, len, ino, d_type);
73         if (p == NULL)
74                 return -ENOMEM;
75  
76 @@ -169,7 +149,7 @@ static int ovl_fill_lower(struct ovl_rea
77         if (p) {
78                 list_move_tail(&p->l_node, &rdd->middle);
79         } else {
80 -               p = ovl_cache_entry_new(rdd->dir, name, namelen, ino, d_type);
81 +               p = ovl_cache_entry_new(rdd, name, namelen, ino, d_type);
82                 if (p == NULL)
83                         rdd->err = -ENOMEM;
84                 else
85 @@ -219,6 +199,43 @@ static int ovl_fill_merge(struct dir_con
86                 return ovl_fill_lower(rdd, name, namelen, offset, ino, d_type);
87  }
88  
89 +static int ovl_check_whiteouts(struct dentry *dir, struct ovl_readdir_data *rdd)
90 +{
91 +       int err = 0;
92 +
93 +       mutex_lock(&dir->d_inode->i_mutex);
94 +       while (rdd->first_maybe_whiteout) {
95 +               struct dentry *dentry;
96 +               const struct cred *old_cred;
97 +               struct cred *override_cred;
98 +               struct ovl_cache_entry *p = rdd->first_maybe_whiteout;
99 +
100 +               rdd->first_maybe_whiteout = p->next_maybe_whiteout;
101 +
102 +               override_cred = prepare_creds();
103 +               if (!override_cred) {
104 +                       err = -ENOMEM;
105 +                       break;
106 +               }
107 +               /*
108 +                * CAP_DAC_OVERRIDE for lookup
109 +                */
110 +               cap_raise(override_cred->cap_effective, CAP_DAC_OVERRIDE);
111 +               old_cred = override_creds(override_cred);
112 +
113 +               dentry = lookup_one_len(p->name, dir, p->len);
114 +               if (!IS_ERR(dentry)) {
115 +                       p->is_whiteout = ovl_is_whiteout(dentry);
116 +                       dput(dentry);
117 +               }
118 +               revert_creds(old_cred);
119 +               put_cred(override_cred);
120 +       }
121 +       mutex_unlock(&dir->d_inode->i_mutex);
122 +
123 +       return err;
124 +}
125 +
126  static inline int ovl_dir_read(struct path *realpath,
127                                struct ovl_readdir_data *rdd)
128  {
129 @@ -229,7 +246,7 @@ static inline int ovl_dir_read(struct pa
130         if (IS_ERR(realfile))
131                 return PTR_ERR(realfile);
132  
133 -       rdd->dir = realpath->dentry;
134 +       rdd->first_maybe_whiteout = NULL;
135         rdd->ctx.pos = 0;
136         do {
137                 rdd->count = 0;
138 @@ -238,6 +255,10 @@ static inline int ovl_dir_read(struct pa
139                 if (err >= 0)
140                         err = rdd->err;
141         } while (!err && rdd->count);
142 +
143 +       if (!err && rdd->first_maybe_whiteout)
144 +               err = ovl_check_whiteouts(realpath->dentry, rdd);
145 +
146         fput(realfile);
147  
148         return err;