Skip to content

Commit 8c8afa6

Browse files
amir73ilgregkh
authored andcommitted
fsnotify: fix inode reference leak in fsnotify_recalc_mask()
[ Upstream commit 4aca914 ] fsnotify_recalc_mask() fails to handle the return value of __fsnotify_recalc_mask(), which may return an inode pointer that needs to be released via fsnotify_drop_object() when the connector's HAS_IREF flag transitions from set to cleared. This manifests as a hung task with the following call trace: INFO: task umount:1234 blocked for more than 120 seconds. Call Trace: __schedule schedule fsnotify_sb_delete generic_shutdown_super kill_anon_super cleanup_mnt task_work_run do_exit do_group_exit The race window that triggers the iref leak: Thread A (adding mark) Thread B (removing mark) ────────────────────── ──────────────────────── fsnotify_add_mark_locked(): fsnotify_add_mark_list(): spin_lock(conn->lock) add mark_B(evictable) to list spin_unlock(conn->lock) return /* ---- gap: no lock held ---- */ fsnotify_detach_mark(mark_A): spin_lock(mark_A->lock) clear ATTACHED flag on mark_A spin_unlock(mark_A->lock) fsnotify_put_mark(mark_A) fsnotify_recalc_mask(): spin_lock(conn->lock) __fsnotify_recalc_mask(): /* mark_A skipped: ATTACHED cleared */ /* only mark_B(evictable) remains */ want_iref = false has_iref = true /* not yet cleared */ -> HAS_IREF transitions true -> false -> returns inode pointer spin_unlock(conn->lock) /* BUG: return value discarded! * iput() and fsnotify_put_sb_watched_objects() * are never called */ Fix this by deferring the transition true -> false of HAS_IREF flag from fsnotify_recalc_mask() (Thread A) to fsnotify_put_mark() (thread B). Fixes: c3638b5 ("fsnotify: allow adding an inode mark without pinning inode") Signed-off-by: Xin Yin <yinxin.x@bytedance.com> Signed-off-by: Amir Goldstein <amir73il@gmail.com> Link: https://patch.msgid.link/CAOQ4uxiPsbHb0o5voUKyPFMvBsDkG914FYDcs4C5UpBMNm0Vcg@mail.gmail.com Signed-off-by: Jan Kara <jack@suse.cz> Signed-off-by: Sasha Levin <sashal@kernel.org>
1 parent bd89108 commit 8c8afa6

1 file changed

Lines changed: 36 additions & 3 deletions

File tree

fs/notify/mark.c

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,12 @@ static struct inode *fsnotify_update_iref(struct fsnotify_mark_connector *conn,
233233
return inode;
234234
}
235235

236-
static void *__fsnotify_recalc_mask(struct fsnotify_mark_connector *conn)
236+
/*
237+
* Calculate mask of events for a list of marks.
238+
*
239+
* Return true if any of the attached marks want to hold an inode reference.
240+
*/
241+
static bool __fsnotify_recalc_mask(struct fsnotify_mark_connector *conn)
237242
{
238243
u32 new_mask = 0;
239244
bool want_iref = false;
@@ -257,6 +262,34 @@ static void *__fsnotify_recalc_mask(struct fsnotify_mark_connector *conn)
257262
*/
258263
WRITE_ONCE(*fsnotify_conn_mask_p(conn), new_mask);
259264

265+
return want_iref;
266+
}
267+
268+
/*
269+
* Calculate mask of events for a list of marks after attach/modify mark
270+
* and get an inode reference for the connector if needed.
271+
*
272+
* A concurrent add of evictable mark and detach of non-evictable mark can
273+
* lead to __fsnotify_recalc_mask() returning false want_iref, but in this
274+
* case we defer clearing iref to fsnotify_recalc_mask_clear_iref() called
275+
* from fsnotify_put_mark().
276+
*/
277+
static void fsnotify_recalc_mask_set_iref(struct fsnotify_mark_connector *conn)
278+
{
279+
bool has_iref = conn->flags & FSNOTIFY_CONN_FLAG_HAS_IREF;
280+
bool want_iref = __fsnotify_recalc_mask(conn) || has_iref;
281+
282+
(void) fsnotify_update_iref(conn, want_iref);
283+
}
284+
285+
/*
286+
* Calculate mask of events for a list of marks after detach mark
287+
* and return the inode object if its reference is no longer needed.
288+
*/
289+
static void *fsnotify_recalc_mask_clear_iref(struct fsnotify_mark_connector *conn)
290+
{
291+
bool want_iref = __fsnotify_recalc_mask(conn);
292+
260293
return fsnotify_update_iref(conn, want_iref);
261294
}
262295

@@ -293,7 +326,7 @@ void fsnotify_recalc_mask(struct fsnotify_mark_connector *conn)
293326

294327
spin_lock(&conn->lock);
295328
update_children = !fsnotify_conn_watches_children(conn);
296-
__fsnotify_recalc_mask(conn);
329+
fsnotify_recalc_mask_set_iref(conn);
297330
update_children &= fsnotify_conn_watches_children(conn);
298331
spin_unlock(&conn->lock);
299332
/*
@@ -408,7 +441,7 @@ void fsnotify_put_mark(struct fsnotify_mark *mark)
408441
/* Update watched objects after detaching mark */
409442
if (sb)
410443
fsnotify_update_sb_watchers(sb, conn);
411-
objp = __fsnotify_recalc_mask(conn);
444+
objp = fsnotify_recalc_mask_clear_iref(conn);
412445
type = conn->type;
413446
}
414447
WRITE_ONCE(mark->connector, NULL);

0 commit comments

Comments
 (0)