| From 8dd601fa8317243be887458c49f6c29c2f3d719f Mon Sep 17 00:00:00 2001 |
| From: NeilBrown <neilb@suse.com> |
| Date: Thu, 15 Feb 2018 20:00:15 +1100 |
| Subject: dm: correctly handle chained bios in dec_pending() |
| |
| From: NeilBrown <neilb@suse.com> |
| |
| commit 8dd601fa8317243be887458c49f6c29c2f3d719f upstream. |
| |
| dec_pending() is given an error status (possibly 0) to be recorded |
| against a bio. It can be called several times on the one 'struct |
| dm_io', and it is careful to only assign a non-zero error to |
| io->status. However when it then assigned io->status to bio->bi_status, |
| it is not careful and could overwrite a genuine error status with 0. |
| |
| This can happen when chained bios are in use. If a bio is chained |
| beneath the bio that this dm_io is handling, the child bio might |
| complete and set bio->bi_status before the dm_io completes. |
| |
| This has been possible since chained bios were introduced in 3.14, and |
| has become a lot easier to trigger with commit 18a25da84354 ("dm: ensure |
| bio submission follows a depth-first tree walk") as that commit caused |
| dm to start using chained bios itself. |
| |
| A particular failure mode is that if a bio spans an 'error' target and a |
| working target, the 'error' fragment will complete instantly and set the |
| ->bi_status, and the other fragment will normally complete a little |
| later, and will clear ->bi_status. |
| |
| The fix is simply to only assign io_error to bio->bi_status when |
| io_error is not zero. |
| |
| Reported-and-tested-by: Milan Broz <gmazyland@gmail.com> |
| Cc: stable@vger.kernel.org (v3.14+) |
| Signed-off-by: NeilBrown <neilb@suse.com> |
| Signed-off-by: Mike Snitzer <snitzer@redhat.com> |
| Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> |
| |
| --- |
| drivers/md/dm.c | 3 ++- |
| 1 file changed, 2 insertions(+), 1 deletion(-) |
| |
| --- a/drivers/md/dm.c |
| +++ b/drivers/md/dm.c |
| @@ -974,7 +974,8 @@ static void dec_pending(struct dm_io *io |
| } else { |
| /* done with normal IO or empty flush */ |
| trace_block_bio_complete(md->queue, bio, io_error); |
| - bio->bi_error = io_error; |
| + if (io_error) |
| + bio->bi_error = io_error; |
| bio_endio(bio); |
| } |
| } |