[PATCH] xfs: add readahead bufs to lru early to prevent post-unmount panic

Brian Foster
2016-06-30T12:53:56Z

Newly allocated XFS metadata buffers are added to the LRU once the hold
count is released, which typically occurs after I/O completion. There is
no other mechanism at current that tracks the existence or I/O state of
a new buffer. Further, readahead I/O tends to be submitted
asynchronously by nature, which means the I/O can remain in flight and
actually complete long after the calling context is gone. This means
that file descriptors or any other holds on the filesystem can be
released, allowing the filesystem to be unmounted while I/O is still in
flight. When I/O completion occurs, core data structures may have been
freed, causing completion to run into invalid memory accesses and likely
to panic.

This problem is reproduced on XFS via directory readahead. A filesystem
is mounted, a directory is opened/closed and the filesystem immediately
unmounted. The open/close cycle triggers a directory readahead that if
delayed long enough, runs buffer I/O completion after the unmount has
completed.

To work around this problem, add readahead buffers to the LRU earlier
than other buffers (when the buffer is allocated, specifically). The
buffer hold count will ultimately remain until I/O completion, which
means any shrinker activity will skip the buffer until then. This makes
the buffer visible to xfs_wait_buftarg(), however, which ensures that an
unmount or quiesce waits for I/O completion appropriately.

Signed-off-by: Brian Foster <bfoster@redhat.com>
---

This addresses the problem reproduced by the recently posted xfstests
test:

http://thread.gmane.org/gmane.comp.file-systems.fstests/2740

This could probably be made more involved, i.e., to create another list
of buffers in flight or some such. This seems more simple/sane to me,
however, and survives my testing so far...

Brian

fs/xfs/xfs_buf.c | 12 ++++++++++++
1 file changed, 12 insertions(+)

diff --git a/fs/xfs/xfs_buf.c b/fs/xfs/xfs_buf.c
index 4665ff6..3f03df9 100644
--- a/fs/xfs/xfs_buf.c
+++ b/fs/xfs/xfs_buf.c
@@ -590,8 +590,20 @@ xfs_buf_get_map(
return NULL;
}

+ /*
+ * If the buffer found doesn't match the one allocated above, somebody
+ * else beat us to insertion and we can toss the new one.
+ *
+ * If we did add the buffer and it happens to be readahead, add to the
+ * LRU now rather than waiting until the hold is released. Otherwise,
+ * the buffer is not visible to xfs_wait_buftarg() while in flight and
+ * nothing else prevents an unmount before I/O completion.
+ */
if (bp != new_bp)
xfs_buf_free(new_bp);
+ else if (flags & XBF_READ_AHEAD &&
+ list_lru_add(&bp->b_target->bt_lru, &bp->b_lru))
+ atomic_inc(&bp->b_hold);

found:
if (!bp->b_addr) {
--
2.5.5