summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAlexander Motin <[email protected]>2019-12-03 12:59:30 -0500
committerBrian Behlendorf <[email protected]>2019-12-03 09:59:30 -0800
commit5ff2249fa579c20c12bce627e284415720fd4412 (patch)
tree2a84802e184e698ba07c49764ac722ebd4e84371
parent624222ae31bc8ce0009c7da9b0877a3c93aa56fb (diff)
Fix use-after-free in case of L2ARC prefetch failure
In case L2ARC read failed, l2arc_read_done() creates _different_ ZIO to read data from the original storage device. Unfortunately pointer to the failed ZIO remains in hdr->b_l1hdr.b_acb->acb_zio_head, and if some other read try to bump the ZIO priority, it will crash. The problem is reproducible by corrupting L2ARC content and reading some data with prefetch if l2arc_noprefetch tunable is changed to 0. With the default setting the issue is probably not reproducible now. Reviewed-by: Tom Caputi <[email protected]> Reviewed-by: Brian Behlendorf <[email protected]> Signed-off-by: Alexander Motin <[email protected]> Sponsored-By: iXsystems, Inc. Closes #9648
-rw-r--r--module/zfs/arc.c19
1 files changed, 16 insertions, 3 deletions
diff --git a/module/zfs/arc.c b/module/zfs/arc.c
index 87df07cc0..2ca4b6bef 100644
--- a/module/zfs/arc.c
+++ b/module/zfs/arc.c
@@ -7873,7 +7873,6 @@ l2arc_read_done(zio_t *zio)
zio->io_private = hdr;
arc_read_done(zio);
} else {
- mutex_exit(hash_lock);
/*
* Buffer didn't survive caching. Increment stats and
* reissue to the original storage device.
@@ -7898,10 +7897,24 @@ l2arc_read_done(zio_t *zio)
ASSERT(!pio || pio->io_child_type == ZIO_CHILD_LOGICAL);
- zio_nowait(zio_read(pio, zio->io_spa, zio->io_bp,
+ zio = zio_read(pio, zio->io_spa, zio->io_bp,
abd, zio->io_size, arc_read_done,
hdr, zio->io_priority, cb->l2rcb_flags,
- &cb->l2rcb_zb));
+ &cb->l2rcb_zb);
+
+ /*
+ * Original ZIO will be freed, so we need to update
+ * ARC header with the new ZIO pointer to be used
+ * by zio_change_priority() in arc_read().
+ */
+ for (struct arc_callback *acb = hdr->b_l1hdr.b_acb;
+ acb != NULL; acb = acb->acb_next)
+ acb->acb_zio_head = zio;
+
+ mutex_exit(hash_lock);
+ zio_nowait(zio);
+ } else {
+ mutex_exit(hash_lock);
}
}