inspect: Check /etc/lsb-release is not too large before calling head on it.
[libguestfs.git] / src / inspect.c
index f4f13dd..fef6816 100644 (file)
@@ -215,6 +215,9 @@ static int parse_unsigned_int (guestfs_h *g, const char *str);
 static int add_fstab_entry (guestfs_h *g, struct inspect_fs *fs,
                             const char *spec, const char *mp);
 static char *resolve_fstab_device (guestfs_h *g, const char *spec);
+static int download_to_tmp (guestfs_h *g, const char *filename, char *localtmp, int64_t max_size);
+static int inspect_with_augeas (guestfs_h *g, struct inspect_fs *fs, const char *filename, int (*f) (guestfs_h *, struct inspect_fs *));
+static char *first_line_of_file (guestfs_h *g, const char *filename);
 
 static int
 check_for_filesystem_on (guestfs_h *g, const char *device)
@@ -331,21 +334,9 @@ static int
 parse_release_file (guestfs_h *g, struct inspect_fs *fs,
                     const char *release_filename)
 {
-  char **product_name = guestfs_head_n (g, 1, release_filename);
-  if (product_name == NULL)
+  fs->product_name = first_line_of_file (g, release_filename);
+  if (fs->product_name == NULL)
     return -1;
-  if (product_name[0] == NULL) {
-    error (g, _("%s: file is empty"), release_filename);
-    guestfs___free_string_list (product_name);
-    return -1;
-  }
-
-  /* Note that this string becomes owned by the handle and will
-   * be freed by guestfs___free_inspect_info.
-   */
-  fs->product_name = product_name[0];
-  free (product_name);
-
   return 0;
 }
 
@@ -380,11 +371,26 @@ parse_major_minor (guestfs_h *g, struct inspect_fs *fs)
 static int
 parse_lsb_release (guestfs_h *g, struct inspect_fs *fs)
 {
+  const char *filename = "/etc/lsb-release";
+  int64_t size;
   char **lines;
   size_t i;
   int r = 0;
 
-  lines = guestfs_head_n (g, 10, "/etc/lsb-release");
+  /* Don't trust guestfs_head_n not to break with very large files.
+   * Check the file size is something reasonable first.
+   */
+  size = guestfs_filesize (g, filename);
+  if (size == -1)
+    /* guestfs_filesize failed and has already set error in handle */
+    return -1;
+  if (size > 1000000) {
+    error (g, _("size of %s is unreasonably large (%" PRIi64 " bytes)"),
+           filename, size);
+    return -1;
+  }
+
+  lines = guestfs_head_n (g, 10, filename);
   if (lines == NULL)
     return -1;
 
@@ -542,7 +548,7 @@ check_linux_root (guestfs_h *g, struct inspect_fs *fs)
    * which filesystems are used by the operating system and how they
    * are mounted.
    */
-  if (check_fstab (g, fs) == -1)
+  if (inspect_with_augeas (g, fs, "/etc/fstab", check_fstab) == -1)
     return -1;
 
   return 0;
@@ -574,40 +580,9 @@ check_architecture (guestfs_h *g, struct inspect_fs *fs)
   }
 }
 
