Commit message: Fix #7723 - crash in ext2 GRUB code because of variable size array in stack
CVE: CVE-2017-9763
CWE: CWE-119
Vulnerability description: The grub_ext2_read_block function in fs/ext2.c in GNU GRUB before 2013-11-12, as used in shlr/grub/fs/ext2.c in radare2 1.5.0, allows remote attackers to cause a denial of service (excessive stack use and application crash) via a crafted binary file, related to use of a variable-size stack array.

Project: radare2

static int cmd_mount(void *data, const char *_input) {
	ut64 off = 0;
	char *input, *oinput, *ptr, *ptr2;
	RList *list;
	RListIter *iter;
	RFSFile *file;
	RFSRoot *root;
	RFSPlugin *plug;
	RFSPartition *part;
	RCore *core = (RCore *)data;

	if (!strncmp ("kdir", _input, 4)) {
		return cmd_mkdir (data, _input);
	}
	if (!strncmp ("v", _input, 1)) {
		return cmd_mv (data, _input);
	}
	input = oinput = strdup (_input);

 	switch (*input) {
 	case ' ':
 		input++;
		if (input[0]==' ') {
 			input++;
		}
 		ptr = strchr (input, ' ');
 		if (ptr) {
 			*ptr = 0;
			ptr++;
			ptr2 = strchr (ptr, ' ');
			if (ptr2) {
 				*ptr2 = 0;
 				off = r_num_math (core->num, ptr2+1);
 			}
			if (!r_fs_mount (core->fs, ptr, input, off)) {
 				eprintf ("Cannot mount %s\n", input);
			}
 		} else {
			if (!(ptr = r_fs_name (core->fs, core->offset))) {
 				eprintf ("Unknown filesystem type\n");
			} else if (!r_fs_mount (core->fs, ptr, input, core->offset)) {
 				eprintf ("Cannot mount %s\n", input);
			}
 			free (ptr);
 		}
 		break;
	case '-':
		r_fs_umount (core->fs, input+1);
		break;
	case '*':
		eprintf ("List commands in radare format\n");
		r_list_foreach (core->fs->roots, iter, root) {
			r_cons_printf ("m %s %s 0x%"PFMT64x"\n",
				root-> path, root->p->name, root->delta);
		}
		break;
	case '\0':
		r_list_foreach (core->fs->roots, iter, root) {
			r_cons_printf ("%s\t0x%"PFMT64x"\t%s\n",
				root->p->name, root->delta, root->path);
		}
		break;
	case 'l': // list of plugins
		r_list_foreach (core->fs->plugins, iter, plug) {
			r_cons_printf ("%10s  %s\n", plug->name, plug->desc);
		}
		break;
	case 'd':
		input++;
		if (input[0]==' ')
			input++;
		list = r_fs_dir (core->fs, input);
		if (list) {
			r_list_foreach (list, iter, file) {
				r_cons_printf ("%c %s\n", file->type, file->name);
			}
			r_list_free (list);
		} else eprintf ("Cannot open '%s' directory\n", input);
		break;
	case 'p':
		input++;
		if (*input == ' ')
			input++;
		ptr = strchr (input, ' ');
		if (ptr) {
			*ptr = 0;
			off = r_num_math (core->num, ptr+1);
		}
		list = r_fs_partitions (core->fs, input, off);
		if (list) {
			r_list_foreach (list, iter, part) {
				r_cons_printf ("%d %02x 0x%010"PFMT64x" 0x%010"PFMT64x"\n",
					part->number, part->type,
					part->start, part->start+part->length);
			}
			r_list_free (list);
		} else eprintf ("Cannot read partition\n");
		break;
	case 'o':
		input++;
		if (input[0]==' ')
			input++;
		file = r_fs_open (core->fs, input);
		if (file) {
			r_fs_read (core->fs, file, 0, file->size);
			r_cons_printf ("f file %d 0x%08"PFMT64x"\n", file->size, file->off);
			r_fs_close (core->fs, file);
		} else eprintf ("Cannot open file\n");
		break;
	case 'g':
		input++;
		if (*input == ' ')
			input++;
		ptr = strchr (input, ' ');
		if (ptr)
			*ptr++ = 0;
		else
			ptr = "./";
		file = r_fs_open (core->fs, input);
		if (file) {
			r_fs_read (core->fs, file, 0, file->size);
			write (1, file->data, file->size);
			r_fs_close (core->fs, file);
			write (1, "\n", 1);
		} else if (!r_fs_dir_dump (core->fs, input, ptr))
			eprintf ("Cannot open file\n");
		break;
	case 'f':
		input++;
		switch (*input) {
		case '?':
			r_cons_printf (
			"Usage: mf[no] [...]\n"
			" mfn /foo *.c       ; search files by name in /foo path\n"
			" mfo /foo 0x5e91    ; search files by offset in /foo path\n"
			);
			break;
		case 'n':
			input++;
			if (*input == ' ')
				input++;
			ptr = strchr (input, ' ');
			if (ptr) {
				*ptr++ = 0;
				list = r_fs_find_name (core->fs, input, ptr);
				r_list_foreach (list, iter, ptr) {
					r_str_chop_path (ptr);
					printf ("%s\n", ptr);
				}
			} else eprintf ("Unknown store path\n");
			break;
		case 'o':
			input++;
			if (*input == ' ')
				input++;
			ptr = strchr (input, ' ');
			if (ptr) {
				*ptr++ = 0;
				ut64 off = r_num_math (core->num, ptr);
				list = r_fs_find_off (core->fs, input, off);
				r_list_foreach (list, iter, ptr) {
					r_str_chop_path (ptr);
					printf ("%s\n", ptr);
				}
			} else eprintf ("Unknown store path\n");
			break;
		}
		break;
	case 's':
		if (core->http_up) {
			free (oinput);
			return false;
		}
		input++;
		if (input[0]==' ')
			input++;
		r_fs_prompt (core->fs, input);
		break;
	case 'y':
		eprintf ("TODO\n");
		break;
	case '?': {
		const char* help_msg[] = {
			"Usage:", "m[-?*dgy] [...] ", "Mountpoints management",
			"m", "", "List all mountpoints in human readable format",
			"m*", "", "Same as above, but in r2 commands",
			"ml", "", "List filesystem plugins",
			"m", " /mnt", "Mount fs at /mnt with autodetect fs and current offset",
			"m", " /mnt ext2 0", "Mount ext2 fs at /mnt with delta 0 on IO",
			"m-/", "", "Umount given path (/)",
			"my", "", "Yank contents of file into clipboard",
			"mo", " /foo", "Get offset and size of given file",
			"mg", " /foo", "Get contents of file/dir dumped to disk (XXX?)",
			"mf", "[?] [o|n]", "Search files for given filename or for offset",
			"md", " /", "List directory contents for path",
			"mp", "", "List all supported partition types",
			"mp", " msdos 0", "Show partitions in msdos format at offset 0",
			"ms", " /mnt", "Open filesystem prompt at /mnt",
			NULL};
		r_core_cmd_help (core, help_msg);
		}
		break;
	}
	free (oinput);
	return 0;
}

