Correct several memory access violations and hangs found during fuzzing.

Signed-off-by: Andrew Hamilton <adham...@gmail.com>
---
 grub-core/fs/ntfs.c | 95 ++++++++++++++++++++++++++++++++++++++-------
 1 file changed, 80 insertions(+), 15 deletions(-)

diff --git a/grub-core/fs/ntfs.c b/grub-core/fs/ntfs.c
index 0d087acd8..45fff2400 100644
--- a/grub-core/fs/ntfs.c
+++ b/grub-core/fs/ntfs.c
@@ -246,6 +246,7 @@ fixup (grub_uint8_t *buf, grub_size_t len, const 
grub_uint8_t *magic)
   grub_uint16_t ss;
   grub_uint8_t *pu;
   grub_uint16_t us;
+  grub_uint16_t pu_offset;
 
   COMPILE_TIME_ASSERT ((1 << GRUB_NTFS_BLK_SHR) == GRUB_DISK_SECTOR_SIZE);
 
@@ -255,7 +256,10 @@ fixup (grub_uint8_t *buf, grub_size_t len, const 
grub_uint8_t *magic)
   ss = u16at (buf, 6) - 1;
   if (ss != len)
     return grub_error (GRUB_ERR_BAD_FS, "size not match");
-  pu = buf + u16at (buf, 4);
+  pu_offset = u16at (buf, 4);
+  if (pu_offset >= (len * GRUB_DISK_SECTOR_SIZE - (2 * ss)))
+    return grub_error (GRUB_ERR_BAD_FS, "pu offset size incorrect");
+  pu = buf + pu_offset;
   us = u16at (pu, 0);
   buf -= 2;
   while (ss > 0)
@@ -373,6 +377,7 @@ find_attr (struct grub_ntfs_attr *at, grub_uint8_t attr)
 
              new_pos = &at->emft_buf[first_attr_off (at->emft_buf)];
              end = &at->emft_buf[emft_buf_size];
+             at->end = end;
 
              while (new_pos && *new_pos != 0xFF)
                {
@@ -395,7 +400,8 @@ find_attr (struct grub_ntfs_attr *at, grub_uint8_t attr)
     }
   at->attr_cur = at->attr_nxt;
   mft_end = at->mft->buf + (at->mft->data->mft_size << GRUB_NTFS_BLK_SHR);
