hivex: Add HIVEX_OPEN_WRITE flag to allow hive to be opened for writing.
[libguestfs.git] / hivex / hivex.c
index e47dd23..44f2998 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:
@@ -23,6 +23,8 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include <stdint.h>
+#include <stddef.h>
+#include <inttypes.h>
 #include <string.h>
 #include <fcntl.h>
 #include <unistd.h>
 #include <iconv.h>
 #include <sys/mman.h>
 #include <sys/stat.h>
-#ifdef HAVE_ENDIAN_H
-#include <endian.h>
-#endif
-#ifdef HAVE_BYTESWAP_H
-#include <byteswap.h>
-#endif
+#include <assert.h>
 
-#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
+#include "full-read.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 "byte_conversions.h"
+
+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;
 
-  /* Memory-mapped (readonly) registry file. */
+  /* Registry file, memory mapped if read-only, or malloc'd if writing. */
   union {
     char *addr;
     struct ntreg_header *hdr;
@@ -95,43 +79,57 @@ struct hive_h {
 #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)                 \
+#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. */
+  /* Fields from the header, extracted from little-endianness hell. */
   size_t rootoffs;              /* Root key offset (always an nk-block). */
-
-  /* Stats. */
-  size_t pages;                 /* Number of hbin pages read. */
-  size_t blocks;                /* Total number of blocks found. */
-  size_t used_blocks;           /* Total number of used blocks found. */
-  size_t used_size;             /* Total size (bytes) of used blocks. */
+  size_t endpages;              /* Offset of end of pages. */
 };
 
 /* NB. All fields are little endian. */
 struct ntreg_header {
   char magic[4];                /* "regf" */
-  uint32_t unknown1;
-  uint32_t unknown2;
+  uint32_t sequence1;
+  uint32_t sequence2;
   char last_modified[8];
-  uint32_t unknown3;            /* 1 */
-  uint32_t unknown4;            /* 3 */
+  uint32_t major_ver;           /* 1 */
+  uint32_t minor_ver;           /* 3 */
   uint32_t unknown5;            /* 0 */
   uint32_t unknown6;            /* 1 */
   uint32_t offset;              /* offset of root key record - 4KB */
-  uint32_t blocks;              /* size in bytes of data (filesize - 4KB) */
+  uint32_t blocks;              /* pointer AFTER last hbin in file - 4KB */
   uint32_t unknown7;            /* 1 */
-  char name[0x1fc-0x2c];
-  uint32_t csum;                /* checksum: sum of 32 bit words 0-0x1fb. */
+  /* 0x30 */
+  char name[64];                /* original file name of hive */
+  char unknown_guid1[16];
+  char unknown_guid2[16];
+  /* 0x90 */
+  uint32_t unknown8;
+  char unknown_guid3[16];
+  uint32_t unknown9;
+  /* 0xa8 */
+  char unknown10[340];
+  /* 0x1fc */
+  uint32_t csum;                /* checksum: xor of dwords 0-0x1fb. */
+  /* 0x200 */
+  char unknown11[3528];
+  /* 0xfc8 */
+  char unknown_guid4[16];
+  char unknown_guid5[16];
+  char unknown_guid6[16];
+  uint32_t unknown12;
+  uint32_t unknown13;
+  /* 0x1000 */
 } __attribute__((__packed__));
 
 struct ntreg_hbin_page {
   char magic[4];                /* "hbin" */
   uint32_t offset_first;        /* offset from 1st block */
-  uint32_t offset_next;         /* offset of next (relative to this) */
+  uint32_t page_size;           /* size of this page (multiple of 4KB) */
   char unknown[20];
   /* Linked list of blocks follows here. */
 } __attribute__((__packed__));
@@ -166,18 +164,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 */
@@ -189,7 +194,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__));
 