Project: radare2
File: ext2.c

grub_ext2_read_block (grub_fshelp_node_t node, grub_disk_addr_t fileblock)
{
  struct grub_ext2_data *data = node->data;
  struct grub_ext2_inode *inode = &node->inode;
  int blknr = -1;
  unsigned int blksz = EXT2_BLOCK_SIZE (data);
  int log2_blksz = LOG2_EXT2_BLOCK_SIZE (data);
 
   if (grub_le_to_cpu32(inode->flags) & EXT4_EXTENTS_FLAG)
     {
	  char * buf = grub_malloc (EXT2_BLOCK_SIZE (data));
          if (!buf) {
              return -1;
          }
       struct grub_ext4_extent_header *leaf;
       struct grub_ext4_extent *ext;
       int i;

      leaf = grub_ext4_find_leaf (data, buf,
		  (struct grub_ext4_extent_header *) inode->blocks.dir_blocks,
		  fileblock);
       if (! leaf)
         {
           grub_error (GRUB_ERR_BAD_FS, "invalid extent");
	  free (buf);
           return -1;
         }
 
      ext = (struct grub_ext4_extent *) (leaf + 1);
      for (i = 0; i < grub_le_to_cpu16 (leaf->entries); i++)
        {
          if (fileblock < grub_le_to_cpu32 (ext[i].block))
            break;
        }

       if (--i >= 0)
         {
           fileblock -= grub_le_to_cpu32 (ext[i].block);
          if (fileblock >= grub_le_to_cpu16 (ext[i].len)) {
  	    free (buf);
             return 0;
          } else
             {
               grub_disk_addr_t start;
 
               start = grub_le_to_cpu16 (ext[i].start_hi);
               start = (start << 32) + grub_le_to_cpu32 (ext[i].start);
  	    free (buf);
 
               return fileblock + start;
             }
         }
       else
         {
           grub_error (GRUB_ERR_BAD_FS, "something wrong with extent");
  	    free (buf);
           return -1;
         }
free (buf);
     }
   /* Direct blocks.  */
   if (fileblock < INDIRECT_BLOCKS) {
    blknr = grub_le_to_cpu32 (inode->blocks.dir_blocks[fileblock]);
  /* Indirect.  */
  } else if (fileblock < INDIRECT_BLOCKS + blksz / 4)
    {
       grub_uint32_t *indir;
 
       indir = grub_malloc (blksz);
      if (! indir) {
 	return grub_errno;
}
 
       if (grub_disk_read (data->disk,
 			  ((grub_disk_addr_t)
 			   grub_le_to_cpu32 (inode->blocks.indir_block))
 			  << log2_blksz,
			  0, blksz, indir)) {
 	return grub_errno;
}
 
       blknr = grub_le_to_cpu32 (indir[fileblock - INDIRECT_BLOCKS]);
       grub_free (indir);
    }
  /* Double indirect.  */
  else if (fileblock < (grub_disk_addr_t)(INDIRECT_BLOCKS + blksz / 4) \
		  * (grub_disk_addr_t)(blksz / 4 + 1))
    {
      unsigned int perblock = blksz / 4;
      unsigned int rblock = fileblock - (INDIRECT_BLOCKS
					 + blksz / 4);
       grub_uint32_t *indir;
 
       indir = grub_malloc (blksz);
      if (! indir) {
 	return grub_errno;
}
 
       if (grub_disk_read (data->disk,
 			  ((grub_disk_addr_t)
 			   grub_le_to_cpu32 (inode->blocks.double_indir_block))
 			  << log2_blksz,
			  0, blksz, indir)) {
 	return grub_errno;
}
 
       if (grub_disk_read (data->disk,
 			  ((grub_disk_addr_t)
 			   grub_le_to_cpu32 (indir[rblock / perblock]))
 			  << log2_blksz,
			  0, blksz, indir)) {
 	return grub_errno;
}
 
       blknr = grub_le_to_cpu32 (indir[rblock % perblock]);
             grub_free (indir);
    }
  /* triple indirect.  */
  else
    {
      grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET,
		  "ext2fs doesn't support triple indirect blocks");
    }

  return blknr;
}

