Move htole*/le*toh macros into a separate header file.
[libguestfs.git] / hivex / hivex.c
index fd65f8c..4cf3ad9 100644 (file)
@@ -1,5 +1,5 @@
 /* hivex - Windows Registry "hive" extraction library.
- * Copyright (C) 2009 Red Hat Inc.
+ * Copyright (C) 2009-2010 Red Hat Inc.
  * Derived from code by Petter Nordahl-Hagen under a compatible license:
  *   Copyright (c) 1997-2007 Petter Nordahl-Hagen.
  * Derived from code by Markus Stephany under a compatible license:
 #include <sys/mman.h>
 #include <sys/stat.h>
 #include <assert.h>
-#ifdef HAVE_ENDIAN_H
-#include <endian.h>
-#endif
-#ifdef HAVE_BYTESWAP_H
-#include <byteswap.h>
-#endif
 
 #define STREQ(a,b) (strcmp((a),(b)) == 0)
 #define STRCASEEQ(a,b) (strcasecmp((a),(b)) == 0)
 //#define STRCASENEQLEN(a,b,n) (strncasecmp((a),(b),(n)) != 0)
 //#define STRPREFIX(a,b) (strncmp((a),(b),strlen((b))) == 0)
 
-#if __BYTE_ORDER == __LITTLE_ENDIAN
-#ifndef be32toh
-#define be32toh(x) __bswap_32 (x)
-#endif
-#ifndef be64toh
-#define be64toh(x) __bswap_64 (x)
-#endif
-#ifndef le16toh
-#define le16toh(x) (x)
-#endif
-#ifndef le32toh
-#define le32toh(x) (x)
-#endif
-#ifndef le64toh
-#define le64toh(x) (x)
-#endif
-#else
-#ifndef be32toh
-#define be32toh(x) (x)
-#endif
-#ifndef be64toh
-#define be64toh(x) (x)
-#endif
-#ifndef le16toh
-#define le16toh(x) __bswap_16 (x)
-#endif
-#ifndef le32toh
-#define le32toh(x) __bswap_32 (x)
-#endif
-#ifndef le64toh
-#define le64toh(x) __bswap_64 (x)
-#endif
-#endif
-
 #include "hivex.h"
+#include "byte_conversions.h"
 
 static char *windows_utf16_to_utf8 (/* const */ char *input, size_t len);
 
