Skip to content

Commit 51232df

Browse files
Gao Xianggregkh
authored andcommitted
staging: erofs: fix race when the managed cache is enabled
When the managed cache is enabled, the last reference count of a workgroup must be used for its workstation. Otherwise, it could lead to incorrect (un)freezes in the reclaim path, and it would be harmful. A typical race as follows: Thread 1 (In the reclaim path) Thread 2 workgroup_freeze(grp, 1) refcnt = 1 ... workgroup_unfreeze(grp, 1) refcnt = 1 workgroup_get(grp) refcnt = 2 (x) workgroup_put(grp) refcnt = 1 (x) ...unexpected behaviors * grp is detached but still used, which violates cache-managed freeze constraint. Reviewed-by: Chao Yu <[email protected]> Signed-off-by: Gao Xiang <[email protected]> Signed-off-by: Greg Kroah-Hartman <[email protected]>
1 parent eef1687 commit 51232df

File tree

2 files changed

+96
-39
lines changed

2 files changed

+96
-39
lines changed

drivers/staging/erofs/internal.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,7 @@ static inline bool erofs_workgroup_get(struct erofs_workgroup *grp, int *ocnt)
250250
}
251251

252252
#define __erofs_workgroup_get(grp) atomic_inc(&(grp)->refcount)
253+
#define __erofs_workgroup_put(grp) atomic_dec(&(grp)->refcount)
253254

254255
extern int erofs_workgroup_put(struct erofs_workgroup *grp);
255256

drivers/staging/erofs/utils.c