Project: radare2
File: ext2.c

grub_ext4_find_leaf (struct grub_ext2_data *data, char *buf,
                     struct grub_ext4_extent_header *ext_block,
                     grub_uint32_t fileblock)
{
  struct grub_ext4_extent_idx *index;

  while (1)
    {
      int i;
      grub_disk_addr_t block;

      index = (struct grub_ext4_extent_idx *) (ext_block + 1);

      if (grub_le_to_cpu16(ext_block->magic) != EXT4_EXT_MAGIC)
        return 0;

      if (ext_block->depth == 0)
        return ext_block;

      for (i = 0; i < grub_le_to_cpu16 (ext_block->entries); i++)
        {
          if (fileblock < grub_le_to_cpu32(index[i].block))
            break;
        }

      if (--i < 0)
        return 0;

      block = grub_le_to_cpu16 (index[i].leaf_hi);
       block = (block << 32) + grub_le_to_cpu32 (index[i].leaf);
       if (grub_disk_read (data->disk,
                           block << LOG2_EXT2_BLOCK_SIZE (data),
                          0, EXT2_BLOCK_SIZE(data), buf)) {
         return 0;
      }
 
       ext_block = (struct grub_ext4_extent_header *) buf;
     }
}

Project: radare2
File: fshelp.c