-  while (at->attr_cur >= at->mft->buf && at->attr_cur < mft_end && 
*at->attr_cur != 0xFF)
+  while (at->attr_cur >= at->mft->buf && at->attr_cur < (mft_end - 4)
+         && *at->attr_cur != 0xFF)
     {
       /* We can't use validate_attribute here because this logic
        * seems to be used for both parsing through attributes
@@ -412,7 +418,7 @@ find_attr (struct grub_ntfs_attr *at, grub_uint8_t attr)
 
       if (*at->attr_cur == GRUB_NTFS_AT_ATTRIBUTE_LIST)
        at->attr_end = at->attr_cur;
-      if ((*at->attr_cur == attr) || (attr == 0))
+      if ((*at->attr_cur == attr) || (attr == 0) || (nsize == 0))
        return at->attr_cur;
       at->attr_cur = at->attr_nxt;
     }
@@ -442,13 +448,23 @@ find_attr (struct grub_ntfs_attr *at, grub_uint8_t attr)
              return NULL;
            }
          at->attr_nxt = at->edat_buf;
-         at->attr_end = at->edat_buf + u32at (pa, 0x30);
+         grub_uint32_t edat_offset = u32at (pa, 0x30);
+         if (edat_offset >= n) {
+           grub_error (GRUB_ERR_BAD_FS, "edat offset is out of bounds");
+           return NULL;
+         }
+         at->attr_end = at->edat_buf + edat_offset;
          pa_end = at->edat_buf + n;
        }
       else
        {
          at->attr_nxt = at->attr_end + res_attr_data_off (pa);
-         at->attr_end = at->attr_end + u32at (pa, 4);
+         grub_uint32_t edat_offset = u32at (pa, 4);
+         if ((at->attr_end + edat_offset) >= (at->end)) {
+           grub_error (GRUB_ERR_BAD_FS, "edat offset is out of bounds");
+           return NULL;
+         }
+         at->attr_end = at->attr_end + edat_offset;
          pa_end = at->mft->buf + (at->mft->data->mft_size << 
GRUB_NTFS_BLK_SHR);
        }
       at->flags |= GRUB_NTFS_AF_ALST;
@@ -470,7 +486,7 @@ find_attr (struct grub_ntfs_attr *at, grub_uint8_t attr)
            at->attr_nxt = NULL;
        }
 
-      if (at->attr_nxt >= at->attr_end || at->attr_nxt == NULL)
+      if ((at->attr_nxt + GRUB_NTFS_ATTRIBUTE_HEADER_SIZE) >= at->attr_end || 
at->attr_nxt == NULL)
        return NULL;
 
       if ((at->flags & GRUB_NTFS_AF_MMFT) && (attr == GRUB_NTFS_AT_DATA))
@@ -537,13 +553,16 @@ locate_attr (struct grub_ntfs_attr *at, struct 
grub_ntfs_file *mft,
     return NULL;
   if ((at->flags & GRUB_NTFS_AF_ALST) == 0)
     {
+      /* Used to make sure we're not stuck in a loop. */
+      grub_uint8_t *last_pa = NULL;
       while (1)
        {
          pa = find_attr (at, attr);
-         if (pa == NULL)
+         if (pa == NULL || pa == last_pa)
            break;
          if (at->flags & GRUB_NTFS_AF_ALST)
            return pa;
+         last_pa = pa;
        }
       grub_errno = GRUB_ERR_NONE;
       free_attr (at);
@@ -639,6 +658,7 @@ read_data (struct grub_ntfs_attr *at, grub_uint8_t *pa, 
grub_uint8_t *dest,
           grub_disk_read_hook_t read_hook, void *read_hook_data)
 {
   struct grub_ntfs_rlst cc, *ctx;
+  grub_uint8_t *end_ptr = (pa + len);
 
   if (len == 0)
     return 0;
@@ -671,7 +691,13 @@ read_data (struct grub_ntfs_attr *at, grub_uint8_t *pa, 
grub_uint8_t *dest,
       return 0;
     }
 
-  ctx->cur_run = pa + u16at (pa, 0x20);
+  grub_uint16_t run_offset = u16at (pa, 0x20);
+  if ((run_offset + pa) >= end_ptr || ((run_offset + pa) >= (at->end)))
+    {
+      return grub_error (GRUB_ERR_BAD_FS, "run offset out of range");
+    }
+
+  ctx->cur_run = pa + run_offset;
 
   ctx->next_vcn = u32at (pa, 0x10);
   ctx->curr_lcn = 0;
@@ -734,6 +760,8 @@ read_attr (struct grub_ntfs_attr *at, grub_uint8_t *dest, 
grub_disk_addr_t ofs,
   grub_uint8_t *pp;
   grub_err_t ret;
 
+  if (at == NULL || at->attr_cur == NULL)
+    return grub_error (GRUB_ERR_BAD_FS, "attribute not found");
   save_cur = at->attr_cur;
   at->attr_nxt = at->attr_cur;
   attr = *at->attr_nxt;
@@ -830,8 +858,11 @@ init_file (struct grub_ntfs_file *mft, grub_uint64_t mftno)
 static void
 free_file (struct grub_ntfs_file *mft)
 {
-  free_attr (&mft->attr);
-  grub_free (mft->buf);
+  if (mft)
+  {
+    free_attr (&mft->attr);
+    grub_free (mft->buf);
+  }
 }
 
 static char *
@@ -924,7 +955,12 @@ list_file (struct grub_ntfs_file *diro, grub_uint8_t *pos, 
grub_uint8_t *end_pos
 
          grub_free (ustr);
        }
-      pos += u16at (pos, 8);
+       grub_uint16_t pos_incr = u16at (pos, 8);
+       if (pos_incr > 0)
+         pos += pos_incr;
+       else
+         return 0;
+
     }
   return 0;
 }
@@ -1035,6 +1071,8 @@ grub_ntfs_iterate_dir (grub_fshelp_node_t dir,
   int ret = 0;
   grub_size_t bitmap_len;
   struct grub_ntfs_file *mft;
+  /* Used to make sure we're not stuck in a loop. */
+  grub_uint8_t *last_pos = NULL;
 
   mft = (struct grub_ntfs_file *) dir;
 
@@ -1054,18 +1092,22 @@ grub_ntfs_iterate_dir (grub_fshelp_node_t dir,
   while (1)
     {
       cur_pos = find_attr (at, GRUB_NTFS_AT_INDEX_ROOT);
-      if (cur_pos == NULL)
+      if (cur_pos == NULL || cur_pos == last_pos)
        {
          grub_error (GRUB_ERR_BAD_FS, "no $INDEX_ROOT");
          goto done;
        }
 
+      last_pos = cur_pos;
+
       /* Resident, Namelen=4, Offset=0x18, Flags=0x00, Name="$I30" */
       if ((u32at (cur_pos, 8) != 0x180400) ||
          (u32at (cur_pos, 0x18) != 0x490024) ||
          (u32at (cur_pos, 0x1C) != 0x300033))
        continue;
       cur_pos += res_attr_data_off (cur_pos);
+      if(cur_pos >= at->end)
+        continue;
       if (*cur_pos != 0x30)    /* Not filename index */
        continue;
       break;
@@ -1084,10 +1126,18 @@ grub_ntfs_iterate_dir (grub_fshelp_node_t dir,
   /* No need to check errors here, as it will already be fine */
   init_attr (at, mft);
 
+  last_pos = NULL;
   while ((cur_pos = find_attr (at, GRUB_NTFS_AT_BITMAP)) != NULL)
     {
       int ofs;
 
+      if (cur_pos == last_pos)
+      {
+        grub_error (GRUB_ERR_BAD_FS, "bitmap attribute loop");
+        goto done;
+      }
+      last_pos = cur_pos;
+
       ofs = cur_pos[0xA];
       /* Namelen=4, Name="$I30" */
       if ((cur_pos[9] == 4) &&
@@ -1135,7 +1185,17 @@ grub_ntfs_iterate_dir (grub_fshelp_node_t dir,
                               "fails to read non-resident $BITMAP");
                   goto done;
                 }
-              bitmap_len = u32at (cur_pos, 0x30);
+                grub_uint32_t tmp_len = u32at (cur_pos, 0x30);
+                if (tmp_len <= bitmap_len)
+                {
+                  bitmap_len = tmp_len;
+                }
+                else
+                {
+                  grub_error (GRUB_ERR_BAD_FS,
+                    "bitmap len too large for non-resident $BITMAP");
+                  goto done;
+                }
             }
 
           bitmap = bmp;
@@ -1144,6 +1204,7 @@ grub_ntfs_iterate_dir (grub_fshelp_node_t dir,
     }
 
   free_attr (at);
+  last_pos = NULL;
   cur_pos = locate_attr (at, mft, GRUB_NTFS_AT_INDEX_ALLOCATION);
   while (cur_pos != NULL)
     {
@@ -1153,6 +1214,9 @@ grub_ntfs_iterate_dir (grub_fshelp_node_t dir,
          (u32at (cur_pos, 0x44) == 0x300033))
        break;
       cur_pos = find_attr (at, GRUB_NTFS_AT_INDEX_ALLOCATION);
+      if (cur_pos == last_pos)
+        break;
+      last_pos = cur_pos;
     }
 
   if ((!cur_pos) && (bitmap))
@@ -1480,7 +1544,7 @@ grub_ntfs_label (grub_device_t device, char **label)
 
   pa = find_attr (&mft->attr, GRUB_NTFS_AT_VOLUME_NAME);
 
-  if (pa >= mft->buf + (mft->data->mft_size << GRUB_NTFS_BLK_SHR))
+  if (pa == NULL || pa >= mft->buf + (mft->data->mft_size << 
GRUB_NTFS_BLK_SHR))
     {
       grub_error (GRUB_ERR_BAD_FS, "can\'t parse volume label");
       goto fail;
@@ -1498,7 +1562,8 @@ grub_ntfs_label (grub_device_t device, char **label)
 
       len = res_attr_data_len (pa) / 2;
       pa += res_attr_data_off (pa);
-      if (mft->buf + (mft->data->mft_size << GRUB_NTFS_BLK_SHR) - pa >= 2 * 
len)
+      if (mft->buf + (mft->data->mft_size << GRUB_NTFS_BLK_SHR) - pa >= 2 * 
len &&
+          pa >= mft->buf && (pa + len < (mft->buf + (mft->data->mft_size << 
GRUB_NTFS_BLK_SHR))))
         *label = get_utf8 (pa, len);
       else
         grub_error (GRUB_ERR_BAD_FS, "can\'t parse volume label");
-- 
2.39.5


_______________________________________________
Grub-devel mailing list
Grub-devel@gnu.org
https://lists.gnu.org/mailman/listinfo/grub-devel

Reply via email to