Skip to content

Commit 94014a2

Browse files
Darrick J. Wongcmaiolino
authored andcommitted
xfs: fix potential pointer access race in xfs_healthmon_get
Pankaj Raghav asks about this code in xfs_healthmon_get: hm = mp->m_healthmon; if (hm && !refcount_inc_not_zero(&hm->ref)) hm = NULL; rcu_read_unlock(); return hm; (slightly edited to compress a mailing list thread) "Nit: Should we do a READ_ONCE(mp->m_healthmon) here to avoid any compiler tricks that can result in an undefined behaviour? I am not sure if I am being paranoid here. "So this is my understanding: RCU guarantees that we get a valid object (actual data of m_healthmon) but does not guarantee the compiler will not reread the pointer between checking if hm is !NULL and accessing the pointer as we are doing it lockless. "So just a barrier() call in rcu_read_lock is enough to make sure this doesn't happen and probably adding a READ_ONCE() is not needed?" After some initial confusion I concluded that he's correct. The compiler could very well eliminate the hm variable in favor of walking the pointers twice, turning the code into: if (mp->m_healthmon && !refcount_inc_not_zero(&mp->m_healthmon->ref)) If this happens, then xfs_healthmon_detach can sneak in between the two sides of the && expression and set mp->m_healthmon to NULL, and thereby cause a null pointer dereference crash. Fix this by using the rcu pointer assignment and dereference functions, which ensure that the proper reordering barriers are in place. Practically speaking, gcc seems to allocate an actual variable for hm and only reads mp->m_healthmon once (as intended), but we ought to be more explicit about requiring this. Reported-by: Pankaj Raghav <pankaj.raghav@linux.dev> Fixes: a48373e ("xfs: start creating infrastructure for health monitoring") Signed-off-by: "Darrick J. Wong" <djwong@kernel.org> Reviewed-by: Christoph Hellwig <hch@lst.de> Reviewed-by: Carlos Maiolino <cmaiolino@redhat.com> Reviewed-by: Pankaj Raghav <p.raghav@samsung.com> Signed-off-by: Carlos Maiolino <cem@kernel.org>
1 parent eb8550f commit 94014a2

2 files changed

Lines changed: 8 additions & 5 deletions

File tree

fs/xfs/xfs_healthmon.c

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ xfs_healthmon_get(
6969
struct xfs_healthmon *hm;
7070

7171
rcu_read_lock();
72-
hm = mp->m_healthmon;
72+
hm = rcu_dereference(mp->m_healthmon);
7373
if (hm && !refcount_inc_not_zero(&hm->ref))
7474
hm = NULL;
7575
rcu_read_unlock();
@@ -110,13 +110,13 @@ xfs_healthmon_attach(
110110
struct xfs_healthmon *hm)
111111
{
112112
spin_lock(&xfs_healthmon_lock);
113-
if (mp->m_healthmon != NULL) {
113+
if (rcu_access_pointer(mp->m_healthmon) != NULL) {
114114
spin_unlock(&xfs_healthmon_lock);
115115
return -EEXIST;
116116
}
117117

118118
refcount_inc(&hm->ref);
119-
mp->m_healthmon = hm;
119+
rcu_assign_pointer(mp->m_healthmon, hm);
120120
hm->mount_cookie = (uintptr_t)mp->m_super;
121121
spin_unlock(&xfs_healthmon_lock);
122122

@@ -128,13 +128,16 @@ STATIC void
128128
xfs_healthmon_detach(
129129
struct xfs_healthmon *hm)
130130
{
131+
struct xfs_mount *mp;
132+
131133
spin_lock(&xfs_healthmon_lock);
132134
if (hm->mount_cookie == DETACHED_MOUNT_COOKIE) {
133135
spin_unlock(&xfs_healthmon_lock);
134136
return;
135137
}
136138

137-
XFS_M((struct super_block *)hm->mount_cookie)->m_healthmon = NULL;
139+
mp = XFS_M((struct super_block *)hm->mount_cookie);
140+
rcu_assign_pointer(mp->m_healthmon, NULL);
138141
hm->mount_cookie = DETACHED_MOUNT_COOKIE;
139142
spin_unlock(&xfs_healthmon_lock);
140143

fs/xfs/xfs_mount.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -345,7 +345,7 @@ typedef struct xfs_mount {
345345
struct xfs_hooks m_dir_update_hooks;
346346

347347
/* Private data referring to a health monitor object. */
348-
struct xfs_healthmon *m_healthmon;
348+
struct xfs_healthmon __rcu *m_healthmon;
349349
} xfs_mount_t;
350350

351351
#define M_IGEO(mp) (&(mp)->m_ino_geo)

0 commit comments

Comments
 (0)