@@ -196,18 +157,25 @@ struct ntreg_nk_record {
   int32_t seg_len;              /* length (always -ve because used) */
   char id[2];                   /* "nk" */
   uint16_t flags;
-  char timestamp[12];
+  char timestamp[8];
+  uint32_t unknown1;
   uint32_t parent;              /* offset of owner/parent */
   uint32_t nr_subkeys;          /* number of subkeys */
-  uint32_t unknown1;
+  uint32_t nr_subkeys_volatile;
   uint32_t subkey_lf;           /* lf record containing list of subkeys */
-  uint32_t unknown2;
+  uint32_t subkey_lf_volatile;
   uint32_t nr_values;           /* number of values */
   uint32_t vallist;             /* value-list record */
   uint32_t sk;                  /* offset of sk-record */
   uint32_t classname;           /* offset of classname record */
-  char unknown3[16];
-  uint32_t unknown4;
+  uint16_t max_subkey_name_len; /* maximum length of a subkey name in bytes
+                                   if the subkey was reencoded as UTF-16LE */
+  uint16_t unknown2;
+  uint32_t unknown3;
+  uint32_t max_vk_name_len;     /* maximum length of any vk name in bytes
+                                   if the name was reencoded as UTF-16LE */
+  uint32_t max_vk_data_len;     /* maximum length of any vk data in bytes */
+  uint32_t unknown6;
   uint16_t name_len;            /* length of name */
   uint16_t classname_len;       /* length of classname */
   char name[1];                 /* name follows here */
@@ -219,7 +187,7 @@ struct ntreg_lf_record {
   uint16_t nr_keys;             /* number of keys in this record */
   struct {
     uint32_t offset;            /* offset of nk-record for this subkey */
-    char name[4];               /* first 4 characters of subkey name */
+    char hash[4];               /* hash of subkey name */
   } keys[1];
 } __attribute__((__packed__));
 
@@ -247,7 +215,7 @@ struct ntreg_vk_record {
    */
   uint32_t data_len;
   uint32_t data_offset;         /* pointer to the data (or data if inline) */
-  hive_type data_type;          /* type of the data */
+  uint32_t data_type;           /* type of the data */
   uint16_t flags;               /* bit 0 set => key name ASCII,
                                    bit 0 clr => key name UTF-16.
                                    Only seen ASCII here in the wild. */
@@ -255,6 +223,21 @@ struct ntreg_vk_record {
   char name[1];                 /* key name follows here */
 } __attribute__((__packed__));
 
+static uint32_t
+header_checksum (const hive_h *h)
+{
+  uint32_t *daddr = (uint32_t *) h->addr;
+  size_t i;
+  uint32_t sum = 0;
+
+  for (i = 0; i < 0x1fc / 4; ++i) {
+    sum ^= le32toh (*daddr);
+    daddr++;
+  }
+
+  return sum;
+}
+
 hive_h *
 hivex_open (const char *filename, int flags)
 {
@@ -323,14 +306,7 @@ hivex_open (const char *filename, int flags)
     goto error;
 
   /* Header checksum. */
-  uint32_t *daddr = (uint32_t *) h->addr;
-  size_t i;
-  uint32_t sum = 0;
-  for (i = 0; i < 0x1fc / 4; ++i) {
-    sum ^= le32toh (*daddr);
-    daddr++;
-  }
-
+  uint32_t sum = header_checksum (h);
   if (sum != le32toh (h->hdr->csum)) {
     fprintf (stderr, "hivex: %s: bad checksum in hive header\n", filename);
     errno = EINVAL;
@@ -433,7 +409,7 @@ hivex_open (const char *filename, int flags)
       int used;
       seg_len = block_len (h, blkoff, &used);
       if (seg_len <= 4 || (seg_len & 3) != 0) {
-        fprintf (stderr, "hivex: %s: block size %d at %zu, bad registry\n",
+        fprintf (stderr, "hivex: %s: block size %" PRIu32 " at 0x%zx, bad registry\n",
                  filename, le32toh (block->seg_len), blkoff);
         errno = ENOTSUP;
         goto error;
@@ -612,34 +588,97 @@ hivex_node_classname (hive_h *h, hive_node_h node)
 }
 #endif
 
-hive_node_h *
-hivex_node_children (hive_h *h, hive_node_h node)
+/* Structure for returning 0-terminated lists of offsets (nodes,
+ * values, etc).
+ */
+struct offset_list {
+  size_t *offsets;
+  size_t len;
+  size_t alloc;
+};
+
+static void
+init_offset_list (struct offset_list *list)
+{
+  list->len = 0;
+  list->alloc = 0;
+  list->offsets = NULL;
+}
+
+#define INIT_OFFSET_LIST(name) \
+  struct offset_list name; \
+  init_offset_list (&name)
+
+/* Preallocates the offset_list, but doesn't make the contents longer. */
+static int
+grow_offset_list (struct offset_list *list, size_t alloc)
+{
+  assert (alloc >= list->len);
+  size_t *p = realloc (list->offsets, alloc * sizeof (size_t));
+  if (p == NULL)
+    return -1;
+  list->offsets = p;
+  list->alloc = alloc;
+  return 0;
+}
+
+static int
+add_to_offset_list (struct offset_list *list, size_t offset)
+{
+  if (list->len >= list->alloc) {
+    if (grow_offset_list (list, list->alloc ? list->alloc * 2 : 4) == -1)
+      return -1;
+  }
+  list->offsets[list->len] = offset;
+  list->len++;
+  return 0;
+}
+
+static void
+free_offset_list (struct offset_list *list)
+{
+  free (list->offsets);
+}
+
+static size_t *
+return_offset_list (struct offset_list *list)
+{
+  if (add_to_offset_list (list, 0) == -1)
+    return NULL;
+  return list->offsets;         /* caller frees */
+}
+
+/* Iterate over children, returning child nodes and intermediate blocks. */
+static int
+get_children (hive_h *h, hive_node_h node,
+              hive_node_h **children_ret, size_t **blocks_ret)
 {
   if (!IS_VALID_BLOCK (h, node) || !BLOCK_ID_EQ (h, node, "nk")) {
     errno = EINVAL;
-    return NULL;
+    return -1;
   }
 
   struct ntreg_nk_record *nk = (struct ntreg_nk_record *) (h->addr + node);
 
   size_t nr_subkeys_in_nk = le32toh (nk->nr_subkeys);
 
+  INIT_OFFSET_LIST (children);
+  INIT_OFFSET_LIST (blocks);
+
   /* Deal with the common "no subkeys" case quickly. */
-  hive_node_h *ret;
-  if (nr_subkeys_in_nk == 0) {
-    ret = malloc (sizeof (hive_node_h));
-    if (ret == NULL)
-      return NULL;
-    ret[0] = 0;
-    return ret;
-  }
+  if (nr_subkeys_in_nk == 0)
+    goto ok;
 
   /* Arbitrarily limit the number of subkeys we will ever deal with. */
   if (nr_subkeys_in_nk > 1000000) {
     errno = ERANGE;
-    return NULL;
+    goto error;
   }
 
+  /* Preallocate space for the children. */
+  if (grow_offset_list (&children, nr_subkeys_in_nk) == -1)
+    goto error;
+
   /* The subkey_lf field can point either to an lf-record, which is
    * the common case, or if there are lots of subkeys, to an
    * ri-record.
@@ -651,9 +690,12 @@ hivex_node_children (hive_h *h, hive_node_h node)
       fprintf (stderr, "hivex_node_children: returning EFAULT because subkey_lf is not a valid block (%zu)\n",
                subkey_lf);
     errno = EFAULT;
-    return NULL;
+    goto error;
   }
 
+  if (add_to_offset_list (&blocks, subkey_lf) == -1)
+    goto error;
+
   struct ntreg_hbin_block *block =
     (struct ntreg_hbin_block *) (h->addr + subkey_lf);
 
@@ -674,7 +716,7 @@ hivex_node_children (hive_h *h, hive_node_h node)
 
     if (nr_subkeys_in_nk != nr_subkeys_in_lf) {
       errno = ENOTSUP;
-      return NULL;
+      goto error;
     }
 
     size_t len = block_len (h, subkey_lf, NULL);
@@ -683,32 +725,24 @@ hivex_node_children (hive_h *h, hive_node_h node)
         fprintf (stderr, "hivex_node_children: returning EFAULT because too many subkeys (%zu, %zu)\n",
                  nr_subkeys_in_lf, len);
       errno = EFAULT;
-      return NULL;
+      goto error;
     }
 
-    /* Allocate space for the returned values.  Note that
-     * nr_subkeys_in_lf is limited to a 16 bit value.
-     */
-    ret = malloc ((1 + nr_subkeys_in_lf) * sizeof (hive_node_h));
-    if (ret == NULL)
-      return NULL;
-
     size_t i;
     for (i = 0; i < nr_subkeys_in_lf; ++i) {
-      hive_node_h subkey = lf->keys[i].offset;
+      hive_node_h subkey = le32toh (lf->keys[i].offset);
       subkey += 0x1000;
       if (!IS_VALID_BLOCK (h, subkey)) {
         if (h->msglvl >= 2)
           fprintf (stderr, "hivex_node_children: returning EFAULT because subkey is not a valid block (0x%zx)\n",
                    subkey);
         errno = EFAULT;
-        free (ret);
-        return NULL;
+        goto error;
       }
-      ret[i] = subkey;
+      if (add_to_offset_list (&children, subkey) == -1)
+        goto error;
     }
-    ret[i] = 0;
-    return ret;
+    goto ok;
   }
   /* Points to ri-record? */
   else if (block->id[0] == 'r' && block->id[1] == 'i') {
@@ -726,13 +760,16 @@ hivex_node_children (hive_h *h, hive_node_h node)
           fprintf (stderr, "hivex_node_children: returning EFAULT because ri-offset is not a valid block (0x%zx)\n",
                    offset);
         errno = EFAULT;
-        return NULL;
+        goto error;
       }
       if (!BLOCK_ID_EQ (h, offset, "lf") && !BLOCK_ID_EQ (h, offset, "lh")) {
         errno = ENOTSUP;
-        return NULL;
+        goto error;
       }
 
+      if (add_to_offset_list (&blocks, offset) == -1)
+        goto error;
+
       struct ntreg_lf_record *lf =
         (struct ntreg_lf_record *) (h->addr + offset);
 
@@ -745,17 +782,12 @@ hivex_node_children (hive_h *h, hive_node_h node)
 
     if (nr_subkeys_in_nk != count) {
       errno = ENOTSUP;
-      return NULL;
+      goto error;
     }
 
     /* Copy list of children.  Note nr_subkeys_in_nk is limited to
      * something reasonable above.
      */
-    ret = malloc ((1 + nr_subkeys_in_nk) * sizeof (hive_node_h));
-    if (ret == NULL)
-      return NULL;
-
-    count = 0;
     for (i = 0; i < nr_offsets; ++i) {
       hive_node_h offset = ri->offset[i];
       offset += 0x1000;
@@ -764,11 +796,11 @@ hivex_node_children (hive_h *h, hive_node_h node)
           fprintf (stderr, "hivex_node_children: returning EFAULT because ri-offset is not a valid block (0x%zx)\n",
                    offset);
         errno = EFAULT;
-        return NULL;
+        goto error;
       }
       if (!BLOCK_ID_EQ (h, offset, "lf") && !BLOCK_ID_EQ (h, offset, "lh")) {
         errno = ENOTSUP;
-        return NULL;
+        goto error;
       }
 
       struct ntreg_lf_record *lf =
@@ -776,27 +808,47 @@ hivex_node_children (hive_h *h, hive_node_h node)
 
       size_t j;
       for (j = 0; j < le16toh (lf->nr_keys); ++j) {
-        hive_node_h subkey = lf->keys[j].offset;
+        hive_node_h subkey = le32toh (lf->keys[j].offset);
         subkey += 0x1000;
         if (!IS_VALID_BLOCK (h, subkey)) {
           if (h->msglvl >= 2)
             fprintf (stderr, "hivex_node_children: returning EFAULT because indirect subkey is not a valid block (0x%zx)\n",
                      subkey);
           errno = EFAULT;
-          free (ret);
-          return NULL;
+          goto error;
         }
-        ret[count++] = subkey;
+        if (add_to_offset_list (&children, subkey) == -1)
+          goto error;
       }
     }
-    ret[count] = 0;
-
-    return ret;
+    goto ok;
   }
-  else {
-    errno = ENOTSUP;
+  /* else not supported, set errno and fall through */
+  errno = ENOTSUP;
+ error:
+  free_offset_list (&children);
+  free_offset_list (&blocks);
+  return -1;
+
+ ok:
+  *children_ret = return_offset_list (&children);
+  *blocks_ret = return_offset_list (&blocks);
+  if (!*children_ret || !*blocks_ret)
+    goto error;
+  return 0;
+}
+
+hive_node_h *
+hivex_node_children (hive_h *h, hive_node_h node)
+{
+  hive_node_h *children;
+  size_t *blocks;
+
+  if (get_children (h, node, &children, &blocks) == -1)
     return NULL;
-  }
+
+  free (blocks);
+  return children;
 }
 
 /* Very inefficient, but at least having a separate API call
@@ -851,12 +903,13 @@ hivex_node_parent (hive_h *h, hive_node_h node)
   return ret;
 }
 
-hive_value_h *
-hivex_node_values (hive_h *h, hive_node_h node)
+static int
+get_values (hive_h *h, hive_node_h node,
+            hive_value_h **values_ret, size_t **blocks_ret)
 {
   if (!IS_VALID_BLOCK (h, node) || !BLOCK_ID_EQ (h, node, "nk")) {
     errno = EINVAL;
-    return 0;
+    return -1;
   }
 
   struct ntreg_nk_record *nk = (struct ntreg_nk_record *) (h->addr + node);
@@ -866,22 +919,23 @@ hivex_node_values (hive_h *h, hive_node_h node)
   if (h->msglvl >= 2)
     fprintf (stderr, "hivex_node_values: nr_values = %zu\n", nr_values);
 
+  INIT_OFFSET_LIST (values);
+  INIT_OFFSET_LIST (blocks);
+
   /* Deal with the common "no values" case quickly. */
-  hive_node_h *ret;
-  if (nr_values == 0) {
-    ret = malloc (sizeof (hive_node_h));
-    if (ret == NULL)
-      return NULL;
-    ret[0] = 0;
-    return ret;
-  }
+  if (nr_values == 0)
+    goto ok;
 
   /* Arbitrarily limit the number of values we will ever deal with. */
   if (nr_values > 100000) {
     errno = ERANGE;
-    return NULL;
+    goto error;
   }
 
+  /* Preallocate space for the values. */
+  if (grow_offset_list (&values, nr_values) == -1)
+    goto error;
+
   /* Get the value list and check it looks reasonable. */
   size_t vlist_offset = le32toh (nk->vallist);
   vlist_offset += 0x1000;
@@ -890,9 +944,12 @@ hivex_node_values (hive_h *h, hive_node_h node)
       fprintf (stderr, "hivex_node_values: returning EFAULT because value list is not a valid block (0x%zx)\n",
                vlist_offset);
     errno = EFAULT;
-    return NULL;
+    goto error;
   }
 
+  if (add_to_offset_list (&blocks, vlist_offset) == -1)
+    goto error;
+
   struct ntreg_value_list *vlist =
     (struct ntreg_value_list *) (h->addr + vlist_offset);
 
@@ -902,14 +959,9 @@ hivex_node_values (hive_h *h, hive_node_h node)
       fprintf (stderr, "hivex_node_values: returning EFAULT because value list is too long (%zu, %zu)\n",
                nr_values, len);
     errno = EFAULT;
-    return NULL;
+    goto error;
   }
 
