/* 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 <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>
+#include <assert.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);
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;
#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). */
+ size_t endpages; /* Offset of end of pages. */
- /* 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. */
+ /* For writing. */
+ size_t endblocks; /* Offset to next block allocation (0
+ if not allocated anything yet). */
};
/* 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__));
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 */
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__));
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 */
- 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__));
+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)
{
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;
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->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;
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' ||
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
*/
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;
}
/* 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)
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'))
}
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;
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;
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;
}
+/*----------------------------------------------------------------------
+ * Reading.
+ */
+
hive_node_h
hivex_root (hive_h *h)
{
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;
}
#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.
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 (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);
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);
- 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') {
/* 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)
- 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")) {
+ 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);
}
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];
+ hive_node_h offset = le32toh (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")) {
+ 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 =
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);
- 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
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;
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);
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) {
+ 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;
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
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;
}
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;
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)
- 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, 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;
}
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)
- 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;
}
- /* 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 (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);
- errno = EFAULT;
- free (ret);
- return NULL;
+ fprintf (stderr, "hivex_value_value: warning: declared data length is longer than the block it is in (data 0x%zx, data len %zu, block len %zu)\n",
+ data_offset, len, blen);
+ len = blen - 4;
}
char *data = h->addr + data_offset + 4;
return ret;
}
+/*----------------------------------------------------------------------
+ * Visiting.
+ */
+
int
hivex_visit (hive_h *h, const struct hivex_visitor *visitor, size_t len,
void *opaque, int flags)
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;
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_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_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_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_binary:
- str = hivex_value_value (h, values[i], &t, &len);
- if (str == NULL) {
- 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 (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;
- 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)
+ 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;
+
+ 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;
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;
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;
+}