@@ -217,17 +222,38 @@ 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 */
-  uint16_t unknown1;            /* possibly always 1 */
+  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.
+                                   NB: this is CLEAR for default key. */
   uint16_t unknown2;
   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)
 {
   hive_h *h = NULL;
 
+  assert (sizeof (struct ntreg_header) == 0x1000);
+  assert (offsetof (struct ntreg_header, csum) == 0x1fc);
+
   h = calloc (1, sizeof *h);
   if (h == NULL)
     goto error;
@@ -239,9 +265,14 @@ hivex_open (const char *filename, int flags)
     h->msglvl = 2;
 
   if (h->msglvl >= 2)
-    printf ("hivex_open: created handle %p\n", h);
+    fprintf (stderr, "hivex_open: created handle %p\n", h);
 
-  h->fd = open (filename, O_RDONLY);
+  h->writable = !!(flags & HIVEX_OPEN_WRITE);
+  h->filename = strdup (filename);
+  if (h->filename == NULL)
+    goto error;
+
+  h->fd = open (filename, O_RDONLY | O_CLOEXEC);
   if (h->fd == -1)
     goto error;
 
@@ -251,12 +282,21 @@ hivex_open (const char *filename, int flags)
 
   h->size = statbuf.st_size;
 
-  h->addr = mmap (NULL, h->size, PROT_READ, MAP_SHARED, h->fd, 0);
-  if (h->addr == MAP_FAILED)
-    goto error;
+  if (!h->writable) {
+    h->addr = mmap (NULL, h->size, PROT_READ, MAP_SHARED, h->fd, 0);
+    if (h->addr == MAP_FAILED)
+      goto error;
 
-  if (h->msglvl >= 2)
-    printf ("hivex_open: mapped file at %p\n", h->addr);
+    if (h->msglvl >= 2)
+      fprintf (stderr, "hivex_open: mapped file at %p\n", h->addr);
+  } else {
+    h->addr = malloc (h->size);
+    if (h->addr == NULL)
+      goto error;
+
+    if (full_read (h->fd, h->addr, h->size) < h->size)
+      goto error;
+  }
 
   /* Check header. */
   if (h->hdr->magic[0] != 'r' ||
@@ -269,36 +309,69 @@ hivex_open (const char *filename, int flags)
     goto error;
   }
 
+  /* Check major version. */
+  uint32_t major_ver = le32toh (h->hdr->major_ver);
+  if (major_ver != 1) {
+    fprintf (stderr,
+             "hivex: %s: hive file major version %" PRIu32 " (expected 1)\n",
+             filename, major_ver);
+    errno = ENOTSUP;
+    goto error;
+  }
+
   h->bitmap = calloc (1 + h->size / 32, 1);
   if (h->bitmap == NULL)
     goto error;
 
-#if 0                           /* Doesn't work. */
   /* Header checksum. */
-  uint32_t *daddr = 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;
     goto error;
   }
-#endif
+
+  if (h->msglvl >= 2) {
+    char *name = windows_utf16_to_utf8 (h->hdr->name, 64);
+
+    fprintf (stderr,
+             "hivex_open: header fields:\n"
+             "  file version             %" PRIu32 ".%" PRIu32 "\n"
+             "  sequence nos             %" PRIu32 " %" PRIu32 "\n"
+             "    (sequences nos should match if hive was synched at shutdown)\n"
+             "  original file name       %s\n"
+             "    (only 32 chars are stored, name is probably truncated)\n"
+             "  root offset              0x%x + 0x1000\n"
+             "  end of last page         0x%x + 0x1000 (total file size 0x%zx)\n"
+             "  checksum                 0x%x (calculated 0x%x)\n",
+             major_ver, le32toh (h->hdr->minor_ver),
+             le32toh (h->hdr->sequence1), le32toh (h->hdr->sequence2),
+             name ? name : "(conversion failed)",
+             le32toh (h->hdr->offset),
+             le32toh (h->hdr->blocks), h->size,
+             le32toh (h->hdr->csum), sum);
+    free (name);
+  }
 
   h->rootoffs = le32toh (h->hdr->offset) + 0x1000;
+  h->endpages = le32toh (h->hdr->blocks) + 0x1000;
 
   if (h->msglvl >= 2)
-    printf ("hivex_open: root offset = %zu\n", h->rootoffs);
+    fprintf (stderr, "hivex_open: root offset = 0x%zx\n", h->rootoffs);
 
   /* We'll set this flag when we see a block with the root offset (ie.
    * the root block).
    */
   int seen_root_block = 0, bad_root_block = 0;
 
+  /* Collect some stats. */
+  size_t pages = 0;           /* Number of hbin pages read. */
+  size_t smallest_page = SIZE_MAX, largest_page = 0;
+  size_t blocks = 0;          /* Total number of blocks found. */
+  size_t smallest_block = SIZE_MAX, largest_block = 0, blocks_bytes = 0;
+  size_t used_blocks = 0;     /* Total number of used blocks found. */
+  size_t used_size = 0;       /* Total size (bytes) of used blocks. */
+
   /* Read the pages and blocks.  The aim here is to be robust against
    * corrupt or malicious registries.  So we make sure the loops
    * always make forward progress.  We add the address of each block
@@ -307,29 +380,32 @@ hivex_open (const char *filename, int flags)
    */
   size_t off;
   struct ntreg_hbin_page *page;
