enable scrub on Debian
[libguestfs.git] / hivex / hivex.c
index c0e456f..7d26eeb 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>
+
+#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 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)
-
-#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
+#define STRPREFIX(a,b) (strncmp((a),(b),strlen((b))) == 0)
 
 #include "hivex.h"
+#include "byte_conversions.h"
+
+/* These limits are in place to stop really stupid stuff and/or exploits. */
+#define HIVEX_MAX_SUBKEYS       10000
+#define HIVEX_MAX_VALUES         1000
+#define HIVEX_MAX_VALUE_LEN   1000000
+#define HIVEX_MAX_ALLOCATION  1000000
 
 static char *windows_utf16_to_utf8 (/* const */ char *input, size_t len);
 
@@ -93,8 +68,9 @@ struct hive_h {
   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;
@@ -120,6 +96,10 @@ struct hive_h {
   /* 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). */
 };
 
 /* NB. All fields are little endian. */
@@ -161,7 +141,7 @@ struct ntreg_header {
 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__));
@@ -196,18 +176,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 */
@@ -215,11 +202,11 @@ struct ntreg_nk_record {
 
 struct ntreg_lf_record {
   int32_t seg_len;
-  char id[2];                   /* "lf" */
+  char id[2];                   /* "lf"|"lh" */
   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__));
 
@@ -242,19 +229,45 @@ struct ntreg_vk_record {
   uint16_t name_len;            /* length of name */
   /* length of the data:
    * If data_len is <= 4, then it's stored inline.
-   * If data_len is 0x80000000, then it's an inline dword.
-   * Top bit may be set or not set at random.
+   * Top bit is set to indicate inline.
    */
   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. */
+                                   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__));
 
+struct ntreg_sk_record {
+  int32_t seg_len;              /* length (always -ve because used) */
+  char id[2];                   /* "sk" */
+  uint16_t unknown1;
+  uint32_t sk_next;             /* linked into a circular list */
+  uint32_t sk_prev;
+  uint32_t refcount;            /* reference count */
+  uint32_t sec_len;             /* length of security info */
+  char sec_desc[1];             /* security info follows */
+} __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)
 {
@@ -276,11 +289,12 @@ hivex_open (const char *filename, int flags)
   if (h->msglvl >= 2)
     fprintf (stderr, "hivex_open: created handle %p\n", h);
 
+  h->writable = !!(flags & HIVEX_OPEN_WRITE);
   h->filename = strdup (filename);
   if (h->filename == NULL)
     goto error;
 
-  h->fd = open (filename, O_RDONLY);
+  h->fd = open (filename, O_RDONLY | O_CLOEXEC);
   if (h->fd == -1)
     goto error;
 
@@ -290,12 +304,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)
-    fprintf (stderr, "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' ||
@@ -323,14 +346,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;
@@ -386,7 +402,7 @@ 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)) {
+  for (off = 0x1000; off < h->size; off += le32toh (page->page_size)) {
     if (off >= h->endpages)
       break;
 
@@ -401,17 +417,17 @@ hivex_open (const char *filename, int flags)
       goto error;
     }
 
-    size_t page_size = le32toh (page->offset_next);
+    size_t page_size = le32toh (page->page_size);
     if (h->msglvl >= 2)
       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 (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);
+    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;
     }
@@ -421,7 +437,7 @@ hivex_open (const char *filename, int flags)
     struct ntreg_hbin_block *block;
     size_t seg_len;
     for (blkoff = off + 0x20;
-         blkoff < off + le32toh (page->offset_next);
+         blkoff < off + page_size;
          blkoff += seg_len) {
       blocks++;
 
@@ -433,7 +449,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;
@@ -494,8 +510,12 @@ 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);
@@ -511,7 +531,10 @@ 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);
@@ -519,6 +542,10 @@ hivex_close (hive_h *h)
   return r;
 }
 
+/*----------------------------------------------------------------------
+ * Reading.
+ */
+
 hive_node_h
 hivex_root (hive_h *h)
 {
@@ -612,34 +639,100 @@ 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. */
+#define GET_CHILDREN_NO_CHECK_NK 1
+
+static int
+get_children (hive_h *h, hive_node_h node,
+              hive_node_h **children_ret, size_t **blocks_ret,
+              int flags)
 {
   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) {
+  if (nr_subkeys_in_nk > HIVEX_MAX_SUBKEYS) {
     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.
@@ -648,12 +741,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)
-      fprintf (stderr, "hivex_node_children: returning EFAULT because subkey_lf is not a valid block (%zu)\n",
+      fprintf (stderr, "hivex_node_children: returning EFAULT because subkey_lf is not a valid block (0x%zx)\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 +770,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 +779,26 @@ 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;
+      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",
+                     subkey);
+          errno = EFAULT;
+          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') {
@@ -719,20 +809,26 @@ hivex_node_children (hive_h *h, hive_node_h node)
     /* Count total number of children. */
     size_t i, count = 0;
     for (i = 0; i < nr_offsets; ++i) {
-      hive_node_h offset = ri->offset[i];
+      hive_node_h offset = le32toh (ri->offset[i]);
       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",
                    offset);
         errno = EFAULT;
-        return NULL;
+        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",
+                   offset);
         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,30 +841,28 @@ 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];
+      hive_node_h offset = le32toh (ri->offset[i]);
       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",
                    offset);
         errno = EFAULT;
