3d407d2
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
3d407d2
From: Darren Kenny <darren.kenny@oracle.com>
3d407d2
Date: Tue, 29 Mar 2022 15:52:46 +0000
3d407d2
Subject: [PATCH] fs/btrfs: Fix more ASAN and SEGV issues found with fuzzing
3d407d2
3d407d2
The fuzzer is generating btrfs file systems that have chunks with
3d407d2
invalid combinations of stripes and substripes for the given RAID
3d407d2
configurations.
3d407d2
3d407d2
After examining the Linux kernel fs/btrfs/tree-checker.c code, it
3d407d2
appears that sub-stripes should only be applied to RAID10, and in that
3d407d2
case there should only ever be 2 of them.
3d407d2
3d407d2
Similarly, RAID single should only have 1 stripe, and RAID1/1C3/1C4
3d407d2
should have 2. 3 or 4 stripes respectively, which is what redundancy
3d407d2
corresponds.
3d407d2
3d407d2
Some of the chunks ended up with a size of 0, which grub_malloc() still
3d407d2
returned memory for and in turn generated ASAN errors later when
3d407d2
accessed.
3d407d2
3d407d2
While it would be possible to specifically limit the number of stripes,
3d407d2
a more correct test was on the combination of the chunk item, and the
3d407d2
number of stripes by the size of the chunk stripe structure in
3d407d2
comparison to the size of the chunk itself.
3d407d2
3d407d2
Signed-off-by: Darren Kenny <darren.kenny@oracle.com>
3d407d2
Reviewed-by: Daniel Kiper <daniel.kiper@oracle.com>
3d407d2
(cherry picked from commit 3849647b4b98a4419366708fc4b7f339c6f55ec7)
3d407d2
---
3d407d2
 grub-core/fs/btrfs.c | 55 ++++++++++++++++++++++++++++++++++++++++++++++++++++
3d407d2
 1 file changed, 55 insertions(+)
