X-Git-Url: http://git.annexia.org/?a=blobdiff_plain;f=hivex%2Fhivex.c;h=6b4b9d87129ed0d1ea5e3b1085607985775accf5;hb=310dcfac962bdb2ae287ab5725e4abd0ee2586a3;hp=f7dbd5fef315f2037bcd264a085c68ee0aa13129;hpb=a5ab005d8a0786540196efab43edf5c99a5287da;p=hivex.git diff --git a/hivex/hivex.c b/hivex/hivex.c index f7dbd5f..6b4b9d8 100644 --- a/hivex/hivex.c +++ b/hivex/hivex.c @@ -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: @@ -33,11 +33,12 @@ #include #include #include -#ifdef HAVE_ENDIAN_H -#include -#endif -#ifdef HAVE_BYTESWAP_H -#include + +#include "full-read.h" +#include "full-write.h" + +#ifndef O_CLOEXEC +#define O_CLOEXEC 0 #endif #define STREQ(a,b) (strcmp((a),(b)) == 0) @@ -50,41 +51,8 @@ //#define STRCASENEQLEN(a,b,n) (strncasecmp((a),(b),(n)) != 0) //#define STRPREFIX(a,b) (strncmp((a),(b),strlen((b))) == 0) -#if __BYTE_ORDER == __LITTLE_ENDIAN -#ifndef be32toh -#define be32toh(x) __bswap_32 (x) -#endif -#ifndef be64toh -#define be64toh(x) __bswap_64 (x) -#endif -#ifndef le16toh -#define le16toh(x) (x) -#endif -#ifndef le32toh -#define le32toh(x) (x) -#endif -#ifndef le64toh -#define le64toh(x) (x) -#endif -#else -#ifndef be32toh -#define be32toh(x) (x) -#endif -#ifndef be64toh -#define be64toh(x) (x) -#endif -#ifndef le16toh -#define le16toh(x) __bswap_16 (x) -#endif -#ifndef le32toh -#define le32toh(x) __bswap_32 (x) -#endif -#ifndef le64toh -#define le64toh(x) __bswap_64 (x) -#endif -#endif - #include "hivex.h" +#include "byte_conversions.h" static char *windows_utf16_to_utf8 (/* const */ char *input, size_t len); @@ -93,8 +61,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 +89,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. */ @@ -196,18 +169,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 +195,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__)); @@ -247,16 +227,28 @@ struct ntreg_vk_record { */ uint32_t data_len; uint32_t data_offset; /* pointer to the data (or data if inline) */ - hive_type data_type; /* type of the data */ + uint32_t data_type; /* type of the data */ uint16_t flags; /* bit 0 set => key name ASCII, bit 0 clr => key name UTF-16. - Only seen ASCII here in the wild. */ + 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 (hive_h *h) +header_checksum (const hive_h *h) { uint32_t *daddr = (uint32_t *) h->addr; size_t i; @@ -291,11 +283,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; @@ -305,12 +298,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' || @@ -441,7 +443,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; @@ -502,8 +504,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); @@ -519,7 +525,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); @@ -527,6 +536,10 @@ hivex_close (hive_h *h) return r; } +/*---------------------------------------------------------------------- + * Reading. + */ + hive_node_h hivex_root (hive_h *h) { @@ -620,34 +633,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) { 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. @@ -656,12 +735,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); @@ -682,7 +764,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); @@ -691,32 +773,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') { @@ -727,20 +803,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); @@ -753,30 +835,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 = @@ -784,27 +864,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 @@ -859,12 +964,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); @@ -874,22 +980,23 @@ hivex_node_values (hive_h *h, hive_node_h node) if (h->msglvl >= 2) fprintf (stderr, "hivex_node_values: nr_values = %zu\n", nr_values); + INIT_OFFSET_LIST (values); + INIT_OFFSET_LIST (blocks); + /* Deal with the common "no values" case quickly. */ - hive_node_h *ret; - if (nr_values == 0) { - ret = malloc (sizeof (hive_node_h)); - if (ret == NULL) - return NULL; - ret[0] = 0; - return ret; - } + if (nr_values == 0) + goto ok; /* Arbitrarily limit the number of values we will ever deal with. */ if (nr_values > 100000) { errno = ERANGE; - return NULL; + goto error; } + /* Preallocate space for the values. */ + if (grow_offset_list (&values, nr_values) == -1) + goto error; + /* Get the value list and check it looks reasonable. */ size_t vlist_offset = le32toh (nk->vallist); vlist_offset += 0x1000; @@ -898,9 +1005,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); @@ -910,14 +1020,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]; @@ -927,14 +1032,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 @@ -1075,7 +1202,7 @@ hivex_value_value (hive_h *h, hive_value_h value, return ret; } - size_t data_offset = vk->data_offset; + size_t data_offset = le32toh (vk->data_offset); data_offset += 0x1000; if (!IS_VALID_BLOCK (h, data_offset)) { if (h->msglvl >= 2) @@ -1086,15 +1213,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; @@ -1308,6 +1438,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) @@ -1551,3 +1685,589 @@ 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 > 1000000) { + 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 >= 6) { + blockhdr->id[0] = id[0]; + blockhdr->id[1] = id[1]; + } + + 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; + len = le32toh (vk->data_len); + if (len == 0x80000000) /* special case */ + len = 4; + len &= 0x7fffffff; + + if (len > 4) { /* 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; +} + +#if 0 +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 -1; + } + + if (name == NULL) { + errno = EINVAL; + return -1; + } + + + + + + +} +#endif + +/* 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); + vk->data_len = htole16 (values[i].len); + vk->flags = name_len == 0 ? 0 : 1; + + if (values[i].len <= 4) /* Store data 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)) + 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; +}