-static int check_fstab_aug_open (guestfs_h *g, struct inspect_fs *fs);
-
 static int
 check_fstab (guestfs_h *g, struct inspect_fs *fs)
 {
-  int r;
-  int64_t size;
-
-  /* Security: Refuse to do this if /etc/fstab is huge. */
-  size = guestfs_filesize (g, "/etc/fstab");
-  if (size == -1 || size > 100000) {
-    error (g, _("size of /etc/fstab unreasonable (%" PRIi64 " bytes)"), size);
-    return -1;
-  }
-
-  /* XXX What if !feature_available (g, "augeas")? */
-  if (guestfs_aug_init (g, "/", 16|32) == -1)
-    return -1;
-
-  /* Tell Augeas to only load /etc/fstab (thanks Raphaël Pinson). */
-  guestfs_aug_rm (g, "/augeas/load//incl[. != \"/etc/fstab\"]");
-  guestfs_aug_load (g);
-
-  r = check_fstab_aug_open (g, fs);
-  guestfs_aug_close (g);
-  if (r == -1)
-    return -1;
-
-  return 0;
-}
-
-static int
-check_fstab_aug_open (guestfs_h *g, struct inspect_fs *fs)
-{
   char **lines = guestfs_aug_ls (g, "/files/etc/fstab");
   if (lines == NULL)
     return -1;
@@ -852,12 +827,7 @@ check_windows_arch (guestfs_h *g, struct inspect_fs *fs)
 static int
 check_windows_registry (guestfs_h *g, struct inspect_fs *fs)
 {
-  TMP_TEMPLATE_ON_STACK (dir);
-#define dir_len (strlen (dir))
-#define software_hive_len (dir_len + 16)
-  char software_hive[software_hive_len];
-#define cmd_len (dir_len + 16)
-  char cmd[cmd_len];
+  TMP_TEMPLATE_ON_STACK (software_local);
 
   size_t len = strlen (fs->windows_systemroot) + 64;
   char software[len];
@@ -875,25 +845,10 @@ check_windows_registry (guestfs_h *g, struct inspect_fs *fs)
   hive_h *h = NULL;
   hive_value_h *values = NULL;
 
-  /* Security: Refuse to download registry if it is huge. */
-  int64_t size = guestfs_filesize (g, software_path);
-  if (size == -1 || size > 100000000) {
-    error (g, _("size of %s unreasonable (%" PRIi64 " bytes)"),
-           software_path, size);
-    goto out;
-  }
-
-  if (mkdtemp (dir) == NULL) {
-    perrorf (g, "mkdtemp");
-    goto out;
-  }
-
-  snprintf (software_hive, software_hive_len, "%s/software", dir);
-
-  if (guestfs_download (g, software_path, software_hive) == -1)
+  if (download_to_tmp (g, software_path, software_local, 100000000) == -1)
     goto out;
 
-  h = hivex_open (software_hive, g->verbose ? HIVEX_OPEN_VERBOSE : 0);
+  h = hivex_open (software_local, g->verbose ? HIVEX_OPEN_VERBOSE : 0);
   if (h == NULL) {
     perrorf (g, "hivex_open");
     goto out;
@@ -970,15 +925,9 @@ check_windows_registry (guestfs_h *g, struct inspect_fs *fs)
   free (values);
   free (software_path);
 
-  /* Free up the temporary directory.  Note the directory name cannot
-   * contain shell meta-characters because of the way it was
-   * constructed above.
-   */
-  snprintf (cmd, cmd_len, "rm -rf %s", dir);
-  ignore_value (system (cmd));
-#undef dir_len
-#undef software_hive_len
-#undef cmd_len
+  /* Free up the temporary file. */
+  unlink (software_local);
+#undef software_local_len
 
   return ret;
 }
@@ -1217,6 +1166,137 @@ guestfs__inspect_get_filesystems (guestfs_h *g, const char *root)
   return ret;
 }
 
+/* Download to a guest file to a local temporary file.  Refuse to
+ * download the guest file if it is larger than max_size.  The caller
+ * is responsible for deleting the temporary file after use.
+ */
+static int
+download_to_tmp (guestfs_h *g, const char *filename,
+                 char *localtmp, int64_t max_size)
+{
+  int fd;
+  char buf[32];
+  int64_t size;
+
+  size = guestfs_filesize (g, filename);
+  if (size == -1)
+    /* guestfs_filesize failed and has already set error in handle */
+    return -1;
+  if (size > max_size) {
+    error (g, _("size of %s is unreasonably large (%" PRIi64 " bytes)"),
+           filename, size);
+    return -1;
+  }
+
+  fd = mkstemp (localtmp);
+  if (fd == -1) {
+    perrorf (g, "mkstemp");
+    return -1;
+  }
+
+  snprintf (buf, sizeof buf, "/dev/fd/%d", fd);
+
+  if (guestfs_download (g, filename, buf) == -1) {
+    close (fd);
+    unlink (localtmp);
+    return -1;
+  }
+
+  if (close (fd) == -1) {
+    perrorf (g, "close: %s", localtmp);
+    unlink (localtmp);
+    return -1;
+  }
+
+  return 0;
+}
+
+/* Call 'f' with Augeas opened and having parsed 'filename' (this file
+ * must exist).  As a security measure, this bails if the file is too
+ * large for a reasonable configuration file.  After the call to 'f'
+ * Augeas is closed.
+ */
+static int
+inspect_with_augeas (guestfs_h *g, struct inspect_fs *fs, const char *filename,
+                     int (*f) (guestfs_h *, struct inspect_fs *))
+{
+  /* Security: Refuse to do this if filename is too large. */
+  int64_t size = guestfs_filesize (g, filename);
+  if (size == -1)
+    /* guestfs_filesize failed and has already set error in handle */
+    return -1;
+  if (size > 100000) {
+    error (g, _("size of %s is unreasonably large (%" PRIi64 " bytes)"),
+           filename, size);
+    return -1;
+  }
+
+  /* If !feature_available (g, "augeas") then the next call will fail.
+   * Arguably we might want to fall back to a non-Augeas method in
+   * this case.
+   */
+  if (guestfs_aug_init (g, "/", 16|32) == -1)
+    return -1;
+
+  int r = -1;
+
+  /* Tell Augeas to only load one file (thanks Raphaël Pinson). */
+  char buf[strlen (filename) + 64];
+  snprintf (buf, strlen (filename) + 64, "/augeas/load//incl[. != \"%s\"]",
+            filename);
+  if (guestfs_aug_rm (g, buf) == -1)
+    goto out;
+
+  if (guestfs_aug_load (g) == -1)
+    goto out;
+
+  r = f (g, fs);
+
+ out:
+  guestfs_aug_close (g);
+
+  return r;
+}
+
+/* Get the first line of a small file, without any trailing newline
+ * character.
+ */
+static char *
+first_line_of_file (guestfs_h *g, const char *filename)
+{
+  char **lines;
+  int64_t size;
+  char *ret;
+
+  /* Don't trust guestfs_head_n not to break with very large files.
+   * Check the file size is something reasonable first.
+   */
+  size = guestfs_filesize (g, filename);
+  if (size == -1)
+    /* guestfs_filesize failed and has already set error in handle */
+    return NULL;
+  if (size > 1000000) {
+    error (g, _("size of %s is unreasonably large (%" PRIi64 " bytes)"),
+           filename, size);
+    return NULL;
+  }
+
+  lines = guestfs_head_n (g, 1, filename);
+  if (lines == NULL)
+    return NULL;
+  if (lines[0] == NULL) {
+    error (g, _("%s: file is empty"), filename);
+    guestfs___free_string_list (lines);
+    return NULL;
+  }
+  /* lines[1] should be NULL because of '1' argument above ... */
+
+  ret = lines[0];               /* caller frees */
+  free (lines);                 /* free the array */
+
+  return ret;
+}
+
 #else /* no PCRE or hivex at compile time */
 
 /* XXX These functions should be in an optgroup. */