-  /* Allocate return array and copy values in. */
-  ret = malloc ((1 + nr_values) * sizeof (hive_node_h));
-  if (ret == NULL)
-    return NULL;
-
   size_t i;
   for (i = 0; i < nr_values; ++i) {
     hive_node_h value = vlist->offset[i];
@@ -919,14 +971,36 @@ hivex_node_values (hive_h *h, hive_node_h node)
         fprintf (stderr, "hivex_node_values: returning EFAULT because value is not a valid block (0x%zx)\n",
                  value);
       errno = EFAULT;
-      free (ret);
-      return NULL;
+      goto error;
     }
-    ret[i] = value;
+    if (add_to_offset_list (&values, value) == -1)
+      goto error;
   }
 
-  ret[i] = 0;
-  return ret;
+ ok:
+  *values_ret = return_offset_list (&values);
+  *blocks_ret = return_offset_list (&blocks);
+  if (!*values_ret || !*blocks_ret)
+    goto error;
+  return 0;
+
+ error:
+  free_offset_list (&values);
+  free_offset_list (&blocks);
+  return -1;
+}
+
+hive_value_h *
+hivex_node_values (hive_h *h, hive_node_h node)
+{
+  hive_value_h *values;
+  size_t *blocks;
+
+  if (get_values (h, node, &values, &blocks) == -1)
+    return NULL;
+
+  free (blocks);
+  return values;
 }
 
 /* Very inefficient, but at least having a separate API call
@@ -1067,7 +1141,7 @@ hivex_value_value (hive_h *h, hive_value_h value,
     return ret;
   }
 
-  size_t data_offset = vk->data_offset;
+  size_t data_offset = le32toh (vk->data_offset);
   data_offset += 0x1000;
   if (!IS_VALID_BLOCK (h, data_offset)) {
     if (h->msglvl >= 2)
@@ -1080,7 +1154,7 @@ hivex_value_value (hive_h *h, hive_value_h value,
 
   /* Check that the declared size isn't larger than the block its in. */
   size_t blen = block_len (h, data_offset, NULL);