-        return NULL;
+        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",
+                   offset);
         errno = ENOTSUP;
-        return NULL;
+        goto error;
       }
 
       struct ntreg_lf_record *lf =
@@ -776,27 +870,52 @@ 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;
+        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",
+                       subkey);
+            errno = EFAULT;
+            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 */
+  if (h->msglvl >= 2)
+    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:
+  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, 0) == -1)
     return NULL;
-  }
+
+  free (blocks);
+  return children;
 }
 
 /* Very inefficient, but at least having a separate API call
@@ -851,12 +970,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 +986,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) {
+  if (nr_values > HIVEX_MAX_VALUES) {
     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 +1011,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 +1026,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 +1038,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
@@ -1009,11 +1150,7 @@ hivex_value_type (hive_h *h, hive_value_h value, hive_type *t, size_t *len)
 
   if (len) {
     *len = le32toh (vk->data_len);
-    if (*len == 0x80000000) {   /* special case */
-      *len = 4;
-      if (t) *t = hive_t_dword;
-    }
-    *len &= 0x7fffffff;
+    *len &= 0x7fffffff;         /* top bit indicates if data is stored inline */
   }
 
   return 0;
@@ -1032,27 +1169,30 @@ hivex_value_value (hive_h *h, hive_value_h value,
 
   hive_type t;
   size_t len;
+  int is_inline;
 
   t = le32toh (vk->data_type);
 
   len = le32toh (vk->data_len);
-  if (len == 0x80000000) {      /* special case */
-    len = 4;
-    t = hive_t_dword;
-  }
+  is_inline = !!(len & 0x80000000);
   len &= 0x7fffffff;
 
   if (h->msglvl >= 2)
-    fprintf (stderr, "hivex_value_value: value=0x%zx, t=%d, len=%zu\n",
-             value, t, len);
+    fprintf (stderr, "hivex_value_value: value=0x%zx, t=%d, len=%zu, inline=%d\n",
+             value, t, len, is_inline);
 
   if (t_rtn)
     *t_rtn = t;
   if (len_rtn)
     *len_rtn = len;
 
+  if (is_inline && len > 4) {
+    errno = ENOTSUP;
+    return NULL;
+  }
+
   /* Arbitrarily limit the length that we will read. */
-  if (len > 1000000) {
+  if (len > HIVEX_MAX_VALUE_LEN) {
     errno = ERANGE;
     return NULL;
   }
@@ -1061,13 +1201,12 @@ hivex_value_value (hive_h *h, hive_value_h value,
   if (ret == NULL)
     return NULL;
 
-  /* If length is <= 4 it's always stored inline. */
-  if (len <= 4) {
+  if (is_inline) {
     memcpy (ret, (char *) &vk->data_offset, len);
     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)
@@ -1078,15 +1217,18 @@ hivex_value_value (hive_h *h, hive_value_h value,
     return NULL;
   }
 
-  /* Check that the declared size isn't larger than the block its in. */
+  /* Check that the declared size isn't larger than the block its in.
+   *
+   * XXX Some apparently valid registries are seen to have this,
+   * so turn this into a warning and substitute the smaller length
+   * instead.
+   */
   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",
+      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);
-    errno = EFAULT;
-    free (ret);
-    return NULL;
+    len = blen - 4;
   }
 
   char *data = h->addr + data_offset + 4;
@@ -1300,6 +1442,10 @@ hivex_value_qword (hive_h *h, hive_value_h value)
   return ret;
 }
 
+/*----------------------------------------------------------------------
+ * Visiting.
+ */
+
 int
 hivex_visit (hive_h *h, const struct hivex_visitor *visitor, size_t len,
              void *opaque, int flags)
@@ -1389,114 +1535,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 (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_binary) {
+          ret = skip_bad ? 0 : -1;
+          goto error;
         }
+        if (vtor->value_binary &&
+            vtor->value_binary (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;
 
-    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_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;
+          }
+          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;
+
+      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;
@@ -1531,3 +1689,869 @@ hivex__visit_node (hive_h *h, hive_node_h node,
   free_strings (strs);
   return ret;
 }
+
+/*----------------------------------------------------------------------
+ * Writing.
+ */
+
+/* Allocate an hbin (page), extending the malloc'd space if necessary,
+ * and updating the hive handle fields (but NOT the hive disk header
+ * -- the hive disk header is updated when we commit).  This function
+ * also extends the bitmap if necessary.
+ *
+ * 'allocation_hint' is the size of the block allocation we would like
+ * to make.  Normally registry blocks are very small (avg 50 bytes)
+ * and are contained in standard-sized pages (4KB), but the registry
+ * can support blocks which are larger than a standard page, in which
+ * case it creates a page of 8KB, 12KB etc.
+ *
+ * Returns:
+ * > 0 : offset of first usable byte of new page (after page header)
+ * 0   : error (errno set)
+ */
+static size_t
+allocate_page (hive_h *h, size_t allocation_hint)
+{
+  /* In almost all cases this will be 1. */
+  size_t nr_4k_pages =
+    1 + (allocation_hint + sizeof (struct ntreg_hbin_page) - 1) / 4096;
+  assert (nr_4k_pages >= 1);
+
+  /* 'extend' is the number of bytes to extend the file by.  Note that
+   * hives found in the wild often contain slack between 'endpages'
+   * and the actual end of the file, so we don't always need to make
+   * the file larger.
+   */
+  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",
+             h->endpages, h->size);
+    fprintf (stderr, "allocate_page: extending file by %zd bytes (<= 0 if no extension)\n",
+             extend);
+  }
+
+  if (extend > 0) {
+    size_t oldsize = h->size;
+    size_t newsize = h->size + extend;
+    char *newaddr = realloc (h->addr, newsize);
+    if (newaddr == NULL)
+      return 0;
+
+    size_t oldbitmapsize = 1 + oldsize / 32;
+    size_t newbitmapsize = 1 + newsize / 32;
+    char *newbitmap = realloc (h->bitmap, newbitmapsize);
+    if (newbitmap == NULL) {
+      free (newaddr);
+      return 0;
+    }
+
+    h->addr = newaddr;
+    h->size = newsize;
+    h->bitmap = newbitmap;
+
+    memset (h->addr + oldsize, 0, newsize - oldsize);
+    memset (h->bitmap + oldbitmapsize, 0, newbitmapsize - oldbitmapsize);
+  }
+
+  size_t offset = h->endpages;
+  h->endpages += nr_4k_pages * 4096;
+
+  if (h->msglvl >= 2)
+    fprintf (stderr, "allocate_page: new endpages = 0x%zx, new size = 0x%zx\n",
+             h->endpages, h->size);
+
+  /* Write the hbin header. */
+  struct ntreg_hbin_page *page =
+    (struct ntreg_hbin_page *) (h->addr + offset);
+  page->magic[0] = 'h';
+  page->magic[1] = 'b';
+  page->magic[2] = 'i';
+  page->magic[3] = 'n';
+  page->offset_first = htole32 (offset - 0x1000);
+  page->page_size = htole32 (nr_4k_pages * 4096);
+  memset (page->unknown, 0, sizeof (page->unknown));
+
+  if (h->msglvl >= 2)
+    fprintf (stderr, "allocate_page: new page at 0x%zx\n", offset);
+
+  /* Offset of first usable byte after the header. */
+  return offset + sizeof (struct ntreg_hbin_page);
+}
+
+/* Allocate a single block, first allocating an hbin (page) at the end
+ * of the current file if necessary.  NB. To keep the implementation
+ * simple and more likely to be correct, we do not reuse existing free
+ * blocks.
+ *
+ * seg_len is the size of the block (this INCLUDES the block header).
+ * The header of the block is initialized to -seg_len (negative to
+ * indicate used).  id[2] is the block ID (type), eg. "nk" for nk-
+ * record.  The block bitmap is updated to show this block as valid.
+ * The rest of the contents of the block will be zero.
+ *
+ * Returns:
+ * > 0 : offset of new block
+ * 0   : error (errno set)
+ */
+static size_t
+allocate_block (hive_h *h, size_t seg_len, const char id[2])
+{
+  if (!h->writable) {
+    errno = EROFS;
+    return 0;
+  }
+
+  if (seg_len < 4) {
+    /* The caller probably forgot to include the header.  Note that
+     * value lists have no ID field, so seg_len == 4 would be possible
+     * for them, albeit unusual.
+     */
+    if (h->msglvl >= 2)
+      fprintf (stderr, "allocate_block: refusing too small allocation (%zu), returning ERANGE\n",
+               seg_len);
+    errno = ERANGE;
+    return 0;
+  }
+
+  /* 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);
+    errno = ERANGE;
+    return 0;
+  }
+
+  /* Round up allocation to multiple of 8 bytes.  All blocks must be
+   * on an 8 byte boundary.
+   */
+  seg_len = (seg_len + 7) & ~7;
+
+  /* Allocate a new page if necessary. */
+  if (h->endblocks == 0 || h->endblocks + seg_len > h->endpages) {
+    size_t newendblocks = allocate_page (h, seg_len);
+    if (newendblocks == 0)
+      return 0;
+    h->endblocks = newendblocks;
+  }
+
+  size_t offset = h->endblocks;
+
+  if (h->msglvl >= 2)
+    fprintf (stderr, "allocate_block: new block at 0x%zx, size %zu\n",
+             offset, seg_len);
+
+  struct ntreg_hbin_block *blockhdr =
+    (struct ntreg_hbin_block *) (h->addr + offset);
+
+  blockhdr->seg_len = htole32 (- (int32_t) seg_len);
+  if (id[0] && id[1] && seg_len >= sizeof (struct ntreg_hbin_block)) {
+    blockhdr->id[0] = id[0];
+    blockhdr->id[1] = id[1];
+  }
+
+  BITMAP_SET (h->bitmap, offset);
+
+  h->endblocks += seg_len;
+
+  /* If there is space after the last block in the last page, then we
+   * have to put a dummy free block header here to mark the rest of
+   * the page as free.
+   */
+  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);
+
+    assert (rem >= 4);
+
+    blockhdr = (struct ntreg_hbin_block *) (h->addr + h->endblocks);
+    blockhdr->seg_len = htole32 ((int32_t) rem);
+  }
+
+  return offset;
+}
+
+/* 'offset' must point to a valid, used block.  This function marks
+ * the block unused (by updating the seg_len field) and invalidates
+ * the bitmap.  It does NOT do this recursively, so to avoid creating
+ * unreachable used blocks, callers may have to recurse over the hive
+ * structures.  Also callers must ensure there are no references to
+ * this block from other parts of the hive.
+ */
+static void
+mark_block_unused (hive_h *h, size_t offset)
+{
+  assert (h->writable);
+  assert (IS_VALID_BLOCK (h, offset));
+
+  if (h->msglvl >= 2)
+    fprintf (stderr, "mark_block_unused: marking 0x%zx unused\n", offset);
+
+  struct ntreg_hbin_block *blockhdr =
+    (struct ntreg_hbin_block *) (h->addr + offset);
+
+  size_t seg_len = block_len (h, offset, NULL);
+  blockhdr->seg_len = htole32 (seg_len);
+
+  BITMAP_CLR (h->bitmap, offset);
+}
+
+/* Delete all existing values at this node. */
+static int
+delete_values (hive_h *h, hive_node_h node)
+{
+  assert (h->writable);
+
+  hive_value_h *values;
+  size_t *blocks;
+  if (get_values (h, node, &values, &blocks) == -1)
+    return -1;
+
+  size_t i;
+  for (i = 0; blocks[i] != 0; ++i)
+    mark_block_unused (h, blocks[i]);
+
+  free (blocks);
+
+  for (i = 0; values[i] != 0; ++i) {
+    struct ntreg_vk_record *vk =
+      (struct ntreg_vk_record *) (h->addr + values[i]);
+
+    size_t len;
+    int is_inline;
+    len = le32toh (vk->data_len);
+    is_inline = !!(len & 0x80000000); /* top bit indicates is inline */
+    len &= 0x7fffffff;
+
+    if (!is_inline) {           /* non-inline, so remove data block */
+      size_t data_offset = le32toh (vk->data_offset);
+      data_offset += 0x1000;
+      mark_block_unused (h, data_offset);
+    }
+
+    /* remove vk record */
+    mark_block_unused (h, values[i]);
+  }
+
+  free (values);
+
+  struct ntreg_nk_record *nk = (struct ntreg_nk_record *) (h->addr + node);
+  nk->nr_values = htole32 (0);
+  nk->vallist = htole32 (0xffffffff);
+
+  return 0;
+}
+
+int
+hivex_commit (hive_h *h, const char *filename, int flags)
+{
+  if (flags != 0) {
+    errno = EINVAL;
+    return -1;
+  }
+
+  if (!h->writable) {
+    errno = EROFS;
+    return -1;
+  }
+
+  filename = filename ? : h->filename;
+  int fd = open (filename, O_WRONLY|O_CREAT|O_TRUNC|O_NOCTTY, 0666);
+  if (fd == -1)
+    return -1;
+
+  /* Update the header fields. */
+  uint32_t sequence = le32toh (h->hdr->sequence1);
+  sequence++;
+  h->hdr->sequence1 = htole32 (sequence);
+  h->hdr->sequence2 = htole32 (sequence);
+  /* XXX Ought to update h->hdr->last_modified. */
+  h->hdr->blocks = htole32 (h->endpages - 0x1000);
+
+  /* Recompute header checksum. */
+  uint32_t sum = header_checksum (h);
+  h->hdr->csum = htole32 (sum);
+
+  if (h->msglvl >= 2)
+    fprintf (stderr, "hivex_commit: new header checksum: 0x%x\n", sum);
+
+  if (full_write (fd, h->addr, h->size) != h->size) {
+    int err = errno;
+    close (fd);
+    errno = err;
+    return -1;
+  }
+
+  if (close (fd) == -1)
+    return -1;
+
+  return 0;
+}
+
+/* Calculate the hash for a lf or lh record offset.
+ */
+static void
+calc_hash (const char *type, const char *name, char *ret)
+{
+  size_t len = strlen (name);
+
+  if (STRPREFIX (type, "lf"))
+    /* Old-style, not used in current registries. */
+    memcpy (ret, name, len < 4 ? len : 4);
+  else {
+    /* New-style for lh-records. */
+    size_t i, c;
+    uint32_t h = 0;
+    for (i = 0; i < len; ++i) {
+      c = c_toupper (name[i]);
+      h *= 37;
+      h += c;
+    }
+    *((uint32_t *) ret) = htole32 (h);
+  }
+}
+
+/* Create a completely new lh-record containing just the single node. */
+static size_t
+new_lh_record (hive_h *h, const char *name, hive_node_h node)
+{
+  static const char id[2] = { 'l', 'h' };
+  size_t seg_len = sizeof (struct ntreg_lf_record);
+  size_t offset = allocate_block (h, seg_len, id);
+  if (offset == 0)
+    return 0;
+
+  struct ntreg_lf_record *lh = (struct ntreg_lf_record *) (h->addr + offset);
+  lh->nr_keys = htole16 (1);
+  lh->keys[0].offset = htole32 (node - 0x1000);
+  calc_hash ("lh", name, lh->keys[0].hash);
+
+  return offset;
+}
+
+/* Insert node into existing lf/lh-record at position.
+ * This allocates a new record and marks the old one as unused.
+ */
+static size_t
+insert_lf_record (hive_h *h, size_t old_offs, size_t posn,
+                  const char *name, hive_node_h node)
+{
+  assert (IS_VALID_BLOCK (h, old_offs));
+
+  /* Work around C stupidity.
+   * http://www.redhat.com/archives/libguestfs/2010-February/msg00056.html
+   */
+  int test = BLOCK_ID_EQ (h, old_offs, "lf") || BLOCK_ID_EQ (h, old_offs, "lh");
+  assert (test);
+
+  struct ntreg_lf_record *old_lf =
+    (struct ntreg_lf_record *) (h->addr + old_offs);
+  size_t nr_keys = le16toh (old_lf->nr_keys);
+
+  nr_keys++; /* in new record ... */
+
+  size_t seg_len = sizeof (struct ntreg_lf_record) + (nr_keys-1) * 8;
+  size_t new_offs = allocate_block (h, seg_len, old_lf->id);
+  if (new_offs == 0)
+    return 0;
+
+  struct ntreg_lf_record *new_lf =
+    (struct ntreg_lf_record *) (h->addr + new_offs);
+  new_lf->nr_keys = htole16 (nr_keys);
+
+  /* Copy the keys until we reach posn, insert the new key there, then
+   * copy the remaining keys.
+   */
+  size_t i;
+  for (i = 0; i < posn; ++i)
+    new_lf->keys[i] = old_lf->keys[i];
+
+  new_lf->keys[i].offset = htole32 (node - 0x1000);
+  calc_hash (new_lf->id, name, new_lf->keys[i].hash);
+
+  for (i = posn+1; i < nr_keys; ++i)
+    new_lf->keys[i] = old_lf->keys[i-1];
+
+  /* Old block is unused, return new block. */
+  mark_block_unused (h, old_offs);
+  return new_offs;
+}
+
+/* Compare name with name in nk-record. */
+static int
+compare_name_with_nk_name (hive_h *h, const char *name, hive_node_h nk_offs)
+{
+  assert (IS_VALID_BLOCK (h, nk_offs));
+  assert (BLOCK_ID_EQ (h, nk_offs, "nk"));
+
+  /* Name in nk is not necessarily nul-terminated. */
+  char *nname = hivex_node_name (h, nk_offs);
+
+  /* Unfortunately we don't have a way to return errors here. */
+  if (!nname) {
+    perror ("compare_name_with_nk_name");
+    return 0;
+  }
+
+  int r = strcasecmp (name, nname);
+  free (nname);
+
+  return r;
+}
+
+hive_node_h
+hivex_node_add_child (hive_h *h, hive_node_h parent, const char *name)
+{
+  if (!h->writable) {
+    errno = EROFS;
+    return 0;
+  }
+
+  if (!IS_VALID_BLOCK (h, parent) || !BLOCK_ID_EQ (h, parent, "nk")) {
+    errno = EINVAL;
+    return 0;
+  }
+
+  if (name == NULL || strlen (name) == 0) {
+    errno = EINVAL;
+    return 0;
+  }
+
+  if (hivex_node_get_child (h, parent, name) != 0) {
+    errno = EEXIST;
+    return 0;
+  }
+
+  /* Create the new nk-record. */
+  static const char nk_id[2] = { 'n', 'k' };
+  size_t seg_len = sizeof (struct ntreg_nk_record) + strlen (name);
+  hive_node_h node = allocate_block (h, seg_len, nk_id);
+  if (node == 0)
+    return 0;
+
+  if (h->msglvl >= 2)
+    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. */
+  nk->parent = htole32 (parent - 0x1000);
+  nk->subkey_lf = htole32 (0xffffffff);
+  nk->subkey_lf_volatile = htole32 (0xffffffff);
+  nk->vallist = htole32 (0xffffffff);
+  nk->classname = htole32 (0xffffffff);
+  nk->name_len = htole16 (strlen (name));
+  strcpy (nk->name, name);
+
+  /* Inherit parent sk. */
+  struct ntreg_nk_record *parent_nk =
+    (struct ntreg_nk_record *) (h->addr + parent);
+  size_t parent_sk_offset = le32toh (parent_nk->sk);
+  parent_sk_offset += 0x1000;
+  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",
+               parent_sk_offset);
+    errno = EFAULT;
+    return 0;
+  }
+  struct ntreg_sk_record *sk =
+    (struct ntreg_sk_record *) (h->addr + parent_sk_offset);
+  sk->refcount = htole32 (le32toh (sk->refcount) + 1);
+  nk->sk = htole32 (parent_sk_offset - 0x1000);
+
+  /* Inherit parent timestamp. */
+  memcpy (nk->timestamp, parent_nk->timestamp, sizeof (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
+   * subkey in a non-sorted position (eg. just add it at the end) then
+   * Windows won't see the subkey _and_ Windows will corrupt the hive
+   * itself when it modifies or saves it.
+   *
+   * So use get_children() to get a list of intermediate
+   * lf/lh-records.  get_children() returns these in reading order
+   * (which is sorted), so we look for the lf/lh-records in sequence
+   * until we find the key name just after the one we are inserting,
+   * and we insert the subkey just before it.
+   *
+   * The only other case is the no-subkeys case, where we have to
+   * create a brand new lh-record.
+   */
+  hive_node_h *unused;
+  size_t *blocks;
+
+  if (get_children (h, parent, &unused, &blocks, 0) == -1)
+    return 0;
+  free (unused);
+
+  size_t i, j;
+  size_t nr_subkeys_in_parent_nk = le32toh (parent_nk->nr_subkeys);
+  if (nr_subkeys_in_parent_nk == 0) { /* No subkeys case. */
+    /* Free up any existing intermediate blocks. */
+    for (i = 0; blocks[i] != 0; ++i)
+      mark_block_unused (h, blocks[i]);
+    size_t lh_offs = new_lh_record (h, name, node);
+    if (lh_offs == 0) {
+      free (blocks);
+      return 0;
+    }
+
+    if (h->msglvl >= 2)
+      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);
+  }
+  else {                        /* Insert subkeys case. */
+    size_t old_offs = 0, new_offs = 0;
+    struct ntreg_lf_record *old_lf = NULL;
+
+    /* Find lf/lh key name just after the one we are inserting. */
+    for (i = 0; blocks[i] != 0; ++i) {
+      if (BLOCK_ID_EQ (h, blocks[i], "lf") ||
+          BLOCK_ID_EQ (h, blocks[i], "lh")) {
+        old_offs = blocks[i];
+        old_lf = (struct ntreg_lf_record *) (h->addr + old_offs);
+        for (j = 0; j < le16toh (old_lf->nr_keys); ++j) {
+          hive_node_h nk_offs = le32toh (old_lf->keys[j].offset);
+          nk_offs += 0x1000;
+          if (compare_name_with_nk_name (h, name, nk_offs) < 0)
+            goto insert_it;
+        }
+      }
+    }
+
+    /* Insert it at the end.
+     * old_offs points to the last lf record, set j.
+     */
+    assert (old_offs != 0);   /* should never happen if nr_subkeys > 0 */
+    j = le16toh (old_lf->nr_keys);
+
+    /* 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);
+
+    new_offs = insert_lf_record (h, old_offs, j, name, node);
+    if (new_offs == 0) {
+      free (blocks);
+      return 0;
+    }
+
+    if (h->msglvl >= 2)
+      fprintf (stderr, "hivex_node_add_child: new lh-record at 0x%zx\n",
+               new_offs);
+
+    /* If the lf/lh-record was directly referenced by the parent nk,
+     * then update the parent nk.
+     */
+    if (le32toh (parent_nk->subkey_lf) + 0x1000 == old_offs)
+      parent_nk->subkey_lf = htole32 (new_offs - 0x1000);
+    /* Else we have to look for the intermediate ri-record and update
+     * that in-place.
+     */
+    else {
+      for (i = 0; blocks[i] != 0; ++i) {
+        if (BLOCK_ID_EQ (h, blocks[i], "ri")) {
+          struct ntreg_ri_record *ri =
+            (struct ntreg_ri_record *) (h->addr + blocks[i]);
+          for (j = 0; j < le16toh (ri->nr_offsets); ++j)
+            if (le32toh (ri->offset[j] + 0x1000) == old_offs) {
+              ri->offset[j] = htole32 (new_offs - 0x1000);
+              goto found_it;
+            }
+        }
+      }
+
+      /* 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");
+      errno = ENOTSUP;
+      free (blocks);
+      return 0;
+
+    found_it:
+      ;
+    }
+  }
+
+  free (blocks);
+
+  /* Update nr_subkeys in parent nk. */
+  nr_subkeys_in_parent_nk++;
+  parent_nk->nr_subkeys = htole32 (nr_subkeys_in_parent_nk);
+
+  /* Update max_subkey_name_len in parent nk. */
+  uint16_t max = le16toh (parent_nk->max_subkey_name_len);
+  if (max < strlen (name) * 2)  /* *2 because "recoded" in UTF16-LE. */
+    parent_nk->max_subkey_name_len = htole16 (strlen (name) * 2);
+
+  return node;
+}
+
+/* Decrement the refcount of an sk-record, and if it reaches zero,
+ * unlink it from the chain and delete it.
+ */
+static int
+delete_sk (hive_h *h, size_t sk_offset)
+{
+  if (!IS_VALID_BLOCK (h, sk_offset) || !BLOCK_ID_EQ (h, sk_offset, "sk")) {
+    if (h->msglvl >= 2)
+      fprintf (stderr, "delete_sk: not an sk record: 0x%zx\n", sk_offset);
+    errno = EFAULT;
+    return -1;
+  }
+
+  struct ntreg_sk_record *sk = (struct ntreg_sk_record *) (h->addr + sk_offset);
+
+  if (sk->refcount == 0) {
+    if (h->msglvl >= 2)
+      fprintf (stderr, "delete_sk: sk record already has refcount 0: 0x%zx\n",
+               sk_offset);
+    errno = EINVAL;
+    return -1;
+  }
+
+  sk->refcount--;
+
+  if (sk->refcount == 0) {
+    size_t sk_prev_offset = sk->sk_prev;
+    sk_prev_offset += 0x1000;
+
+    size_t sk_next_offset = sk->sk_next;
+    sk_next_offset += 0x1000;
+
+    /* Update sk_prev/sk_next SKs, unless they both point back to this
+     * cell in which case we are deleting the last SK.
+     */
+    if (sk_prev_offset != sk_offset && sk_next_offset != sk_offset) {
+      struct ntreg_sk_record *sk_prev =
+        (struct ntreg_sk_record *) (h->addr + sk_prev_offset);
+      struct ntreg_sk_record *sk_next =
+        (struct ntreg_sk_record *) (h->addr + sk_next_offset);
+
+      sk_prev->sk_next = htole32 (sk_next_offset - 0x1000);
+      sk_next->sk_prev = htole32 (sk_prev_offset - 0x1000);
+    }
+
+    /* Refcount is zero so really delete this block. */
+    mark_block_unused (h, sk_offset);
+  }
+
+  return 0;
+}
+
+/* Callback from hivex_node_delete_child which is called to delete a
+ * node AFTER its subnodes have been visited.  The subnodes have been
+ * deleted but we still have to delete any lf/lh/li/ri records and the
+ * value list block and values, followed by deleting the node itself.
+ */
+static int
+delete_node (hive_h *h, void *opaque, hive_node_h node, const char *name)
+{
+  /* Get the intermediate blocks.  The subkeys have already been
+   * deleted by this point, so tell get_children() not to check for
+   * validity of the nk-records.
+   */
+  hive_node_h *unused;
+  size_t *blocks;
+  if (get_children (h, node, &unused, &blocks, GET_CHILDREN_NO_CHECK_NK) == -1)
+    return -1;
+  free (unused);
+
+  /* We don't care what's in these intermediate blocks, so we can just
+   * delete them unconditionally.
+   */
+  size_t i;
+  for (i = 0; blocks[i] != 0; ++i)
+    mark_block_unused (h, blocks[i]);
+
+  free (blocks);
+
+  /* Delete the values in the node. */
+  if (delete_values (h, node) == -1)
+    return -1;
+
+  struct ntreg_nk_record *nk = (struct ntreg_nk_record *) (h->addr + node);
+
+  /* If the NK references an SK, delete it. */
+  size_t sk_offs = le32toh (nk->sk);
+  if (sk_offs != 0xffffffff) {
+    sk_offs += 0x1000;
+    if (delete_sk (h, sk_offs) == -1)
+      return -1;
+    nk->sk = htole32 (0xffffffff);
+  }
+
+  /* If the NK references a classname, delete it. */
+  size_t cl_offs = le32toh (nk->classname);
+  if (cl_offs != 0xffffffff) {
+    cl_offs += 0x1000;
+    mark_block_unused (h, cl_offs);
+    nk->classname = htole32 (0xffffffff);
+  }
+
+  /* Delete the node itself. */
+  mark_block_unused (h, node);
+
+  return 0;
+}
+
+int
+hivex_node_delete_child (hive_h *h, hive_node_h node)
+{
+  if (!h->writable) {
+    errno = EROFS;
+    return -1;
+  }
+
+  if (!IS_VALID_BLOCK (h, node) || !BLOCK_ID_EQ (h, node, "nk")) {
+    errno = EINVAL;
+    return -1;
+  }
+
+  if (node == hivex_root (h)) {
+    if (h->msglvl >= 2)
+      fprintf (stderr, "hivex_node_delete_child: cannot delete root node\n");
+    errno = EINVAL;
+    return -1;
+  }
+
+  hive_node_h parent = hivex_node_parent (h, node);
+  if (parent == 0)
+    return -1;
+
+  /* Delete node and all its children and values recursively. */
+  static const struct hivex_visitor visitor = { .node_end = delete_node };
+  if (hivex_visit_node (h, node, &visitor, sizeof visitor, NULL, 0) == -1)
+    return -1;
+
+  /* Delete the link from parent to child.  We need to find the lf/lh
+   * record which contains the offset and remove the offset from that
+   * record, then decrement the element count in that record, and
+   * decrement the overall number of subkeys stored in the parent
+   * node.
+   */
+  hive_node_h *unused;
+  size_t *blocks;
+  if (get_children (h, parent, &unused, &blocks, GET_CHILDREN_NO_CHECK_NK)== -1)
+    return -1;
+  free (unused);
+
+  size_t i, j;
+  for (i = 0; blocks[i] != 0; ++i) {
+    struct ntreg_hbin_block *block =
+      (struct ntreg_hbin_block *) (h->addr + blocks[i]);
+
+    if (block->id[0] == 'l' && (block->id[1] == 'f' || block->id[1] == 'h')) {
+      struct ntreg_lf_record *lf = (struct ntreg_lf_record *) block;
+
+      size_t nr_subkeys_in_lf = le16toh (lf->nr_keys);
+
+      for (j = 0; j < nr_subkeys_in_lf; ++j)
+        if (le32toh (lf->keys[j].offset) + 0x1000 == node) {
+          for (; j < nr_subkeys_in_lf - 1; ++j)
+            memcpy (&lf->keys[j], &lf->keys[j+1], sizeof (lf->keys[j]));
+          lf->nr_keys = htole16 (nr_subkeys_in_lf - 1);
+          goto found;
+        }
+    }
+  }
+  if (h->msglvl >= 2)
+    fprintf (stderr, "hivex_node_delete_child: could not find parent to child link\n");
+  errno = ENOTSUP;
+  return -1;
+
+ found:;
+  struct ntreg_nk_record *nk = (struct ntreg_nk_record *) (h->addr + parent);
+  size_t nr_subkeys_in_nk = le32toh (nk->nr_subkeys);
+  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);
+
+  return 0;
+}
+
+int
+hivex_node_set_values (hive_h *h, hive_node_h node,
+                       size_t nr_values, const hive_set_value *values,
+                       int flags)
+{
+  if (!h->writable) {
+    errno = EROFS;
+    return -1;
+  }
+
+  if (!IS_VALID_BLOCK (h, node) || !BLOCK_ID_EQ (h, node, "nk")) {
+    errno = EINVAL;
+    return -1;
+  }
+
+  /* Delete all existing values. */
+  if (delete_values (h, node) == -1)
+    return -1;
+
+  if (nr_values == 0)
+    return 0;
+
+  /* Allocate value list node.  Value lists have no id field. */
+  static const char nul_id[2] = { 0, 0 };
+  size_t seg_len =
+    sizeof (struct ntreg_value_list) + (nr_values - 1) * sizeof (uint32_t);
+  size_t vallist_offs = allocate_block (h, seg_len, nul_id);
+  if (vallist_offs == 0)
+    return -1;
+
+  struct ntreg_nk_record *nk = (struct ntreg_nk_record *) (h->addr + node);
+  nk->nr_values = htole32 (nr_values);
+  nk->vallist = htole32 (vallist_offs - 0x1000);
+
+  struct ntreg_value_list *vallist =
+    (struct ntreg_value_list *) (h->addr + vallist_offs);
+
+  size_t i;
+  for (i = 0; i < nr_values; ++i) {
+    /* Allocate vk record to store this (key, value) pair. */
+    static const char vk_id[2] = { 'v', 'k' };
+    seg_len = sizeof (struct ntreg_vk_record) + strlen (values[i].key);
+    size_t vk_offs = allocate_block (h, seg_len, vk_id);
+    if (vk_offs == 0)
+      return -1;
+
+    vallist->offset[i] = htole32 (vk_offs - 0x1000);
+
+    struct ntreg_vk_record *vk = (struct ntreg_vk_record *) (h->addr + vk_offs);
+    size_t name_len = strlen (values[i].key);
+    vk->name_len = htole16 (name_len);
+    strcpy (vk->name, values[i].key);
+    vk->data_type = htole32 (values[i].t);
+    uint32_t len = values[i].len;
+    if (len <= 4)               /* store it inline => set MSB flag */
+      len |= 0x80000000;
+    vk->data_len = htole32 (len);
+    vk->flags = name_len == 0 ? 0 : 1;
+
+    if (values[i].len <= 4)     /* store it inline */
+      memcpy (&vk->data_offset, values[i].value, values[i].len);
+    else {
+      size_t offs = allocate_block (h, values[i].len + 4, nul_id);
+      if (offs == 0)
+        return -1;
+      memcpy (h->addr + offs + 4, values[i].value, values[i].len);
+      vk->data_offset = htole32 (offs - 0x1000);
+    }
+
+    if (name_len * 2 > le32toh (nk->max_vk_name_len))
+      /* * 2 for UTF16-LE "reencoding" */
+      nk->max_vk_name_len = htole32 (name_len * 2);
+    if (values[i].len > le32toh (nk->max_vk_data_len))
+      nk->max_vk_data_len = htole32 (values[i].len);
+  }
+
+  return 0;
+}