From: Ignat Korchagin <ig...@cloudflare.com>

commit 8e14f610159d524cd7aac37982826d3ef75c09e8 upstream.

Sometimes, when dm-crypt executes decryption in a tasklet, we may get
"BUG: KASAN: use-after-free in tasklet_action_common.constprop..."
with a kasan-enabled kernel.

When the decryption fully completes in the tasklet, dm-crypt will call
bio_endio(), which in turn will call clone_endio() from dm.c core code. That
function frees the resources associated with the bio, including per bio private
structures. For dm-crypt it will free the current struct dm_crypt_io, which
contains our tasklet object, causing use-after-free, when the tasklet is being
dequeued by the kernel.

To avoid this, do not call bio_endio() from the current tasklet context, but
delay its execution to the dm-crypt IO workqueue.

Fixes: 39d42fa96ba1 ("dm crypt: add flags to optionally bypass kcryptd 
workqueues")
Cc: <sta...@vger.kernel.org> # v5.9+
Signed-off-by: Ignat Korchagin <ig...@cloudflare.com>
Signed-off-by: Mike Snitzer <snit...@redhat.com>
Signed-off-by: Greg Kroah-Hartman <gre...@linuxfoundation.org>

---
 drivers/md/dm-crypt.c |   24 +++++++++++++++++++++++-
 1 file changed, 23 insertions(+), 1 deletion(-)

--- a/drivers/md/dm-crypt.c
+++ b/drivers/md/dm-crypt.c
@@ -1730,6 +1730,12 @@ static void crypt_inc_pending(struct dm_
        atomic_inc(&io->io_pending);
 }
 
+static void kcryptd_io_bio_endio(struct work_struct *work)
+{
+       struct dm_crypt_io *io = container_of(work, struct dm_crypt_io, work);
+       bio_endio(io->base_bio);
+}
+
 /*
  * One of the bios was finished. Check for completion of
  * the whole request and correctly clean up the buffer.
@@ -1752,7 +1758,23 @@ static void crypt_dec_pending(struct dm_
                kfree(io->integrity_metadata);
 
        base_bio->bi_status = error;
-       bio_endio(base_bio);
+
+       /*
+        * If we are running this function from our tasklet,
+        * we can't call bio_endio() here, because it will call
+        * clone_endio() from dm.c, which in turn will
+        * free the current struct dm_crypt_io structure with
+        * our tasklet. In this case we need to delay bio_endio()
+        * execution to after the tasklet is done and dequeued.
+        */
+       if (tasklet_trylock(&io->tasklet)) {
+               tasklet_unlock(&io->tasklet);
+               bio_endio(base_bio);
+               return;
+       }
+
+       INIT_WORK(&io->work, kcryptd_io_bio_endio);
+       queue_work(cc->io_queue, &io->work);
 }
 
 /*


Reply via email to