-  if (len > blen) {
+  if (len > blen - 4 /* subtract 4 for block header */) {
     if (h->msglvl >= 2)
       fprintf (stderr, "hivex_value_value: returning EFAULT because data is longer than its block (data 0x%zx, data len %zu, block len %zu)\n",
                data_offset, len, blen);
@@ -1389,114 +1463,126 @@ hivex__visit_node (hive_h *h, hive_node_h node,
       goto error;
     }
 
-    switch (t) {
-    case hive_t_none:
+    if (vtor->value_any) {
       str = hivex_value_value (h, values[i], &t, &len);
       if (str == NULL) {
         ret = skip_bad ? 0 : -1;
         goto error;
       }
-      if (t != hive_t_none) {
-        ret = skip_bad ? 0 : -1;
-        goto error;
-      }
-      if (vtor->value_none &&
-          vtor->value_none (h, opaque, node, values[i], t, len, key, str) == -1)
+      if (vtor->value_any (h, opaque, node, values[i], t, len, key, str) == -1)
         goto error;
       free (str); str = NULL;
-      break;
-
-    case hive_t_string:
-    case hive_t_expand_string:
-    case hive_t_link:
-      str = hivex_value_string (h, values[i]);
-      if (str == NULL) {
-        if (errno != EILSEQ && errno != EINVAL) {
+    }
+    else {
+      switch (t) {
+      case hive_t_none:
+        str = hivex_value_value (h, values[i], &t, &len);
+        if (str == NULL) {
           ret = skip_bad ? 0 : -1;
           goto error;
         }
-        if (vtor->value_string_invalid_utf16) {
-          str = hivex_value_value (h, values[i], &t, &len);
-          if (vtor->value_string_invalid_utf16 (h, opaque, node, values[i], t, len, key, str) == -1)
-            goto error;
-          free (str); str = NULL;
+        if (t != hive_t_none) {
+          ret = skip_bad ? 0 : -1;
+          goto error;
         }
+        if (vtor->value_none &&
+            vtor->value_none (h, opaque, node, values[i], t, len, key, str) == -1)
+          goto error;
+        free (str); str = NULL;
         break;
-      }
-      if (vtor->value_string &&
-          vtor->value_string (h, opaque, node, values[i], t, len, key, str) == -1)
-        goto error;
-      free (str); str = NULL;
-      break;
 
-    case hive_t_dword:
-    case hive_t_dword_be: {
-      int32_t i32 = hivex_value_dword (h, values[i]);
-      if (vtor->value_dword &&
-          vtor->value_dword (h, opaque, node, values[i], t, len, key, i32) == -1)
-        goto error;
-      break;
-    }
-
-    case hive_t_qword: {
-      int64_t i64 = hivex_value_qword (h, values[i]);
-      if (vtor->value_qword &&
-          vtor->value_qword (h, opaque, node, values[i], t, len, key, i64) == -1)
-        goto error;
-      break;
-    }
+      case hive_t_string:
+      case hive_t_expand_string:
+      case hive_t_link:
+        str = hivex_value_string (h, values[i]);
+        if (str == NULL) {
+          if (errno != EILSEQ && errno != EINVAL) {
+            ret = skip_bad ? 0 : -1;
+            goto error;
+          }
+          if (vtor->value_string_invalid_utf16) {
+            str = hivex_value_value (h, values[i], &t, &len);
+            if (vtor->value_string_invalid_utf16 (h, opaque, node, values[i], t, len, key, str) == -1)
+              goto error;
+            free (str); str = NULL;
+          }
+          break;
+        }
+        if (vtor->value_string &&
+            vtor->value_string (h, opaque, node, values[i], t, len, key, str) == -1)
+          goto error;
+        free (str); str = NULL;
+        break;
 
-    case hive_t_binary:
-      str = hivex_value_value (h, values[i], &t, &len);
-      if (str == NULL) {
-        ret = skip_bad ? 0 : -1;
-        goto error;
+      case hive_t_dword:
+      case hive_t_dword_be: {
+        int32_t i32 = hivex_value_dword (h, values[i]);
+        if (vtor->value_dword &&
+            vtor->value_dword (h, opaque, node, values[i], t, len, key, i32) == -1)
+          goto error;
+        break;
       }
-      if (t != hive_t_binary) {
-        ret = skip_bad ? 0 : -1;
-        goto error;
+
+      case hive_t_qword: {
+        int64_t i64 = hivex_value_qword (h, values[i]);
+        if (vtor->value_qword &&
+            vtor->value_qword (h, opaque, node, values[i], t, len, key, i64) == -1)
+          goto error;
+        break;
       }
-      if (vtor->value_binary &&
-          vtor->value_binary (h, opaque, node, values[i], t, len, key, str) == -1)
-        goto error;
-      free (str); str = NULL;
-      break;
 
-    case hive_t_multiple_strings:
-      strs = hivex_value_multiple_strings (h, values[i]);
-      if (strs == NULL) {
-        if (errno != EILSEQ && errno != EINVAL) {
+      case hive_t_binary:
+        str = hivex_value_value (h, values[i], &t, &len);
+        if (str == NULL) {
+          ret = skip_bad ? 0 : -1;
+          goto error;
+        }
+        if (t != hive_t_binary) {
           ret = skip_bad ? 0 : -1;
           goto error;
         }
-        if (vtor->value_string_invalid_utf16) {
-          str = hivex_value_value (h, values[i], &t, &len);
-          if (vtor->value_string_invalid_utf16 (h, opaque, node, values[i], t, len, key, str) == -1)
+        if (vtor->value_binary &&
+            vtor->value_binary (h, opaque, node, values[i], t, len, key, str) == -1)
+          goto error;
+        free (str); str = NULL;
+        break;
+
+      case hive_t_multiple_strings:
+        strs = hivex_value_multiple_strings (h, values[i]);
+        if (strs == NULL) {
+          if (errno != EILSEQ && errno != EINVAL) {
+            ret = skip_bad ? 0 : -1;
             goto error;
-          free (str); str = NULL;
+          }
+          if (vtor->value_string_invalid_utf16) {
+            str = hivex_value_value (h, values[i], &t, &len);
+            if (vtor->value_string_invalid_utf16 (h, opaque, node, values[i], t, len, key, str) == -1)
+              goto error;
+            free (str); str = NULL;
+          }
+          break;
         }
+        if (vtor->value_multiple_strings &&
+            vtor->value_multiple_strings (h, opaque, node, values[i], t, len, key, strs) == -1)
+          goto error;
+        free_strings (strs); strs = NULL;
         break;
-      }
-      if (vtor->value_multiple_strings &&
-          vtor->value_multiple_strings (h, opaque, node, values[i], t, len, key, strs) == -1)
-        goto error;
-      free_strings (strs); strs = NULL;
-      break;
 
-    case hive_t_resource_list:
-    case hive_t_full_resource_description:
-    case hive_t_resource_requirements_list:
-    default:
-      str = hivex_value_value (h, values[i], &t, &len);
-      if (str == NULL) {
-        ret = skip_bad ? 0 : -1;
-        goto error;
+      case hive_t_resource_list:
+      case hive_t_full_resource_description:
+      case hive_t_resource_requirements_list:
+      default:
+        str = hivex_value_value (h, values[i], &t, &len);
+        if (str == NULL) {
+          ret = skip_bad ? 0 : -1;
+          goto error;
+        }
+        if (vtor->value_other &&
+            vtor->value_other (h, opaque, node, values[i], t, len, key, str) == -1)
+          goto error;
+        free (str); str = NULL;
+        break;
       }
-      if (vtor->value_other &&
-          vtor->value_other (h, opaque, node, values[i], t, len, key, str) == -1)
-        goto error;
-      free (str); str = NULL;
-      break;
     }
 
     free (key); key = NULL;