@@ -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
98107extern 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+
100115int 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+
113197unsigned 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