[X,1/6] md/raid0: avoid RAID0 data corruption due to layout confusion.
diff mbox series

Message ID 20200115091428.32502-2-stefan.bader@canonical.com
State New
Headers show
Series
  • (Re)apply RAID0 layout support
Related show

Commit Message

Stefan Bader Jan. 15, 2020, 9:14 a.m. UTC
From: NeilBrown <neilb@suse.de>

BugLink: https://bugs.launchpad.net/bugs/1850540

[ Upstream commit c84a1372df929033cb1a0441fb57bd3932f39ac9 ]

If the drives in a RAID0 are not all the same size, the array is
divided into zones.
The first zone covers all drives, to the size of the smallest.
The second zone covers all drives larger than the smallest, up to
the size of the second smallest - etc.

A change in Linux 3.14 unintentionally changed the layout for the
second and subsequent zones.  All the correct data is still stored, but
each chunk may be assigned to a different device than in pre-3.14 kernels.
This can lead to data corruption.

It is not possible to determine what layout to use - it depends which
kernel the data was written by.
So we add a module parameter to allow the old (0) or new (1) layout to be
specified, and refused to assemble an affected array if that parameter is
not set.

Fixes: 20d0189b1012 ("block: Introduce new bio_split()")
cc: stable@vger.kernel.org (3.14+)
Acked-by: Guoqing Jiang <guoqing.jiang@cloud.ionos.com>
Signed-off-by: NeilBrown <neilb@suse.de>
Signed-off-by: Song Liu <songliubraving@fb.com>
Signed-off-by: Sasha Levin <sashal@kernel.org>

Signed-off-by: dann frazier <dann.frazier@canonical.com>
(backported from bionic/18.04 submission into xenial/16.04)
[smb: minor contextual changes]
Signed-off-by: Stefan Bader <stefan.bader@canonical.com>
---
 drivers/md/raid0.c | 32 +++++++++++++++++++++++++++++++-
 drivers/md/raid0.h | 14 ++++++++++++++
 2 files changed, 45 insertions(+), 1 deletion(-)

Patch
diff mbox series

diff --git a/drivers/md/raid0.c b/drivers/md/raid0.c
index 7a67e7dcf546..7aa64c55f68d 100644
--- a/drivers/md/raid0.c
+++ b/drivers/md/raid0.c
@@ -25,6 +25,9 @@ 
 #include "raid0.h"
 #include "raid5.h"
 
+static int default_layout = 0;
+module_param(default_layout, int, 0644);
+
 static int raid0_congested(struct mddev *mddev, int bits)
 {
 	struct r0conf *conf = mddev->private;
@@ -137,6 +140,19 @@  static int create_strip_zones(struct mddev *mddev, struct r0conf **private_conf)
 	}
 	pr_debug("md/raid0:%s: FINAL %d zones\n",
 		 mdname(mddev), conf->nr_strip_zones);
+
+	if (conf->nr_strip_zones == 1) {
+		conf->layout = RAID0_ORIG_LAYOUT;
+	} else if (default_layout == RAID0_ORIG_LAYOUT ||
+		   default_layout == RAID0_ALT_MULTIZONE_LAYOUT) {
+		conf->layout = default_layout;
+	} else {
+		pr_err("md/raid0:%s: cannot assemble multi-zone RAID0 with default_layout setting\n",
+		       mdname(mddev));
+		pr_err("md/raid0: please set raid.default_layout to 1 or 2\n");
+		err = -ENOTSUPP;
+		goto abort;
+	}
 	/*
 	 * now since we have the hard sector sizes, we can make sure
 	 * chunk size is a multiple of that sector size
@@ -454,6 +470,7 @@  static inline int is_io_in_chunk_boundary(struct mddev *mddev,
 
 static void raid0_make_request(struct mddev *mddev, struct bio *bio)
 {
+	struct r0conf *conf = mddev->private;
 	struct strip_zone *zone;
 	struct md_rdev *tmp_dev;
 	struct bio *split;
@@ -465,6 +482,7 @@  static void raid0_make_request(struct mddev *mddev, struct bio *bio)
 
 	do {
 		sector_t sector = bio->bi_iter.bi_sector;
+		sector_t orig_sector;
 		unsigned chunk_sects = mddev->chunk_sectors;
 
 		unsigned sectors = chunk_sects -
@@ -482,8 +500,20 @@  static void raid0_make_request(struct mddev *mddev, struct bio *bio)
 			split = bio;
 		}
 
+		orig_sector = sector;
 		zone = find_zone(mddev->private, &sector);
-		tmp_dev = map_sector(mddev, zone, sector, &sector);
+		switch (conf->layout) {
+		case RAID0_ORIG_LAYOUT:
+			tmp_dev = map_sector(mddev, zone, orig_sector, &sector);
+			break;
+		case RAID0_ALT_MULTIZONE_LAYOUT:
+			tmp_dev = map_sector(mddev, zone, sector, &sector);
+			break;
+		default:
+			WARN("md/raid0:%s: Invalid layout\n", mdname(mddev));
+			bio_io_error(bio);
+			return true;
+		}
 		split->bi_bdev = tmp_dev->bdev;
 		split->bi_iter.bi_sector = sector + zone->dev_start +
 			tmp_dev->data_offset;
diff --git a/drivers/md/raid0.h b/drivers/md/raid0.h
index 7127a623f5da..ad60bfa58bd5 100644
--- a/drivers/md/raid0.h
+++ b/drivers/md/raid0.h
@@ -7,11 +7,25 @@  struct strip_zone {
 	int	 nb_dev;	/* # of devices attached to the zone */
 };
 
+/* Linux 3.14 (20d0189b101) made an unintended change to
+ * the RAID0 layout for multi-zone arrays (where devices aren't all
+ * the same size.
+ * RAID0_ORIG_LAYOUT restores the original layout
+ * RAID0_ALT_MULTIZONE_LAYOUT uses the altered layout
+ * The layouts are identical when there is only one zone (all
+ * devices the same size).
+ */
+enum r0layout {
+	RAID0_ORIG_LAYOUT = 1,
+	RAID0_ALT_MULTIZONE_LAYOUT = 2,
+};
+
 struct r0conf {
 	struct strip_zone	*strip_zone;
 	struct md_rdev		**devlist; /* lists of rdevs, pointed to
 					    * by strip_zone->dev */
 	int			nr_strip_zones;
+	enum r0layout		layout;
 };
 
 #endif