Skip to content

Commit

Permalink
NFSv4: Fix a pNFS layout related use-after-free race when freeing the…
Browse files Browse the repository at this point in the history
… inode

When returning the layout in nfs4_evict_inode(), we need to ensure that
the layout is actually done being freed before we can proceed to free the
inode itself.

Signed-off-by: Trond Myklebust <trond.myklebust@hammerspace.com>
  • Loading branch information
Trond Myklebust committed Dec 2, 2020
1 parent 1706846 commit b6d49ec
Show file tree
Hide file tree
Showing 3 changed files with 37 additions and 3 deletions.
2 changes: 1 addition & 1 deletion fs/nfs/nfs4super.c
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ static void nfs4_evict_inode(struct inode *inode)
nfs_inode_evict_delegation(inode);
/* Note that above delegreturn would trigger pnfs return-on-close */
pnfs_return_layout(inode);
pnfs_destroy_layout(NFS_I(inode));
pnfs_destroy_layout_final(NFS_I(inode));
/* First call standard NFS clear_inode() code */
nfs_clear_inode(inode);
nfs4_xattr_cache_zap(inode);
Expand Down
33 changes: 31 additions & 2 deletions fs/nfs/pnfs.c
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,7 @@ void
pnfs_put_layout_hdr(struct pnfs_layout_hdr *lo)
{
struct inode *inode;
unsigned long i_state;

if (!lo)
return;
Expand All @@ -304,8 +305,12 @@ pnfs_put_layout_hdr(struct pnfs_layout_hdr *lo)
if (!list_empty(&lo->plh_segs))
WARN_ONCE(1, "NFS: BUG unfreed layout segments.\n");
pnfs_detach_layout_hdr(lo);
i_state = inode->i_state;
spin_unlock(&inode->i_lock);
pnfs_free_layout_hdr(lo);
/* Notify pnfs_destroy_layout_final() that we're done */
if (i_state & (I_FREEING | I_CLEAR))
wake_up_var(lo);
}
}

Expand Down Expand Up @@ -734,8 +739,7 @@ pnfs_free_lseg_list(struct list_head *free_me)
}
}

void
pnfs_destroy_layout(struct nfs_inode *nfsi)
static struct pnfs_layout_hdr *__pnfs_destroy_layout(struct nfs_inode *nfsi)
{
struct pnfs_layout_hdr *lo;
LIST_HEAD(tmp_list);
Expand All @@ -753,9 +757,34 @@ pnfs_destroy_layout(struct nfs_inode *nfsi)
pnfs_put_layout_hdr(lo);
} else
spin_unlock(&nfsi->vfs_inode.i_lock);
return lo;
}

void pnfs_destroy_layout(struct nfs_inode *nfsi)
{
__pnfs_destroy_layout(nfsi);
}
EXPORT_SYMBOL_GPL(pnfs_destroy_layout);

static bool pnfs_layout_removed(struct nfs_inode *nfsi,
struct pnfs_layout_hdr *lo)
{
bool ret;

spin_lock(&nfsi->vfs_inode.i_lock);
ret = nfsi->layout != lo;
spin_unlock(&nfsi->vfs_inode.i_lock);
return ret;
}

void pnfs_destroy_layout_final(struct nfs_inode *nfsi)
{
struct pnfs_layout_hdr *lo = __pnfs_destroy_layout(nfsi);

if (lo)
wait_var_event(lo, pnfs_layout_removed(nfsi, lo));
}

static bool
pnfs_layout_add_bulk_destroy_list(struct inode *inode,
struct list_head *layout_list)
Expand Down
5 changes: 5 additions & 0 deletions fs/nfs/pnfs.h
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,7 @@ struct pnfs_layout_segment *pnfs_layout_process(struct nfs4_layoutget *lgp);
void pnfs_layoutget_free(struct nfs4_layoutget *lgp);
void pnfs_free_lseg_list(struct list_head *tmp_list);
void pnfs_destroy_layout(struct nfs_inode *);
void pnfs_destroy_layout_final(struct nfs_inode *);
void pnfs_destroy_all_layouts(struct nfs_client *);
int pnfs_destroy_layouts_byfsid(struct nfs_client *clp,
struct nfs_fsid *fsid,
Expand Down Expand Up @@ -712,6 +713,10 @@ static inline void pnfs_destroy_layout(struct nfs_inode *nfsi)
{
}

static inline void pnfs_destroy_layout_final(struct nfs_inode *nfsi)
{
}

static inline struct pnfs_layout_segment *
pnfs_get_lseg(struct pnfs_layout_segment *lseg)
{
Expand Down

0 comments on commit b6d49ec

Please sign in to comment.