Skip to content

Commit

Permalink
gfs2: Fix gfs2_lookup_by_inum lock inversion
Browse files Browse the repository at this point in the history
The current gfs2_lookup_by_inum takes the glock of a presumed inode
identified by block number, verifies that the block is indeed an inode,
and then instantiates and reads the new inode via gfs2_inode_lookup.

However, instantiating a new inode may block on freeing a previous
instance of that inode (__wait_on_freeing_inode), and freeing an inode
requires to take the glock already held, leading to lock inversion and
deadlock.

Fix this by first instantiating the new inode, then verifying that the
block is an inode (if required), and then reading in the new inode, all
in gfs2_inode_lookup.

If the block we are looking for is not an inode, we discard the new
inode via iget_failed, which marks inodes as bad and unhashes them.
Other tasks waiting on that inode will get back a bad inode back from
ilookup or iget_locked; in that case, retry the lookup.

Signed-off-by: Andreas Gruenbacher <[email protected]>
Signed-off-by: Bob Peterson <[email protected]>
  • Loading branch information
Andreas Gruenbacher authored and AstralBob committed Jun 27, 2016
1 parent 1e875f5 commit 3ce37b2
Show file tree
Hide file tree
Showing 5 changed files with 81 additions and 33 deletions.
3 changes: 2 additions & 1 deletion fs/gfs2/dir.c
Original file line number Diff line number Diff line change
Expand Up @@ -1660,7 +1660,8 @@ struct inode *gfs2_dir_search(struct inode *dir, const struct qstr *name,
brelse(bh);
if (fail_on_exist)
return ERR_PTR(-EEXIST);
inode = gfs2_inode_lookup(dir->i_sb, dtype, addr, formal_ino);
inode = gfs2_inode_lookup(dir->i_sb, dtype, addr, formal_ino,
GFS2_BLKST_FREE /* ignore */);
if (!IS_ERR(inode))
GFS2_I(inode)->i_rahead = rahead;
return inode;
Expand Down
4 changes: 2 additions & 2 deletions fs/gfs2/glock.c
Original file line number Diff line number Diff line change
Expand Up @@ -576,7 +576,7 @@ static void delete_work_func(struct work_struct *work)
struct gfs2_glock *gl = container_of(work, struct gfs2_glock, gl_delete);
struct gfs2_sbd *sdp = gl->gl_name.ln_sbd;
struct gfs2_inode *ip;
struct inode *inode;
struct inode *inode = NULL;
u64 no_addr = gl->gl_name.ln_number;

/* If someone's using this glock to create a new dinode, the block must
Expand All @@ -590,7 +590,7 @@ static void delete_work_func(struct work_struct *work)

if (ip)
inode = gfs2_ilookup(sdp->sd_vfs, no_addr);
else
if (IS_ERR_OR_NULL(inode))
inode = gfs2_lookup_by_inum(sdp, no_addr, NULL, GFS2_BLKST_UNLINKED);
if (inode && !IS_ERR(inode)) {
d_prune_aliases(inode);
Expand Down
101 changes: 73 additions & 28 deletions fs/gfs2/inode.c
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,33 @@

struct inode *gfs2_ilookup(struct super_block *sb, u64 no_addr)
{
return ilookup(sb, (unsigned long)no_addr);
struct inode *inode;

repeat:
inode = ilookup(sb, no_addr);
if (!inode)
return inode;
if (is_bad_inode(inode)) {
iput(inode);
goto repeat;
}
return inode;
}

static struct inode *gfs2_iget(struct super_block *sb, u64 no_addr)
{
struct inode *inode;

repeat:
inode = iget_locked(sb, no_addr);
if (!inode)
return inode;
if (is_bad_inode(inode)) {
iput(inode);
goto repeat;
}
GFS2_I(inode)->i_no_addr = no_addr;
return inode;
}

/**
Expand Down Expand Up @@ -78,26 +104,37 @@ static void gfs2_set_iop(struct inode *inode)
/**
* gfs2_inode_lookup - Lookup an inode
* @sb: The super block
* @no_addr: The inode number
* @type: The type of the inode
* @no_addr: The inode number
* @no_formal_ino: The inode generation number
* @blktype: Requested block type (GFS2_BLKST_DINODE or GFS2_BLKST_UNLINKED;
* GFS2_BLKST_FREE do indicate not to verify)
*
* If @type is DT_UNKNOWN, the inode type is fetched from disk.
*
* If @blktype is anything other than GFS2_BLKST_FREE (which is used as a
* placeholder because it doesn't otherwise make sense), the on-disk block type
* is verified to be @blktype.
*
* Returns: A VFS inode, or an error
*/

struct inode *gfs2_inode_lookup(struct super_block *sb, unsigned int type,
u64 no_addr, u64 no_formal_ino)
u64 no_addr, u64 no_formal_ino,
unsigned int blktype)
{
struct inode *inode;
struct gfs2_inode *ip;
struct gfs2_glock *io_gl = NULL;
struct gfs2_holder i_gh;
bool unlock = false;
int error;

inode = iget_locked(sb, (unsigned long)no_addr);
inode = gfs2_iget(sb, no_addr);
if (!inode)
return ERR_PTR(-ENOMEM);

ip = GFS2_I(inode);
ip->i_no_addr = no_addr;

if (inode->i_state & I_NEW) {
struct gfs2_sbd *sdp = GFS2_SB(inode);
Expand All @@ -112,10 +149,30 @@ struct inode *gfs2_inode_lookup(struct super_block *sb, unsigned int type,
if (unlikely(error))
goto fail_put;

if (type == DT_UNKNOWN || blktype != GFS2_BLKST_FREE) {
/*
* The GL_SKIP flag indicates to skip reading the inode
* block. We read the inode with gfs2_inode_refresh
* after possibly checking the block type.
*/
error = gfs2_glock_nq_init(ip->i_gl, LM_ST_EXCLUSIVE,
GL_SKIP, &i_gh);
if (error)
goto fail_put;
unlock = true;

if (blktype != GFS2_BLKST_FREE) {
error = gfs2_check_blk_type(sdp, no_addr,
blktype);
if (error)
goto fail_put;
}
}

set_bit(GIF_INVALID, &ip->i_flags);
error = gfs2_glock_nq_init(io_gl, LM_ST_SHARED, GL_EXACT, &ip->i_iopen_gh);
if (unlikely(error))
goto fail_iopen;
goto fail_put;

ip->i_iopen_gh.gh_gl->gl_object = ip;
gfs2_glock_put(io_gl);
Expand All @@ -134,17 +191,20 @@ struct inode *gfs2_inode_lookup(struct super_block *sb, unsigned int type,
unlock_new_inode(inode);
}

if (unlock)
gfs2_glock_dq_uninit(&i_gh);
return inode;

fail_refresh:
ip->i_iopen_gh.gh_flags |= GL_NOCACHE;
ip->i_iopen_gh.gh_gl->gl_object = NULL;
gfs2_glock_dq_wait(&ip->i_iopen_gh);
gfs2_holder_uninit(&ip->i_iopen_gh);
fail_iopen:
fail_put:
if (io_gl)
gfs2_glock_put(io_gl);
fail_put:
if (unlock)
gfs2_glock_dq_uninit(&i_gh);
ip->i_gl->gl_object = NULL;
fail:
iget_failed(inode);
Expand All @@ -155,23 +215,12 @@ struct inode *gfs2_lookup_by_inum(struct gfs2_sbd *sdp, u64 no_addr,
u64 *no_formal_ino, unsigned int blktype)
{
struct super_block *sb = sdp->sd_vfs;
struct gfs2_holder i_gh;
struct inode *inode = NULL;
struct inode *inode;
int error;

/* Must not read in block until block type is verified */
error = gfs2_glock_nq_num(sdp, no_addr, &gfs2_inode_glops,
LM_ST_EXCLUSIVE, GL_SKIP, &i_gh);
if (error)
return ERR_PTR(error);

error = gfs2_check_blk_type(sdp, no_addr, blktype);
if (error)
goto fail;

inode = gfs2_inode_lookup(sb, DT_UNKNOWN, no_addr, 0);
inode = gfs2_inode_lookup(sb, DT_UNKNOWN, no_addr, 0, blktype);
if (IS_ERR(inode))
goto fail;
return inode;

/* Two extra checks for NFS only */
if (no_formal_ino) {
Expand All @@ -182,16 +231,12 @@ struct inode *gfs2_lookup_by_inum(struct gfs2_sbd *sdp, u64 no_addr,
error = -EIO;
if (GFS2_I(inode)->i_diskflags & GFS2_DIF_SYSTEM)
goto fail_iput;

error = 0;
}
return inode;

fail:
gfs2_glock_dq_uninit(&i_gh);
return error ? ERR_PTR(error) : inode;
fail_iput:
iput(inode);
goto fail;
return ERR_PTR(error);
}


Expand Down
3 changes: 2 additions & 1 deletion fs/gfs2/inode.h
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,8 @@ static inline int gfs2_check_internal_file_size(struct inode *inode,
}

extern struct inode *gfs2_inode_lookup(struct super_block *sb, unsigned type,
u64 no_addr, u64 no_formal_ino);
u64 no_addr, u64 no_formal_ino,
unsigned int blktype);
extern struct inode *gfs2_lookup_by_inum(struct gfs2_sbd *sdp, u64 no_addr,
u64 *no_formal_ino,
unsigned int blktype);
Expand Down
3 changes: 2 additions & 1 deletion fs/gfs2/ops_fstype.c
Original file line number Diff line number Diff line change
Expand Up @@ -454,7 +454,8 @@ static int gfs2_lookup_root(struct super_block *sb, struct dentry **dptr,
struct dentry *dentry;
struct inode *inode;

inode = gfs2_inode_lookup(sb, DT_DIR, no_addr, 0);
inode = gfs2_inode_lookup(sb, DT_DIR, no_addr, 0,
GFS2_BLKST_FREE /* ignore */);
if (IS_ERR(inode)) {
fs_err(sdp, "can't read in %s inode: %ld\n", name, PTR_ERR(inode));
return PTR_ERR(inode);
Expand Down

0 comments on commit 3ce37b2

Please sign in to comment.