find_file (const char *currpath, grub_fshelp_node_t currroot,
 	   grub_fshelp_node_t *currfound,
 	   struct grub_fshelp_find_file_closure *c)
 {
 	char *fpath = grub_malloc (grub_strlen (currpath) + 1);
   char *name = fpath;
   char *next;
   enum grub_fshelp_filetype type = GRUB_FSHELP_DIR;
  grub_fshelp_node_t currnode = currroot;
  grub_fshelp_node_t oldnode = currroot;

  c->currroot = currroot;

  grub_strncpy (fpath, currpath, grub_strlen (currpath) + 1);

  /* Remove all leading slashes.  */
  while (*name == '/')
    name++;

   if (! *name)
     {
       *currfound = currnode;
free (fpath);
       return 0;
     }
 
  for (;;)
    {
      int found;
      struct find_file_closure cc;

      /* Extract the actual part from the pathname.  */
      next = grub_strchr (name, '/');
      if (next)
	{
	  /* Remove all leading slashes.  */
	  while (*next == '/')
	    *(next++) = '\0';
	}

      /* At this point it is expected that the current node is a
	 directory, check if this is true.  */
       if (type != GRUB_FSHELP_DIR)
 	{
 	  free_node (currnode, c);
free (fpath);
 	  return grub_error (GRUB_ERR_BAD_FILE_TYPE, "not a directory");
 	}
 
      cc.name = name;
      cc.type = &type;
      cc.oldnode = &oldnode;
      cc.currnode = &currnode;
      /* Iterate over the directory.  */
       found = c->iterate_dir (currnode, iterate, &cc);
       if (! found)
 	{
	  if (grub_errno) {
free (fpath);
 	    return grub_errno;
}
 
 	  break;
 	}

      /* Read in the symlink and follow it.  */
      if (type == GRUB_FSHELP_SYMLINK)
	{
	  char *symlink;

	  /* Test if the symlink does not loop.  */
	  if (++(c->symlinknest) == 8)
 	    {
 	      free_node (currnode, c);
 	      free_node (oldnode, c);
free (fpath);
 	      return grub_error (GRUB_ERR_SYMLINK_LOOP,
 				 "too deep nesting of symlinks");
 	    }

	  symlink = c->read_symlink (currnode);
	  free_node (currnode, c);

 	  if (!symlink)
 	    {
 	      free_node (oldnode, c);
free (fpath);
 	      return grub_errno;
 	    }
 
	  /* The symlink is an absolute path, go back to the root inode.  */
	  if (symlink[0] == '/')
	    {
	      free_node (oldnode, c);
	      oldnode = c->rootnode;
	    }

	  /* Lookup the node the symlink points to.  */
	  find_file (symlink, oldnode, &currnode, c);
	  type = c->foundtype;
	  grub_free (symlink);

 	  if (grub_errno)
 	    {
 	      free_node (oldnode, c);
free (fpath);
 	      return grub_errno;
 	    }
 	}

      free_node (oldnode, c);

      /* Found the node!  */
      if (! next || *next == '\0')
 	{
 	  *currfound = currnode;
 	  c->foundtype = type;
free (fpath);
 	  return 0;
 	}
 
       name = next;
     }
 
free (fpath);
   return grub_error (GRUB_ERR_FILE_NOT_FOUND, "file not found");
 }

Project: radare2

 void grubfs_free (GrubFS *gf) {
 	if (gf) {
		if (gf->file && gf->file->device) {
 			free (gf->file->device->disk);
		}
 		free (gf->file);
 		free (gf);
	}
}

Project: radare2
File: grubfs.c

 static grub_err_t read_foo (struct grub_disk *disk, grub_disk_addr_t sector, grub_size_t size, char *buf) {
	if (!disk) {
		eprintf ("oops. no disk\n");
		return 1;
	}
	const int blocksize = 512; // TODO unhardcode 512
	RIOBind *iob = disk->data;
	if (bio) {
		iob = bio;
	}
	//printf ("io %p\n", file->root->iob.io);
	if (iob->read_at (iob->io, delta+(blocksize*sector), (ut8*)buf, size*blocksize) == -1) {
		return 1;
	}
	return 0;
 }