3d407d2
3d407d2
diff --git a/grub-core/fs/btrfs.c b/grub-core/fs/btrfs.c
3d407d2
index 2fcfb738fe..0e9b450413 100644
3d407d2
--- a/grub-core/fs/btrfs.c
3d407d2
+++ b/grub-core/fs/btrfs.c
3d407d2
@@ -941,6 +941,12 @@ grub_btrfs_read_logical (struct grub_btrfs_data *data, grub_disk_addr_t addr,
3d407d2
 	return grub_error (GRUB_ERR_BAD_FS,
3d407d2
 			   "couldn't find the chunk descriptor");
3d407d2
 
3d407d2
+      if (!chsize)
3d407d2
+	{
3d407d2
+	  grub_dprintf ("btrfs", "zero-size chunk\n");
3d407d2
+	  return grub_error (GRUB_ERR_BAD_FS,
3d407d2
+			     "got an invalid zero-size chunk");
3d407d2
+	}
3d407d2
       chunk = grub_malloc (chsize);
3d407d2
       if (!chunk)
3d407d2
 	return grub_errno;
3d407d2
@@ -999,6 +1005,16 @@ grub_btrfs_read_logical (struct grub_btrfs_data *data, grub_disk_addr_t addr,
3d407d2
 	      stripe_length = grub_divmod64 (grub_le_to_cpu64 (chunk->size),
3d407d2
 					     nstripes,
3d407d2
 					     NULL);
3d407d2
+
3d407d2
+	      /* For single, there should be exactly 1 stripe. */
3d407d2
+	      if (grub_le_to_cpu16 (chunk->nstripes) != 1)
3d407d2
+		{
3d407d2
+		  grub_dprintf ("btrfs", "invalid RAID_SINGLE: nstripes != 1 (%u)\n",
3d407d2
+				grub_le_to_cpu16 (chunk->nstripes));
3d407d2
+		  return grub_error (GRUB_ERR_BAD_FS,
3d407d2
+				     "invalid RAID_SINGLE: nstripes != 1 (%u)",
3d407d2
+				      grub_le_to_cpu16 (chunk->nstripes));
3d407d2
+		}
3d407d2
 	      if (stripe_length == 0)
3d407d2
 		stripe_length = 512;
3d407d2
 	      stripen = grub_divmod64 (off, stripe_length, &stripe_offset);
3d407d2
@@ -1018,6 +1034,19 @@ grub_btrfs_read_logical (struct grub_btrfs_data *data, grub_disk_addr_t addr,
3d407d2
 	      stripen = 0;
3d407d2
 	      stripe_offset = off;
3d407d2
 	      csize = grub_le_to_cpu64 (chunk->size) - off;
3d407d2
+
3d407d2
+             /*
3d407d2
+	      * Redundancy, and substripes only apply to RAID10, and there
3d407d2
+	      * should be exactly 2 sub-stripes.
3d407d2
+	      */
3d407d2
+	     if (grub_le_to_cpu16 (chunk->nstripes) != redundancy)
3d407d2
+               {
3d407d2
+                 grub_dprintf ("btrfs", "invalid RAID1: nstripes != %u (%u)\n",
3d407d2
+                               redundancy, grub_le_to_cpu16 (chunk->nstripes));
3d407d2
+                 return grub_error (GRUB_ERR_BAD_FS,
3d407d2
+                                    "invalid RAID1: nstripes != %u (%u)",
3d407d2
+                                    redundancy, grub_le_to_cpu16 (chunk->nstripes));
3d407d2
+               }
3d407d2
 	      break;
3d407d2
 	    }
3d407d2
 	  case GRUB_BTRFS_CHUNK_TYPE_RAID0:
3d407d2
@@ -1054,6 +1083,20 @@ grub_btrfs_read_logical (struct grub_btrfs_data *data, grub_disk_addr_t addr,
3d407d2
 	      stripe_offset = low + chunk_stripe_length
3d407d2
 		* high;
3d407d2
 	      csize = chunk_stripe_length - low;
3d407d2
+
3d407d2
+	      /*
3d407d2
+	       * Substripes only apply to RAID10, and there
3d407d2
+	       * should be exactly 2 sub-stripes.
3d407d2
+	       */
3d407d2
+	      if (grub_le_to_cpu16 (chunk->nsubstripes) != 2)
3d407d2
+		{
3d407d2
+		  grub_dprintf ("btrfs", "invalid RAID10: nsubstripes != 2 (%u)",
3d407d2
+				grub_le_to_cpu16 (chunk->nsubstripes));
3d407d2
+		  return grub_error (GRUB_ERR_BAD_FS,
3d407d2
+				     "invalid RAID10: nsubstripes != 2 (%u)",
3d407d2
+				     grub_le_to_cpu16 (chunk->nsubstripes));
3d407d2
+		}
3d407d2
+
3d407d2
 	      break;
3d407d2
 	    }
3d407d2
 	  case GRUB_BTRFS_CHUNK_TYPE_RAID5:
3d407d2
@@ -1153,6 +1196,8 @@ grub_btrfs_read_logical (struct grub_btrfs_data *data, grub_disk_addr_t addr,
3d407d2
 
3d407d2
 	for (j = 0; j < 2; j++)
3d407d2
 	  {
3d407d2
+	    grub_size_t est_chunk_alloc = 0;
3d407d2
+
3d407d2
 	    grub_dprintf ("btrfs", "chunk 0x%" PRIxGRUB_UINT64_T
3d407d2
 			  "+0x%" PRIxGRUB_UINT64_T
3d407d2
 			  " (%d stripes (%d substripes) of %"
3d407d2
@@ -1165,6 +1210,16 @@ grub_btrfs_read_logical (struct grub_btrfs_data *data, grub_disk_addr_t addr,
3d407d2
 	    grub_dprintf ("btrfs", "reading laddr 0x%" PRIxGRUB_UINT64_T "\n",
3d407d2
 			  addr);
3d407d2
 
3d407d2
+	    if (grub_mul (sizeof (struct grub_btrfs_chunk_stripe),
3d407d2
+			  grub_le_to_cpu16 (chunk->nstripes), &est_chunk_alloc) ||
3d407d2
+		grub_add (est_chunk_alloc,
3d407d2
+			  sizeof (struct grub_btrfs_chunk_item), &est_chunk_alloc) ||
3d407d2
+		est_chunk_alloc > chunk->size)
3d407d2
+	      {
3d407d2
+		err = GRUB_ERR_BAD_FS;
3d407d2
+		break;
3d407d2
+	      }
3d407d2
+
3d407d2
 	    if (is_raid56)
3d407d2
 	      {
3d407d2
 		err = btrfs_read_from_chunk (data, chunk, stripen,