]> git.ipfire.org Git - thirdparty/grub.git/commitdiff
Handle symlinks and long names on tar and cpio.
authorVladimir 'phcoder' Serbinenko <phcoder@gmail.com>
Sun, 30 Oct 2011 22:06:25 +0000 (23:06 +0100)
committerVladimir 'phcoder' Serbinenko <phcoder@gmail.com>
Sun, 30 Oct 2011 22:06:25 +0000 (23:06 +0100)
* grub-core/fs/cpio.c (ATTR_TYPE): New definition.
(ATTR_FILE): Likewise.
(ATTR_DIR): Likewise.
(ATTR_LNK): Likewise.
(grub_cpio_data) [MODE_USTAR]: New fields linkname and linkname_alloc.
(grub_cpio_find_file): Fill mode, handle linkname field as well as
L and K entries.
(grub_cpio_mount): Zero-fill data.
(handle_symlink): New function.

ChangeLog
grub-core/fs/cpio.c

index 223afb1e6e698db103fc87bce6b63ef5866481c3..543823e9cb48af98410ea3aa3c31ca29a7fe90d5 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,17 @@
+2011-10-30  Vladimir Serbinenko  <phcoder@gmail.com>
+
+       Handle symlinks and long names on tar and cpio.
+
+       * grub-core/fs/cpio.c (ATTR_TYPE): New definition.
+       (ATTR_FILE): Likewise.
+       (ATTR_DIR): Likewise.
+       (ATTR_LNK): Likewise.
+       (grub_cpio_data) [MODE_USTAR]: New fields linkname and linkname_alloc.
+       (grub_cpio_find_file): Fill mode, handle linkname field as well as
+       L and K entries.
+       (grub_cpio_mount): Zero-fill data.
+       (handle_symlink): New function.
+
 2011-10-30  Vladimir Serbinenko  <phcoder@gmail.com>
 
        Fix iso9660 filename limitations and fix memory leaks.
index 92531390e060d8181e8fda1a3a27637badb3544c..b43e54ec93bb5a3a7d265a11d83fc09d7c6be524 100644 (file)
 
 GRUB_MOD_LICENSE ("GPLv3+");
 
+#define ATTR_TYPE  0160000
+#define ATTR_FILE  0100000
+#define ATTR_DIR   0040000
+#define ATTR_LNK   0120000
+
 #ifndef MODE_USTAR
 /* cpio support */
 #define        MAGIC_BCPIO     070707
@@ -74,6 +79,10 @@ struct grub_cpio_data
   grub_off_t hofs;
   grub_off_t dofs;
   grub_off_t size;
+#ifdef MODE_USTAR
+  char *linkname;
+  grub_size_t linkname_alloc;
+#endif
 };
 
 static grub_dl_t my_mod;