-  for (off = 0x1000; off < h->size; off += le32toh (page->offset_next)) {
-    h->pages++;
+  for (off = 0x1000; off < h->size; off += le32toh (page->page_size)) {
+    if (off >= h->endpages)
+      break;
 
     page = (struct ntreg_hbin_page *) (h->addr + off);
     if (page->magic[0] != 'h' ||
         page->magic[1] != 'b' ||
         page->magic[2] != 'i' ||
         page->magic[3] != 'n') {
-      /* This error is seemingly common in uncorrupt registry files. */
-      /*
-      fprintf (stderr, "hivex: %s: ignoring trailing garbage at end of file (at %zu, after %zu pages)\n",
-               filename, off, h->pages);
-      */
-      break;
+      fprintf (stderr, "hivex: %s: trailing garbage at end of file (at 0x%zx, after %zu pages)\n",
+               filename, off, pages);
+      errno = ENOTSUP;
+      goto error;
     }
 
+    size_t page_size = le32toh (page->page_size);
     if (h->msglvl >= 2)
-      printf ("hivex_open: page at %zu\n", off);
-
-    if (le32toh (page->offset_next) <= sizeof (struct ntreg_hbin_page) ||
-        (le32toh (page->offset_next) & 3) != 0) {
-      fprintf (stderr, "hivex: %s: pagesize %d at %zu, bad registry\n",
-               filename, le32toh (page->offset_next), off);
+      fprintf (stderr, "hivex_open: page at 0x%zx, size %zu\n", off, page_size);
+    pages++;
+    if (page_size < smallest_page) smallest_page = page_size;
+    if (page_size > largest_page) largest_page = page_size;
+
+    if (page_size <= sizeof (struct ntreg_hbin_page) ||
+        (page_size & 0x0fff) != 0) {
+      fprintf (stderr, "hivex: %s: page size %zu at 0x%zx, bad registry\n",
+               filename, page_size, off);
       errno = ENOTSUP;
       goto error;
     }
@@ -337,11 +413,11 @@ hivex_open (const char *filename, int flags)
     /* Read the blocks in this page. */
     size_t blkoff;
     struct ntreg_hbin_block *block;
-    int32_t seg_len;
+    size_t seg_len;
     for (blkoff = off + 0x20;
-         blkoff < off + le32toh (page->offset_next);
+         blkoff < off + page_size;
          blkoff += seg_len) {
-      h->blocks++;
+      blocks++;
 
       int is_root = blkoff == h->rootoffs;
       if (is_root)
@@ -351,23 +427,27 @@ 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;
       }
 
       if (h->msglvl >= 2)
-        printf ("hivex_open: %s block id %d,%d at %zu%s\n",
-                used ? "used" : "free", block->id[0], block->id[1], blkoff,
-                is_root ? " (root)" : "");
+        fprintf (stderr, "hivex_open: %s block id %d,%d at 0x%zx size %zu%s\n",
+                 used ? "used" : "free", block->id[0], block->id[1], blkoff,
+                 seg_len, is_root ? " (root)" : "");
+
+      blocks_bytes += seg_len;
+      if (seg_len < smallest_block) smallest_block = seg_len;
+      if (seg_len > largest_block) largest_block = seg_len;
 
       if (is_root && !used)
         bad_root_block = 1;
 
       if (used) {
-        h->used_blocks++;
-        h->used_size += seg_len;
+        used_blocks++;
+        used_size += seg_len;
 
         /* Root block must be an nk-block. */
         if (is_root && (block->id[0] != 'n' || block->id[1] != 'k'))
@@ -392,12 +472,15 @@ hivex_open (const char *filename, int flags)
   }
 
   if (h->msglvl >= 1)
-    printf ("hivex_open: successfully read Windows Registry hive file:\n"
-            "  pages:                  %zu\n"
-            "  blocks:                 %zu\n"
-            "  blocks used:            %zu\n"
-            "  bytes used:             %zu\n",
-            h->pages, h->blocks, h->used_blocks, h->used_size);
+    fprintf (stderr,
+             "hivex_open: successfully read Windows Registry hive file:\n"
+             "  pages:          %zu [sml: %zu, lge: %zu]\n"
+             "  blocks:         %zu [sml: %zu, avg: %zu, lge: %zu]\n"
+             "  blocks used:    %zu\n"
+             "  bytes used:     %zu\n",
+             pages, smallest_page, largest_page,
+             blocks, smallest_block, blocks_bytes / blocks, largest_block,
+             used_blocks, used_size);
 
   return h;
 
@@ -405,10 +488,15 @@ hivex_open (const char *filename, int flags)
   int err = errno;
   if (h) {
     free (h->bitmap);
-    if (h->addr && h->size && h->addr != MAP_FAILED)
-      munmap (h->addr, h->size);
+    if (h->addr && h->size && h->addr != MAP_FAILED) {
+      if (!h->writable)
+        munmap (h->addr, h->size);
+      else
+        free (h->addr);
+    }
     if (h->fd >= 0)
       close (h->fd);
+    free (h->filename);
     free (h);
   }
   errno = err;
@@ -421,8 +509,12 @@ hivex_close (hive_h *h)
   int r;
 
   free (h->bitmap);
-  munmap (h->addr, h->size);
+  if (!h->writable)
+    munmap (h->addr, h->size);
+  else
+    free (h->addr);
   r = close (h->fd);
+  free (h->filename);
   free (h);
 
   return r;
@@ -461,7 +553,7 @@ 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)
-      printf ("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;
@@ -521,34 +613,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.
@@ -557,12 +712,15 @@ hivex_node_children (hive_h *h, hive_node_h node)
   subkey_lf += 0x1000;
   if (!IS_VALID_BLOCK (h, subkey_lf)) {
     if (h->msglvl >= 2)
-      printf ("hivex_node_children: returning EFAULT because subkey_lf is not a valid block (%zu)\n",
-              subkey_lf);
+      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);
 
@@ -578,46 +736,38 @@ hivex_node_children (hive_h *h, hive_node_h node)
     size_t nr_subkeys_in_lf = le16toh (lf->nr_keys);
 
     if (h->msglvl >= 2)
-      printf ("hivex_node_children: nr_subkeys_in_nk = %zu, nr_subkeys_in_lf = %zu\n",
-              nr_subkeys_in_nk, nr_subkeys_in_lf);
+      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) {
       errno = ENOTSUP;
-      return NULL;
+      goto error;
     }
 
     size_t len = block_len (h, subkey_lf, NULL);
     if (8 + nr_subkeys_in_lf * 8 > len) {
       if (h->msglvl >= 2)
-        printf ("hivex_node_children: returning EFAULT because too many subkeys (%zu, %zu)\n",
-                nr_subkeys_in_lf, len);
+        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)
-          printf ("hivex_node_children: returning EFAULT because subkey is not a valid block (%zu)\n",
-                  subkey);
+          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') {
@@ -632,16 +782,19 @@ hivex_node_children (hive_h *h, hive_node_h node)
       offset += 0x1000;
       if (!IS_VALID_BLOCK (h, offset)) {
         if (h->msglvl >= 2)
-          printf ("hivex_node_children: returning EFAULT because ri-offset is not a valid block (%zu)\n",
-                  offset);
+          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);
 
@@ -649,35 +802,30 @@ hivex_node_children (hive_h *h, hive_node_h node)
     }
 
     if (h->msglvl >= 2)
-      printf ("hivex_node_children: nr_subkeys_in_nk = %zu, counted = %zu\n",
-              nr_subkeys_in_nk, count);
+      fprintf (stderr, "hivex_node_children: nr_subkeys_in_nk = %zu, counted = %zu\n",
+               nr_subkeys_in_nk, count);
 
     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;
       if (!IS_VALID_BLOCK (h, offset)) {
         if (h->msglvl >= 2)
-          printf ("hivex_node_children: returning EFAULT because ri-offset is not a valid block (%zu)\n",
-                  offset);
+          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 =
@@ -685,27 +833,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)
-            printf ("hivex_node_children: returning EFAULT because indirect subkey is not a valid block (%zu)\n",
-                    subkey);
+            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
@@ -750,10 +918,9 @@ hivex_node_parent (hive_h *h, hive_node_h node)
 
   hive_node_h ret = le32toh (nk->parent);
   ret += 0x1000;
-  printf ("parent = %zu\n", ret);
   if (!IS_VALID_BLOCK (h, ret)) {
     if (h->msglvl >= 2)
-      printf ("hivex_node_parent: returning EFAULT because parent is not a valid block (%zu)\n",
+      fprintf (stderr, "hivex_node_parent: returning EFAULT because parent is not a valid block (0x%zx)\n",
               ret);
     errno = EFAULT;
     return 0;
@@ -761,12 +928,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);
@@ -774,69 +942,90 @@ hivex_node_values (hive_h *h, hive_node_h node)
   size_t nr_values = le32toh (nk->nr_values);
 
   if (h->msglvl >= 2)
-    printf ("hivex_node_values: nr_values = %zu\n", nr_values);
+    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;
   if (!IS_VALID_BLOCK (h, vlist_offset)) {
     if (h->msglvl >= 2)
-      printf ("hivex_node_values: returning EFAULT because value list is not a valid block (%zu)\n",
-              vlist_offset);
+      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);
 
   size_t len = block_len (h, vlist_offset, NULL);
   if (4 + nr_values * 4 > len) {
     if (h->msglvl >= 2)
-      printf ("hivex_node_values: returning EFAULT because value list is too long (%zu, %zu)\n",
-              nr_values, len);
+      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];
     value += 0x1000;
     if (!IS_VALID_BLOCK (h, value)) {
       if (h->msglvl >= 2)
-        printf ("hivex_node_values: returning EFAULT because value is not a valid block (%zu)\n",
-                value);
+        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
@@ -890,8 +1079,8 @@ hivex_value_key (hive_h *h, hive_value_h value)
   size_t seg_len = block_len (h, value, NULL);
   if (sizeof (struct ntreg_vk_record) + len - 1 > seg_len) {
     if (h->msglvl >= 2)
-      printf ("hivex_value_key: returning EFAULT because key length is too long (%zu, %zu)\n",
-              len, seg_len);
+      fprintf (stderr, "hivex_value_key: returning EFAULT because key length is too long (%zu, %zu)\n",
+               len, seg_len);
     errno = EFAULT;
     return NULL;
   }
@@ -953,8 +1142,8 @@ hivex_value_value (hive_h *h, hive_value_h value,
   len &= 0x7fffffff;
 
   if (h->msglvl >= 2)
-    printf ("hivex_value_value: value=%zu, t=%d, len=%zu\n",
-            value, t, len);
+    fprintf (stderr, "hivex_value_value: value=0x%zx, t=%d, len=%zu\n",
+             value, t, len);
 
   if (t_rtn)
     *t_rtn = t;
@@ -977,12 +1166,12 @@ 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)
-      printf ("hivex_value_value: returning EFAULT because data offset is not a valid block (%zu)\n",
-              data_offset);
+      fprintf (stderr, "hivex_value_value: returning EFAULT because data offset is not a valid block (0x%zx)\n",
+               data_offset);
     errno = EFAULT;
     free (ret);
     return NULL;
@@ -990,10 +1179,10 @@ 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 (blen < len) {
+  if (len > blen - 4 /* subtract 4 for block header */) {
     if (h->msglvl >= 2)
-      printf ("hivex_value_value: returning EFAULT because data is longer than its block (%zu, %zu)\n",
-              blen, len);
+      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);
     errno = EFAULT;
     free (ret);
     return NULL;
@@ -1265,8 +1454,8 @@ hivex__visit_node (hive_h *h, hive_node_h node,
 
   if (!BITMAP_TST (unvisited, node)) {
     if (h->msglvl >= 2)
-      printf ("hivex__visit_node: contains cycle: visited node %zu already\n",
-              node);
+      fprintf (stderr, "hivex__visit_node: contains cycle: visited node 0x%zx already\n",
+               node);
 
     errno = ELOOP;
     return skip_bad ? 0 : -1;
@@ -1299,114 +1488,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;
@@ -1420,8 +1621,8 @@ hivex__visit_node (hive_h *h, hive_node_h node,
 
   for (i = 0; children[i] != 0; ++i) {
     if (h->msglvl >= 2)
-      printf ("hivex__visit_node: %s: visiting subkey %d (%zu)\n",
-              name, i, children[i]);
+      fprintf (stderr, "hivex__visit_node: %s: visiting subkey %d (0x%zx)\n",
+               name, i, children[i]);
 
     if (hivex__visit_node (h, children[i], vtor, unvisited, opaque, flags) == -1)
       goto error;