aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBrian Foster <bfoster@redhat.com>2015-08-18 20:32:33 -0400
committerDave Chinner <david@fromorbit.com>2015-08-18 20:32:33 -0400
commit7df1c170b9a45ab3a7401c79bbefa9939bf8eafb (patch)
tree0536f7d862f6006ae430dcd1b76801796688c05b
parent1b867d3ab562b6b03e46113fad3e87b05fbfbb85 (diff)
xfs: swap leaf buffer into path struct atomically during path shift
The node directory lookup code uses a state structure that tracks the path of buffers used to search for the hash of a filename through the leaf blocks. When the lookup encounters a block that ends with the requested hash, but the entry has not yet been found, it must shift over to the next block and continue looking for the entry (i.e., duplicate hashes could continue over into the next block). This shift mechanism involves walking back up and down the state structure, replacing buffers at the appropriate btree levels as necessary. When a buffer is replaced, the old buffer is released and the new buffer read into the active slot in the path structure. Because the buffer is read directly into the path slot, a buffer read failure can result in setting a NULL buffer pointer in an active slot. This throws off the state cleanup code in xfs_dir2_node_lookup(), which expects to release a buffer from each active slot. Instead, a BUG occurs due to a NULL pointer dereference: BUG: unable to handle kernel NULL pointer dereference at 00000000000001e8 IP: [<ffffffffa0585063>] xfs_trans_brelse+0x2a3/0x3c0 [xfs] ... RIP: 0010:[<ffffffffa0585063>] [<ffffffffa0585063>] xfs_trans_brelse+0x2a3/0x3c0 [xfs] ... Call Trace: [<ffffffffa05250c6>] xfs_dir2_node_lookup+0xa6/0x2c0 [xfs] [<ffffffffa0519f7c>] xfs_dir_lookup+0x1ac/0x1c0 [xfs] [<ffffffffa055d0e1>] xfs_lookup+0x91/0x290 [xfs] [<ffffffffa05580b3>] xfs_vn_lookup+0x73/0xb0 [xfs] [<ffffffff8122de8d>] lookup_real+0x1d/0x50 [<ffffffff8123330e>] path_openat+0x91e/0x1490 [<ffffffff81235079>] do_filp_open+0x89/0x100 ... This has been reproduced via a parallel fsstress and filesystem shutdown workload in a loop. The shutdown triggers the read error in the aforementioned codepath and causes the BUG in xfs_dir2_node_lookup(). Update xfs_da3_path_shift() to update the active path slot atomically with respect to the caller when a buffer is replaced. This ensures that the caller always sees the old or new buffer in the slot and prevents the NULL pointer dereference. Signed-off-by: Brian Foster <bfoster@redhat.com> Reviewed-by: Dave Chinner <dchinner@redhat.com> Signed-off-by: Dave Chinner <david@fromorbit.com>
-rw-r--r--fs/xfs/libxfs/xfs_da_btree.c23
1 files changed, 14 insertions, 9 deletions
diff --git a/fs/xfs/libxfs/xfs_da_btree.c b/fs/xfs/libxfs/xfs_da_btree.c
index e9f6709a3846..f6a8631dc771 100644
--- a/fs/xfs/libxfs/xfs_da_btree.c
+++ b/fs/xfs/libxfs/xfs_da_btree.c
@@ -1822,6 +1822,7 @@ xfs_da3_path_shift(
1822 struct xfs_da_args *args; 1822 struct xfs_da_args *args;
1823 struct xfs_da_node_entry *btree; 1823 struct xfs_da_node_entry *btree;
1824 struct xfs_da3_icnode_hdr nodehdr; 1824 struct xfs_da3_icnode_hdr nodehdr;
1825 struct xfs_buf *bp;
1825 xfs_dablk_t blkno = 0; 1826 xfs_dablk_t blkno = 0;
1826 int level; 1827 int level;
1827 int error; 1828 int error;
@@ -1866,20 +1867,24 @@ xfs_da3_path_shift(
1866 */ 1867 */
1867 for (blk++, level++; level < path->active; blk++, level++) { 1868 for (blk++, level++; level < path->active; blk++, level++) {
1868 /* 1869 /*
1869 * Release the old block. 1870 * Read the next child block into a local buffer.
1870 * (if it's dirty, trans won't actually let go)
1871 */ 1871 */
1872 if (release) 1872 error = xfs_da3_node_read(args->trans, dp, blkno, -1, &bp,
1873 xfs_trans_brelse(args->trans, blk->bp); 1873 args->whichfork);
1874 if (error)
1875 return error;
1874 1876
1875 /* 1877 /*
1876 * Read the next child block. 1878 * Release the old block (if it's dirty, the trans doesn't
1879 * actually let go) and swap the local buffer into the path
1880 * structure. This ensures failure of the above read doesn't set
1881 * a NULL buffer in an active slot in the path.
1877 */ 1882 */
1883 if (release)
1884 xfs_trans_brelse(args->trans, blk->bp);
1878 blk->blkno = blkno; 1885 blk->blkno = blkno;
1879 error = xfs_da3_node_read(args->trans, dp, blkno, -1, 1886 blk->bp = bp;
1880 &blk->bp, args->whichfork); 1887
1881 if (error)
1882 return error;
1883 info = blk->bp->b_addr; 1888 info = blk->bp->b_addr;
1884 ASSERT(info->magic == cpu_to_be16(XFS_DA_NODE_MAGIC) || 1889 ASSERT(info->magic == cpu_to_be16(XFS_DA_NODE_MAGIC) ||
1885 info->magic == cpu_to_be16(XFS_DA3_NODE_MAGIC) || 1890 info->magic == cpu_to_be16(XFS_DA3_NODE_MAGIC) ||