hivex: Add offset-&-length function for long value data
[hivex.git] / lib / hivex.c
index 2b22924..df313bf 100644 (file)
@@ -1,5 +1,5 @@
 /* hivex - Windows Registry "hive" extraction library.
- * Copyright (C) 2009-2010 Red Hat Inc.
+ * Copyright (C) 2009-2011 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 <unistd.h>
 #include <errno.h>
 #include <iconv.h>
-#include <sys/mman.h>
 #include <sys/stat.h>
 #include <assert.h>
 
+#ifdef HAVE_MMAP
+#include <sys/mman.h>
+#else
+/* On systems without mmap (and munmap), use a replacement function. */
+#include "mmap.h"
+#endif
+
 #include "c-ctype.h"
 #include "full-read.h"
 #include "full-write.h"
 
-#ifndef O_CLOEXEC
-#define O_CLOEXEC 0
-#endif
-
-#define STREQ(a,b) (strcmp((a),(b)) == 0)
-#define STRCASEEQ(a,b) (strcasecmp((a),(b)) == 0)
-//#define STRNEQ(a,b) (strcmp((a),(b)) != 0)
-//#define STRCASENEQ(a,b) (strcasecmp((a),(b)) != 0)
-#define STREQLEN(a,b,n) (strncmp((a),(b),(n)) == 0)
-//#define STRCASEEQLEN(a,b,n) (strncasecmp((a),(b),(n)) == 0)
-//#define STRNEQLEN(a,b,n) (strncmp((a),(b),(n)) != 0)
-//#define STRCASENEQLEN(a,b,n) (strncasecmp((a),(b),(n)) != 0)
-#define STRPREFIX(a,b) (strncmp((a),(b),strlen((b))) == 0)
-
 #include "hivex.h"
+#include "hivex-internal.h"
 #include "byte_conversions.h"
 
 /* These limits are in place to stop really stupid stuff and/or exploits. */
 #define HIVEX_MAX_ALLOCATION  1000000
 
 static char *windows_utf16_to_utf8 (/* const */ char *input, size_t len);
-
-struct hive_h {
-  char *filename;
-  int fd;
-  size_t size;
-  int msglvl;
-  int writable;
-
-  /* Registry file, memory mapped if read-only, or malloc'd if writing. */
-  union {
-    char *addr;
-    struct ntreg_header *hdr;
-  };
-
-  /* Use a bitmap to store which file offsets are valid (point to a
-   * used block).  We only need to store 1 bit per 32 bits of the file
-   * (because blocks are 4-byte aligned).  We found that the average
-   * block size in a registry file is ~50 bytes.  So roughly 1 in 12
-   * bits in the bitmap will be set, making it likely a more efficient
-   * structure than a hash table.
-   */
-  char *bitmap;
-#define BITMAP_SET(bitmap,off) (bitmap[(off)>>5] |= 1 << (((off)>>2)&7))
-#define BITMAP_CLR(bitmap,off) (bitmap[(off)>>5] &= ~ (1 << (((off)>>2)&7)))
-#define BITMAP_TST(bitmap,off) (bitmap[(off)>>5] & (1 << (((off)>>2)&7)))
-#define IS_VALID_BLOCK(h,off)               \
-  (((off) & 3) == 0 &&                      \
-   (off) >= 0x1000 &&                       \
-   (off) < (h)->size &&                     \
-   BITMAP_TST((h)->bitmap,(off)))
-
-  /* Fields from the header, extracted from little-endianness hell. */
-  size_t rootoffs;              /* Root key offset (always an nk-block). */
-  size_t endpages;              /* Offset of end of pages. */
-
-  /* For writing. */
-  size_t endblocks;             /* Offset to next block allocation (0
-                                   if not allocated anything yet). */
-};
+static size_t utf16_string_len_in_bytes_max (const char *str, size_t len);
 
 /* NB. All fields are little endian. */
 struct ntreg_header {
   char magic[4];                /* "regf" */
   uint32_t sequence1;
   uint32_t sequence2;
-  char last_modified[8];
+  int64_t last_modified;
   uint32_t major_ver;           /* 1 */
   uint32_t minor_ver;           /* 3 */
   uint32_t unknown5;            /* 0 */
@@ -176,7 +131,7 @@ struct ntreg_nk_record {
   int32_t seg_len;              /* length (always -ve because used) */
   char id[2];                   /* "nk" */
   uint16_t flags;
-  char timestamp[8];
+  int64_t timestamp;
   uint32_t unknown1;
   uint32_t parent;              /* offset of owner/parent */
   uint32_t nr_subkeys;          /* number of subkeys */
@@ -296,9 +251,16 @@ hivex_open (const char *filename, int flags)
   if (h->filename == NULL)
     goto error;
 
+#ifdef O_CLOEXEC
   h->fd = open (filename, O_RDONLY | O_CLOEXEC);
+#else
+  h->fd = open (filename, O_RDONLY);
+#endif
   if (h->fd == -1)
     goto error;
+#ifndef O_CLOEXEC
+  fcntl (h->fd, F_SETFD, FD_CLOEXEC);
+#endif
 
   struct stat statbuf;
   if (fstat (h->fd, &statbuf) == -1)
@@ -320,6 +282,13 @@ hivex_open (const char *filename, int flags)
 
     if (full_read (h->fd, h->addr, h->size) < h->size)
       goto error;
+
+    /* We don't need the file descriptor along this path, since we
+     * have read all the data.
+     */
+    if (close (h->fd) == -1)
+      goto error;
+    h->fd = -1;
   }
 
   /* Check header. */
@@ -355,6 +324,9 @@ hivex_open (const char *filename, int flags)
     goto error;
   }
 
