gfs2: fix an oops in gfs2_permission
authorAl Viro <viro@zeniv.linux.org.uk>
Mon, 2 Oct 2023 02:33:44 +0000 (03:33 +0100)
committerAndreas Gruenbacher <agruenba@redhat.com>
Tue, 3 Oct 2023 14:47:21 +0000 (16:47 +0200)
In RCU mode, we might race with gfs2_evict_inode(), which zeroes
->i_gl.  Freeing of the object it points to is RCU-delayed, so
if we manage to fetch the pointer before it's been replaced with
NULL, we are fine.  Check if we'd fetched NULL and treat that
as "bail out and tell the caller to get out of RCU mode".

Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
Signed-off-by: Andreas Gruenbacher <agruenba@redhat.com>
fs/gfs2/inode.c
fs/gfs2/super.c

index 2880d6ca19e10bdc5e12e9ac2ed68e6fb7f98b1a..697f95047033d27cdf08772495483f8fccea805b 100644 (file)
@@ -1868,14 +1868,21 @@ int gfs2_permission(struct mnt_idmap *idmap, struct inode *inode,
 {
        struct gfs2_inode *ip;
        struct gfs2_holder i_gh;
+       struct gfs2_glock *gl;
        int error;
 
        gfs2_holder_mark_uninitialized(&i_gh);
        ip = GFS2_I(inode);
-       if (gfs2_glock_is_locked_by_me(ip->i_gl) == NULL) {
+       gl = rcu_dereference(ip->i_gl);
+       if (unlikely(!gl)) {
+               /* inode is getting torn down, must be RCU mode */
+               WARN_ON_ONCE(!(mask & MAY_NOT_BLOCK));
+               return -ECHILD;
+        }
+       if (gfs2_glock_is_locked_by_me(gl) == NULL) {
                if (mask & MAY_NOT_BLOCK)
                        return -ECHILD;
-               error = gfs2_glock_nq_init(ip->i_gl, LM_ST_SHARED, LM_FLAG_ANY, &i_gh);
+               error = gfs2_glock_nq_init(gl, LM_ST_SHARED, LM_FLAG_ANY, &i_gh);
                if (error)
                        return error;
        }
index 3141db77189e99b1ded6373b14b6a23508150bec..dd2720a5ca62f88b578ac2f2a276f2f7c1d877f3 100644 (file)
@@ -1540,7 +1540,7 @@ out:
                wait_on_bit_io(&ip->i_flags, GIF_GLOP_PENDING, TASK_UNINTERRUPTIBLE);
                gfs2_glock_add_to_lru(ip->i_gl);
                gfs2_glock_put_eventually(ip->i_gl);
-               ip->i_gl = NULL;
+               rcu_assign_pointer(ip->i_gl, NULL);
        }
 }