mirror of
				https://git.proxmox.com/git/grub2
				synced 2025-11-04 14:33:39 +00:00 
			
		
		
		
	These could be triggered by a crafted filesystem with very large files. Fixes: CVE-2020-15707 Signed-off-by: Colin Watson <cjwatson@debian.org> Reviewed-by: Jan Setje-Eilers <jan.setjeeilers@oracle.com> Reviewed-by: Daniel Kiper <daniel.kiper@oracle.com>
		
			
				
	
	
		
			336 lines
		
	
	
		
			7.5 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			336 lines
		
	
	
		
			7.5 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
#include <grub/types.h>
 | 
						|
#include <grub/err.h>
 | 
						|
#include <grub/linux.h>
 | 
						|
#include <grub/misc.h>
 | 
						|
#include <grub/file.h>
 | 
						|
#include <grub/mm.h>
 | 
						|
#include <grub/safemath.h>
 | 
						|
 | 
						|
struct newc_head
 | 
						|
{
 | 
						|
  char magic[6];
 | 
						|
  char ino[8];
 | 
						|
  char mode[8];
 | 
						|
  char uid[8];
 | 
						|
  char gid[8];
 | 
						|
  char nlink[8];
 | 
						|
  char mtime[8];
 | 
						|
  char filesize[8];
 | 
						|
  char devmajor[8];
 | 
						|
  char devminor[8];
 | 
						|
  char rdevmajor[8];
 | 
						|
  char rdevminor[8];
 | 
						|
  char namesize[8];
 | 
						|
  char check[8];
 | 
						|
} GRUB_PACKED;
 | 
						|
 | 
						|
struct grub_linux_initrd_component
 | 
						|
{
 | 
						|
  grub_file_t file;
 | 
						|
  char *newc_name;
 | 
						|
  grub_off_t size;
 | 
						|
};
 | 
						|
 | 
						|
struct dir
 | 
						|
{
 | 
						|
  char *name;
 | 
						|
  struct dir *next;
 | 
						|
  struct dir *child;
 | 
						|
};
 | 
						|
 | 
						|
static char
 | 
						|