+  /* Last modified time. */
+  h->last_modified = le64toh ((int64_t) h->hdr->last_modified);
+
   if (h->msglvl >= 2) {
     char *name = windows_utf16_to_utf8 (h->hdr->name, 64);
 
@@ -363,6 +335,8 @@ hivex_open (const char *filename, int flags)
              "  file version             %" PRIu32 ".%" PRIu32 "\n"
              "  sequence nos             %" PRIu32 " %" PRIu32 "\n"
              "    (sequences nos should match if hive was synched at shutdown)\n"
+             "  last modified            %" PRIu64 "\n"
+             "    (Windows filetime, x 100 ns since 1601-01-01)\n"
              "  original file name       %s\n"
              "    (only 32 chars are stored, name is probably truncated)\n"
              "  root offset              0x%x + 0x1000\n"
@@ -370,6 +344,7 @@ hivex_open (const char *filename, int flags)
              "  checksum                 0x%x (calculated 0x%x)\n",
              major_ver, le32toh (h->hdr->minor_ver),
              le32toh (h->hdr->sequence1), le32toh (h->hdr->sequence2),
+             h->last_modified,
              name ? name : "(conversion failed)",
              le32toh (h->hdr->offset),
              le32toh (h->hdr->blocks), h->size,
@@ -413,7 +388,8 @@ hivex_open (const char *filename, int flags)
         page->magic[1] != 'b' ||
         page->magic[2] != 'i' ||
         page->magic[3] != 'n') {
-      fprintf (stderr, "hivex: %s: trailing garbage at end of file (at 0x%zx, after %zu pages)\n",
+      fprintf (stderr, "hivex: %s: trailing garbage at end of file "
+               "(at 0x%zx, after %zu pages)\n",
                filename, off, pages);
       errno = ENOTSUP;
       goto error;
@@ -451,7 +427,8 @@ 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 %" PRIu32 " at 0x%zx, 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;
@@ -532,12 +509,18 @@ hivex_close (hive_h *h)
 {
   int r;
 
+  if (h->msglvl >= 1)
+    fprintf (stderr, "hivex_close\n");
+
   free (h->bitmap);
   if (!h->writable)
     munmap (h->addr, h->size);
   else
     free (h->addr);
-  r = close (h->fd);
+  if (h->fd >= 0)
+    r = close (h->fd);
+  else
+    r = 0;
   free (h->filename);
   free (h);
 
@@ -553,7 +536,31 @@ hivex_root (hive_h *h)
 {
   hive_node_h ret = h->rootoffs;
   if (!IS_VALID_BLOCK (h, ret)) {
-    errno = ENOKEY;
+    errno = HIVEX_NO_KEY;
+    return 0;
+  }
+  return ret;
+}
+
+size_t
+hivex_node_struct_length (hive_h *h, hive_node_h node)
+{
+  if (!IS_VALID_BLOCK (h, node) || !BLOCK_ID_EQ (h, node, "nk")) {
+    errno = EINVAL;
+    return 0;
+  }
+
+  struct ntreg_nk_record *nk = (struct ntreg_nk_record *) (h->addr + node);
+  size_t name_len = le16toh (nk->name_len);
+  /* -1 to avoid double-counting the first name character */
+  size_t ret = name_len + sizeof (struct ntreg_nk_record) - 1;
+  int used;
+  size_t seg_len = block_len (h, node, &used);
+  if (ret > seg_len) {
+    if (h->msglvl >= 2)
+      fprintf (stderr, "hivex_node_struct_length: returning EFAULT because"
+               " node name is too long (%zu, %zu)\n", name_len, seg_len);
+    errno = EFAULT;
     return 0;
   }
   return ret;
@@ -581,7 +588,8 @@ hivex_node_name (hive_h *h, hive_node_h node)
   size_t seg_len = block_len (h, node, NULL);
   if (sizeof (struct ntreg_nk_record) + len - 1 > seg_len) {
     if (h->msglvl >= 2)
-      fprintf (stderr, "hivex_node_name: returning EFAULT because node name is too long (%zu, %zu)\n",
+      fprintf (stderr, "hivex_node_name: returning EFAULT because node name"
+               " is too long (%zu, %zu)\n",
               len, seg_len);
     errno = EFAULT;
     return NULL;
@@ -595,6 +603,43 @@ hivex_node_name (hive_h *h, hive_node_h node)
   return ret;
 }
 
+static int64_t
+timestamp_check (hive_h *h, hive_node_h node, int64_t timestamp)
+{
+  if (timestamp < 0) {
+    if (h->msglvl >= 2)
+      fprintf (stderr, "hivex: timestamp_check: "
+               "negative time reported at %zu: %" PRIi64 "\n",
+               node, timestamp);
+    errno = EINVAL;
+    return -1;
+  }
+
+  return timestamp;
+}
+
+int64_t
+hivex_last_modified (hive_h *h)
+{
+  return timestamp_check (h, 0, h->last_modified);
+}
+
+int64_t
+hivex_node_timestamp (hive_h *h, hive_node_h node)
+{
+  int64_t ret;
+
+  if (!IS_VALID_BLOCK (h, node) || !BLOCK_ID_EQ (h, node, "nk")) {
+    errno = EINVAL;
+    return -1;
+  }
+
+  struct ntreg_nk_record *nk = (struct ntreg_nk_record *) (h->addr + node);
+
+  ret = le64toh (nk->timestamp);
+  return timestamp_check (h, node, ret);
+}
+
 #if 0
 /* I think the documentation for the sk and classname fields in the nk
  * record is wrong, or else the offset field is in the wrong place.
@@ -728,7 +773,8 @@ get_children (hive_h *h, hive_node_h node,
   /* Arbitrarily limit the number of subkeys we will ever deal with. */
   if (nr_subkeys_in_nk > HIVEX_MAX_SUBKEYS) {
     if (h->msglvl >= 2)
-      fprintf (stderr, "hivex: get_children: returning ERANGE because nr_subkeys_in_nk > HIVEX_MAX_SUBKEYS (%zu > %d)\n",
+      fprintf (stderr, "hivex: get_children: returning ERANGE because "
+               "nr_subkeys_in_nk > HIVEX_MAX_SUBKEYS (%zu > %d)\n",
                nr_subkeys_in_nk, HIVEX_MAX_SUBKEYS);
     errno = ERANGE;
     goto error;
@@ -746,7 +792,8 @@ get_children (hive_h *h, hive_node_h node,
   subkey_lf += 0x1000;
   if (!IS_VALID_BLOCK (h, subkey_lf)) {
     if (h->msglvl >= 2)
-      fprintf (stderr, "hivex_node_children: returning EFAULT because subkey_lf is not a valid block (0x%zx)\n",
+      fprintf (stderr, "hivex_node_children: returning EFAULT"
+               " because subkey_lf is not a valid block (0x%zx)\n",
                subkey_lf);
     errno = EFAULT;
     goto error;
@@ -770,7 +817,8 @@ get_children (hive_h *h, hive_node_h node,
     size_t nr_subkeys_in_lf = le16toh (lf->nr_keys);
 
     if (h->msglvl >= 2)
-      fprintf (stderr, "hivex_node_children: nr_subkeys_in_nk = %zu, nr_subkeys_in_lf = %zu\n",
+      fprintf (stderr, "hivex_node_children: nr_subkeys_in_nk = %zu,"
+               " nr_subkeys_in_lf = %zu\n",
                nr_subkeys_in_nk, nr_subkeys_in_lf);
 
     if (nr_subkeys_in_nk != nr_subkeys_in_lf) {
@@ -781,7 +829,8 @@ get_children (hive_h *h, hive_node_h node,
     size_t len = block_len (h, subkey_lf, NULL);
     if (8 + nr_subkeys_in_lf * 8 > len) {
       if (h->msglvl >= 2)
-        fprintf (stderr, "hivex_node_children: returning EFAULT because too many subkeys (%zu, %zu)\n",
+        fprintf (stderr, "hivex_node_children: returning EFAULT"
+                 " because too many subkeys (%zu, %zu)\n",
                  nr_subkeys_in_lf, len);
       errno = EFAULT;
       goto error;
@@ -794,7 +843,8 @@ get_children (hive_h *h, hive_node_h node,
       if (!(flags & GET_CHILDREN_NO_CHECK_NK)) {
         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",
+            fprintf (stderr, "hivex_node_children: returning EFAULT"
+                     " because subkey is not a valid block (0x%zx)\n",
                      subkey);
           errno = EFAULT;
           goto error;
@@ -818,14 +868,16 @@ get_children (hive_h *h, hive_node_h node,
       offset += 0x1000;
       if (!IS_VALID_BLOCK (h, offset)) {
         if (h->msglvl >= 2)
-          fprintf (stderr, "hivex_node_children: returning EFAULT because ri-offset is not a valid block (0x%zx)\n",
+          fprintf (stderr, "hivex_node_children: returning EFAULT"
+                   " because ri-offset is not a valid block (0x%zx)\n",
                    offset);
         errno = EFAULT;
         goto error;
       }
       if (!BLOCK_ID_EQ (h, offset, "lf") && !BLOCK_ID_EQ (h, offset, "lh")) {
         if (h->msglvl >= 2)
-          fprintf (stderr, "get_children: returning ENOTSUP because ri-record offset does not point to lf/lh (0x%zx)\n",
+          fprintf (stderr, "get_children: returning ENOTSUP"
+                   " because ri-record offset does not point to lf/lh (0x%zx)\n",
                    offset);
         errno = ENOTSUP;
         goto error;
@@ -841,7 +893,8 @@ get_children (hive_h *h, hive_node_h node,
     }
 
     if (h->msglvl >= 2)
-      fprintf (stderr, "hivex_node_children: nr_subkeys_in_nk = %zu, counted = %zu\n",
+      fprintf (stderr, "hivex_node_children: nr_subkeys_in_nk = %zu,"
+               " counted = %zu\n",
                nr_subkeys_in_nk, count);
 
     if (nr_subkeys_in_nk != count) {
@@ -857,14 +910,16 @@ get_children (hive_h *h, hive_node_h node,
       offset += 0x1000;
       if (!IS_VALID_BLOCK (h, offset)) {
         if (h->msglvl >= 2)
-          fprintf (stderr, "hivex_node_children: returning EFAULT because ri-offset is not a valid block (0x%zx)\n",
+          fprintf (stderr, "hivex_node_children: returning EFAULT"
+                   " because ri-offset is not a valid block (0x%zx)\n",
                    offset);
         errno = EFAULT;
         goto error;
       }
       if (!BLOCK_ID_EQ (h, offset, "lf") && !BLOCK_ID_EQ (h, offset, "lh")) {
         if (h->msglvl >= 2)
-          fprintf (stderr, "get_children: returning ENOTSUP because ri-record offset does not point to lf/lh (0x%zx)\n",
+          fprintf (stderr, "get_children: returning ENOTSUP"
+                   " because ri-record offset does not point to lf/lh (0x%zx)\n",
                    offset);
         errno = ENOTSUP;
         goto error;
@@ -880,7 +935,8 @@ get_children (hive_h *h, hive_node_h node,
         if (!(flags & GET_CHILDREN_NO_CHECK_NK)) {
           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",
+              fprintf (stderr, "hivex_node_children: returning EFAULT"
+                       " because indirect subkey is not a valid block (0x%zx)\n",
                        subkey);
             errno = EFAULT;
             goto error;
@@ -894,7 +950,8 @@ get_children (hive_h *h, hive_node_h node,
   }
   /* else not supported, set errno and fall through */
   if (h->msglvl >= 2)
-    fprintf (stderr, "get_children: returning ENOTSUP because subkey block is not lf/lh/ri (0x%zx, %d, %d)\n",
+    fprintf (stderr, "get_children: returning ENOTSUP"
+             " because subkey block is not lf/lh/ri (0x%zx, %d, %d)\n",
              subkey_lf, block->id[0], block->id[1]);
   errno = ENOTSUP;
  error:
@@ -967,7 +1024,8 @@ hivex_node_parent (hive_h *h, hive_node_h node)
   ret += 0x1000;
   if (!IS_VALID_BLOCK (h, ret)) {
     if (h->msglvl >= 2)
-      fprintf (stderr, "hivex_node_parent: returning EFAULT because parent is not a valid block (0x%zx)\n",
+      fprintf (stderr, "hivex_node_parent: returning EFAULT"
+               " because parent is not a valid block (0x%zx)\n",
               ret);
     errno = EFAULT;
     return 0;
@@ -1001,7 +1059,8 @@ get_values (hive_h *h, hive_node_h node,
   /* Arbitrarily limit the number of values we will ever deal with. */
   if (nr_values > HIVEX_MAX_VALUES) {
     if (h->msglvl >= 2)
-      fprintf (stderr, "hivex: get_values: returning ERANGE because nr_values > HIVEX_MAX_VALUES (%zu > %d)\n",
+      fprintf (stderr, "hivex: get_values: returning ERANGE"
+               " because nr_values > HIVEX_MAX_VALUES (%zu > %d)\n",
                nr_values, HIVEX_MAX_VALUES);
     errno = ERANGE;
     goto error;
@@ -1016,7 +1075,8 @@ get_values (hive_h *h, hive_node_h node,
   vlist_offset += 0x1000;
   if (!IS_VALID_BLOCK (h, vlist_offset)) {
     if (h->msglvl >= 2)
-      fprintf (stderr, "hivex_node_values: returning EFAULT because value list is not a valid block (0x%zx)\n",
+      fprintf (stderr, "hivex_node_values: returning EFAULT"
+               " because value list is not a valid block (0x%zx)\n",
                vlist_offset);
     errno = EFAULT;
     goto error;
@@ -1031,7 +1091,8 @@ get_values (hive_h *h, hive_node_h node,
   size_t len = block_len (h, vlist_offset, NULL);
   if (4 + nr_values * 4 > len) {
     if (h->msglvl >= 2)
-      fprintf (stderr, "hivex_node_values: returning EFAULT because value list is too long (%zu, %zu)\n",
+      fprintf (stderr, "hivex_node_values: returning EFAULT"
+               " because value list is too long (%zu, %zu)\n",
                nr_values, len);
     errno = EFAULT;
     goto error;
@@ -1039,11 +1100,12 @@ get_values (hive_h *h, hive_node_h node,
 
   size_t i;
   for (i = 0; i < nr_values; ++i) {
-    hive_node_h value = vlist->offset[i];
+    hive_node_h value = le32toh (vlist->offset[i]);
     value += 0x1000;
     if (!IS_VALID_BLOCK (h, value)) {
       if (h->msglvl >= 2)
-        fprintf (stderr, "hivex_node_values: returning EFAULT because value is not a valid block (0x%zx)\n",
+        fprintf (stderr, "hivex_node_values: returning EFAULT"
+                 " because value is not a valid block (0x%zx)\n",
                  value);
       errno = EFAULT;
       goto error;
@@ -1108,8 +1170,22 @@ hivex_node_get_value (hive_h *h, hive_node_h node, const char *key)
   return ret;
 }
 
-char *
-hivex_value_key (hive_h *h, hive_value_h value)
+size_t
+hivex_value_struct_length (hive_h *h, hive_value_h value)
+{
+  size_t key_len;
+
+  errno = 0;
+  key_len = hivex_value_key_len (h, value);
+  if (key_len == 0 && errno != 0)
+    return 0;
+
+  /* -1 to avoid double-counting the first name character */
+  return key_len + sizeof (struct ntreg_vk_record) - 1;
+}
+
+size_t
+hivex_value_key_len (hive_h *h, hive_value_h value)
 {
   if (!IS_VALID_BLOCK (h, value) || !BLOCK_ID_EQ (h, value, "vk")) {
     errno = EINVAL;
@@ -1118,22 +1194,39 @@ hivex_value_key (hive_h *h, hive_value_h value)
 
   struct ntreg_vk_record *vk = (struct ntreg_vk_record *) (h->addr + value);
 
-  /* AFAIK the key is always plain ASCII, so no conversion to UTF-8 is
-   * necessary.  However we do need to nul-terminate the string.
-   */
-
   /* vk->name_len is unsigned, 16 bit, so this is safe ...  However
    * we have to make sure the length doesn't exceed the block length.
    */
-  size_t len = le16toh (vk->name_len);
+  size_t ret = le16toh (vk->name_len);
   size_t seg_len = block_len (h, value, NULL);
-  if (sizeof (struct ntreg_vk_record) + len - 1 > seg_len) {
+  if (sizeof (struct ntreg_vk_record) + ret - 1 > seg_len) {
     if (h->msglvl >= 2)
-      fprintf (stderr, "hivex_value_key: returning EFAULT because key length is too long (%zu, %zu)\n",
-               len, seg_len);
+      fprintf (stderr, "hivex_value_key_len: returning EFAULT"
+               " because key length is too long (%zu, %zu)\n",
+               ret, seg_len);
     errno = EFAULT;
-    return NULL;
+    return 0;
   }
+  return ret;
+}
+
+char *
+hivex_value_key (hive_h *h, hive_value_h value)
+{
+  if (!IS_VALID_BLOCK (h, value) || !BLOCK_ID_EQ (h, value, "vk")) {
+    errno = EINVAL;
+    return 0;
+  }
+
+  struct ntreg_vk_record *vk = (struct ntreg_vk_record *) (h->addr + value);
+
+  /* AFAIK the key is always plain ASCII, so no conversion to UTF-8 is
+   * necessary.  However we do need to nul-terminate the string.
+   */
+  errno = 0;
+  size_t len = hivex_value_key_len (h, value);
+  if (len == 0 && errno != 0)
+    return NULL;
 
   char *ret = malloc (len + 1);
   if (ret == NULL)
@@ -1164,6 +1257,66 @@ hivex_value_type (hive_h *h, hive_value_h value, hive_type *t, size_t *len)
   return 0;
 }
 
+hive_value_h
+hivex_value_data_cell_offset (hive_h *h, hive_value_h value, size_t *len)
+{
+  if (!IS_VALID_BLOCK (h, value) || !BLOCK_ID_EQ (h, value, "vk")) {
+    errno = EINVAL;
+    return 0;
+  }
+
+  if (h->msglvl >= 2)
+    fprintf (stderr, "hivex_value_data_cell_offset: value=0x%zx\n", value);
+  struct ntreg_vk_record *vk = (struct ntreg_vk_record *) (h->addr + value);
+
+  size_t data_len;
+  int is_inline;
+
+  data_len = le32toh (vk->data_len);
+  is_inline = !!(data_len & 0x80000000);
+  data_len &= 0x7fffffff;
+
+  if (h->msglvl >= 2)
+    fprintf (stderr, "hivex_value_data_cell_offset: is_inline=%d\n", is_inline);
+
+  if (h->msglvl >= 2)
+    fprintf (stderr, "hivex_value_data_cell_offset: data_len=%zx\n", data_len);
+
+  if (is_inline && data_len > 4) {
+    errno = ENOTSUP;
+    return 0;
+  }
+
+  if (is_inline) {
+    /* There is no other location for the value data. */
+    if (len)
+      *len = 0;
+    return 0;
+  } else {
+    if (len)
+      *len = data_len + 4;  /* Include 4 header length bytes */
+  }
+
+  if (h->msglvl >= 2)
+    fprintf (stderr, "hivex_value_data_cell_offset: Proceeding with indirect data.\n");
+
+  size_t data_offset = le32toh (vk->data_offset);
+  data_offset += 0x1000;  /* Add 0x1000 because everything's off by 4KiB */
+  if (!IS_VALID_BLOCK (h, data_offset)) {
+    if (h->msglvl >= 2)
+      fprintf (stderr, "hivex_value_data_cell_offset: returning EFAULT because data "
+               "offset is not a valid block (0x%zx)\n",
+               data_offset);
+    errno = EFAULT;
+    return 0;
+  }
+
+  if (h->msglvl >= 2)
+    fprintf (stderr, "hivex_value_data_cell_offset: data_offset=%zx\n", data_offset);
+
+  return data_offset;
+}
+
 char *
 hivex_value_value (hive_h *h, hive_value_h value,
                    hive_type *t_rtn, size_t *len_rtn)
@@ -1202,7 +1355,8 @@ hivex_value_value (hive_h *h, hive_value_h value,
   /* Arbitrarily limit the length that we will read. */
   if (len > HIVEX_MAX_VALUE_LEN) {
     if (h->msglvl >= 2)
-      fprintf (stderr, "hivex_value_value: returning ERANGE because data length > HIVEX_MAX_VALUE_LEN (%zu > %d)\n",
+      fprintf (stderr, "hivex_value_value: returning ERANGE because data "
+               "length > HIVEX_MAX_VALUE_LEN (%zu > %d)\n",
                len, HIVEX_MAX_SUBKEYS);
     errno = ERANGE;
     return NULL;
@@ -1221,7 +1375,8 @@ hivex_value_value (hive_h *h, hive_value_h value,
   data_offset += 0x1000;
   if (!IS_VALID_BLOCK (h, data_offset)) {
     if (h->msglvl >= 2)
-      fprintf (stderr, "hivex_value_value: returning EFAULT because data offset is not a valid block (0x%zx)\n",
+      fprintf (stderr, "hivex_value_value: returning EFAULT because data "
+               "offset is not a valid block (0x%zx)\n",
                data_offset);
     errno = EFAULT;
     free (ret);
@@ -1237,9 +1392,15 @@ hivex_value_value (hive_h *h, hive_value_h value,
   size_t blen = block_len (h, data_offset, NULL);
   if (len > blen - 4 /* subtract 4 for block header */) {
     if (h->msglvl >= 2)
-      fprintf (stderr, "hivex_value_value: warning: declared data length is longer than the block it is in (data 0x%zx, data len %zu, block len %zu)\n",
+      fprintf (stderr, "hivex_value_value: warning: declared data length "
+               "is longer than the block it is in "
+               "(data 0x%zx, data len %zu, block len %zu)\n",
                data_offset, len, blen);
     len = blen - 4;
+
+    /* Return the smaller length to the caller too. */
+    if (len_rtn)
+      *len_rtn = len;
   }
 
   char *data = h->addr + data_offset + 4;
@@ -1319,6 +1480,20 @@ hivex_value_string (hive_h *h, hive_value_h value)
     return NULL;
   }
 
+  /* Deal with the case where Windows has allocated a large buffer
+   * full of random junk, and only the first few bytes of the buffer
+   * contain a genuine UTF-16 string.
+   *
+   * In this case, iconv would try to process the junk bytes as UTF-16
+   * and inevitably find an illegal sequence (EILSEQ).  Instead, stop
+   * after we find the first \0\0.
+   *
+   * (Found by Hilko Bengen in a fresh Windows XP SOFTWARE hive).
+   */
+  size_t slen = utf16_string_len_in_bytes_max (data, len);
+  if (slen < len)
+    len = slen;
+
   char *ret = windows_utf16_to_utf8 (data, len);
   free (data);
   if (ret == NULL)
@@ -1340,16 +1515,18 @@ free_strings (char **argv)
 }
 
 /* Get the length of a UTF-16 format string.  Handle the string as
- * pairs of bytes, looking for the first \0\0 pair.
+ * pairs of bytes, looking for the first \0\0 pair.  Only read up to
+ * 'len' maximum bytes.
  */
 static size_t
-utf16_string_len_in_bytes (const char *str)
+utf16_string_len_in_bytes_max (const char *str, size_t len)
 {
   size_t ret = 0;
 
-  while (str[0] || str[1]) {
+  while (len >= 2 && (str[0] || str[1])) {
     str += 2;
     ret += 2;
+    len -= 2;
   }
 
   return ret;
@@ -1383,7 +1560,8 @@ hivex_value_multiple_strings (hive_h *h, hive_value_h value)
   char *p = data;
   size_t plen;
 
-  while (p < data + len && (plen = utf16_string_len_in_bytes (p)) > 0) {
+  while (p < data + len &&
+         (plen = utf16_string_len_in_bytes_max (p, data + len - p)) > 0) {
     nr_strings++;
     char **ret2 = realloc (ret, (1 + nr_strings) * sizeof (char *));
     if (ret2 == NULL) {
@@ -1468,7 +1646,9 @@ hivex_visit (hive_h *h, const struct hivex_visitor *visitor, size_t len,
   return hivex_visit_node (h, hivex_root (h), visitor, len, opaque, flags);
 }
 
-static int hivex__visit_node (hive_h *h, hive_node_h node, const struct hivex_visitor *vtor, char *unvisited, void *opaque, int flags);
+static int hivex__visit_node (hive_h *h, hive_node_h node,
+                              const struct hivex_visitor *vtor,
+                              char *unvisited, void *opaque, int flags);
 
 int
 hivex_visit_node (hive_h *h, hive_node_h node,
@@ -1516,7 +1696,8 @@ hivex__visit_node (hive_h *h, hive_node_h node,
 
   if (!BITMAP_TST (unvisited, node)) {
     if (h->msglvl >= 2)
-      fprintf (stderr, "hivex__visit_node: contains cycle: visited node 0x%zx already\n",
+      fprintf (stderr, "hivex__visit_node: contains cycle:"
+               " visited node 0x%zx already\n",
                node);
 
     errno = ELOOP;
@@ -1589,14 +1770,16 @@ hivex__visit_node (hive_h *h, hive_node_h node,
           }
           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_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)
+            vtor->value_string (h, opaque, node, values[i],
+                                t, len, key, str) == -1)
           goto error;
         free (str); str = NULL;
         break;
@@ -1605,7 +1788,8 @@ hivex__visit_node (hive_h *h, hive_node_h node,
       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)
+            vtor->value_dword (h, opaque, node, values[i],
+                               t, len, key, i32) == -1)
           goto error;
         break;
       }
@@ -1613,7 +1797,8 @@ hivex__visit_node (hive_h *h, hive_node_h node,
       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)
+            vtor->value_qword (h, opaque, node, values[i],
+                               t, len, key, i64) == -1)
           goto error;
         break;
       }
@@ -1629,7 +1814,8 @@ hivex__visit_node (hive_h *h, hive_node_h node,
           goto error;
         }
         if (vtor->value_binary &&
-            vtor->value_binary (h, opaque, node, values[i], t, len, key, str) == -1)
+            vtor->value_binary (h, opaque, node, values[i],
+                                t, len, key, str) == -1)
           goto error;
         free (str); str = NULL;
         break;
@@ -1643,14 +1829,16 @@ hivex__visit_node (hive_h *h, hive_node_h node,
           }
           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_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)
+            vtor->value_multiple_strings (h, opaque, node, values[i],
+                                          t, len, key, strs) == -1)
           goto error;
         free_strings (strs); strs = NULL;
         break;
@@ -1665,7 +1853,8 @@ hivex__visit_node (hive_h *h, hive_node_h node,
           goto error;
         }
         if (vtor->value_other &&
-            vtor->value_other (h, opaque, node, values[i], t, len, key, str) == -1)
+            vtor->value_other (h, opaque, node, values[i],
+                               t, len, key, str) == -1)
           goto error;
         free (str); str = NULL;
         break;
@@ -1740,9 +1929,11 @@ allocate_page (hive_h *h, size_t allocation_hint)
   ssize_t extend = h->endpages + nr_4k_pages * 4096 - h->size;
 
   if (h->msglvl >= 2) {
-    fprintf (stderr, "allocate_page: current endpages = 0x%zx, current size = 0x%zx\n",
+    fprintf (stderr, "allocate_page: current endpages = 0x%zx,"
+             " current size = 0x%zx\n",
              h->endpages, h->size);
-    fprintf (stderr, "allocate_page: extending file by %zd bytes (<= 0 if no extension)\n",
+    fprintf (stderr, "allocate_page: extending file by %zd bytes"
+             " (<= 0 if no extension)\n",
              extend);
   }
 
@@ -1827,8 +2018,8 @@ allocate_block (hive_h *h, size_t seg_len, const char id[2])
      * for them, albeit unusual.
      */
     if (h->msglvl >= 2)
-      fprintf (stderr, "allocate_block: refusing too small allocation (%zu), returning ERANGE\n",
-               seg_len);
+      fprintf (stderr, "allocate_block: refusing too small allocation (%zu),"
+               " returning ERANGE\n", seg_len);
     errno = ERANGE;
     return 0;
   }
@@ -1836,8 +2027,8 @@ allocate_block (hive_h *h, size_t seg_len, const char id[2])
   /* Refuse really large allocations. */
   if (seg_len > HIVEX_MAX_ALLOCATION) {
     if (h->msglvl >= 2)
-      fprintf (stderr, "allocate_block: refusing large allocation (%zu), returning ERANGE\n",
-               seg_len);
+      fprintf (stderr, "allocate_block: refusing large allocation (%zu),"
+               " returning ERANGE\n", seg_len);
     errno = ERANGE;
     return 0;
   }
@@ -1883,8 +2074,8 @@ allocate_block (hive_h *h, size_t seg_len, const char id[2])
   ssize_t rem = h->endpages - h->endblocks;
   if (rem > 0) {
     if (h->msglvl >= 2)
-      fprintf (stderr, "allocate_block: marking remainder of page free starting at 0x%zx, size %zd\n",
-               h->endblocks, rem);
+      fprintf (stderr, "allocate_block: marking remainder of page free"
+               " starting at 0x%zx, size %zd\n", h->endblocks, rem);
 
     assert (rem >= 4);
 
@@ -2162,7 +2353,8 @@ hivex_node_add_child (hive_h *h, hive_node_h parent, const char *name)
     return 0;
 
   if (h->msglvl >= 2)
-    fprintf (stderr, "hivex_node_add_child: allocated new nk-record for child at 0x%zx\n", node);
+    fprintf (stderr, "hivex_node_add_child: allocated new nk-record"
+             " for child at 0x%zx\n", node);
 
   struct ntreg_nk_record *nk = (struct ntreg_nk_record *) (h->addr + node);
   nk->flags = htole16 (0x0020); /* key is ASCII. */
@@ -2182,7 +2374,8 @@ hivex_node_add_child (hive_h *h, hive_node_h parent, const char *name)
   if (!IS_VALID_BLOCK (h, parent_sk_offset) ||
       !BLOCK_ID_EQ (h, parent_sk_offset, "sk")) {
     if (h->msglvl >= 2)
-      fprintf (stderr, "hivex_node_add_child: returning EFAULT because parent sk is not a valid block (%zu)\n",
+      fprintf (stderr, "hivex_node_add_child: returning EFAULT"
+               " because parent sk is not a valid block (%zu)\n",
                parent_sk_offset);
     errno = EFAULT;
     return 0;
@@ -2193,7 +2386,7 @@ hivex_node_add_child (hive_h *h, hive_node_h parent, const char *name)
   nk->sk = htole32 (parent_sk_offset - 0x1000);
 
   /* Inherit parent timestamp. */
-  memcpy (nk->timestamp, parent_nk->timestamp, sizeof (parent_nk->timestamp));
+  nk->timestamp = parent_nk->timestamp;
 
   /* What I found out the hard way (not documented anywhere): the
    * subkeys in lh-records must be kept sorted.  If you just add a
@@ -2236,7 +2429,8 @@ hivex_node_add_child (hive_h *h, hive_node_h parent, const char *name)
     parent_nk = (struct ntreg_nk_record *) (h->addr + parent);
 
     if (h->msglvl >= 2)
-      fprintf (stderr, "hivex_node_add_child: no keys, allocated new lh-record at 0x%zx\n", lh_offs);
+      fprintf (stderr, "hivex_node_add_child: no keys, allocated new"
+               " lh-record at 0x%zx\n", lh_offs);
 
     parent_nk->subkey_lf = htole32 (lh_offs - 0x1000);
   }
@@ -2268,7 +2462,8 @@ hivex_node_add_child (hive_h *h, hive_node_h parent, const char *name)
     /* Insert it. */
   insert_it:
     if (h->msglvl >= 2)
-      fprintf (stderr, "hivex_node_add_child: insert key in existing lh-record at 0x%zx, posn %zu\n", old_offs, j);
+      fprintf (stderr, "hivex_node_add_child: insert key in existing"
+               " lh-record at 0x%zx, posn %zu\n", old_offs, j);
 
     new_offs = insert_lf_record (h, old_offs, j, name, node);
     if (new_offs == 0) {
@@ -2309,7 +2504,8 @@ hivex_node_add_child (hive_h *h, hive_node_h parent, const char *name)
 
       /* Not found ..  This is an internal error. */
       if (h->msglvl >= 2)
-        fprintf (stderr, "hivex_node_add_child: returning ENOTSUP because could not find ri->lf link\n");
+        fprintf (stderr, "hivex_node_add_child: returning ENOTSUP"
+                 " because could not find ri->lf link\n");
       errno = ENOTSUP;
       free (blocks);
       return 0;
@@ -2502,7 +2698,8 @@ hivex_node_delete_child (hive_h *h, hive_node_h node)
     }
   }
   if (h->msglvl >= 2)
-    fprintf (stderr, "hivex_node_delete_child: could not find parent to child link\n");
+    fprintf (stderr, "hivex_node_delete_child: could not find parent"
+             " to child link\n");
   errno = ENOTSUP;
   return -1;
 
@@ -2512,8 +2709,8 @@ hivex_node_delete_child (hive_h *h, hive_node_h node)
   nk->nr_subkeys = htole32 (nr_subkeys_in_nk - 1);
 
   if (h->msglvl >= 2)
-    fprintf (stderr, "hivex_node_delete_child: updating nr_subkeys in parent 0x%zx to %zu\n",
-             parent, nr_subkeys_in_nk);
+    fprintf (stderr, "hivex_node_delete_child: updating nr_subkeys"
+             " in parent 0x%zx to %zu\n", parent, nr_subkeys_in_nk);
 
   return 0;
 }
@@ -2613,7 +2810,7 @@ hivex_node_set_values (hive_h *h, hive_node_h node,
 
 int
 hivex_node_set_value (hive_h *h, hive_node_h node,
-                     const hive_set_value *val, int flags)
+                      const hive_set_value *val, int flags)
 {
   hive_value_h *prev_values = hivex_node_values (h, node);
   if (prev_values == NULL)
@@ -2679,8 +2876,7 @@ hivex_node_set_value (hive_h *h, hive_node_h node,
 
  leave_partial:
   for (int i = 0; i < alloc_ct; i += 2) {
-    if (values[i / 2].value != NULL)
-      free (values[i / 2].value);
+    free (values[i / 2].value);
     if (i + 1 < alloc_ct && values[i / 2].key != NULL)
       free (values[i / 2].key);
   }