@@ -101,7 +110,8 @@ canonicalize (char *name)
 
 static grub_err_t
 grub_cpio_find_file (struct grub_cpio_data *data, char **name,
-                    grub_int32_t *mtime, grub_disk_addr_t * ofs)
+                    grub_int32_t *mtime, grub_disk_addr_t *ofs,
+                    grub_uint32_t *mode)
 {
 #ifndef MODE_USTAR
   struct head hd;
@@ -115,11 +125,14 @@ grub_cpio_find_file (struct grub_cpio_data *data, char **name,
   data->size = (((grub_uint32_t) hd.filesize_1) << 16) + hd.filesize_2;
   if (mtime)
     *mtime = (((grub_uint32_t) hd.mtime_1) << 16) + hd.mtime_2;
+  if (mode)
+    *mode = hd.mode;
 
   if (hd.namesize & 1)
     hd.namesize++;
 
-  if ((*name = grub_malloc (hd.namesize)) == NULL)
+  *name = grub_malloc (hd.namesize + 1);
+  if (*name == NULL)
     return grub_errno;
 
   if (grub_disk_read (data->disk, 0, data->hofs + sizeof (hd),
@@ -128,9 +141,10 @@ grub_cpio_find_file (struct grub_cpio_data *data, char **name,
       grub_free (*name);
       return grub_errno;
     }
+  (*name)[hd.namesize] = 0;
 
   if (data->size == 0 && hd.mode == 0 && hd.namesize == 11 + 1
-      && ! grub_memcmp(*name, "TRAILER!!!", 11))
+      && grub_memcmp(*name, "TRAILER!!!", 11) == 0)
     {
       *ofs = 0;
       return GRUB_ERR_NONE;
@@ -144,29 +158,118 @@ grub_cpio_find_file (struct grub_cpio_data *data, char **name,
     (*ofs)++;
 #else
   struct head hd;
+  int reread = 0, have_longname = 0, have_longlink = 0;
 
-  if (grub_disk_read (data->disk, 0, data->hofs, sizeof (hd), &hd))
-    return grub_errno;
-
-  if (!hd.name[0])
+  for (reread = 0; reread < 3; reread++)
     {
-      *ofs = 0;
-      return GRUB_ERR_NONE;
-    }
+      if (grub_disk_read (data->disk, 0, data->hofs, sizeof (hd), &hd))
+       return grub_errno;
 
-  if (grub_memcmp (hd.magic, MAGIC_USTAR, sizeof (MAGIC_USTAR) - 1))
-    return grub_error (GRUB_ERR_BAD_FS, "invalid tar archive");
+      if (!hd.name[0])
+       {
+         *ofs = 0;
+         return GRUB_ERR_NONE;
+       }
 
-  if ((*name = grub_strdup (hd.name)) == NULL)
-    return grub_errno;
+      if (grub_memcmp (hd.magic, MAGIC_USTAR, sizeof (MAGIC_USTAR) - 1))
+       return grub_error (GRUB_ERR_BAD_FS, "invalid tar archive");
 
-  data->size = grub_strtoull (hd.size, NULL, 8);
-  data->dofs = data->hofs + GRUB_DISK_SECTOR_SIZE;
-  *ofs = data->dofs + ((data->size + GRUB_DISK_SECTOR_SIZE - 1) &
-                      ~(GRUB_DISK_SECTOR_SIZE - 1));
-  if (mtime)
-    *mtime = grub_strtoul (hd.mtime, NULL, 8);
-  canonicalize (*name);
+      if (hd.typeflag == 'L')
+       {
+         grub_err_t err;
+         grub_size_t namesize = grub_strtoull (hd.size, NULL, 8);
+         *name = grub_malloc (namesize + 1);
+         if (*name == NULL)
+           return grub_errno;
+         err = grub_disk_read (data->disk, 0,
+                               data->hofs + GRUB_DISK_SECTOR_SIZE, namesize,
+                               *name);
+         (*name)[namesize] = 0;
+         if (err)
+           return err;
+         data->hofs += GRUB_DISK_SECTOR_SIZE
+           + ((namesize + GRUB_DISK_SECTOR_SIZE - 1) &
+              ~(GRUB_DISK_SECTOR_SIZE - 1));
+         have_longname = 1;
+         continue;
+       }
+
+      if (hd.typeflag == 'K')
+       {
+         grub_err_t err;
+         grub_size_t linksize = grub_strtoull (hd.size, NULL, 8);
+         if (data->linkname_alloc < linksize + 1)
+           {
+             char *n;
+             n = grub_malloc (2 * (linksize + 1));
+             if (!n)
+               return grub_errno;
+             grub_free (data->linkname);
+             data->linkname = n;
+             data->linkname_alloc = 2 * (linksize + 1);
+           }
+
+         err = grub_disk_read (data->disk, 0,
+                               data->hofs + GRUB_DISK_SECTOR_SIZE, linksize,
+                               data->linkname);
+         if (err)
+           return err;
+         data->linkname[linksize] = 0;
+         data->hofs += GRUB_DISK_SECTOR_SIZE
+           + ((linksize + GRUB_DISK_SECTOR_SIZE - 1) &
+              ~(GRUB_DISK_SECTOR_SIZE - 1));
+         have_longlink = 1;
+         continue;
+       }
+
+      if (!have_longname)
+       {
+         *name = grub_strdup (hd.name);
+         if (*name == NULL)
+           return grub_errno;
+       }
+
+      data->size = grub_strtoull (hd.size, NULL, 8);
+      data->dofs = data->hofs + GRUB_DISK_SECTOR_SIZE;
+      *ofs = data->dofs + ((data->size + GRUB_DISK_SECTOR_SIZE - 1) &
+                          ~(GRUB_DISK_SECTOR_SIZE - 1));
+      if (mtime)
+       *mtime = grub_strtoul (hd.mtime, NULL, 8);
+      if (mode)
+       {
+         *mode = grub_strtoul (hd.mode, NULL, 8);
+         switch (hd.typeflag)
+           {
+           case '2':
+             *mode |= ATTR_LNK;
+             break;
+           case '0':
+             *mode |= ATTR_FILE;
+             break;
+           case '5':
+             *mode |= ATTR_DIR;
+             break;
+           }
+       }
+      if (!have_longlink)
+       {
+         if (data->linkname_alloc < 101)
+           {
+             char *n;
+             n = grub_malloc (101);
+             if (!n)
+               return grub_errno;
+             grub_free (data->linkname);
+             data->linkname = n;
+             data->linkname_alloc = 101;
+           }
+         grub_memcpy (data->linkname, hd.linkname, sizeof (hd.linkname));
+         data->linkname[100] = 0;
+       }
+
+      canonicalize (*name);
+      return GRUB_ERR_NONE;
+    }
 #endif
   return GRUB_ERR_NONE;
 }
@@ -188,7 +291,7 @@ grub_cpio_mount (grub_disk_t disk)
 #endif
     goto fail;
 
-  data = (struct grub_cpio_data *) grub_malloc (sizeof (*data));
+  data = (struct grub_cpio_data *) grub_zalloc (sizeof (*data));
   if (!data)
     goto fail;
 
@@ -208,15 +311,105 @@ fail:
 }
 
 static grub_err_t
-grub_cpio_dir (grub_device_t device, const char *path,
+handle_symlink (struct grub_cpio_data *data,
+               const char *fn, char **name,
+               grub_uint32_t mode, int *restart)
+{
+  grub_size_t flen;
+  char *target;
+#ifndef MODE_USTAR
+  grub_err_t err;
+#endif
+  char *ptr;
+  char *lastslash;
+  grub_size_t prefixlen;
+  char *rest;
+  grub_size_t size;
+
+  *restart = 0;
+
+  if ((mode & ATTR_TYPE) != ATTR_LNK)
+    return GRUB_ERR_NONE;
+  flen = grub_strlen (fn);
+  if (grub_memcmp (*name, fn, flen) != 0 
+      || ((*name)[flen] != 0 && (*name)[flen] != '/'))
+    return GRUB_ERR_NONE;
+  rest = *name + flen;
+  lastslash = rest;
+  if (*rest)
+    rest++;
+  while (lastslash >= *name && *lastslash != '/')
+    lastslash--;
+  if (lastslash >= *name)
+    prefixlen = lastslash - *name;
+  else
+    prefixlen = 0;
+
+  if (prefixlen)
+    prefixlen++;
+
+#ifdef MODE_USTAR
+  size = grub_strlen (data->linkname);
+#else
+  size = data->size;
+#endif
+  if (size == 0)
+    return GRUB_ERR_NONE;
+  target = grub_malloc (size + grub_strlen (*name) + 2);
+  if (!target)
+    return grub_errno;
+
+#ifdef MODE_USTAR
+  grub_memcpy (target + prefixlen, data->linkname, size);
+#else
+  err = grub_disk_read (data->disk, 0, data->dofs, data->size, 
+                       target + prefixlen);
+  if (err)
+    return err;
+#endif
+  if (target[prefixlen] == '/')
+    {
+      grub_memmove (target, target + prefixlen, data->size - 1);
+      ptr = target + data->size - 1;
+      ptr = grub_stpcpy (ptr, rest);
+      *ptr = 0;
+      grub_dprintf ("cpio", "symlink redirected %s to %s\n",
+                   *name, target);
+      grub_free (*name);
+      *name = target;
+      *restart = 1;
+      return GRUB_ERR_NONE;
+    }
+  if (prefixlen)
+    {
+      grub_memcpy (target, *name, prefixlen);
+      target[prefixlen] = '/';
+    }
+  ptr = target + prefixlen + size;
+  ptr = grub_stpcpy (ptr, rest);
+  *ptr = 0;
+  grub_dprintf ("cpio", "symlink redirected %s to %s\n",
+               *name, target);
+  grub_free (*name);
+  *name = target;
+  *restart = 1;
+  return GRUB_ERR_NONE;
+}
+
+static grub_err_t
+grub_cpio_dir (grub_device_t device, const char *path_in,
               int (*hook) (const char *filename,
                            const struct grub_dirhook_info *info))
 {
   struct grub_cpio_data *data;
   grub_disk_addr_t ofs;
-  char *prev, *name;
-  const char *np;
+  char *prev, *name, *path;
   grub_size_t len;
+  int symlinknest = 0;
+
+  path = grub_strdup (path_in + 1);
+  if (!path)
+    return grub_errno;
 
   grub_dl_ref (my_mod);
 
@@ -224,23 +417,27 @@ grub_cpio_dir (grub_device_t device, const char *path,
 
   data = grub_cpio_mount (device->disk);
   if (!data)
-    goto fail;
-
-  np = path + 1;
-  len = grub_strlen (path) - 1;
+    {
+      grub_free (path);
+      return grub_errno;
+    }
 
+  len = grub_strlen (path);
   data->hofs = 0;
   while (1)
     {
       grub_int32_t mtime;
+      grub_uint32_t mode;
+      grub_err_t err;
 
-      if (grub_cpio_find_file (data, &name, &mtime, &ofs))
+      if (grub_cpio_find_file (data, &name, &mtime, &ofs, &mode))
        goto fail;
 
       if (!ofs)
        break;
 
-      if (grub_memcmp (np, name, len) == 0)
+      if (grub_memcmp (path, name, len) == 0
+         && (name[len] == 0 || name[len] == '/' || len == 0))
        {
          char *p, *n;
 
@@ -265,14 +462,35 @@ grub_cpio_dir (grub_device_t device, const char *path,
              prev = name;
            }
          else
-           grub_free (name);
+           {
+             int restart;
+             err = handle_symlink (data, name, &path, mode, &restart);
+             grub_free (name);
+             if (err)
+               goto fail;
+             if (restart)
+               {
+                 len = grub_strlen (path);
+                 if (++symlinknest == 8)
+                   {
+                     grub_error (GRUB_ERR_SYMLINK_LOOP,
+                                 "too deep nesting of symlinks");
+                     goto fail;
+                   }
+                 ofs = 0;
+               }
+           }
        }
       data->hofs = ofs;
     }
 
 fail:
 
+  grub_free (path);
   grub_free (prev);
+#ifdef MODE_USTAR
+  grub_free (data->linkname);
+#endif
   grub_free (data);
 
   grub_dl_unref (my_mod);
@@ -281,23 +499,33 @@ fail:
 }
 
 static grub_err_t
-grub_cpio_open (grub_file_t file, const char *name)
+grub_cpio_open (grub_file_t file, const char *name_in)
 {
   struct grub_cpio_data *data;
   grub_disk_addr_t ofs;
   char *fn;
-  int i, j;
+  char *name = grub_strdup (name_in + 1);
+  int symlinknest = 0;
+
+  if (!name)
+    return grub_errno;
 
   grub_dl_ref (my_mod);
 
   data = grub_cpio_mount (file->device->disk);
   if (!data)
-    goto fail;
+    {
+      grub_free (name);
+      return grub_errno;
+    }
 
   data->hofs = 0;
   while (1)
     {
-      if (grub_cpio_find_file (data, &fn, NULL, &ofs))
+      grub_uint32_t mode;
+      int restart;
+      
+      if (grub_cpio_find_file (data, &fn, NULL, &ofs, &mode))
        goto fail;
 
       if (!ofs)
@@ -306,33 +534,31 @@ grub_cpio_open (grub_file_t file, const char *name)
          break;
        }
 
-      /* Compare NAME and FN by hand in order to cope with duplicate
-        slashes.  */
-      i = 0;
-      j = 0;
-      while (name[i] == '/')
-       i++;
-      while (1)
+      if (handle_symlink (data, fn, &name, mode, &restart))
        {
-         if (name[i] != fn[j])
-           goto no_match;
-
-         if (name[i] == '\0')
-           break;
-
-         while (name[i] == '/' && name[i+1] == '/')
-           i++;
+         grub_free (fn);
+         goto fail;
+       }
 
-         i++;
-         j++;
+      if (restart)
+       {
+         ofs = 0;
+         if (++symlinknest == 8)
+           {
+             grub_error (GRUB_ERR_SYMLINK_LOOP,
+                         "too deep nesting of symlinks");
+             goto fail;
+           }
+         goto no_match;
        }
 
-      if (name[i] != fn[j])
+      if (grub_strcmp (name, fn) != 0)
        goto no_match;
 
       file->data = data;
       file->size = data->size;
       grub_free (fn);
+      grub_free (name);
 
       return GRUB_ERR_NONE;
 
@@ -343,8 +569,11 @@ grub_cpio_open (grub_file_t file, const char *name)
     }
 
 fail:
-
+#ifdef MODE_USTAR
+  grub_free (data->linkname);
+#endif
   grub_free (data);
+  grub_free (name);
 
   grub_dl_unref (my_mod);
 
@@ -364,7 +593,13 @@ grub_cpio_read (grub_file_t file, char *buf, grub_size_t len)
 static grub_err_t
 grub_cpio_close (grub_file_t file)
 {
-  grub_free (file->data);
+  struct grub_cpio_data *data;
+
+  data = file->data;
+#ifdef MODE_USTAR
+  grub_free (data->linkname);
+#endif
+  grub_free (data);
 
   grub_dl_unref (my_mod);