hex (grub_uint8_t val)
 | 
						|
{
 | 
						|
  if (val < 10)
 | 
						|
    return '0' + val;
 | 
						|
  return 'a' + val - 10;
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
set_field (char *var, grub_uint32_t val)
 | 
						|
{
 | 
						|
  int i;
 | 
						|
  char *ptr = var;
 | 
						|
  for (i = 28; i >= 0; i -= 4)
 | 
						|
    *ptr++ = hex((val >> i) & 0xf);
 | 
						|
}
 | 
						|
 | 
						|
static grub_uint8_t *
 | 
						|
make_header (grub_uint8_t *ptr,
 | 
						|
	     const char *name, grub_size_t len,
 | 
						|
	     grub_uint32_t mode,
 | 
						|
	     grub_off_t fsize)
 | 
						|
{
 | 
						|
  struct newc_head *head = (struct newc_head *) ptr;
 | 
						|
  grub_uint8_t *optr;
 | 
						|
  grub_size_t oh = 0;
 | 
						|
  grub_memcpy (head->magic, "070701", 6);
 | 
						|
  set_field (head->ino, 0);
 | 
						|
  set_field (head->mode, mode);
 | 
						|
  set_field (head->uid, 0);
 | 
						|
  set_field (head->gid, 0);
 | 
						|
  set_field (head->nlink, 1);
 | 
						|
  set_field (head->mtime, 0);
 | 
						|
  set_field (head->filesize, fsize);
 | 
						|
  set_field (head->devmajor, 0);
 | 
						|
  set_field (head->devminor, 0);
 | 
						|
  set_field (head->rdevmajor, 0);
 | 
						|
  set_field (head->rdevminor, 0);
 | 
						|
  set_field (head->namesize, len);
 | 
						|
  set_field (head->check, 0);
 | 
						|
  optr = ptr;
 | 
						|
  ptr += sizeof (struct newc_head);
 | 
						|
  grub_memcpy (ptr, name, len);
 | 
						|
  ptr += len;
 | 
						|
  oh = ALIGN_UP_OVERHEAD (ptr - optr, 4);
 | 
						|
  grub_memset (ptr, 0, oh);
 | 
						|
  ptr += oh;
 | 
						|
  return ptr;
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
free_dir (struct dir *root)
 | 
						|
{
 | 
						|
  if (!root)
 | 
						|
    return;
 | 
						|
  free_dir (root->next);
 | 
						|
  free_dir (root->child);
 | 
						|
  grub_free (root->name);
 | 
						|
  grub_free (root);
 | 
						|
}
 | 
						|
 | 
						|
static grub_err_t
 | 
						|
insert_dir (const char *name, struct dir **root,
 | 
						|
	    grub_uint8_t *ptr, grub_size_t *size)
 | 
						|
{
 | 
						|
  struct dir *cur, **head = root;
 | 
						|
  const char *cb, *ce = name;
 | 
						|
  *size = 0;
 | 
						|
  while (1)
 | 
						|
    {
 | 
						|
      for (cb = ce; *cb == '/'; cb++);
 | 
						|
      for (ce = cb; *ce && *ce != '/'; ce++);
 | 
						|
      if (!*ce)
 | 
						|
	break;
 | 
						|
 | 
						|
      for (cur = *root; cur; cur = cur->next)
 | 
						|
	if (grub_memcmp (cur->name, cb, ce - cb)
 | 
						|
	    && cur->name[ce - cb] == 0)
 | 
						|
	  break;
 | 
						|
      if (!cur)
 | 
						|
	{
 | 
						|
	  struct dir *n;
 | 
						|
	  n = grub_zalloc (sizeof (*n));
 | 
						|
	  if (!n)
 | 
						|
	    return 0;
 | 
						|
	  n->next = *head;
 | 
						|
	  n->name = grub_strndup (cb, ce - cb);
 | 
						|
	  if (ptr)
 | 
						|
	    {
 | 
						|
	      grub_dprintf ("linux", "Creating directory %s, %s\n", name, ce);
 | 
						|
	      ptr = make_header (ptr, name, ce - name,
 | 
						|
				 040777, 0);
 | 
						|
	    }
 | 
						|
	  if (grub_add (*size,
 | 
						|
		        ALIGN_UP ((ce - (char *) name)
 | 
						|
				  + sizeof (struct newc_head), 4),
 | 
						|
			size))
 | 
						|
	    {
 | 
						|
	      grub_error (GRUB_ERR_OUT_OF_RANGE, N_("overflow is detected"));
 | 
						|
	      grub_free (n->name);
 | 
						|
	      grub_free (n);
 | 
						|
	      return grub_errno;
 | 
						|
	    }
 | 
						|
	  *head = n;
 | 
						|
	  cur = n;
 | 
						|
	}
 | 
						|
      root = &cur->next;
 | 
						|
    }
 | 
						|
  return GRUB_ERR_NONE;
 | 
						|
}
 | 
						|
 | 
						|
grub_err_t
 | 
						|
grub_initrd_init (int argc, char *argv[],
 | 
						|
		  struct grub_linux_initrd_context *initrd_ctx)
 | 
						|
{
 | 
						|
  int i;
 | 
						|
  int newc = 0;
 | 
						|
  struct dir *root = 0;
 | 
						|
 | 
						|
  initrd_ctx->nfiles = 0;
 | 
						|
  initrd_ctx->components = 0;
 | 
						|
 | 
						|
  initrd_ctx->components = grub_calloc (argc, sizeof (initrd_ctx->components[0]));
 | 
						|
  if (!initrd_ctx->components)
 | 
						|
    return grub_errno;
 | 
						|
 | 
						|
  initrd_ctx->size = 0;
 | 
						|
 | 
						|
  for (i = 0; i < argc; i++)
 | 
						|
    {
 | 
						|
      const char *fname = argv[i];
 | 
						|
 | 
						|
      initrd_ctx->size = ALIGN_UP (initrd_ctx->size, 4);
 | 
						|
 | 
						|
      if (grub_memcmp (argv[i], "newc:", 5) == 0)
 | 
						|
	{
 | 
						|
	  const char *ptr, *eptr;
 | 
						|
	  ptr = argv[i] + 5;
 | 
						|
	  while (*ptr == '/')
 | 
						|
	    ptr++;
 | 
						|
	  eptr = grub_strchr (ptr, ':');
 | 
						|
	  if (eptr)
 | 
						|
	    {
 | 
						|
	      grub_size_t dir_size, name_len;
 | 
						|
 | 
						|
	      initrd_ctx->components[i].newc_name = grub_strndup (ptr, eptr - ptr);
 | 
						|
	      if (!initrd_ctx->components[i].newc_name ||
 | 
						|
		  insert_dir (initrd_ctx->components[i].newc_name, &root, 0,
 | 
						|
			      &dir_size))
 | 
						|
		{
 | 
						|
		  grub_initrd_close (initrd_ctx);
 | 
						|
		  return grub_errno;
 | 
						|
		}
 | 
						|
	      name_len = grub_strlen (initrd_ctx->components[i].newc_name);
 | 
						|
	      if (grub_add (initrd_ctx->size,
 | 
						|
			    ALIGN_UP (sizeof (struct newc_head) + name_len, 4),
 | 
						|
			    &initrd_ctx->size) ||
 | 
						|
		  grub_add (initrd_ctx->size, dir_size, &initrd_ctx->size))
 | 
						|
		goto overflow;
 | 
						|
	      newc = 1;
 | 
						|
	      fname = eptr + 1;
 | 
						|
	    }
 | 
						|
	}
 | 
						|
      else if (newc)
 | 
						|
	{
 | 
						|
	  if (grub_add (initrd_ctx->size,
 | 
						|
			ALIGN_UP (sizeof (struct newc_head)
 | 
						|
				  + sizeof ("TRAILER!!!") - 1, 4),
 | 
						|
			&initrd_ctx->size))
 | 
						|
	    goto overflow;
 | 
						|
	  free_dir (root);
 | 
						|
	  root = 0;
 | 
						|
	  newc = 0;
 | 
						|
	}
 | 
						|
      initrd_ctx->components[i].file = grub_file_open (fname,
 | 
						|
						       GRUB_FILE_TYPE_LINUX_INITRD
 | 
						|
						       | GRUB_FILE_TYPE_NO_DECOMPRESS);
 | 
						|
      if (!initrd_ctx->components[i].file)
 | 
						|
	{
 | 
						|
	  grub_initrd_close (initrd_ctx);
 | 
						|
	  return grub_errno;
 | 
						|
	}
 | 
						|
      initrd_ctx->nfiles++;
 | 
						|
      initrd_ctx->components[i].size
 | 
						|
	= grub_file_size (initrd_ctx->components[i].file);
 | 
						|
      if (grub_add (initrd_ctx->size, initrd_ctx->components[i].size,
 | 
						|
		    &initrd_ctx->size))
 | 
						|
	goto overflow;
 | 
						|
    }
 | 
						|
 | 
						|
  if (newc)
 | 
						|
    {
 | 
						|
      initrd_ctx->size = ALIGN_UP (initrd_ctx->size, 4);
 | 
						|
      if (grub_add (initrd_ctx->size,
 | 
						|
		    ALIGN_UP (sizeof (struct newc_head)
 | 
						|
			      + sizeof ("TRAILER!!!") - 1, 4),
 | 
						|
		    &initrd_ctx->size))
 | 
						|
	goto overflow;
 | 
						|
      free_dir (root);
 | 
						|
      root = 0;
 | 
						|
    }
 | 
						|
  
 | 
						|
  return GRUB_ERR_NONE;
 | 
						|
 | 
						|
 overflow:
 | 
						|
  free_dir (root);
 | 
						|
  grub_initrd_close (initrd_ctx);
 | 
						|
  return grub_error (GRUB_ERR_OUT_OF_RANGE, N_("overflow is detected"));
 | 
						|
}
 | 
						|
 | 
						|
grub_size_t
 | 
						|
grub_get_initrd_size (struct grub_linux_initrd_context *initrd_ctx)
 | 
						|
{
 | 
						|
  return initrd_ctx->size;
 | 
						|
}
 | 
						|
 | 
						|
void
 | 
						|
grub_initrd_close (struct grub_linux_initrd_context *initrd_ctx)
 | 
						|
{
 | 
						|
  int i;
 | 
						|
  if (!initrd_ctx->components)
 | 
						|
    return;
 | 
						|
  for (i = 0; i < initrd_ctx->nfiles; i++)
 | 
						|
    {
 | 
						|
      grub_free (initrd_ctx->components[i].newc_name);
 | 
						|
      grub_file_close (initrd_ctx->components[i].file);
 | 
						|
    }
 | 
						|
  grub_free (initrd_ctx->components);
 | 
						|
  initrd_ctx->components = 0;
 | 
						|
}
 | 
						|
 | 
						|
grub_err_t
 | 
						|
grub_initrd_load (struct grub_linux_initrd_context *initrd_ctx,
 | 
						|
		  char *argv[], void *target)
 | 
						|
{
 | 
						|
  grub_uint8_t *ptr = target;
 | 
						|
  int i;
 | 
						|
  int newc = 0;
 | 
						|
  struct dir *root = 0;
 | 
						|
  grub_ssize_t cursize = 0;
 | 
						|
 | 
						|
  for (i = 0; i < initrd_ctx->nfiles; i++)
 | 
						|
    {
 | 
						|
      grub_memset (ptr, 0, ALIGN_UP_OVERHEAD (cursize, 4));
 | 
						|
      ptr += ALIGN_UP_OVERHEAD (cursize, 4);
 | 
						|
 | 
						|
      if (initrd_ctx->components[i].newc_name)
 | 
						|
	{
 | 
						|
	  grub_size_t dir_size;
 | 
						|
 | 
						|
	  if (insert_dir (initrd_ctx->components[i].newc_name, &root, ptr,
 | 
						|
			  &dir_size))
 | 
						|
	    {
 | 
						|
	      free_dir (root);
 | 
						|
	      grub_initrd_close (initrd_ctx);
 | 
						|
	      return grub_errno;
 | 
						|
	    }
 | 
						|
	  ptr += dir_size;
 | 
						|
	  ptr = make_header (ptr, initrd_ctx->components[i].newc_name,
 | 
						|
			     grub_strlen (initrd_ctx->components[i].newc_name),
 | 
						|
			     0100777,
 | 
						|
			     initrd_ctx->components[i].size);
 | 
						|
	  newc = 1;
 | 
						|
	}
 | 
						|
      else if (newc)
 | 
						|
	{
 | 
						|
	  ptr = make_header (ptr, "TRAILER!!!", sizeof ("TRAILER!!!") - 1,
 | 
						|
			     0, 0);
 | 
						|
	  free_dir (root);
 | 
						|
	  root = 0;
 | 
						|
	  newc = 0;
 | 
						|
	}
 | 
						|
 | 
						|
      cursize = initrd_ctx->components[i].size;
 | 
						|
      if (grub_file_read (initrd_ctx->components[i].file, ptr, cursize)
 | 
						|
	  != cursize)
 | 
						|
	{
 | 
						|
	  if (!grub_errno)
 | 
						|
	    grub_error (GRUB_ERR_FILE_READ_ERROR, N_("premature end of file %s"),
 | 
						|
			argv[i]);
 | 
						|
	  grub_initrd_close (initrd_ctx);
 | 
						|
	  return grub_errno;
 | 
						|
	}
 | 
						|
      ptr += cursize;
 | 
						|
    }
 | 
						|
  if (newc)
 | 
						|
    {
 | 
						|
      grub_memset (ptr, 0, ALIGN_UP_OVERHEAD (cursize, 4));
 | 
						|
      ptr += ALIGN_UP_OVERHEAD (cursize, 4);
 | 
						|
      ptr = make_header (ptr, "TRAILER!!!", sizeof ("TRAILER!!!") - 1, 0, 0);
 | 
						|
    }
 | 
						|
  free_dir (root);
 | 
						|
  root = 0;
 | 
						|
  return GRUB_ERR_NONE;
 | 
						|
}
 |