hive_value_h hivex_value_data_cell_offset (hive_h *h, hive_value_h value, size_t *len) { if (!IS_VALID_BLOCK (h, value) || !block_id_eq (h, value, "vk")) { SET_ERRNO (EINVAL, "invalid block or not an 'vk' block"); return 0; } DEBUG (2, "value=0x%zx", value); struct ntreg_vk_record *vk = (struct ntreg_vk_record *) ((char *) h->addr + value); size_t data_len; int is_inline; data_len = le32toh (vk->data_len); is_inline = !!(data_len & 0x80000000); data_len &= 0x7fffffff; DEBUG (2, "is_inline=%d", is_inline); DEBUG (2, "data_len=%zx", data_len); if (is_inline && data_len > 4) { SET_ERRNO (ENOTSUP, "inline data with declared length (%zx) > 4", data_len); return 0; } if (is_inline) { /* There is no other location for the value data. */ if (len) *len = 0; errno = 0; return 0; } else { if (len) *len = data_len + 4; /* Include 4 header length bytes */ } DEBUG (2, "proceeding with indirect data"); size_t data_offset = le32toh (vk->data_offset); data_offset += 0x1000; /* Add 0x1000 because everything's off by 4KiB */ if (!IS_VALID_BLOCK (h, data_offset)) { SET_ERRNO (EFAULT, "data offset is not a valid block (0x%zx)", data_offset); return 0; } DEBUG (2, "data_offset=%zx", data_offset); return data_offset; }
char * hivex_value_key (hive_h *h, hive_value_h value) { if (!IS_VALID_BLOCK (h, value) || !block_id_eq (h, value, "vk")) { SET_ERRNO (EINVAL, "invalid block or not an 'vk' block"); return 0; } struct ntreg_vk_record *vk = (struct ntreg_vk_record *) ((char *) h->addr + value); size_t flags = le16toh (vk->flags); size_t len = le16toh (vk->name_len); size_t seg_len = block_len (h, value, NULL); if (sizeof (struct ntreg_vk_record) + len - 1 > seg_len) { SET_ERRNO (EFAULT, "key length is too long (%zu, %zu)", len, seg_len); return NULL; } if (flags & 0x01) { return _hivex_windows_latin1_to_utf8 (vk->name, len); } else { return _hivex_windows_utf16_to_utf8 (vk->name, len); } }
/* 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 *) ((char *) h->addr + old_offs); size_t nr_keys = le16toh (old_lf->nr_keys); if (nr_keys == UINT16_MAX) { SET_ERRNO (EOVERFLOW, "cannot extend record because it already contains the maximum number of subkeys (%zu)", nr_keys); return 0; } nr_keys++; /* in new record ... */ size_t seg_len = sizeof (struct ntreg_lf_record) + (nr_keys-1) * 8; /* Copy the old_lf->id in case it moves during allocate_block. */ char id[2]; memcpy (id, old_lf->id, sizeof id); size_t new_offs = allocate_block (h, seg_len, id); if (new_offs == 0) return 0; /* old_lf could have been invalidated by allocate_block. */ old_lf = (struct ntreg_lf_record *) ((char *) h->addr + old_offs); struct ntreg_lf_record *new_lf = (struct ntreg_lf_record *) ((char *) 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; }
/* Insert node into existing li-record at position. Pretty much the * same as insert_lf_record above, but the record layout is a bit * different. */ static size_t insert_li_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)); assert (block_id_eq (h, old_offs, "li")); struct ntreg_ri_record *old_li = (struct ntreg_ri_record *) ((char *) h->addr + old_offs); size_t nr_offsets = le16toh (old_li->nr_offsets); if (nr_offsets == UINT16_MAX) { SET_ERRNO (EOVERFLOW, "cannot extend record because it already contains the maximum number of subkeys (%zu)", nr_offsets); return 0; } nr_offsets++; /* in new record ... */ size_t seg_len = sizeof (struct ntreg_ri_record) + (nr_offsets-1) * 4; /* Copy the old_li->id in case it moves during allocate_block. */ char id[2]; memcpy (id, old_li->id, sizeof id); size_t new_offs = allocate_block (h, seg_len, id); if (new_offs == 0) return 0; /* old_li could have been invalidated by allocate_block. */ old_li = (struct ntreg_ri_record *) ((char *) h->addr + old_offs); struct ntreg_ri_record *new_li = (struct ntreg_ri_record *) ((char *) h->addr + new_offs); new_li->nr_offsets = htole16 (nr_offsets); /* Copy the offsets until we reach posn, insert the new offset * there, then copy the remaining offsets. */ size_t i; for (i = 0; i < posn; ++i) new_li->offset[i] = old_li->offset[i]; new_li->offset[i] = htole32 (node - 0x1000); for (i = posn+1; i < nr_offsets; ++i) new_li->offset[i] = old_li->offset[i-1]; /* Old block is unused, return new block. */ mark_block_unused (h, old_offs); return new_offs; }
size_t hivex_node_nr_values (hive_h *h, hive_node_h node) { if (!IS_VALID_BLOCK (h, node) || !block_id_eq (h, node, "nk")) { SET_ERRNO (EINVAL, "invalid block or not an 'nk' block"); return 0; } struct ntreg_nk_record *nk = (struct ntreg_nk_record *) ((char *) h->addr + node); size_t nr_values = le32toh (nk->nr_values); return nr_values; }
/* '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)); DEBUG (2, "marking 0x%zx unused", offset); struct ntreg_hbin_block *blockhdr = (struct ntreg_hbin_block *) ((char *) h->addr + offset); size_t seg_len = block_len (h, offset, NULL); blockhdr->seg_len = htole32 (seg_len); BITMAP_CLR (h->bitmap, offset); }
/* 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")) { SET_ERRNO (EFAULT, "not an sk record: 0x%zx", sk_offset); return -1; } struct ntreg_sk_record *sk = (struct ntreg_sk_record *) ((char *) h->addr + sk_offset); if (sk->refcount == 0) { SET_ERRNO (EINVAL, "sk record already has refcount 0: 0x%zx", sk_offset); 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 *) ((char *) h->addr + sk_prev_offset); struct ntreg_sk_record *sk_next = (struct ntreg_sk_record *) ((char *) 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; }
/* 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; }
int hivex_value_type (hive_h *h, hive_value_h value, hive_type *t, size_t *len) { if (!IS_VALID_BLOCK (h, value) || !block_id_eq (h, value, "vk")) { SET_ERRNO (EINVAL, "invalid block or not an 'vk' block"); return -1; } struct ntreg_vk_record *vk = (struct ntreg_vk_record *) ((char *) h->addr + value); if (t) *t = le32toh (vk->data_type); if (len) { *len = le32toh (vk->data_len); *len &= 0x7fffffff; /* top bit indicates if data is stored inline */ } return 0; }
size_t hivex_value_key_len (hive_h *h, hive_value_h value) { if (!IS_VALID_BLOCK (h, value) || !block_id_eq (h, value, "vk")) { SET_ERRNO (EINVAL, "invalid block or not an 'vk' block"); return 0; } struct ntreg_vk_record *vk = (struct ntreg_vk_record *) ((char *) h->addr + value); /* vk->name_len is unsigned, 16 bit, so this is safe ... However * we have to make sure the length doesn't exceed the block length. */ size_t len = le16toh (vk->name_len); size_t seg_len = block_len (h, value, NULL); if (sizeof (struct ntreg_vk_record) + len - 1 > seg_len) { SET_ERRNO (EFAULT, "key length is too long (%zu, %zu)", len, seg_len); return 0; } return _hivex_utf8_strlen (vk->name, len, ! (le16toh (vk->flags) & 0x01)); }
int _hivex_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")) { SET_ERRNO (EINVAL, "invalid block or not an 'nk' block"); return -1; } struct ntreg_nk_record *nk = (struct ntreg_nk_record *) ((char *) h->addr + node); size_t nr_values = le32toh (nk->nr_values); DEBUG (2, "nr_values = %zu", nr_values); offset_list values, blocks; _hivex_init_offset_list (h, &values); _hivex_init_offset_list (h, &blocks); /* Deal with the common "no values" case quickly. */ if (nr_values == 0) goto ok; /* Arbitrarily limit the number of values we will ever deal with. */ if (nr_values > HIVEX_MAX_VALUES) { SET_ERRNO (ERANGE, "nr_values > HIVEX_MAX_VALUES (%zu > %d)", nr_values, HIVEX_MAX_VALUES); goto error; } /* Preallocate space for the values. */ if (_hivex_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)) { SET_ERRNO (EFAULT, "value list is not a valid block (0x%zx)", vlist_offset); goto error; } if (_hivex_add_to_offset_list (&blocks, vlist_offset) == -1) goto error; struct ntreg_value_list *vlist = (struct ntreg_value_list *) ((char *) h->addr + vlist_offset); size_t len = block_len (h, vlist_offset, NULL); if (4 + nr_values * 4 > len) { SET_ERRNO (EFAULT, "value list is too long (%zu, %zu)", nr_values, len); goto error; } size_t i; for (i = 0; i < nr_values; ++i) { hive_node_h value = le32toh (vlist->offset[i]); value += 0x1000; if (!IS_VALID_BLOCK (h, value)) { SET_ERRNO (EFAULT, "value is not a valid block (0x%zx)", value); goto error; } if (_hivex_add_to_offset_list (&values, value) == -1) goto error; } ok: *values_ret = _hivex_return_offset_list (&values); *blocks_ret = _hivex_return_offset_list (&blocks); if (!*values_ret || !*blocks_ret) goto error; return 0; error: _hivex_free_offset_list (&values); _hivex_free_offset_list (&blocks); return -1; }
char * hivex_value_value (hive_h *h, hive_value_h value, hive_type *t_rtn, size_t *len_rtn) { if (!IS_VALID_BLOCK (h, value) || !block_id_eq (h, value, "vk")) { SET_ERRNO (EINVAL, "invalid block or not an 'vk' block"); return NULL; } struct ntreg_vk_record *vk = (struct ntreg_vk_record *) ((char *) h->addr + value); hive_type t; size_t len; int is_inline; t = le32toh (vk->data_type); len = le32toh (vk->data_len); is_inline = !!(len & 0x80000000); len &= 0x7fffffff; DEBUG (2, "value=0x%zx, t=%u, len=%zu, inline=%d", value, t, len, is_inline); if (t_rtn) *t_rtn = t; if (len_rtn) *len_rtn = len; if (is_inline && len > 4) { SET_ERRNO (ENOTSUP, "inline data with declared length (%zx) > 4", len); return NULL; } /* Arbitrarily limit the length that we will read. */ if (len > HIVEX_MAX_VALUE_LEN) { SET_ERRNO (ERANGE, "data length > HIVEX_MAX_VALUE_LEN (%zu > %d)", len, HIVEX_MAX_VALUE_LEN); return NULL; } char *ret = malloc (len); if (ret == NULL) return NULL; if (is_inline) { memcpy (ret, (char *) &vk->data_offset, len); return ret; } size_t data_offset = le32toh (vk->data_offset); data_offset += 0x1000; if (!IS_VALID_BLOCK (h, data_offset)) { SET_ERRNO (EFAULT, "data offset is not a valid block (0x%zx)", data_offset); free (ret); return NULL; } /* 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 - 4 /* subtract 4 for block header */) { char *data = (char *) h->addr + data_offset + 4; memcpy (ret, data, len); return ret; } else { if (!IS_VALID_BLOCK (h, data_offset) || !block_id_eq (h, data_offset, "db")) { SET_ERRNO (EINVAL, "declared data length is longer than the block and " "block is not a db block (data 0x%zx, data len %zu)", data_offset, len); free (ret); return NULL; } struct ntreg_db_record *db = (struct ntreg_db_record *) ((char *) h->addr + data_offset); size_t blocklist_offset = le32toh (db->blocklist_offset); blocklist_offset += 0x1000; size_t nr_blocks = le16toh (db->nr_blocks); if (!IS_VALID_BLOCK (h, blocklist_offset)) { SET_ERRNO (EINVAL, "blocklist is not a valid block " "(db block 0x%zx, blocklist 0x%zx)", data_offset, blocklist_offset); free (ret); return NULL; } struct ntreg_value_list *bl = (struct ntreg_value_list *) ((char *) h->addr + blocklist_offset); size_t i, off; for (i = off = 0; i < nr_blocks; ++i) { size_t subblock_offset = le32toh (bl->offset[i]); subblock_offset += 0x1000; if (!IS_VALID_BLOCK (h, subblock_offset)) { SET_ERRNO (EINVAL, "subblock is not valid " "(db block 0x%zx, block list 0x%zx, data subblock 0x%zx)", data_offset, blocklist_offset, subblock_offset); free (ret); return NULL; } int32_t seg_len = block_len(h, subblock_offset, NULL); struct ntreg_db_block *subblock = (struct ntreg_db_block *) ((char *) h->addr + subblock_offset); int32_t sz = seg_len - 8; /* don't copy the last 4 bytes */ if (off + sz > len) { sz = len - off; } memcpy (ret + off, subblock->data, sz); off += sz; } if (off != *len_rtn) { DEBUG (2, "warning: declared data length " "and amount of data found in sub-blocks differ " "(db block 0x%zx, data len %zu, found data %zu)", data_offset, *len_rtn, off); *len_rtn = off; } return ret; } }
int hivex_node_set_values (hive_h *h, hive_node_h node, size_t nr_values, const hive_set_value *values, int flags) { CHECK_WRITABLE (-1); if (!IS_VALID_BLOCK (h, node) || !block_id_eq (h, node, "nk")) { SET_ERRNO (EINVAL, "invalid block or not an 'nk' block"); 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 *) ((char *) h->addr + node); nk->nr_values = htole32 (nr_values); nk->vallist = htole32 (vallist_offs - 0x1000); 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' }; size_t recoded_name_len; int use_utf16; char* recoded_name = _hivex_encode_string (h, values[i].key, &recoded_name_len, &use_utf16); seg_len = sizeof (struct ntreg_vk_record) + recoded_name_len; size_t vk_offs = allocate_block (h, seg_len, vk_id); if (vk_offs == 0) return -1; /* Recalculate pointers that could have been invalidated by * previous call to allocate_block. */ nk = (struct ntreg_nk_record *) ((char *) h->addr + node); struct ntreg_value_list *vallist = (struct ntreg_value_list *) ((char *) h->addr + vallist_offs); vallist->offset[i] = htole32 (vk_offs - 0x1000); struct ntreg_vk_record *vk = (struct ntreg_vk_record *) ((char *) h->addr + vk_offs); vk->name_len = htole16 (recoded_name_len); memcpy (vk->name, recoded_name, recoded_name_len); free (recoded_name); 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); if (recoded_name_len == 0) vk->flags = 0; else vk->flags = htole16 (!use_utf16); 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; /* Recalculate pointers that could have been invalidated by * previous call to allocate_block. */ nk = (struct ntreg_nk_record *) ((char *) h->addr + node); /* vallist could be invalid here */ vk = (struct ntreg_vk_record *) ((char *) h->addr + vk_offs); memcpy ((char *) h->addr + offs + 4, values[i].value, values[i].len); vk->data_offset = htole32 (offs - 0x1000); } size_t utf16_len = use_utf16 ? recoded_name_len : recoded_name_len * 2; if (utf16_len > le32toh (nk->max_vk_name_len)) nk->max_vk_name_len = htole32 (utf16_len); if (values[i].len > le32toh (nk->max_vk_data_len)) nk->max_vk_data_len = htole32 (values[i].len); } return 0; }
int hivex_node_delete_child (hive_h *h, hive_node_h node) { CHECK_WRITABLE (-1); if (!IS_VALID_BLOCK (h, node) || !block_id_eq (h, node, "nk")) { SET_ERRNO (EINVAL, "invalid block or not an 'nk' block"); return -1; } if (node == hivex_root (h)) { SET_ERRNO (EINVAL, "cannot delete root node"); 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 (_hivex_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 *) ((char *) 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; } } } free (blocks); SET_ERRNO (ENOTSUP, "could not find parent to child link"); return -1; found:; free (blocks); struct ntreg_nk_record *nk = (struct ntreg_nk_record *) ((char *) h->addr + parent); size_t nr_subkeys_in_nk = le32toh (nk->nr_subkeys); nk->nr_subkeys = htole32 (nr_subkeys_in_nk - 1); DEBUG (2, "updating nr_subkeys in parent 0x%zx to %zu", parent, nr_subkeys_in_nk); return 0; }
hive_node_h hivex_node_add_child (hive_h *h, hive_node_h parent, const char *name) { CHECK_WRITABLE (0); if (!IS_VALID_BLOCK (h, parent) || !block_id_eq (h, parent, "nk")) { SET_ERRNO (EINVAL, "invalid block or not an 'nk' block"); return 0; } if (name == NULL || strlen (name) == 0) { SET_ERRNO (EINVAL, "name is NULL or zero length"); return 0; } if (hivex_node_get_child (h, parent, name) != 0) { SET_ERRNO (EEXIST, "a child with that name exists already"); return 0; } size_t recoded_name_len; int use_utf16 = 0; char *recoded_name = _hivex_encode_string (h, name, &recoded_name_len, &use_utf16); if (recoded_name == NULL) { SET_ERRNO (EINVAL, "malformed name"); return 0; } /* Create the new nk-record. */ static const char nk_id[2] = { 'n', 'k' }; size_t seg_len = sizeof (struct ntreg_nk_record) + recoded_name_len; hive_node_h nkoffset = allocate_block (h, seg_len, nk_id); if (nkoffset == 0) { free (recoded_name); return 0; } DEBUG (2, "allocated new nk-record for child at 0x%zx", nkoffset); struct ntreg_nk_record *nk = (struct ntreg_nk_record *) ((char *) h->addr + nkoffset); if (use_utf16) nk->flags = htole16 (0x0000); else nk->flags = htole16 (0x0020); 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 (recoded_name_len); memcpy (nk->name, recoded_name, recoded_name_len); free (recoded_name); /* Inherit parent sk. */ struct ntreg_nk_record *parent_nk = (struct ntreg_nk_record *) ((char *) 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")) { SET_ERRNO (EFAULT, "parent sk is not a valid block (%zu)", parent_sk_offset); return 0; } struct ntreg_sk_record *sk = (struct ntreg_sk_record *) ((char *) h->addr + parent_sk_offset); sk->refcount = htole32 (le32toh (sk->refcount) + 1); nk->sk = htole32 (parent_sk_offset - 0x1000); /* Inherit parent timestamp. */ nk->timestamp = parent_nk->timestamp; /* What I found out the hard way (not documented anywhere): the * subkeys in lh-records must be kept sorted. If you just add a * 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 records. * get_children() returns these in reading order (which is sorted), * so we look for the lf/lh/li-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 (_hivex_get_children (h, parent, &unused, &blocks, 0) == -1) return 0; free (unused); size_t i; size_t nr_subkeys_in_parent_nk = le32toh (parent_nk->nr_subkeys); if (nr_subkeys_in_parent_nk == UINT32_MAX) { free (blocks); SET_ERRNO (EOVERFLOW, "too many subkeys (%zu)", nr_subkeys_in_parent_nk); return 0; } 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, nkoffset); if (lh_offs == 0) { free (blocks); return 0; } /* Recalculate pointers that could have been invalidated by * previous call to allocate_block (via new_lh_record). */ /* nk could be invalid here */ parent_nk = (struct ntreg_nk_record *) ((char *) h->addr + parent); DEBUG (2, "no keys, allocated new lh-record at 0x%zx", lh_offs); parent_nk->subkey_lf = htole32 (lh_offs - 0x1000); } else { /* Insert subkeys case. */ if (insert_subkey (h, name, parent, nkoffset, blocks) == -1) { free (blocks); return 0; } /* Recalculate pointers that could have been invalidated by * previous call to allocate_block (via new_lh_record). */ /* nk could be invalid here */ parent_nk = (struct ntreg_nk_record *) ((char *) h->addr + parent); } 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. */ size_t utf16_len = use_utf16 ? recoded_name_len : recoded_name_len * 2; if (le16toh (parent_nk->max_subkey_name_len) < utf16_len) parent_nk->max_subkey_name_len = htole16 (utf16_len); return nkoffset; }