Lines changed: 95 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -83,12 +83,21 @@ int erofs_register_workgroup(struct super_block *sb,
8383

8484
grp = xa_tag_pointer(grp, tag);
8585

86-
err = radix_tree_insert(&sbi->workstn_tree,
87-
grp->index, grp);
86+
/*
87+
* Bump up reference count before making this workgroup
88+
* visible to other users in order to avoid potential UAF
89+
* without serialized by erofs_workstn_lock.
90+
*/
91+
__erofs_workgroup_get(grp);
8892

89-
if (!err) {
90-
__erofs_workgroup_get(grp);
91-
}
93+
err = radix_tree_insert(&sbi->workstn_tree,
94+
grp->index, grp);
95+
if (unlikely(err))
96+
/*
97+
* it's safe to decrease since the workgroup isn't visible
98+
* and refcount >= 2 (cannot be freezed).
99+
*/
100+
__erofs_workgroup_put(grp);
92101

93102
erofs_workstn_unlock(sbi);
94103
radix_tree_preload_end();
@@ -97,19 +106,94 @@ int erofs_register_workgroup(struct super_block *sb,
97106

98107
extern void erofs_workgroup_free_rcu(struct erofs_workgroup *grp);
99108

109+
static void __erofs_workgroup_free(struct erofs_workgroup *grp)
110+
{
111+
atomic_long_dec(&erofs_global_shrink_cnt);
112+
erofs_workgroup_free_rcu(grp);
113+
}
114+
100115
int erofs_workgroup_put(struct erofs_workgroup *grp)
101116
{
102117
int count = atomic_dec_return(&grp->refcount);
103118

104119
if (count == 1)
105120
atomic_long_inc(&erofs_global_shrink_cnt);
106-
else if (!count) {
107-
atomic_long_dec(&erofs_global_shrink_cnt);
108-
erofs_workgroup_free_rcu(grp);
109-
}
121+
else if (!count)
122+
__erofs_workgroup_free(grp);
110123
return count;
111124
}
112125

126+
#ifdef EROFS_FS_HAS_MANAGED_CACHE
127+
/* for cache-managed case, customized reclaim paths exist */
128+
static void erofs_workgroup_unfreeze_final(struct erofs_workgroup *grp)
129+
{
130+
erofs_workgroup_unfreeze(grp, 0);
131+
__erofs_workgroup_free(grp);
132+
}
133+
134+
bool erofs_try_to_release_workgroup(struct erofs_sb_info *sbi,
135+
struct erofs_workgroup *grp,
136+
bool cleanup)
137+
{
138+
/*
139+
* for managed cache enabled, the refcount of workgroups
140+
* themselves could be < 0 (freezed). So there is no guarantee
141+
* that all refcount > 0 if managed cache is enabled.
142+
*/
143+
if (!erofs_workgroup_try_to_freeze(grp, 1))
144+
return false;
145+
146+
/*
147+
* note that all cached pages should be unlinked
148+
* before delete it from the radix tree.
149+
* Otherwise some cached pages of an orphan old workgroup
150+
* could be still linked after the new one is available.
151+
*/
152+
if (erofs_try_to_free_all_cached_pages(sbi, grp)) {
153+
erofs_workgroup_unfreeze(grp, 1);
154+
return false;
155+
}
156+
157+
/*
158+
* it is impossible to fail after the workgroup is freezed,
159+
* however in order to avoid some race conditions, add a
160+
* DBG_BUGON to observe this in advance.
161+
*/
162+
DBG_BUGON(xa_untag_pointer(radix_tree_delete(&sbi->workstn_tree,
163+
grp->index)) != grp);
164+
165+
/*
166+
* if managed cache is enable, the last refcount
167+
* should indicate the related workstation.
168+
*/
169+
erofs_workgroup_unfreeze_final(grp);
170+
return true;
171+
}
172+
173+
#else
174+
/* for nocache case, no customized reclaim path at all */
175+
bool erofs_try_to_release_workgroup(struct erofs_sb_info *sbi,
176+
struct erofs_workgroup *grp,
177+
bool cleanup)
178+
{
179+
int cnt = atomic_read(&grp->refcount);
180+
181+
DBG_BUGON(cnt <= 0);
182+
DBG_BUGON(cleanup && cnt != 1);
183+
184+
if (cnt > 1)
185+
return false;
186+
187+
DBG_BUGON(xa_untag_pointer(radix_tree_delete(&sbi->workstn_tree,
188+
grp->index)) != grp);
189+
190+
/* (rarely) could be grabbed again when freeing */
191+
erofs_workgroup_put(grp);
192+
return true;
193+
}
194+
195+
#endif
196+
113197
unsigned long erofs_shrink_workstation(struct erofs_sb_info *sbi,
114198
unsigned long nr_shrink,
115199
bool cleanup)
@@ -126,42 +210,14 @@ unsigned long erofs_shrink_workstation(struct erofs_sb_info *sbi,
126210
batch, first_index, PAGEVEC_SIZE);
127211

128212
for (i = 0; i < found; ++i) {
129-
int cnt;
130213
struct erofs_workgroup *grp = xa_untag_pointer(batch[i]);
131214

132215
first_index = grp->index + 1;
133216

134-
cnt = atomic_read(&grp->refcount);
135-
BUG_ON(cnt <= 0);
136-
137-
if (cleanup)
138-
BUG_ON(cnt != 1);
139-
140-
#ifndef EROFS_FS_HAS_MANAGED_CACHE
141-
else if (cnt > 1)
142-
#else
143-
if (!erofs_workgroup_try_to_freeze(grp, 1))
144-
#endif
217+
/* try to shrink each valid workgroup */
218+
if (!erofs_try_to_release_workgroup(sbi, grp, cleanup))
145219
continue;
146220

147-
if (xa_untag_pointer(radix_tree_delete(&sbi->workstn_tree,
148-
grp->index)) != grp) {
149-
#ifdef EROFS_FS_HAS_MANAGED_CACHE
150-
skip:
151-
erofs_workgroup_unfreeze(grp, 1);
152-
#endif
153-
continue;
154-
}
155-
156-
#ifdef EROFS_FS_HAS_MANAGED_CACHE
157-
if (erofs_try_to_free_all_cached_pages(sbi, grp))
158-
goto skip;
159-
160-
erofs_workgroup_unfreeze(grp, 1);
161-
#endif
162-
/* (rarely) grabbed again when freeing */
163-
erofs_workgroup_put(grp);
164-
165221
++freed;
166222
if (unlikely(!--nr_shrink))
167223
break;

0 commit comments

Comments
 (0)