Richard W.M. Jones
2013-Jul-25 10:38 UTC
[Libguestfs] [PATCH hivex 00/19] Fix read/write handling of li-records.
This is, hopefully, a full fix for handling of li-records. See: https://bugzilla.redhat.com/show_bug.cgi?id=717583 https://bugzilla.redhat.com/show_bug.cgi?id=987463 Rich.
Richard W.M. Jones
2013-Jul-25 10:38 UTC
[Libguestfs] [PATCH hivex 01/19] build: Ignore .gdb_history file.
From: "Richard W.M. Jones" <rjones@redhat.com> --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index e98fda1..719cd7f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +.gdb_history *~ *.a ABOUT-NLS -- 1.8.3.1
Richard W.M. Jones
2013-Jul-25 10:38 UTC
[Libguestfs] [PATCH hivex 02/19] build: Add *~ to CLEANFILES.
From: "Richard W.M. Jones" <rjones@redhat.com> --- Makefile.am | 2 +- lib/Makefile.am | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile.am b/Makefile.am index 80ea8cb..ef5618d 100644 --- a/Makefile.am +++ b/Makefile.am @@ -66,4 +66,4 @@ WEBSITEDIR = $(HOME)/d/redhat/websites/libguestfs website: $(HTMLFILES) cp $(HTMLFILES) $(WEBSITEDIR) -CLEANFILES = $(HTMLFILES) pod2*.tmp +CLEANFILES = $(HTMLFILES) pod2*.tmp *~ diff --git a/lib/Makefile.am b/lib/Makefile.am index 8d7ff69..9a30b41 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -68,7 +68,7 @@ $(top_builddir)/html/hivex.3.html: hivex.pod --outfile $(top_builddir)/html/hivex.3.html \ $< -CLEANFILES = $(man_MANS) +CLEANFILES = $(man_MANS) *~ # Tests. -- 1.8.3.1
Richard W.M. Jones
2013-Jul-25 10:38 UTC
[Libguestfs] [PATCH hivex 03/19] lib: Move the 'offset_list' struct into a separate compilation unit.
From: "Richard W.M. Jones" <rjones@redhat.com> --- lib/Makefile.am | 14 +++---- lib/hivex-internal.h | 17 ++++++++ lib/hivex.c | 104 +++++++++++-------------------------------------- lib/offset-list.c | 108 +++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 155 insertions(+), 88 deletions(-) create mode 100644 lib/offset-list.c diff --git a/lib/Makefile.am b/lib/Makefile.am index 9a30b41..ea130d4 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -24,13 +24,13 @@ EXTRA_DIST = \ lib_LTLIBRARIES = libhivex.la libhivex_la_SOURCES = \ - hivex.c \ - hivex.h \ - hivex-internal.h \ - byte_conversions.h \ - gettext.h \ - mmap.h \ - hivex.syms + byte_conversions.h \ + gettext.h \ + hivex.c \ + hivex.h \ + hivex-internal.h \ + mmap.h \ + offset-list.c libhivex_la_LIBADD = ../gnulib/lib/libgnu.la $(LTLIBOBJS) libhivex_la_LDFLAGS = \ diff --git a/lib/hivex-internal.h b/lib/hivex-internal.h index b053850..14aa4d2 100644 --- a/lib/hivex-internal.h +++ b/lib/hivex-internal.h @@ -66,6 +66,23 @@ struct hive_h { #endif }; +/* offset-list.c */ +typedef struct offset_list offset_list; +struct offset_list { + hive_h *h; + size_t *offsets; + size_t len; + size_t alloc; + size_t limit; +}; +extern void _hivex_init_offset_list (hive_h *h, offset_list *list); +extern int _hivex_grow_offset_list (offset_list *list, size_t alloc); +extern int _hivex_add_to_offset_list (offset_list *list, size_t offset); +extern size_t _hivex_get_offset_list_length (offset_list *list); +extern void _hivex_set_offset_list_limit (offset_list *list, size_t limit); +extern void _hivex_free_offset_list (offset_list *list); +extern size_t * _hivex_return_offset_list (offset_list *list); + #define STREQ(a,b) (strcmp((a),(b)) == 0) #define STRCASEEQ(a,b) (strcasecmp((a),(b)) == 0) #define STRNEQ(a,b) (strcmp((a),(b)) != 0) diff --git a/lib/hivex.c b/lib/hivex.c index 6f6e207..8fa02a7 100644 --- a/lib/hivex.c +++ b/lib/hivex.c @@ -702,66 +702,6 @@ hivex_node_classname (hive_h *h, hive_node_h node) } #endif -/* 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 @@ -780,8 +720,9 @@ get_children (hive_h *h, hive_node_h node, size_t nr_subkeys_in_nk = le32toh (nk->nr_subkeys); - INIT_OFFSET_LIST (children); - INIT_OFFSET_LIST (blocks); + offset_list children, blocks; + _hivex_init_offset_list (h, &children); + _hivex_init_offset_list (h, &blocks); /* Deal with the common "no subkeys" case quickly. */ if (nr_subkeys_in_nk == 0) @@ -798,7 +739,7 @@ get_children (hive_h *h, hive_node_h node, } /* Preallocate space for the children. */ - if (grow_offset_list (&children, nr_subkeys_in_nk) == -1) + if (_hivex_grow_offset_list (&children, nr_subkeys_in_nk) == -1) goto error; /* The subkey_lf field can point either to an lf-record, which is @@ -816,7 +757,7 @@ get_children (hive_h *h, hive_node_h node, goto error; } - if (add_to_offset_list (&blocks, subkey_lf) == -1) + if (_hivex_add_to_offset_list (&blocks, subkey_lf) == -1) goto error; struct ntreg_hbin_block *block @@ -867,7 +808,7 @@ get_children (hive_h *h, hive_node_h node, goto error; } } - if (add_to_offset_list (&children, subkey) == -1) + if (_hivex_add_to_offset_list (&children, subkey) == -1) goto error; } goto ok; @@ -902,7 +843,7 @@ get_children (hive_h *h, hive_node_h node, goto error; } - if (add_to_offset_list (&blocks, offset) == -1) + if (_hivex_add_to_offset_list (&blocks, offset) == -1) goto error; struct ntreg_lf_record *lf @@ -954,7 +895,7 @@ get_children (hive_h *h, hive_node_h node, goto error; } } - if (add_to_offset_list (&children, subkey) == -1) + if (_hivex_add_to_offset_list (&children, subkey) == -1) goto error; } } else { /* "lf" or "lh" block */ @@ -975,7 +916,7 @@ get_children (hive_h *h, hive_node_h node, goto error; } } - if (add_to_offset_list (&children, subkey) == -1) + if (_hivex_add_to_offset_list (&children, subkey) == -1) goto error; } } @@ -989,13 +930,13 @@ get_children (hive_h *h, hive_node_h node, subkey_lf, block->id[0], block->id[1]); errno = ENOTSUP; error: - free_offset_list (&children); - free_offset_list (&blocks); + _hivex_free_offset_list (&children); + _hivex_free_offset_list (&blocks); return -1; ok: - *children_ret = return_offset_list (&children); - *blocks_ret = return_offset_list (&blocks); + *children_ret = _hivex_return_offset_list (&children); + *blocks_ret = _hivex_return_offset_list (&blocks); if (!*children_ret || !*blocks_ret) goto error; return 0; @@ -1085,8 +1026,9 @@ get_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); + 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) @@ -1103,7 +1045,7 @@ get_values (hive_h *h, hive_node_h node, } /* Preallocate space for the values. */ - if (grow_offset_list (&values, nr_values) == -1) + if (_hivex_grow_offset_list (&values, nr_values) == -1) goto error; /* Get the value list and check it looks reasonable. */ @@ -1118,7 +1060,7 @@ get_values (hive_h *h, hive_node_h node, goto error; } - if (add_to_offset_list (&blocks, vlist_offset) == -1) + if (_hivex_add_to_offset_list (&blocks, vlist_offset) == -1) goto error; struct ntreg_value_list *vlist @@ -1146,20 +1088,20 @@ get_values (hive_h *h, hive_node_h node, errno = EFAULT; goto error; } - if (add_to_offset_list (&values, value) == -1) + if (_hivex_add_to_offset_list (&values, value) == -1) goto error; } ok: - *values_ret = return_offset_list (&values); - *blocks_ret = return_offset_list (&blocks); + *values_ret = _hivex_return_offset_list (&values); + *blocks_ret = _hivex_return_offset_list (&blocks); if (!*values_ret || !*blocks_ret) goto error; return 0; error: - free_offset_list (&values); - free_offset_list (&blocks); + _hivex_free_offset_list (&values); + _hivex_free_offset_list (&blocks); return -1; } diff --git a/lib/offset-list.c b/lib/offset-list.c new file mode 100644 index 0000000..b4d9bb7 --- /dev/null +++ b/lib/offset-list.c @@ -0,0 +1,108 @@ +/* hivex - Windows Registry "hive" extraction library. + * Copyright (C) 2013 Red Hat Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * See file LICENSE for the full license. + */ + +/* Structure for returning 0-terminated lists of offsets (nodes, + * values, etc). + */ + +#include <config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <errno.h> +#include <assert.h> + +#include "hivex.h" +#include "hivex-internal.h" + +void +_hivex_init_offset_list (hive_h *h, offset_list *list) +{ + list->h = h; + list->len = 0; + list->alloc = 0; + list->offsets = NULL; + list->limit = SIZE_MAX; +} + +/* Preallocates the offset_list, but doesn't make the contents longer. */ +int +_hivex_grow_offset_list (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 (offset_list *list, size_t offset) +{ + if (list->len >= list->alloc) { + if (_hivex_grow_offset_list (list, list->alloc ? list->alloc * 2 : 4) == -1) + return -1; + } + list->offsets[list->len] = offset; + list->len++; + return 0; +} + +int +_hivex_add_to_offset_list (offset_list *list, size_t offset) +{ + assert (offset != 0); /* internal error if this happens */ + + if (list->len >= list->limit) { + if (list->h->msglvl >= 2) + fprintf (stderr, "hivex: returning ERANGE because list of offsets " + "has exceeded limit (limit = %zu)\n", + list->limit); + errno = ERANGE; + return -1; + } + + return add_to_offset_list (list, offset); +} + +size_t +_hivex_get_offset_list_length (offset_list *list) +{ + return list->len; +} + +void +_hivex_set_offset_list_limit (offset_list *list, size_t limit) +{ + list->limit = limit; +} + +void +_hivex_free_offset_list (offset_list *list) +{ + free (list->offsets); +} + +size_t * +_hivex_return_offset_list (offset_list *list) +{ + if (add_to_offset_list (list, 0) == -1) + return NULL; + return list->offsets; /* caller frees */ +} -- 1.8.3.1
Richard W.M. Jones
2013-Jul-25 10:38 UTC
[Libguestfs] [PATCH hivex 04/19] Revert "patch for read support of "li"-records from "ri" intermediate"
From: "Richard W.M. Jones" <rjones@redhat.com> This reverts commit c29d2625c2286b026c4e36a8b5469991c41b4299. --- lib/hivex.c | 79 ++++++++++++++++++++++++------------------------------------- 1 file changed, 31 insertions(+), 48 deletions(-) diff --git a/lib/hivex.c b/lib/hivex.c index 8fa02a7..2629d56 100644 --- a/lib/hivex.c +++ b/lib/hivex.c @@ -167,9 +167,9 @@ struct ntreg_lf_record { struct ntreg_ri_record { int32_t seg_len; - char id[2]; /* "ri"|"li" */ - uint16_t nr_offsets; /* number of pointers to lf/lh/li records */ - uint32_t offset[1]; /* list of pointers to lf/lh/li records */ + char id[2]; /* "ri" */ + uint16_t nr_offsets; /* number of pointers to lh records */ + uint32_t offset[1]; /* list of pointers to lh records */ } __attribute__((__packed__)); /* This has no ID header. */ @@ -832,12 +832,10 @@ get_children (hive_h *h, hive_node_h node, errno = EFAULT; goto error; } - if (!BLOCK_ID_EQ (h, offset, "lf") && - !BLOCK_ID_EQ (h, offset, "lh") && - !BLOCK_ID_EQ (h, offset, "li")) { + 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/li (0x%zx)\n", + fprintf (stderr, "get_children: returning ENOTSUP" + " because ri-record offset does not point to lf/lh (0x%zx)\n", offset); errno = ENOTSUP; goto error; @@ -876,49 +874,34 @@ get_children (hive_h *h, hive_node_h node, errno = EFAULT; goto error; } - if (BLOCK_ID_EQ (h, offset, "li")) { - /* "ri" and "li" are basically the same */ - struct ntreg_ri_record *li - (struct ntreg_ri_record *) ((char *) h->addr + offset); + 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; + goto error; + } - size_t j; - for (j = 0; j < le16toh (li->nr_offsets); ++j) { - hive_node_h subkey = le32toh (li->offset[j]); - subkey += 0x1000; - 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" - " li indirect subkey is not a valid block (0x%zx)\n", - subkey); - errno = EFAULT; - goto error; - } - } - if (_hivex_add_to_offset_list (&children, subkey) == -1) - goto error; - } - } else { /* "lf" or "lh" block */ - struct ntreg_lf_record *lf - (struct ntreg_lf_record *) ((char *) h->addr + offset); + struct ntreg_lf_record *lf + (struct ntreg_lf_record *) ((char *) h->addr + offset); - size_t j; - for (j = 0; j < le16toh (lf->nr_keys); ++j) { - hive_node_h subkey = le32toh (lf->keys[j].offset); - subkey += 0x1000; - 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" - " lf/lh indirect subkey is not a valid block (0x%zx)\n", - subkey); - errno = EFAULT; - goto error; - } - } - if (_hivex_add_to_offset_list (&children, subkey) == -1) + size_t j; + for (j = 0; j < le16toh (lf->nr_keys); ++j) { + hive_node_h subkey = le32toh (lf->keys[j].offset); + subkey += 0x1000; + 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; + } } + if (_hivex_add_to_offset_list (&children, subkey) == -1) + goto error; } } goto ok; @@ -926,7 +909,7 @@ get_children (hive_h *h, hive_node_h node, /* 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/li/ri (0x%zx, %d, %d)\n", + " because subkey block is not lf/lh/ri (0x%zx, %d, %d)\n", subkey_lf, block->id[0], block->id[1]); errno = ENOTSUP; error: -- 1.8.3.1
Richard W.M. Jones
2013-Jul-25 10:38 UTC
[Libguestfs] [PATCH hivex 05/19] lib: Add some debugging.
From: "Richard W.M. Jones" <rjones@redhat.com> --- lib/hivex.c | 45 +++++++++++++++++++++++++++------------------ 1 file changed, 27 insertions(+), 18 deletions(-) diff --git a/lib/hivex.c b/lib/hivex.c index 2629d56..b4706f4 100644 --- a/lib/hivex.c +++ b/lib/hivex.c @@ -750,7 +750,7 @@ get_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" + fprintf (stderr, "hivex: get_children: returning EFAULT" " because subkey_lf is not a valid block (0x%zx)\n", subkey_lf); errno = EFAULT; @@ -775,7 +775,7 @@ get_children (hive_h *h, hive_node_h node, size_t nr_subkeys_in_lf = le16toh (lf->nr_keys); if (h->msglvl >= 2) - fprintf (stderr, "hivex_node_children: nr_subkeys_in_nk = %zu," + fprintf (stderr, "hivex: get_children: nr_subkeys_in_nk = %zu," " nr_subkeys_in_lf = %zu\n", nr_subkeys_in_nk, nr_subkeys_in_lf); @@ -787,7 +787,7 @@ get_children (hive_h *h, hive_node_h node, size_t len = block_len (h, subkey_lf, NULL); if (8 + nr_subkeys_in_lf * 8 > len) { if (h->msglvl >= 2) - fprintf (stderr, "hivex_node_children: returning EFAULT" + fprintf (stderr, "hivex: get_children: returning EFAULT" " because too many subkeys (%zu, %zu)\n", nr_subkeys_in_lf, len); errno = EFAULT; @@ -801,7 +801,7 @@ get_children (hive_h *h, hive_node_h node, if (!(flags & GET_CHILDREN_NO_CHECK_NK)) { if (!IS_VALID_BLOCK (h, subkey)) { if (h->msglvl >= 2) - fprintf (stderr, "hivex_node_children: returning EFAULT" + fprintf (stderr, "hivex: get_children: returning EFAULT" " because subkey is not a valid block (0x%zx)\n", subkey); errno = EFAULT; @@ -826,17 +826,21 @@ get_children (hive_h *h, hive_node_h node, offset += 0x1000; if (!IS_VALID_BLOCK (h, offset)) { if (h->msglvl >= 2) - fprintf (stderr, "hivex_node_children: returning EFAULT" + fprintf (stderr, "hivex: get_children: returning EFAULT" " because ri-offset is not a valid block (0x%zx)\n", offset); errno = EFAULT; 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); + if (h->msglvl >= 2) { + struct ntreg_lf_record *block + (struct ntreg_lf_record *) ((char *) h->addr + offset); + fprintf (stderr, "hivex: get_children: returning ENOTSUP" + " because ri-record offset does not point to lf/lh" + " (0x%zx, %d, %d)\n", + offset, block->id[0], block->id[1]); + } errno = ENOTSUP; goto error; } @@ -851,7 +855,7 @@ get_children (hive_h *h, hive_node_h node, } if (h->msglvl >= 2) - fprintf (stderr, "hivex_node_children: nr_subkeys_in_nk = %zu," + fprintf (stderr, "hivex: get_children: nr_subkeys_in_nk = %zu," " counted = %zu\n", nr_subkeys_in_nk, count); @@ -868,17 +872,21 @@ get_children (hive_h *h, hive_node_h node, offset += 0x1000; if (!IS_VALID_BLOCK (h, offset)) { if (h->msglvl >= 2) - fprintf (stderr, "hivex_node_children: returning EFAULT" + fprintf (stderr, "hivex: get_children: returning EFAULT" " because ri-offset is not a valid block (0x%zx)\n", offset); errno = EFAULT; 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); + if (h->msglvl >= 2) { + struct ntreg_lf_record *block + (struct ntreg_lf_record *) ((char *) h->addr + offset); + fprintf (stderr, "hivex: get_children: returning ENOTSUP" + " because ri-record offset does not point to lf/lh" + " (0x%zx, %d, %d)\n", + offset, block->id[0], block->id[1]); + } errno = ENOTSUP; goto error; } @@ -893,8 +901,9 @@ get_children (hive_h *h, hive_node_h node, 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", + fprintf (stderr, "hivex: get_children: returning EFAULT" + " because indirect subkey is not a valid block" + " (0x%zx)\n", subkey); errno = EFAULT; goto error; @@ -908,7 +917,7 @@ get_children (hive_h *h, hive_node_h node, } /* else not supported, set errno and fall through */ if (h->msglvl >= 2) - fprintf (stderr, "get_children: returning ENOTSUP" + fprintf (stderr, "hivex: 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; -- 1.8.3.1
Richard W.M. Jones
2013-Jul-25 10:38 UTC
[Libguestfs] [PATCH hivex 06/19] lib: Add a DEBUG macro and use it instead of fprintf.
From: "Richard W.M. Jones" <rjones@redhat.com> Allows us to control the format of debug messages more carefully. --- lib/hivex-internal.h | 11 +- lib/hivex.c | 369 ++++++++++++++++++++------------------------------- lib/offset-list.c | 7 +- 3 files changed, 154 insertions(+), 233 deletions(-) diff --git a/lib/hivex-internal.h b/lib/hivex-internal.h index 14aa4d2..16fced1 100644 --- a/lib/hivex-internal.h +++ b/lib/hivex-internal.h @@ -19,13 +19,14 @@ #ifndef HIVEX_INTERNAL_H_ #define HIVEX_INTERNAL_H_ +#include <stdarg.h> #include <stddef.h> struct hive_h { char *filename; int fd; size_t size; - int msglvl; + int msglvl; /* 1 = verbose, 2 or 3 = debug */ int writable; /* Registry file, memory mapped if read-only, or malloc'd if writing. */ @@ -93,4 +94,12 @@ extern size_t * _hivex_return_offset_list (offset_list *list); #define STRCASENEQLEN(a,b,n) (strncasecmp((a),(b),(n)) != 0) #define STRPREFIX(a,b) (strncmp((a),(b),strlen((b))) == 0) +#define DEBUG(lvl,fs,...) \ + do { \ + if (h->msglvl >= (lvl)) { \ + fprintf (stderr, "%s: %s: " fs "\n", \ + "hivex", __func__, ## __VA_ARGS__); \ + } \ + } while (0) + #endif /* HIVEX_INTERNAL_H_ */ diff --git a/lib/hivex.c b/lib/hivex.c index b4706f4..ddab7ea 100644 --- a/lib/hivex.c +++ b/lib/hivex.c @@ -256,8 +256,7 @@ hivex_open (const char *filename, int flags) if (debug && STREQ (debug, "1")) h->msglvl = 2; - if (h->msglvl >= 2) - fprintf (stderr, "hivex_open: created handle %p\n", h); + DEBUG (2, "created handle %p", h); h->writable = !!(flags & HIVEX_OPEN_WRITE); h->filename = strdup (filename); @@ -286,8 +285,7 @@ hivex_open (const char *filename, int flags) if (h->addr == MAP_FAILED) goto error; - if (h->msglvl >= 2) - fprintf (stderr, "hivex_open: mapped file at %p\n", h->addr); + DEBUG (2, "mapped file at %p", h->addr); } else { h->addr = malloc (h->size); if (h->addr == NULL) @@ -368,8 +366,7 @@ hivex_open (const char *filename, int flags) h->rootoffs = le32toh (h->hdr->offset) + 0x1000; h->endpages = le32toh (h->hdr->blocks) + 0x1000; - if (h->msglvl >= 2) - fprintf (stderr, "hivex_open: root offset = 0x%zx\n", h->rootoffs); + DEBUG (2, "root offset = 0x%zx", h->rootoffs); /* We'll set this flag when we see a block with the root offset (ie. * the root block). @@ -409,8 +406,7 @@ hivex_open (const char *filename, int flags) } size_t page_size = le32toh (page->page_size); - if (h->msglvl >= 2) - fprintf (stderr, "hivex_open: page at 0x%zx, size %zu\n", off, page_size); + DEBUG (2, "page at 0x%zx, size %zu", off, page_size); pages++; if (page_size < smallest_page) smallest_page = page_size; if (page_size > largest_page) largest_page = page_size; @@ -447,10 +443,9 @@ hivex_open (const char *filename, int flags) goto error; } - if (h->msglvl >= 2) - 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)" : ""); + DEBUG (2, "%s block id %d,%d at 0x%zx size %zu%s", + 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; @@ -485,16 +480,14 @@ hivex_open (const char *filename, int flags) goto error; } - if (h->msglvl >= 1) - 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); + DEBUG (1, "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", + pages, smallest_page, largest_page, + blocks, smallest_block, blocks_bytes / blocks, largest_block, + used_blocks, used_size); return h; @@ -522,8 +515,7 @@ hivex_close (hive_h *h) { int r; - if (h->msglvl >= 1) - fprintf (stderr, "hivex_close\n"); + DEBUG (1, "hivex_close"); free (h->bitmap); if (!h->writable) @@ -571,9 +563,8 @@ hivex_node_struct_length (hive_h *h, hive_node_h node) int used; size_t seg_len = block_len (h, node, &used); if (ret > seg_len) { - if (h->msglvl >= 2) - fprintf (stderr, "hivex_node_struct_length: returning EFAULT because" - " node name is too long (%zu, %zu)\n", name_len, seg_len); + DEBUG (2, "returning EFAULT because" + " node name is too long (%zu, %zu)", name_len, seg_len); errno = EFAULT; return 0; } @@ -602,10 +593,9 @@ hivex_node_name (hive_h *h, hive_node_h node) size_t len = le16toh (nk->name_len); size_t seg_len = block_len (h, node, NULL); if (sizeof (struct ntreg_nk_record) + len - 1 > seg_len) { - if (h->msglvl >= 2) - fprintf (stderr, "hivex_node_name: returning EFAULT because node name" - " is too long (%zu, %zu)\n", - len, seg_len); + DEBUG (2, "returning EFAULT because node name" + " is too long (%zu, %zu)", + len, seg_len); errno = EFAULT; return NULL; } @@ -622,10 +612,7 @@ static int64_t timestamp_check (hive_h *h, hive_node_h node, int64_t timestamp) { if (timestamp < 0) { - if (h->msglvl >= 2) - fprintf (stderr, "hivex: timestamp_check: " - "negative time reported at %zu: %" PRIi64 "\n", - node, timestamp); + DEBUG (2, "negative time reported at %zu: %" PRIi64, node, timestamp); errno = EINVAL; return -1; } @@ -730,10 +717,9 @@ get_children (hive_h *h, hive_node_h node, /* Arbitrarily limit the number of subkeys we will ever deal with. */ if (nr_subkeys_in_nk > HIVEX_MAX_SUBKEYS) { - if (h->msglvl >= 2) - fprintf (stderr, "hivex: get_children: returning ERANGE because " - "nr_subkeys_in_nk > HIVEX_MAX_SUBKEYS (%zu > %d)\n", - nr_subkeys_in_nk, HIVEX_MAX_SUBKEYS); + DEBUG (2, "returning ERANGE because " + "nr_subkeys_in_nk > HIVEX_MAX_SUBKEYS (%zu > %d)", + nr_subkeys_in_nk, HIVEX_MAX_SUBKEYS); errno = ERANGE; goto error; } @@ -749,10 +735,9 @@ get_children (hive_h *h, hive_node_h node, size_t subkey_lf = le32toh (nk->subkey_lf); subkey_lf += 0x1000; if (!IS_VALID_BLOCK (h, subkey_lf)) { - if (h->msglvl >= 2) - fprintf (stderr, "hivex: get_children: returning EFAULT" - " because subkey_lf is not a valid block (0x%zx)\n", - subkey_lf); + DEBUG (2, "returning EFAULT" + " because subkey_lf is not a valid block (0x%zx)", + subkey_lf); errno = EFAULT; goto error; } @@ -774,10 +759,8 @@ get_children (hive_h *h, hive_node_h node, */ size_t nr_subkeys_in_lf = le16toh (lf->nr_keys); - if (h->msglvl >= 2) - fprintf (stderr, "hivex: get_children: nr_subkeys_in_nk = %zu," - " nr_subkeys_in_lf = %zu\n", - nr_subkeys_in_nk, nr_subkeys_in_lf); + DEBUG (2, "nr_subkeys_in_nk = %zu, nr_subkeys_in_lf = %zu", + nr_subkeys_in_nk, nr_subkeys_in_lf); if (nr_subkeys_in_nk != nr_subkeys_in_lf) { errno = ENOTSUP; @@ -786,10 +769,8 @@ get_children (hive_h *h, hive_node_h node, size_t len = block_len (h, subkey_lf, NULL); if (8 + nr_subkeys_in_lf * 8 > len) { - if (h->msglvl >= 2) - fprintf (stderr, "hivex: get_children: returning EFAULT" - " because too many subkeys (%zu, %zu)\n", - nr_subkeys_in_lf, len); + DEBUG (2, "returning EFAULT because too many subkeys (%zu, %zu)", + nr_subkeys_in_lf, len); errno = EFAULT; goto error; } @@ -800,10 +781,9 @@ get_children (hive_h *h, hive_node_h node, subkey += 0x1000; if (!(flags & GET_CHILDREN_NO_CHECK_NK)) { if (!IS_VALID_BLOCK (h, subkey)) { - if (h->msglvl >= 2) - fprintf (stderr, "hivex: get_children: returning EFAULT" - " because subkey is not a valid block (0x%zx)\n", - subkey); + DEBUG (2, "returning EFAULT" + " because subkey is not a valid block (0x%zx)", + subkey); errno = EFAULT; goto error; } @@ -825,22 +805,18 @@ get_children (hive_h *h, hive_node_h node, hive_node_h offset = le32toh (ri->offset[i]); offset += 0x1000; if (!IS_VALID_BLOCK (h, offset)) { - if (h->msglvl >= 2) - fprintf (stderr, "hivex: get_children: returning EFAULT" - " because ri-offset is not a valid block (0x%zx)\n", - offset); + DEBUG (2, "returning EFAULT because ri-offset is not a valid block (0x%zx)", + offset); errno = EFAULT; goto error; } if (!BLOCK_ID_EQ (h, offset, "lf") && !BLOCK_ID_EQ (h, offset, "lh")) { - if (h->msglvl >= 2) { - struct ntreg_lf_record *block - (struct ntreg_lf_record *) ((char *) h->addr + offset); - fprintf (stderr, "hivex: get_children: returning ENOTSUP" - " because ri-record offset does not point to lf/lh" - " (0x%zx, %d, %d)\n", - offset, block->id[0], block->id[1]); - } + struct ntreg_lf_record *block + (struct ntreg_lf_record *) ((char *) h->addr + offset); + DEBUG (2, "returning ENOTSUP" + " because ri-record offset does not point to lf/lh" + " (0x%zx, %d, %d)", + offset, block->id[0], block->id[1]); errno = ENOTSUP; goto error; } @@ -854,10 +830,8 @@ get_children (hive_h *h, hive_node_h node, count += le16toh (lf->nr_keys); } - if (h->msglvl >= 2) - fprintf (stderr, "hivex: get_children: nr_subkeys_in_nk = %zu," - " counted = %zu\n", - nr_subkeys_in_nk, count); + DEBUG (2, "nr_subkeys_in_nk = %zu, counted = %zu", + nr_subkeys_in_nk, count); if (nr_subkeys_in_nk != count) { errno = ENOTSUP; @@ -871,22 +845,19 @@ get_children (hive_h *h, hive_node_h node, hive_node_h offset = le32toh (ri->offset[i]); offset += 0x1000; if (!IS_VALID_BLOCK (h, offset)) { - if (h->msglvl >= 2) - fprintf (stderr, "hivex: get_children: returning EFAULT" - " because ri-offset is not a valid block (0x%zx)\n", - offset); + DEBUG (2, "returning EFAULT" + " because ri-offset is not a valid block (0x%zx)", + offset); errno = EFAULT; goto error; } if (!BLOCK_ID_EQ (h, offset, "lf") && !BLOCK_ID_EQ (h, offset, "lh")) { - if (h->msglvl >= 2) { - struct ntreg_lf_record *block - (struct ntreg_lf_record *) ((char *) h->addr + offset); - fprintf (stderr, "hivex: get_children: returning ENOTSUP" - " because ri-record offset does not point to lf/lh" - " (0x%zx, %d, %d)\n", - offset, block->id[0], block->id[1]); - } + struct ntreg_lf_record *block + (struct ntreg_lf_record *) ((char *) h->addr + offset); + DEBUG (2, "returning ENOTSUP" + " because ri-record offset does not point to lf/lh" + " (0x%zx, %d, %d)", + offset, block->id[0], block->id[1]); errno = ENOTSUP; goto error; } @@ -900,11 +871,10 @@ get_children (hive_h *h, hive_node_h node, subkey += 0x1000; if (!(flags & GET_CHILDREN_NO_CHECK_NK)) { if (!IS_VALID_BLOCK (h, subkey)) { - if (h->msglvl >= 2) - fprintf (stderr, "hivex: get_children: returning EFAULT" - " because indirect subkey is not a valid block" - " (0x%zx)\n", - subkey); + DEBUG (2, "returning EFAULT" + " because indirect subkey is not a valid block" + " (0x%zx)", + subkey); errno = EFAULT; goto error; } @@ -916,10 +886,9 @@ get_children (hive_h *h, hive_node_h node, goto ok; } /* else not supported, set errno and fall through */ - if (h->msglvl >= 2) - fprintf (stderr, "hivex: 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]); + DEBUG (2, "returning ENOTSUP" + " because subkey block is not lf/lh/ri (0x%zx, %d, %d)", + subkey_lf, block->id[0], block->id[1]); errno = ENOTSUP; error: _hivex_free_offset_list (&children); @@ -991,10 +960,9 @@ hivex_node_parent (hive_h *h, hive_node_h node) hive_node_h ret = le32toh (nk->parent); ret += 0x1000; if (!IS_VALID_BLOCK (h, ret)) { - if (h->msglvl >= 2) - fprintf (stderr, "hivex_node_parent: returning EFAULT" - " because parent is not a valid block (0x%zx)\n", - ret); + DEBUG (2, "returning EFAULT" + " because parent is not a valid block (0x%zx)", + ret); errno = EFAULT; return 0; } @@ -1015,8 +983,7 @@ get_values (hive_h *h, hive_node_h node, size_t nr_values = le32toh (nk->nr_values); - if (h->msglvl >= 2) - fprintf (stderr, "hivex_node_values: nr_values = %zu\n", nr_values); + DEBUG (2, "nr_values = %zu", nr_values); offset_list values, blocks; _hivex_init_offset_list (h, &values); @@ -1028,10 +995,9 @@ get_values (hive_h *h, hive_node_h node, /* Arbitrarily limit the number of values we will ever deal with. */ if (nr_values > HIVEX_MAX_VALUES) { - if (h->msglvl >= 2) - fprintf (stderr, "hivex: get_values: returning ERANGE" - " because nr_values > HIVEX_MAX_VALUES (%zu > %d)\n", - nr_values, HIVEX_MAX_VALUES); + DEBUG (2, "returning ERANGE" + " because nr_values > HIVEX_MAX_VALUES (%zu > %d)", + nr_values, HIVEX_MAX_VALUES); errno = ERANGE; goto error; } @@ -1044,10 +1010,9 @@ get_values (hive_h *h, hive_node_h node, size_t vlist_offset = le32toh (nk->vallist); vlist_offset += 0x1000; if (!IS_VALID_BLOCK (h, vlist_offset)) { - if (h->msglvl >= 2) - fprintf (stderr, "hivex_node_values: returning EFAULT" - " because value list is not a valid block (0x%zx)\n", - vlist_offset); + DEBUG (2, "returning EFAULT" + " because value list is not a valid block (0x%zx)", + vlist_offset); errno = EFAULT; goto error; } @@ -1060,10 +1025,9 @@ get_values (hive_h *h, hive_node_h node, size_t len = block_len (h, vlist_offset, NULL); if (4 + nr_values * 4 > len) { - if (h->msglvl >= 2) - fprintf (stderr, "hivex_node_values: returning EFAULT" - " because value list is too long (%zu, %zu)\n", - nr_values, len); + DEBUG (2, "returning EFAULT" + " because value list is too long (%zu, %zu)", + nr_values, len); errno = EFAULT; goto error; } @@ -1073,10 +1037,9 @@ get_values (hive_h *h, hive_node_h node, hive_node_h value = le32toh (vlist->offset[i]); value += 0x1000; if (!IS_VALID_BLOCK (h, value)) { - if (h->msglvl >= 2) - fprintf (stderr, "hivex_node_values: returning EFAULT" - " because value is not a valid block (0x%zx)\n", - value); + DEBUG (2, "returning EFAULT" + " because value is not a valid block (0x%zx)", + value); errno = EFAULT; goto error; } @@ -1171,10 +1134,9 @@ hivex_value_key_len (hive_h *h, hive_value_h value) size_t ret = le16toh (vk->name_len); size_t seg_len = block_len (h, value, NULL); if (sizeof (struct ntreg_vk_record) + ret - 1 > seg_len) { - if (h->msglvl >= 2) - fprintf (stderr, "hivex_value_key_len: returning EFAULT" - " because key length is too long (%zu, %zu)\n", - ret, seg_len); + DEBUG (2, "returning EFAULT" + " because key length is too long (%zu, %zu)", + ret, seg_len); errno = EFAULT; return 0; } @@ -1238,8 +1200,7 @@ hivex_value_data_cell_offset (hive_h *h, hive_value_h value, size_t *len) return 0; } - if (h->msglvl >= 2) - fprintf (stderr, "hivex_value_data_cell_offset: value=0x%zx\n", value); + DEBUG (2, "value=0x%zx", value); struct ntreg_vk_record *vk (struct ntreg_vk_record *) ((char *) h->addr + value); @@ -1250,11 +1211,9 @@ hivex_value_data_cell_offset (hive_h *h, hive_value_h value, size_t *len) is_inline = !!(data_len & 0x80000000); data_len &= 0x7fffffff; - if (h->msglvl >= 2) - fprintf (stderr, "hivex_value_data_cell_offset: is_inline=%d\n", is_inline); + DEBUG (2, "is_inline=%d", is_inline); - if (h->msglvl >= 2) - fprintf (stderr, "hivex_value_data_cell_offset: data_len=%zx\n", data_len); + DEBUG (2, "data_len=%zx", data_len); if (is_inline && data_len > 4) { errno = ENOTSUP; @@ -1271,22 +1230,19 @@ hivex_value_data_cell_offset (hive_h *h, hive_value_h value, size_t *len) *len = data_len + 4; /* Include 4 header length bytes */ } - if (h->msglvl >= 2) - fprintf (stderr, "hivex_value_data_cell_offset: Proceeding with indirect data.\n"); + 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)) { - if (h->msglvl >= 2) - fprintf (stderr, "hivex_value_data_cell_offset: returning EFAULT because data " - "offset is not a valid block (0x%zx)\n", - data_offset); + DEBUG (2, "returning EFAULT because data " + "offset is not a valid block (0x%zx)", + data_offset); errno = EFAULT; return 0; } - if (h->msglvl >= 2) - fprintf (stderr, "hivex_value_data_cell_offset: data_offset=%zx\n", data_offset); + DEBUG (2, "data_offset=%zx", data_offset); return data_offset; } @@ -1313,9 +1269,8 @@ hivex_value_value (hive_h *h, hive_value_h value, is_inline = !!(len & 0x80000000); len &= 0x7fffffff; - if (h->msglvl >= 2) - fprintf (stderr, "hivex_value_value: value=0x%zx, t=%d, len=%zu, inline=%d\n", - value, t, len, is_inline); + DEBUG (2, "value=0x%zx, t=%d, len=%zu, inline=%d", + value, t, len, is_inline); if (t_rtn) *t_rtn = t; @@ -1329,10 +1284,9 @@ hivex_value_value (hive_h *h, hive_value_h value, /* Arbitrarily limit the length that we will read. */ if (len > HIVEX_MAX_VALUE_LEN) { - if (h->msglvl >= 2) - fprintf (stderr, "hivex_value_value: returning ERANGE because data " - "length > HIVEX_MAX_VALUE_LEN (%zu > %d)\n", - len, HIVEX_MAX_SUBKEYS); + DEBUG (2, "returning ERANGE because data " + "length > HIVEX_MAX_VALUE_LEN (%zu > %d)", + len, HIVEX_MAX_SUBKEYS); errno = ERANGE; return NULL; } @@ -1349,10 +1303,9 @@ hivex_value_value (hive_h *h, hive_value_h value, size_t data_offset = le32toh (vk->data_offset); data_offset += 0x1000; if (!IS_VALID_BLOCK (h, data_offset)) { - if (h->msglvl >= 2) - fprintf (stderr, "hivex_value_value: returning EFAULT because data " - "offset is not a valid block (0x%zx)\n", - data_offset); + DEBUG (2, "returning EFAULT because data " + "offset is not a valid block (0x%zx)", + data_offset); errno = EFAULT; free (ret); return NULL; @@ -1371,11 +1324,10 @@ hivex_value_value (hive_h *h, hive_value_h value, return ret; } else { if (!IS_VALID_BLOCK (h, data_offset) || !BLOCK_ID_EQ (h, data_offset, "db")) { - if (h->msglvl >= 2) - fprintf (stderr, "hivex_value_value: warning: declared data length " - "is longer than the block and block is not a db block " - "(data 0x%zx, data len %zu)\n", - data_offset, len); + DEBUG (2, "warning: declared data length " + "is longer than the block and block is not a db block " + "(data 0x%zx, data len %zu)", + data_offset, len); errno = EINVAL; free (ret); return NULL; @@ -1386,10 +1338,9 @@ hivex_value_value (hive_h *h, hive_value_h value, blocklist_offset += 0x1000; size_t nr_blocks = le16toh (db->nr_blocks); if (!IS_VALID_BLOCK (h, blocklist_offset)) { - if (h->msglvl >= 2) - fprintf (stderr, "hivex_value_value: warning: blocklist is not a " - "valid block (db block 0x%zx, blocklist 0x%zx)\n", - data_offset, blocklist_offset); + DEBUG (2, "warning: blocklist is not a " + "valid block (db block 0x%zx, blocklist 0x%zx)", + data_offset, blocklist_offset); errno = EINVAL; free (ret); return NULL; @@ -1401,10 +1352,9 @@ hivex_value_value (hive_h *h, hive_value_h value, size_t subblock_offset = le32toh (bl->offset[i]); subblock_offset += 0x1000; if (!IS_VALID_BLOCK (h, subblock_offset)) { - if (h->msglvl >= 2) - fprintf (stderr, "hivex_value_value: warning: subblock is not " - "valid (db block 0x%zx, block list 0x%zx, data subblock 0x%zx)\n", - data_offset, blocklist_offset, subblock_offset); + DEBUG (2, "warning: subblock is not " + "valid (db block 0x%zx, block list 0x%zx, data subblock 0x%zx)", + data_offset, blocklist_offset, subblock_offset); errno = EINVAL; free (ret); return NULL; @@ -1420,11 +1370,10 @@ hivex_value_value (hive_h *h, hive_value_h value, off += sz; } if (off != *len_rtn) { - if (h->msglvl >= 2) - fprintf (stderr, "hivex_value_value: warning: declared data length " - "and amount of data found in sub-blocks differ " - "(db block 0x%zx, data len %zu, found data %zu)\n", - data_offset, *len_rtn, off); + 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; @@ -1718,10 +1667,8 @@ hivex__visit_node (hive_h *h, hive_node_h node, int ret = -1; if (!BITMAP_TST (unvisited, node)) { - if (h->msglvl >= 2) - fprintf (stderr, "hivex__visit_node: contains cycle:" - " visited node 0x%zx already\n", - node); + DEBUG (2, "contains cycle: visited node 0x%zx already", + node); errno = ELOOP; return skip_bad ? 0 : -1; @@ -1894,9 +1841,7 @@ hivex__visit_node (hive_h *h, hive_node_h node, } for (i = 0; children[i] != 0; ++i) { - if (h->msglvl >= 2) - fprintf (stderr, "hivex__visit_node: %s: visiting subkey %d (0x%zx)\n", - name, i, children[i]); + DEBUG (2, "%s: visiting subkey %d (0x%zx)", name, i, children[i]); if (hivex__visit_node (h, children[i], vtor, unvisited, opaque, flags) == -1) goto error; @@ -1951,14 +1896,10 @@ allocate_page (hive_h *h, size_t allocation_hint) */ 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); - } + DEBUG (2, "current endpages = 0x%zx, current size = 0x%zx", + h->endpages, h->size); + DEBUG (2, "extending file by %zd bytes (<= 0 if no extension)", + extend); if (extend > 0) { size_t oldsize = h->size; @@ -1986,9 +1927,7 @@ allocate_page (hive_h *h, size_t allocation_hint) 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); + DEBUG (2, "new endpages = 0x%zx, new size = 0x%zx", h->endpages, h->size); /* Write the hbin header. */ struct ntreg_hbin_page *page @@ -2001,8 +1940,7 @@ allocate_page (hive_h *h, size_t allocation_hint) 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); + DEBUG (2, "new page at 0x%zx", offset); /* Offset of first usable byte after the header. */ return offset + sizeof (struct ntreg_hbin_page); @@ -2040,18 +1978,14 @@ allocate_block (hive_h *h, size_t seg_len, const char id[2]) * 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); + DEBUG (2, "refusing too small allocation (%zu), returning ERANGE", 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); + DEBUG (2, "refusing large allocation (%zu), returning ERANGE", seg_len); errno = ERANGE; return 0; } @@ -2071,9 +2005,7 @@ allocate_block (hive_h *h, size_t seg_len, const char id[2]) size_t offset = h->endblocks; - if (h->msglvl >= 2) - fprintf (stderr, "allocate_block: new block at 0x%zx, size %zu\n", - offset, seg_len); + DEBUG (2, "new block at 0x%zx, size %zu", offset, seg_len); struct ntreg_hbin_block *blockhdr (struct ntreg_hbin_block *) ((char *) h->addr + offset); @@ -2096,9 +2028,8 @@ allocate_block (hive_h *h, size_t seg_len, const char id[2]) */ 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); + DEBUG (2, "marking remainder of page free" + " starting at 0x%zx, size %zd", h->endblocks, rem); assert (rem >= 4); @@ -2122,8 +2053,7 @@ 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); + DEBUG (2, "marking 0x%zx unused", offset); struct ntreg_hbin_block *blockhdr (struct ntreg_hbin_block *) ((char *) h->addr + offset); @@ -2221,8 +2151,7 @@ hivex_commit (hive_h *h, const char *filename, int flags) 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); + DEBUG (2, "hivex_commit: new header checksum: 0x%x", sum); if (full_write (fd, h->addr, h->size) != h->size) { int err = errno; @@ -2387,9 +2316,7 @@ hivex_node_add_child (hive_h *h, hive_node_h parent, const char *name) 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); + DEBUG (2, "allocated new nk-record for child at 0x%zx", node); struct ntreg_nk_record *nk (struct ntreg_nk_record *) ((char *) h->addr + node); @@ -2409,10 +2336,9 @@ hivex_node_add_child (hive_h *h, hive_node_h parent, const char *name) 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); + DEBUG (2, "returning EFAULT" + " because parent sk is not a valid block (%zu)", + parent_sk_offset); errno = EFAULT; return 0; } @@ -2464,9 +2390,7 @@ hivex_node_add_child (hive_h *h, hive_node_h parent, const char *name) nk = (struct ntreg_nk_record *) ((char *) h->addr + node); parent_nk = (struct ntreg_nk_record *) ((char *) h->addr + parent); - if (h->msglvl >= 2) - fprintf (stderr, "hivex_node_add_child: no keys, allocated new" - " lh-record at 0x%zx\n", lh_offs); + DEBUG (2, "no keys, allocated new lh-record at 0x%zx", lh_offs); parent_nk->subkey_lf = htole32 (lh_offs - 0x1000); } @@ -2497,9 +2421,8 @@ hivex_node_add_child (hive_h *h, hive_node_h parent, const char *name) /* 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); + DEBUG (2, "insert key in existing lh-record at 0x%zx, posn %zu", + old_offs, j); new_offs = insert_lf_record (h, old_offs, j, name, node); if (new_offs == 0) { @@ -2513,9 +2436,7 @@ hivex_node_add_child (hive_h *h, hive_node_h parent, const char *name) nk = (struct ntreg_nk_record *) ((char *) h->addr + node); parent_nk = (struct ntreg_nk_record *) ((char *) h->addr + parent); - if (h->msglvl >= 2) - fprintf (stderr, "hivex_node_add_child: new lh-record at 0x%zx\n", - new_offs); + DEBUG (2, "new lh-record at 0x%zx", new_offs); /* If the lf/lh-record was directly referenced by the parent nk, * then update the parent nk. @@ -2539,9 +2460,7 @@ hivex_node_add_child (hive_h *h, hive_node_h parent, const char *name) } /* 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"); + DEBUG (2, "returning ENOTSUP because could not find ri->lf link"); errno = ENOTSUP; free (blocks); return 0; @@ -2572,8 +2491,7 @@ 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); + DEBUG (2, "not an sk record: 0x%zx", sk_offset); errno = EFAULT; return -1; } @@ -2582,8 +2500,7 @@ delete_sk (hive_h *h, size_t sk_offset) (struct ntreg_sk_record *) ((char *) 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", + DEBUG (2, "sk record already has refcount 0: 0x%zx", sk_offset); errno = EINVAL; return -1; @@ -2689,8 +2606,7 @@ hivex_node_delete_child (hive_h *h, hive_node_h node) } if (node == hivex_root (h)) { - if (h->msglvl >= 2) - fprintf (stderr, "hivex_node_delete_child: cannot delete root node\n"); + DEBUG (2, "cannot delete root node"); errno = EINVAL; return -1; } @@ -2735,9 +2651,7 @@ hivex_node_delete_child (hive_h *h, hive_node_h node) } } } - if (h->msglvl >= 2) - fprintf (stderr, "hivex_node_delete_child: could not find parent" - " to child link\n"); + DEBUG (2, "could not find parent to child link"); errno = ENOTSUP; return -1; @@ -2747,9 +2661,8 @@ hivex_node_delete_child (hive_h *h, hive_node_h node) 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); + DEBUG (2, "updating nr_subkeys in parent 0x%zx to %zu", + parent, nr_subkeys_in_nk); return 0; } diff --git a/lib/offset-list.c b/lib/offset-list.c index b4d9bb7..b35969f 100644 --- a/lib/offset-list.c +++ b/lib/offset-list.c @@ -70,10 +70,9 @@ _hivex_add_to_offset_list (offset_list *list, size_t offset) assert (offset != 0); /* internal error if this happens */ if (list->len >= list->limit) { - if (list->h->msglvl >= 2) - fprintf (stderr, "hivex: returning ERANGE because list of offsets " - "has exceeded limit (limit = %zu)\n", - list->limit); + hive_h *h = list->h; /* for DEBUG macro */ + DEBUG (2, "returning ERANGE because list of offsets " + "has exceeded limit (limit = %zu)\n", list->limit); errno = ERANGE; return -1; } -- 1.8.3.1
Richard W.M. Jones
2013-Jul-25 10:38 UTC
[Libguestfs] [PATCH hivex 07/19] lib: Add macros for setting errno and checking if the hive is writable.
From: "Richard W.M. Jones" <rjones@redhat.com> This is code cleanup, but there are a couple of small changes: - the msglvl for errno explanations moves from debug (2) to verbose (1) - add and tidy up some of the error messages --- lib/hivex-internal.h | 15 +++ lib/hivex.c | 322 +++++++++++++++++++-------------------------------- lib/offset-list.c | 8 +- 3 files changed, 138 insertions(+), 207 deletions(-) diff --git a/lib/hivex-internal.h b/lib/hivex-internal.h index 16fced1..62182a0 100644 --- a/lib/hivex-internal.h +++ b/lib/hivex-internal.h @@ -102,4 +102,19 @@ extern size_t * _hivex_return_offset_list (offset_list *list); } \ } while (0) +#define SET_ERRNO(errval,fs,...) \ + do { \ + DEBUG (1, "returning " #errval " because: " fs, ## __VA_ARGS__); \ + errno = errval; \ + } while (0) + +#define CHECK_WRITABLE(retcode) \ + do { \ + if (!h->writable) { \ + SET_ERRNO (EROFS, \ + "HIVEX_OPEN_WRITE flag was not specified when opening this hive"); \ + return (retcode); \ + } \ + } while (0) + #endif /* HIVEX_INTERNAL_H_ */ diff --git a/lib/hivex.c b/lib/hivex.c index ddab7ea..ebf1957 100644 --- a/lib/hivex.c +++ b/lib/hivex.c @@ -307,19 +307,17 @@ hivex_open (const char *filename, int flags) h->hdr->magic[1] != 'e' || h->hdr->magic[2] != 'g' || h->hdr->magic[3] != 'f') { - fprintf (stderr, "hivex: %s: not a Windows NT Registry hive file\n", - filename); - errno = ENOTSUP; + SET_ERRNO (ENOTSUP, + "%s: not a Windows NT Registry hive file", filename); 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; + SET_ERRNO (ENOTSUP, + "%s: hive file major version %" PRIu32 " (expected 1)", + filename, major_ver); goto error; } @@ -330,8 +328,7 @@ hivex_open (const char *filename, int flags) /* Header checksum. */ 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; + SET_ERRNO (EINVAL, "%s: bad checksum in hive header", filename); goto error; } @@ -398,10 +395,10 @@ hivex_open (const char *filename, int flags) page->magic[1] != 'b' || page->magic[2] != 'i' || page->magic[3] != 'n') { - fprintf (stderr, "hivex: %s: trailing garbage at end of file " - "(at 0x%zx, after %zu pages)\n", - filename, off, pages); - errno = ENOTSUP; + SET_ERRNO (ENOTSUP, + "%s: trailing garbage at end of file " + "(at 0x%zx, after %zu pages)", + filename, off, pages); goto error; } @@ -413,9 +410,9 @@ hivex_open (const char *filename, int flags) 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; + SET_ERRNO (ENOTSUP, + "%s: page size %zu at 0x%zx, bad registry", + filename, page_size, off); goto error; } @@ -436,10 +433,9 @@ 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 %" PRIu32 " at 0x%zx," - " bad registry\n", - filename, le32toh (block->seg_len), blkoff); - errno = ENOTSUP; + SET_ERRNO (ENOTSUP, + "%s: block size %" PRIu32 " at 0x%zx, bad registry", + filename, le32toh (block->seg_len), blkoff); goto error; } @@ -469,14 +465,12 @@ hivex_open (const char *filename, int flags) } if (!seen_root_block) { - fprintf (stderr, "hivex: %s: no root block found\n", filename); - errno = ENOTSUP; + SET_ERRNO (ENOTSUP, "%s: no root block found", filename); goto error; } if (bad_root_block) { - fprintf (stderr, "hivex: %s: bad root block (free or not nk)\n", filename); - errno = ENOTSUP; + SET_ERRNO (ENOTSUP, "%s: bad root block (free or not nk)", filename); goto error; } @@ -541,7 +535,7 @@ hivex_root (hive_h *h) { hive_node_h ret = h->rootoffs; if (!IS_VALID_BLOCK (h, ret)) { - errno = HIVEX_NO_KEY; + SET_ERRNO (HIVEX_NO_KEY, "no root key"); return 0; } return ret; @@ -551,7 +545,7 @@ size_t hivex_node_struct_length (hive_h *h, hive_node_h node) { if (!IS_VALID_BLOCK (h, node) || !BLOCK_ID_EQ (h, node, "nk")) { - errno = EINVAL; + SET_ERRNO (EINVAL, "invalid block or not an 'nk' block"); return 0; } @@ -563,9 +557,7 @@ hivex_node_struct_length (hive_h *h, hive_node_h node) int used; size_t seg_len = block_len (h, node, &used); if (ret > seg_len) { - DEBUG (2, "returning EFAULT because" - " node name is too long (%zu, %zu)", name_len, seg_len); - errno = EFAULT; + SET_ERRNO (EFAULT, "node name is too long (%zu, %zu)", name_len, seg_len); return 0; } return ret; @@ -575,7 +567,7 @@ char * hivex_node_name (hive_h *h, hive_node_h node) { if (!IS_VALID_BLOCK (h, node) || !BLOCK_ID_EQ (h, node, "nk")) { - errno = EINVAL; + SET_ERRNO (EINVAL, "invalid block or not an 'nk' block"); return NULL; } @@ -593,10 +585,7 @@ hivex_node_name (hive_h *h, hive_node_h node) size_t len = le16toh (nk->name_len); size_t seg_len = block_len (h, node, NULL); if (sizeof (struct ntreg_nk_record) + len - 1 > seg_len) { - DEBUG (2, "returning EFAULT because node name" - " is too long (%zu, %zu)", - len, seg_len); - errno = EFAULT; + SET_ERRNO (EFAULT, "node name is too long (%zu, %zu)", len, seg_len); return NULL; } @@ -612,8 +601,8 @@ static int64_t timestamp_check (hive_h *h, hive_node_h node, int64_t timestamp) { if (timestamp < 0) { - DEBUG (2, "negative time reported at %zu: %" PRIi64, node, timestamp); - errno = EINVAL; + SET_ERRNO (EINVAL, + "negative time reported at %zu: %" PRIi64, node, timestamp); return -1; } @@ -632,7 +621,7 @@ hivex_node_timestamp (hive_h *h, hive_node_h node) int64_t ret; if (!IS_VALID_BLOCK (h, node) || !BLOCK_ID_EQ (h, node, "nk")) { - errno = EINVAL; + SET_ERRNO (EINVAL, "invalid block or not an 'nk' block"); return -1; } @@ -654,7 +643,7 @@ hive_security_h hivex_node_security (hive_h *h, hive_node_h node) { if (!IS_VALID_BLOCK (h, node) || !BLOCK_ID_EQ (h, node, "nk")) { - errno = EINVAL; + SET_ERRNO (EINVAL, "invalid block or not an 'nk' block"); return 0; } @@ -663,7 +652,7 @@ hivex_node_security (hive_h *h, hive_node_h node) hive_node_h ret = le32toh (nk->sk); ret += 0x1000; if (!IS_VALID_BLOCK (h, ret)) { - errno = EFAULT; + SET_ERRNO (EFAULT, "invalid block"); return 0; } return ret; @@ -673,7 +662,7 @@ hive_classname_h hivex_node_classname (hive_h *h, hive_node_h node) { if (!IS_VALID_BLOCK (h, node) || !BLOCK_ID_EQ (h, node, "nk")) { - errno = EINVAL; + SET_ERRNO (EINVAL, "invalid block or not an 'nk' block"); return 0; } @@ -682,7 +671,7 @@ hivex_node_classname (hive_h *h, hive_node_h node) hive_node_h ret = le32toh (nk->classname); ret += 0x1000; if (!IS_VALID_BLOCK (h, ret)) { - errno = EFAULT; + SET_ERRNO (EFAULT, "invalid block"); return 0; } return ret; @@ -698,7 +687,7 @@ get_children (hive_h *h, hive_node_h node, int flags) { if (!IS_VALID_BLOCK (h, node) || !BLOCK_ID_EQ (h, node, "nk")) { - errno = EINVAL; + SET_ERRNO (EINVAL, "invalid block or not an 'nk' block"); return -1; } @@ -717,10 +706,9 @@ get_children (hive_h *h, hive_node_h node, /* Arbitrarily limit the number of subkeys we will ever deal with. */ if (nr_subkeys_in_nk > HIVEX_MAX_SUBKEYS) { - DEBUG (2, "returning ERANGE because " - "nr_subkeys_in_nk > HIVEX_MAX_SUBKEYS (%zu > %d)", - nr_subkeys_in_nk, HIVEX_MAX_SUBKEYS); - errno = ERANGE; + SET_ERRNO (ERANGE, + "nr_subkeys_in_nk > HIVEX_MAX_SUBKEYS (%zu > %d)", + nr_subkeys_in_nk, HIVEX_MAX_SUBKEYS); goto error; } @@ -735,10 +723,8 @@ get_children (hive_h *h, hive_node_h node, size_t subkey_lf = le32toh (nk->subkey_lf); subkey_lf += 0x1000; if (!IS_VALID_BLOCK (h, subkey_lf)) { - DEBUG (2, "returning EFAULT" - " because subkey_lf is not a valid block (0x%zx)", - subkey_lf); - errno = EFAULT; + SET_ERRNO (EFAULT, + "subkey_lf is not a valid block (0x%zx)", subkey_lf); goto error; } @@ -759,19 +745,16 @@ get_children (hive_h *h, hive_node_h node, */ size_t nr_subkeys_in_lf = le16toh (lf->nr_keys); - DEBUG (2, "nr_subkeys_in_nk = %zu, nr_subkeys_in_lf = %zu", - nr_subkeys_in_nk, nr_subkeys_in_lf); - if (nr_subkeys_in_nk != nr_subkeys_in_lf) { - errno = ENOTSUP; + SET_ERRNO (ENOTSUP, + "nr_subkeys_in_nk = %zu is not equal to nr_subkeys_in_lf = %zu", + nr_subkeys_in_nk, nr_subkeys_in_lf); goto error; } size_t len = block_len (h, subkey_lf, NULL); if (8 + nr_subkeys_in_lf * 8 > len) { - DEBUG (2, "returning EFAULT because too many subkeys (%zu, %zu)", - nr_subkeys_in_lf, len); - errno = EFAULT; + SET_ERRNO (EFAULT, "too many subkeys (%zu, %zu)", nr_subkeys_in_lf, len); goto error; } @@ -781,10 +764,7 @@ get_children (hive_h *h, hive_node_h node, subkey += 0x1000; if (!(flags & GET_CHILDREN_NO_CHECK_NK)) { if (!IS_VALID_BLOCK (h, subkey)) { - DEBUG (2, "returning EFAULT" - " because subkey is not a valid block (0x%zx)", - subkey); - errno = EFAULT; + SET_ERRNO (EFAULT, "subkey is not a valid block (0x%zx)", subkey); goto error; } } @@ -805,19 +785,15 @@ get_children (hive_h *h, hive_node_h node, hive_node_h offset = le32toh (ri->offset[i]); offset += 0x1000; if (!IS_VALID_BLOCK (h, offset)) { - DEBUG (2, "returning EFAULT because ri-offset is not a valid block (0x%zx)", - offset); - errno = EFAULT; + SET_ERRNO (EFAULT, "ri-offset is not a valid block (0x%zx)", offset); goto error; } if (!BLOCK_ID_EQ (h, offset, "lf") && !BLOCK_ID_EQ (h, offset, "lh")) { struct ntreg_lf_record *block (struct ntreg_lf_record *) ((char *) h->addr + offset); - DEBUG (2, "returning ENOTSUP" - " because ri-record offset does not point to lf/lh" - " (0x%zx, %d, %d)", - offset, block->id[0], block->id[1]); - errno = ENOTSUP; + SET_ERRNO (ENOTSUP, + "ri-record offset does not point to lf/lh (0x%zx, %d, %d)", + offset, block->id[0], block->id[1]); goto error; } @@ -830,11 +806,10 @@ get_children (hive_h *h, hive_node_h node, count += le16toh (lf->nr_keys); } - DEBUG (2, "nr_subkeys_in_nk = %zu, counted = %zu", - nr_subkeys_in_nk, count); - if (nr_subkeys_in_nk != count) { - errno = ENOTSUP; + SET_ERRNO (ENOTSUP, + "nr_subkeys_in_nk = %zu is not equal to counted = %zu", + nr_subkeys_in_nk, count); goto error; } @@ -845,20 +820,15 @@ get_children (hive_h *h, hive_node_h node, hive_node_h offset = le32toh (ri->offset[i]); offset += 0x1000; if (!IS_VALID_BLOCK (h, offset)) { - DEBUG (2, "returning EFAULT" - " because ri-offset is not a valid block (0x%zx)", - offset); - errno = EFAULT; + SET_ERRNO (EFAULT, "ri-offset is not a valid block (0x%zx)", offset); goto error; } if (!BLOCK_ID_EQ (h, offset, "lf") && !BLOCK_ID_EQ (h, offset, "lh")) { struct ntreg_lf_record *block (struct ntreg_lf_record *) ((char *) h->addr + offset); - DEBUG (2, "returning ENOTSUP" - " because ri-record offset does not point to lf/lh" - " (0x%zx, %d, %d)", - offset, block->id[0], block->id[1]); - errno = ENOTSUP; + SET_ERRNO (ENOTSUP, + "ri-record offset does not point to lf/lh (0x%zx, %d, %d)", + offset, block->id[0], block->id[1]); goto error; } @@ -871,11 +841,9 @@ get_children (hive_h *h, hive_node_h node, subkey += 0x1000; if (!(flags & GET_CHILDREN_NO_CHECK_NK)) { if (!IS_VALID_BLOCK (h, subkey)) { - DEBUG (2, "returning EFAULT" - " because indirect subkey is not a valid block" - " (0x%zx)", - subkey); - errno = EFAULT; + SET_ERRNO (EFAULT, + "indirect subkey is not a valid block (0x%zx)", + subkey); goto error; } } @@ -886,10 +854,9 @@ get_children (hive_h *h, hive_node_h node, goto ok; } /* else not supported, set errno and fall through */ - DEBUG (2, "returning ENOTSUP" - " because subkey block is not lf/lh/ri (0x%zx, %d, %d)", - subkey_lf, block->id[0], block->id[1]); - errno = ENOTSUP; + SET_ERRNO (ENOTSUP, + "subkey block is not lf/lh/ri (0x%zx, %d, %d)", + subkey_lf, block->id[0], block->id[1]); error: _hivex_free_offset_list (&children); _hivex_free_offset_list (&blocks); @@ -950,7 +917,7 @@ hive_node_h hivex_node_parent (hive_h *h, hive_node_h node) { if (!IS_VALID_BLOCK (h, node) || !BLOCK_ID_EQ (h, node, "nk")) { - errno = EINVAL; + SET_ERRNO (EINVAL, "invalid block or not an 'nk' block"); return 0; } @@ -960,10 +927,7 @@ hivex_node_parent (hive_h *h, hive_node_h node) hive_node_h ret = le32toh (nk->parent); ret += 0x1000; if (!IS_VALID_BLOCK (h, ret)) { - DEBUG (2, "returning EFAULT" - " because parent is not a valid block (0x%zx)", - ret); - errno = EFAULT; + SET_ERRNO (EFAULT, "parent is not a valid block (0x%zx)", ret); return 0; } return ret; @@ -974,7 +938,7 @@ 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; + SET_ERRNO (EINVAL, "invalid block or not an 'nk' block"); return -1; } @@ -995,10 +959,9 @@ get_values (hive_h *h, hive_node_h node, /* Arbitrarily limit the number of values we will ever deal with. */ if (nr_values > HIVEX_MAX_VALUES) { - DEBUG (2, "returning ERANGE" - " because nr_values > HIVEX_MAX_VALUES (%zu > %d)", - nr_values, HIVEX_MAX_VALUES); - errno = ERANGE; + SET_ERRNO (ERANGE, + "nr_values > HIVEX_MAX_VALUES (%zu > %d)", + nr_values, HIVEX_MAX_VALUES); goto error; } @@ -1010,10 +973,8 @@ get_values (hive_h *h, hive_node_h node, size_t vlist_offset = le32toh (nk->vallist); vlist_offset += 0x1000; if (!IS_VALID_BLOCK (h, vlist_offset)) { - DEBUG (2, "returning EFAULT" - " because value list is not a valid block (0x%zx)", - vlist_offset); - errno = EFAULT; + SET_ERRNO (EFAULT, + "value list is not a valid block (0x%zx)", vlist_offset); goto error; } @@ -1025,10 +986,7 @@ get_values (hive_h *h, hive_node_h node, size_t len = block_len (h, vlist_offset, NULL); if (4 + nr_values * 4 > len) { - DEBUG (2, "returning EFAULT" - " because value list is too long (%zu, %zu)", - nr_values, len); - errno = EFAULT; + SET_ERRNO (EFAULT, "value list is too long (%zu, %zu)", nr_values, len); goto error; } @@ -1037,10 +995,7 @@ get_values (hive_h *h, hive_node_h node, hive_node_h value = le32toh (vlist->offset[i]); value += 0x1000; if (!IS_VALID_BLOCK (h, value)) { - DEBUG (2, "returning EFAULT" - " because value is not a valid block (0x%zx)", - value); - errno = EFAULT; + SET_ERRNO (EFAULT, "value is not a valid block (0x%zx)", value); goto error; } if (_hivex_add_to_offset_list (&values, value) == -1) @@ -1121,7 +1076,7 @@ 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")) { - errno = EINVAL; + SET_ERRNO (EINVAL, "invalid block or not an 'vk' block"); return 0; } @@ -1134,10 +1089,7 @@ hivex_value_key_len (hive_h *h, hive_value_h value) size_t ret = le16toh (vk->name_len); size_t seg_len = block_len (h, value, NULL); if (sizeof (struct ntreg_vk_record) + ret - 1 > seg_len) { - DEBUG (2, "returning EFAULT" - " because key length is too long (%zu, %zu)", - ret, seg_len); - errno = EFAULT; + SET_ERRNO (EFAULT, "key length is too long (%zu, %zu)", ret, seg_len); return 0; } return ret; @@ -1147,7 +1099,7 @@ char * hivex_value_key (hive_h *h, hive_value_h value) { if (!IS_VALID_BLOCK (h, value) || !BLOCK_ID_EQ (h, value, "vk")) { - errno = EINVAL; + SET_ERRNO (EINVAL, "invalid block or not an 'vk' block"); return 0; } @@ -1174,7 +1126,7 @@ 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")) { - errno = EINVAL; + SET_ERRNO (EINVAL, "invalid block or not an 'vk' block"); return -1; } @@ -1196,7 +1148,7 @@ 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")) { - errno = EINVAL; + SET_ERRNO (EINVAL, "invalid block or not an 'vk' block"); return 0; } @@ -1216,7 +1168,7 @@ hivex_value_data_cell_offset (hive_h *h, hive_value_h value, size_t *len) DEBUG (2, "data_len=%zx", data_len); if (is_inline && data_len > 4) { - errno = ENOTSUP; + SET_ERRNO (ENOTSUP, "inline data with declared length (%zx) > 4", data_len); return 0; } @@ -1235,10 +1187,7 @@ hivex_value_data_cell_offset (hive_h *h, hive_value_h value, size_t *len) 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)) { - DEBUG (2, "returning EFAULT because data " - "offset is not a valid block (0x%zx)", - data_offset); - errno = EFAULT; + SET_ERRNO (EFAULT, "data offset is not a valid block (0x%zx)", data_offset); return 0; } @@ -1252,7 +1201,7 @@ 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")) { - errno = EINVAL; + SET_ERRNO (EINVAL, "invalid block or not an 'vk' block"); return NULL; } @@ -1278,16 +1227,14 @@ hivex_value_value (hive_h *h, hive_value_h value, *len_rtn = len; if (is_inline && len > 4) { - errno = ENOTSUP; + 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) { - DEBUG (2, "returning ERANGE because data " - "length > HIVEX_MAX_VALUE_LEN (%zu > %d)", - len, HIVEX_MAX_SUBKEYS); - errno = ERANGE; + SET_ERRNO (ERANGE, "data length > HIVEX_MAX_VALUE_LEN (%zu > %d)", + len, HIVEX_MAX_SUBKEYS); return NULL; } @@ -1303,10 +1250,7 @@ hivex_value_value (hive_h *h, hive_value_h value, size_t data_offset = le32toh (vk->data_offset); data_offset += 0x1000; if (!IS_VALID_BLOCK (h, data_offset)) { - DEBUG (2, "returning EFAULT because data " - "offset is not a valid block (0x%zx)", - data_offset); - errno = EFAULT; + SET_ERRNO (EFAULT, "data offset is not a valid block (0x%zx)", data_offset); free (ret); return NULL; } @@ -1323,12 +1267,12 @@ hivex_value_value (hive_h *h, hive_value_h value, memcpy (ret, data, len); return ret; } else { - if (!IS_VALID_BLOCK (h, data_offset) || !BLOCK_ID_EQ (h, data_offset, "db")) { - DEBUG (2, "warning: declared data length " - "is longer than the block and block is not a db block " - "(data 0x%zx, data len %zu)", - data_offset, len); - errno = EINVAL; + 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; } @@ -1338,10 +1282,10 @@ hivex_value_value (hive_h *h, hive_value_h value, blocklist_offset += 0x1000; size_t nr_blocks = le16toh (db->nr_blocks); if (!IS_VALID_BLOCK (h, blocklist_offset)) { - DEBUG (2, "warning: blocklist is not a " - "valid block (db block 0x%zx, blocklist 0x%zx)", - data_offset, blocklist_offset); - errno = EINVAL; + SET_ERRNO (EINVAL, + "blocklist is not a valid block " + "(db block 0x%zx, blocklist 0x%zx)", + data_offset, blocklist_offset); free (ret); return NULL; } @@ -1352,10 +1296,10 @@ hivex_value_value (hive_h *h, hive_value_h value, size_t subblock_offset = le32toh (bl->offset[i]); subblock_offset += 0x1000; if (!IS_VALID_BLOCK (h, subblock_offset)) { - DEBUG (2, "warning: subblock is not " - "valid (db block 0x%zx, block list 0x%zx, data subblock 0x%zx)", - data_offset, blocklist_offset, subblock_offset); - errno = EINVAL; + 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; } @@ -1448,7 +1392,7 @@ hivex_value_string (hive_h *h, hive_value_h value) if (t != hive_t_string && t != hive_t_expand_string && t != hive_t_link) { free (data); - errno = EINVAL; + SET_ERRNO (EINVAL, "type is not string/expand_string/link"); return NULL; } @@ -1517,7 +1461,7 @@ hivex_value_multiple_strings (hive_h *h, hive_value_h value) if (t != hive_t_multiple_strings) { free (data); - errno = EINVAL; + SET_ERRNO (EINVAL, "type is not multiple_strings"); return NULL; } @@ -1570,7 +1514,7 @@ hivex_value_dword (hive_h *h, hive_value_h value) if ((t != hive_t_dword && t != hive_t_dword_be) || len < 4) { free (data); - errno = EINVAL; + SET_ERRNO (EINVAL, "type is not dword/dword_be"); return -1; } @@ -1596,7 +1540,7 @@ hivex_value_qword (hive_h *h, hive_value_h value) if (t != hive_t_qword || len < 8) { free (data); - errno = EINVAL; + SET_ERRNO (EINVAL, "type is not qword or length < 8"); return -1; } @@ -1667,10 +1611,7 @@ hivex__visit_node (hive_h *h, hive_node_h node, int ret = -1; if (!BITMAP_TST (unvisited, node)) { - DEBUG (2, "contains cycle: visited node 0x%zx already", - node); - - errno = ELOOP; + SET_ERRNO (ELOOP, "contains cycle: visited node 0x%zx already", node); return skip_bad ? 0 : -1; } BITMAP_CLR (unvisited, node); @@ -1968,25 +1909,20 @@ allocate_page (hive_h *h, size_t allocation_hint) static size_t allocate_block (hive_h *h, size_t seg_len, const char id[2]) { - if (!h->writable) { - errno = EROFS; - return 0; - } + CHECK_WRITABLE (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. */ - DEBUG (2, "refusing too small allocation (%zu), returning ERANGE", seg_len); - errno = ERANGE; + SET_ERRNO (ERANGE, "refusing too small allocation (%zu)", seg_len); return 0; } /* Refuse really large allocations. */ if (seg_len > HIVEX_MAX_ALLOCATION) { - DEBUG (2, "refusing large allocation (%zu), returning ERANGE", seg_len); - errno = ERANGE; + SET_ERRNO (ERANGE, "refusing too large allocation (%zu)", seg_len); return 0; } @@ -2117,14 +2053,11 @@ hivex_commit (hive_h *h, const char *filename, int flags) int fd; if (flags != 0) { - errno = EINVAL; + SET_ERRNO (EINVAL, "flags != 0"); return -1; } - if (!h->writable) { - errno = EROFS; - return -1; - } + CHECK_WRITABLE (-1); filename = filename ? : h->filename; #ifdef O_CLOEXEC @@ -2289,23 +2222,20 @@ compare_name_with_nk_name (hive_h *h, const char *name, hive_node_h nk_offs) hive_node_h hivex_node_add_child (hive_h *h, hive_node_h parent, const char *name) { - if (!h->writable) { - errno = EROFS; - return 0; - } + CHECK_WRITABLE (0); if (!IS_VALID_BLOCK (h, parent) || !BLOCK_ID_EQ (h, parent, "nk")) { - errno = EINVAL; + SET_ERRNO (EINVAL, "invalid block or not an 'nk' block"); return 0; } if (name == NULL || strlen (name) == 0) { - errno = EINVAL; + SET_ERRNO (EINVAL, "name is NULL or zero length"); return 0; } if (hivex_node_get_child (h, parent, name) != 0) { - errno = EEXIST; + SET_ERRNO (EEXIST, "a child with that name exists already"); return 0; } @@ -2336,10 +2266,8 @@ hivex_node_add_child (hive_h *h, hive_node_h parent, const char *name) parent_sk_offset += 0x1000; if (!IS_VALID_BLOCK (h, parent_sk_offset) || !BLOCK_ID_EQ (h, parent_sk_offset, "sk")) { - DEBUG (2, "returning EFAULT" - " because parent sk is not a valid block (%zu)", - parent_sk_offset); - errno = EFAULT; + SET_ERRNO (EFAULT, + "parent sk is not a valid block (%zu)", parent_sk_offset); return 0; } struct ntreg_sk_record *sk @@ -2460,8 +2388,7 @@ hivex_node_add_child (hive_h *h, hive_node_h parent, const char *name) } /* Not found .. This is an internal error. */ - DEBUG (2, "returning ENOTSUP because could not find ri->lf link"); - errno = ENOTSUP; + SET_ERRNO (ENOTSUP, "could not find ri->lf link"); free (blocks); return 0; @@ -2491,8 +2418,7 @@ 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")) { - DEBUG (2, "not an sk record: 0x%zx", sk_offset); - errno = EFAULT; + SET_ERRNO (EFAULT, "not an sk record: 0x%zx", sk_offset); return -1; } @@ -2500,9 +2426,7 @@ delete_sk (hive_h *h, size_t sk_offset) (struct ntreg_sk_record *) ((char *) h->addr + sk_offset); if (sk->refcount == 0) { - DEBUG (2, "sk record already has refcount 0: 0x%zx", - sk_offset); - errno = EINVAL; + SET_ERRNO (EINVAL, "sk record already has refcount 0: 0x%zx", sk_offset); return -1; } @@ -2595,19 +2519,15 @@ delete_node (hive_h *h, void *opaque, hive_node_h node, const char *name) int hivex_node_delete_child (hive_h *h, hive_node_h node) { - if (!h->writable) { - errno = EROFS; - return -1; - } + CHECK_WRITABLE (-1); if (!IS_VALID_BLOCK (h, node) || !BLOCK_ID_EQ (h, node, "nk")) { - errno = EINVAL; + SET_ERRNO (EINVAL, "invalid block or not an 'nk' block"); return -1; } if (node == hivex_root (h)) { - DEBUG (2, "cannot delete root node"); - errno = EINVAL; + SET_ERRNO (EINVAL, "cannot delete root node"); return -1; } @@ -2651,8 +2571,7 @@ hivex_node_delete_child (hive_h *h, hive_node_h node) } } } - DEBUG (2, "could not find parent to child link"); - errno = ENOTSUP; + SET_ERRNO (ENOTSUP, "could not find parent to child link"); return -1; found:; @@ -2672,13 +2591,10 @@ 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; - } + CHECK_WRITABLE (-1); if (!IS_VALID_BLOCK (h, node) || !BLOCK_ID_EQ (h, node, "nk")) { - errno = EINVAL; + SET_ERRNO (EINVAL, "invalid block or not an 'nk' block"); return -1; } diff --git a/lib/offset-list.c b/lib/offset-list.c index b35969f..fcf9dcb 100644 --- a/lib/offset-list.c +++ b/lib/offset-list.c @@ -70,10 +70,10 @@ _hivex_add_to_offset_list (offset_list *list, size_t offset) assert (offset != 0); /* internal error if this happens */ if (list->len >= list->limit) { - hive_h *h = list->h; /* for DEBUG macro */ - DEBUG (2, "returning ERANGE because list of offsets " - "has exceeded limit (limit = %zu)\n", list->limit); - errno = ERANGE; + hive_h *h = list->h; /* for SET_ERRNO macro */ + SET_ERRNO (ERANGE, + "list of offsets has exceeded limit (limit = %zu)", + list->limit); return -1; } -- 1.8.3.1
From: "Richard W.M. Jones" <rjones@redhat.com> --- .gitignore | 223 +++++++++++++++++++++++++++++++------------------------------ 1 file changed, 114 insertions(+), 109 deletions(-) diff --git a/.gitignore b/.gitignore index 719cd7f..3feda38 100644 --- a/.gitignore +++ b/.gitignore @@ -1,124 +1,129 @@ -.gdb_history +/local* + *~ *.a -ABOUT-NLS -aclocal.m4 -autom4te.cache *.bak -ChangeLog *.class *.cma *.cmi *.cmo *.cmx *.cmxa -compile -config.guess -config.h -config.h.in -config.log -config.status -config.sub -configure -depcomp -.deps -generator/.pod2text.data.version.2 -generator/stamp-generator -hivex.pc -hivex-*.tar.gz -html/hivex.3.html -html/hivexget.1.html -html/hivexml.1.html -html/hivexregedit.1.html -html/hivexsh.1.html -images/large -images/mklarge -install-sh *.la -lib/*.3 -lib/hivex.h -lib/hivex.pod -lib/hivex.syms -lib/test-just-header -lib/tools/*.opt -.libs -libtool *.lo -localconfigure -localrepo -ltmain.sh -m4/gnulib-cache.m4 -m4/intmax.m4 -m4/libtool.m4 -m4/lt~obsolete.m4 -m4/ltoptions.m4 -m4/ltsugar.m4 -m4/ltversion.m4 -Makefile -Makefile.in -missing +*.log *.o -ocaml/hivex.ml -ocaml/hivex.mli -ocaml/hivex_c.c -ocaml/META -ocaml/*.so -ocaml/t/hivex_005_load -ocaml/t/hivex_010_open -ocaml/t/hivex_020_root -ocaml/t/hivex_100_errors -ocaml/t/hivex_110_gc_handle -ocaml/t/hivex_120_rlenvalue -ocaml/t/hivex_200_write -ocaml/t/hivex_300_fold -perl/blib -perl/Hivex.bs -perl/Hivex.c -perl/Hivex.xs -perl/lib/Win/Hivex.pm -perl/Makefile-pl -perl/Makefile-pl.old -perl/Makefile.PL -perl/MYMETA.json -perl/MYMETA.yml -perl/pm_to_blib +*.trs + pod2htm?.tmp -po/*.gmo -po/Makevars.template -po/POTFILES -po/remove-potcdate.sed -po/stamp-it -po/stamp-po -po/LINGUAS -po/Makefile.in.in -po/Makevars -po/Rules-quot -po/boldquot.sed -po/en@boldquot.header -po/en@quot.header -po/insert-header.sin -po/quot.sed -po/remove-potcdate.sin -python/*.pyc -python/__pycache__/ -python/hivex-py.c -python/hivex.py -python/run-python-tests -regedit/hivexregedit.1 -ruby/doc/site/api -ruby/ext/hivex/extconf.h -ruby/ext/hivex/_hivex.bundle -ruby/ext/hivex/_hivex.c -ruby/ext/hivex/_hivex.so -ruby/ext/hivex/mkmf.log -ruby/Rakefile -sh/*.1 -sh/hivexsh -stamp-h1 -xml/*.1 -xml/hivexml -/GNUmakefile -/maint.mk + +.deps +.libs +Makefile +Makefile.in + +/.gdb_history +/.git-module-status +/ABOUT-NLS +/aclocal.m4 +/autom4te.cache /build-aux +/ChangeLog +/compile +/config.guess +/config.h +/config.h.in +/config.log +/config.status +/config.sub +/configure +/depcomp +/generator/.pod2text.data.version.2 +/generator/stamp-generator +/GNUmakefile /gnulib -.git-module-status +/hivex.pc +/hivex-*.tar.gz +/html/hivex.3.html +/html/hivexget.1.html +/html/hivexml.1.html +/html/hivexregedit.1.html +/html/hivexsh.1.html +/images/large +/images/mklarge +/install-sh +/lib/*.3 +/lib/hivex.h +/lib/hivex.pod +/lib/hivex.syms +/lib/test-just-header +/lib/tools/*.opt +/libtool +/ltmain.sh +/m4/gnulib-cache.m4 +/m4/intmax.m4 +/m4/libtool.m4 +/m4/lt~obsolete.m4 +/m4/ltoptions.m4 +/m4/ltsugar.m4 +/m4/ltversion.m4 +/maint.mk +/missing +/ocaml/hivex.ml +/ocaml/hivex.mli +/ocaml/hivex_c.c +/ocaml/META +/ocaml/*.so +/ocaml/t/hivex_005_load +/ocaml/t/hivex_010_open +/ocaml/t/hivex_020_root +/ocaml/t/hivex_100_errors +/ocaml/t/hivex_110_gc_handle +/ocaml/t/hivex_120_rlenvalue +/ocaml/t/hivex_200_write +/ocaml/t/hivex_300_fold +/perl/blib +/perl/Hivex.bs +/perl/Hivex.c +/perl/Hivex.xs +/perl/lib/Win/Hivex.pm +/perl/Makefile-pl +/perl/Makefile-pl.old +/perl/Makefile.PL +/perl/MYMETA.json +/perl/MYMETA.yml +/perl/pm_to_blib +/po/*.gmo +/po/Makevars.template +/po/POTFILES +/po/remove-potcdate.sed +/po/stamp-it +/po/stamp-po +/po/LINGUAS +/po/Makefile.in.in +/po/Makevars +/po/Rules-quot +/po/boldquot.sed +/po/en@boldquot.header +/po/en@quot.header +/po/insert-header.sin +/po/quot.sed +/po/remove-potcdate.sin +/python/*.pyc +/python/__pycache__/ +/python/hivex-py.c +/python/hivex.py +/python/run-python-tests +/regedit/hivexregedit.1 +/ruby/doc/site/api +/ruby/ext/hivex/extconf.h +/ruby/ext/hivex/_hivex.bundle +/ruby/ext/hivex/_hivex.c +/ruby/ext/hivex/_hivex.so +/ruby/ext/hivex/mkmf.log +/ruby/Rakefile +/sh/*.1 +/sh/hivexsh +/stamp-h1 +/xml/*.1 +/xml/hivexml -- 1.8.3.1
Richard W.M. Jones
2013-Jul-25 10:38 UTC
[Libguestfs] [PATCH hivex 09/19] lib: Split up the large file lib/hivex.c into multiple small source files.
From: "Richard W.M. Jones" <rjones@redhat.com> --- lib/Makefile.am | 10 +- lib/handle.c | 403 ++++++++ lib/hivex-internal.h | 184 ++++ lib/hivex.c | 2765 -------------------------------------------------- lib/node.c | 438 ++++++++ lib/utf16.c | 104 ++ lib/util.c | 39 + lib/value.c | 567 +++++++++++ lib/visit.c | 283 ++++++ lib/write.c | 955 +++++++++++++++++ 10 files changed, 2981 insertions(+), 2767 deletions(-) create mode 100644 lib/handle.c delete mode 100644 lib/hivex.c create mode 100644 lib/node.c create mode 100644 lib/utf16.c create mode 100644 lib/util.c create mode 100644 lib/value.c create mode 100644 lib/visit.c create mode 100644 lib/write.c diff --git a/lib/Makefile.am b/lib/Makefile.am index ea130d4..f711f22 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -26,11 +26,17 @@ lib_LTLIBRARIES = libhivex.la libhivex_la_SOURCES = \ byte_conversions.h \ gettext.h \ - hivex.c \ + handle.c \ hivex.h \ hivex-internal.h \ mmap.h \ - offset-list.c + node.c \ + offset-list.c \ + utf16.c \ + util.c \ + value.c \ + visit.c \ + write.c libhivex_la_LIBADD = ../gnulib/lib/libgnu.la $(LTLIBOBJS) libhivex_la_LDFLAGS = \ diff --git a/lib/handle.c b/lib/handle.c new file mode 100644 index 0000000..a818c4a --- /dev/null +++ b/lib/handle.c @@ -0,0 +1,403 @@ +/* hivex - Windows Registry "hive" extraction library. + * Copyright (C) 2009-2011 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: + * Copyright (c) 2000-2004, Markus Stephany. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * See file LICENSE for the full license. + */ + +#include <config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <inttypes.h> +#include <string.h> +#include <unistd.h> +#include <fcntl.h> +#include <sys/stat.h> +#include <errno.h> +#include <assert.h> + +#ifdef HAVE_MMAP +#include <sys/mman.h> +#else +/* On systems without mmap (and munmap), use a replacement function. */ +#include "mmap.h" +#endif + +#include "full-read.h" +#include "full-write.h" + +#include "hivex.h" +#include "hivex-internal.h" +#include "byte_conversions.h" + +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; +} + +#define HIVEX_OPEN_MSGLVL_MASK (HIVEX_OPEN_VERBOSE|HIVEX_OPEN_DEBUG) + +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 = flags & HIVEX_OPEN_MSGLVL_MASK; + + const char *debug = getenv ("HIVEX_DEBUG"); + if (debug && STREQ (debug, "1")) + h->msglvl = 2; + + DEBUG (2, "created handle %p", h); + + h->writable = !!(flags & HIVEX_OPEN_WRITE); + h->filename = strdup (filename); + if (h->filename == NULL) + goto error; + +#ifdef O_CLOEXEC + h->fd = open (filename, O_RDONLY | O_CLOEXEC | O_BINARY); +#else + h->fd = open (filename, O_RDONLY | O_BINARY); +#endif + if (h->fd == -1) + goto error; +#ifndef O_CLOEXEC + fcntl (h->fd, F_SETFD, FD_CLOEXEC); +#endif + + struct stat statbuf; + if (fstat (h->fd, &statbuf) == -1) + goto error; + + h->size = statbuf.st_size; + + if (!h->writable) { + h->addr = mmap (NULL, h->size, PROT_READ, MAP_SHARED, h->fd, 0); + if (h->addr == MAP_FAILED) + goto error; + + DEBUG (2, "mapped file at %p", 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; + + /* We don't need the file descriptor along this path, since we + * have read all the data. + */ + if (close (h->fd) == -1) + goto error; + h->fd = -1; + } + + /* Check header. */ + if (h->hdr->magic[0] != 'r' || + h->hdr->magic[1] != 'e' || + h->hdr->magic[2] != 'g' || + h->hdr->magic[3] != 'f') { + SET_ERRNO (ENOTSUP, + "%s: not a Windows NT Registry hive file", filename); + goto error; + } + + /* Check major version. */ + uint32_t major_ver = le32toh (h->hdr->major_ver); + if (major_ver != 1) { + SET_ERRNO (ENOTSUP, + "%s: hive file major version %" PRIu32 " (expected 1)", + filename, major_ver); + goto error; + } + + h->bitmap = calloc (1 + h->size / 32, 1); + if (h->bitmap == NULL) + goto error; + + /* Header checksum. */ + uint32_t sum = header_checksum (h); + if (sum != le32toh (h->hdr->csum)) { + SET_ERRNO (EINVAL, "%s: bad checksum in hive header", filename); + goto error; + } + + /* Last modified time. */ + h->last_modified = le64toh ((int64_t) h->hdr->last_modified); + + if (h->msglvl >= 2) { + char *name = _hivex_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" + " last modified %" PRIu64 "\n" + " (Windows filetime, x 100 ns since 1601-01-01)\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), + h->last_modified, + 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; + + DEBUG (2, "root offset = 0x%zx", 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 + * we read to a hash table so pointers will only reference the start + * of valid blocks. + */ + size_t off; + struct ntreg_hbin_page *page; + for (off = 0x1000; off < h->size; off += le32toh (page->page_size)) { + if (off >= h->endpages) + break; + + page = (struct ntreg_hbin_page *) ((char *) h->addr + off); + if (page->magic[0] != 'h' || + page->magic[1] != 'b' || + page->magic[2] != 'i' || + page->magic[3] != 'n') { + SET_ERRNO (ENOTSUP, + "%s: trailing garbage at end of file " + "(at 0x%zx, after %zu pages)", + filename, off, pages); + goto error; + } + + size_t page_size = le32toh (page->page_size); + DEBUG (2, "page at 0x%zx, size %zu", 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) { + SET_ERRNO (ENOTSUP, + "%s: page size %zu at 0x%zx, bad registry", + filename, page_size, off); + goto error; + } + + /* Read the blocks in this page. */ + size_t blkoff; + struct ntreg_hbin_block *block; + size_t seg_len; + for (blkoff = off + 0x20; + blkoff < off + page_size; + blkoff += seg_len) { + blocks++; + + int is_root = blkoff == h->rootoffs; + if (is_root) + seen_root_block = 1; + + block = (struct ntreg_hbin_block *) ((char *) h->addr + blkoff); + int used; + seg_len = block_len (h, blkoff, &used); + if (seg_len <= 4 || (seg_len & 3) != 0) { + SET_ERRNO (ENOTSUP, + "%s: block size %" PRIu32 " at 0x%zx, bad registry", + filename, le32toh (block->seg_len), blkoff); + goto error; + } + + DEBUG (2, "%s block id %d,%d at 0x%zx size %zu%s", + 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) { + used_blocks++; + used_size += seg_len; + + /* Root block must be an nk-block. */ + if (is_root && (block->id[0] != 'n' || block->id[1] != 'k')) + bad_root_block = 1; + + /* Note this blkoff is a valid address. */ + BITMAP_SET (h->bitmap, blkoff); + } + } + } + + if (!seen_root_block) { + SET_ERRNO (ENOTSUP, "%s: no root block found", filename); + goto error; + } + + if (bad_root_block) { + SET_ERRNO (ENOTSUP, "%s: bad root block (free or not nk)", filename); + goto error; + } + + DEBUG (1, "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", + pages, smallest_page, largest_page, + blocks, smallest_block, blocks_bytes / blocks, largest_block, + used_blocks, used_size); + + return h; + + error:; + int err = errno; + if (h) { + free (h->bitmap); + 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; + return NULL; +} + +int +hivex_close (hive_h *h) +{ + int r; + + DEBUG (1, "hivex_close"); + + free (h->bitmap); + if (!h->writable) + munmap (h->addr, h->size); + else + free (h->addr); + if (h->fd >= 0) + r = close (h->fd); + else + r = 0; + free (h->filename); + free (h); + + return r; +} + +int +hivex_commit (hive_h *h, const char *filename, int flags) +{ + int fd; + + if (flags != 0) { + SET_ERRNO (EINVAL, "flags != 0"); + return -1; + } + + CHECK_WRITABLE (-1); + + filename = filename ? : h->filename; +#ifdef O_CLOEXEC + fd = open (filename, O_WRONLY|O_CREAT|O_TRUNC|O_NOCTTY|O_CLOEXEC|O_BINARY, + 0666); +#else + fd = open (filename, O_WRONLY|O_CREAT|O_TRUNC|O_NOCTTY|O_BINARY, 0666); +#endif + if (fd == -1) + return -1; +#ifndef O_CLOEXEC + fcntl (fd, F_SETFD, FD_CLOEXEC); +#endif + + /* 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); + + DEBUG (2, "hivex_commit: new header checksum: 0x%x", 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; +} diff --git a/lib/hivex-internal.h b/lib/hivex-internal.h index 62182a0..1d1083a 100644 --- a/lib/hivex-internal.h +++ b/lib/hivex-internal.h @@ -67,6 +67,174 @@ struct hive_h { #endif }; +/* Format of registry blocks. NB. All fields are little endian. */ +struct ntreg_header { + char magic[4]; /* "regf" */ + uint32_t sequence1; + uint32_t sequence2; + int64_t last_modified; + 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; /* pointer AFTER last hbin in file - 4KB */ + uint32_t unknown7; /* 1 */ + /* 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 page_size; /* size of this page (multiple of 4KB) */ + char unknown[20]; + /* Linked list of blocks follows here. */ +} __attribute__((__packed__)); + +struct ntreg_hbin_block { + int32_t seg_len; /* length of this block (-ve for used block) */ + char id[2]; /* the block type (eg. "nk" for nk record) */ + /* Block data follows here. */ +} __attribute__((__packed__)); + +#define BLOCK_ID_EQ(h,offs,eqid) \ + (STREQLEN (((struct ntreg_hbin_block *)((char *) (h)->addr + (offs)))->id, (eqid), 2)) + +struct ntreg_nk_record { + int32_t seg_len; /* length (always -ve because used) */ + char id[2]; /* "nk" */ + uint16_t flags; + int64_t timestamp; + uint32_t unknown1; + uint32_t parent; /* offset of owner/parent */ + uint32_t nr_subkeys; /* number of subkeys */ + uint32_t nr_subkeys_volatile; + uint32_t subkey_lf; /* lf record containing list of subkeys */ + 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 */ + 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 */ +} __attribute__((__packed__)); + +struct ntreg_lf_record { + int32_t seg_len; + 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 hash[4]; /* hash of subkey name */ + } keys[1]; +} __attribute__((__packed__)); + +struct ntreg_ri_record { + int32_t seg_len; + char id[2]; /* "ri" */ + uint16_t nr_offsets; /* number of pointers to lh records */ + uint32_t offset[1]; /* list of pointers to lh records */ +} __attribute__((__packed__)); + +/* This has no ID header. */ +struct ntreg_value_list { + int32_t seg_len; + uint32_t offset[1]; /* list of pointers to vk records */ +} __attribute__((__packed__)); + +struct ntreg_vk_record { + int32_t seg_len; /* length (always -ve because used) */ + char id[2]; /* "vk" */ + uint16_t name_len; /* length of name */ + /* length of the data: + * If data_len is <= 4, then it's stored inline. + * Top bit is set to indicate inline. + */ + uint32_t data_len; + uint32_t data_offset; /* pointer to the data (or data if inline) */ + 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__)); + +struct ntreg_db_record { + int32_t seg_len; /* length (always -ve because used) */ + char id[2]; /* "db" */ + uint16_t nr_blocks; + uint32_t blocklist_offset; + uint32_t unknown1; +} __attribute__((__packed__)); + +struct ntreg_db_block { + int32_t seg_len; + char data[1]; +} __attribute__((__packed__)); + +static inline size_t +block_len (hive_h *h, size_t blkoff, int *used) +{ + struct ntreg_hbin_block *block; + block = (struct ntreg_hbin_block *) ((char *) h->addr + blkoff); + + int32_t len = le32toh (block->seg_len); + if (len < 0) { + if (used) *used = 1; + len = -len; + } else { + if (used) *used = 0; + } + + return (size_t) len; +} + +/* node.c */ +#define GET_CHILDREN_NO_CHECK_NK 1 +extern int _hivex_get_children (hive_h *h, hive_node_h node, hive_node_h **children_ret, size_t **blocks_ret, int flags); + /* offset-list.c */ typedef struct offset_list offset_list; struct offset_list { @@ -84,6 +252,16 @@ extern void _hivex_set_offset_list_limit (offset_list *list, size_t limit); extern void _hivex_free_offset_list (offset_list *list); extern size_t * _hivex_return_offset_list (offset_list *list); +/* utf16.c */ +extern char *_hivex_windows_utf16_to_utf8 (/* const */ char *input, size_t len); +extern size_t _hivex_utf16_string_len_in_bytes_max (const char *str, size_t len); + +/* util.c */ +extern void _hivex_free_strings (char **argv); + +/* value.c */ +extern int _hivex_get_values (hive_h *h, hive_node_h node, hive_value_h **values_ret, size_t **blocks_ret); + #define STREQ(a,b) (strcmp((a),(b)) == 0) #define STRCASEEQ(a,b) (strcasecmp((a),(b)) == 0) #define STRNEQ(a,b) (strcmp((a),(b)) != 0) @@ -117,4 +295,10 @@ extern size_t * _hivex_return_offset_list (offset_list *list); } \ } while (0) +/* These limits are in place to stop really stupid stuff and/or exploits. */ +#define HIVEX_MAX_SUBKEYS 15000 +#define HIVEX_MAX_VALUES 10000 +#define HIVEX_MAX_VALUE_LEN 1000000 +#define HIVEX_MAX_ALLOCATION 1000000 + #endif /* HIVEX_INTERNAL_H_ */ diff --git a/lib/hivex.c b/lib/hivex.c deleted file mode 100644 index ebf1957..0000000 --- a/lib/hivex.c +++ /dev/null @@ -1,2765 +0,0 @@ -/* hivex - Windows Registry "hive" extraction library. - * Copyright (C) 2009-2011 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: - * Copyright (c) 2000-2004, Markus Stephany. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * See file LICENSE for the full license. - */ - -#include <config.h> - -#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 <errno.h> -#include <iconv.h> -#include <sys/stat.h> -#include <assert.h> - -#ifdef HAVE_MMAP -#include <sys/mman.h> -#else -/* On systems without mmap (and munmap), use a replacement function. */ -#include "mmap.h" -#endif - -#include "c-ctype.h" -#include "full-read.h" -#include "full-write.h" - -#include "hivex.h" -#include "hivex-internal.h" -#include "byte_conversions.h" - -/* These limits are in place to stop really stupid stuff and/or exploits. */ -#define HIVEX_MAX_SUBKEYS 15000 -#define HIVEX_MAX_VALUES 10000 -#define HIVEX_MAX_VALUE_LEN 1000000 -#define HIVEX_MAX_ALLOCATION 1000000 - -static char *windows_utf16_to_utf8 (/* const */ char *input, size_t len); -static size_t utf16_string_len_in_bytes_max (const char *str, size_t len); - -/* NB. All fields are little endian. */ -struct ntreg_header { - char magic[4]; /* "regf" */ - uint32_t sequence1; - uint32_t sequence2; - int64_t last_modified; - 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; /* pointer AFTER last hbin in file - 4KB */ - uint32_t unknown7; /* 1 */ - /* 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 page_size; /* size of this page (multiple of 4KB) */ - char unknown[20]; - /* Linked list of blocks follows here. */ -} __attribute__((__packed__)); - -struct ntreg_hbin_block { - int32_t seg_len; /* length of this block (-ve for used block) */ - char id[2]; /* the block type (eg. "nk" for nk record) */ - /* Block data follows here. */ -} __attribute__((__packed__)); - -#define BLOCK_ID_EQ(h,offs,eqid) \ - (STREQLEN (((struct ntreg_hbin_block *)((char *) (h)->addr + (offs)))->id, (eqid), 2)) - -static size_t -block_len (hive_h *h, size_t blkoff, int *used) -{ - struct ntreg_hbin_block *block; - block = (struct ntreg_hbin_block *) ((char *) h->addr + blkoff); - - int32_t len = le32toh (block->seg_len); - if (len < 0) { - if (used) *used = 1; - len = -len; - } else { - if (used) *used = 0; - } - - return (size_t) len; -} - -struct ntreg_nk_record { - int32_t seg_len; /* length (always -ve because used) */ - char id[2]; /* "nk" */ - uint16_t flags; - int64_t timestamp; - uint32_t unknown1; - uint32_t parent; /* offset of owner/parent */ - uint32_t nr_subkeys; /* number of subkeys */ - uint32_t nr_subkeys_volatile; - uint32_t subkey_lf; /* lf record containing list of subkeys */ - 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 */ - 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 */ -} __attribute__((__packed__)); - -struct ntreg_lf_record { - int32_t seg_len; - 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 hash[4]; /* hash of subkey name */ - } keys[1]; -} __attribute__((__packed__)); - -struct ntreg_ri_record { - int32_t seg_len; - char id[2]; /* "ri" */ - uint16_t nr_offsets; /* number of pointers to lh records */ - uint32_t offset[1]; /* list of pointers to lh records */ -} __attribute__((__packed__)); - -/* This has no ID header. */ -struct ntreg_value_list { - int32_t seg_len; - uint32_t offset[1]; /* list of pointers to vk records */ -} __attribute__((__packed__)); - -struct ntreg_vk_record { - int32_t seg_len; /* length (always -ve because used) */ - char id[2]; /* "vk" */ - uint16_t name_len; /* length of name */ - /* length of the data: - * If data_len is <= 4, then it's stored inline. - * Top bit is set to indicate inline. - */ - uint32_t data_len; - uint32_t data_offset; /* pointer to the data (or data if inline) */ - 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__)); - -struct ntreg_db_record { - int32_t seg_len; /* length (always -ve because used) */ - char id[2]; /* "db" */ - uint16_t nr_blocks; - uint32_t blocklist_offset; - uint32_t unknown1; -} __attribute__((__packed__)); - -struct ntreg_db_block { - int32_t seg_len; - char data[1]; -} __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; -} - -#define HIVEX_OPEN_MSGLVL_MASK (HIVEX_OPEN_VERBOSE|HIVEX_OPEN_DEBUG) - -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 = flags & HIVEX_OPEN_MSGLVL_MASK; - - const char *debug = getenv ("HIVEX_DEBUG"); - if (debug && STREQ (debug, "1")) - h->msglvl = 2; - - DEBUG (2, "created handle %p", h); - - h->writable = !!(flags & HIVEX_OPEN_WRITE); - h->filename = strdup (filename); - if (h->filename == NULL) - goto error; - -#ifdef O_CLOEXEC - h->fd = open (filename, O_RDONLY | O_CLOEXEC | O_BINARY); -#else - h->fd = open (filename, O_RDONLY | O_BINARY); -#endif - if (h->fd == -1) - goto error; -#ifndef O_CLOEXEC - fcntl (h->fd, F_SETFD, FD_CLOEXEC); -#endif - - struct stat statbuf; - if (fstat (h->fd, &statbuf) == -1) - goto error; - - h->size = statbuf.st_size; - - if (!h->writable) { - h->addr = mmap (NULL, h->size, PROT_READ, MAP_SHARED, h->fd, 0); - if (h->addr == MAP_FAILED) - goto error; - - DEBUG (2, "mapped file at %p", 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; - - /* We don't need the file descriptor along this path, since we - * have read all the data. - */ - if (close (h->fd) == -1) - goto error; - h->fd = -1; - } - - /* Check header. */ - if (h->hdr->magic[0] != 'r' || - h->hdr->magic[1] != 'e' || - h->hdr->magic[2] != 'g' || - h->hdr->magic[3] != 'f') { - SET_ERRNO (ENOTSUP, - "%s: not a Windows NT Registry hive file", filename); - goto error; - } - - /* Check major version. */ - uint32_t major_ver = le32toh (h->hdr->major_ver); - if (major_ver != 1) { - SET_ERRNO (ENOTSUP, - "%s: hive file major version %" PRIu32 " (expected 1)", - filename, major_ver); - goto error; - } - - h->bitmap = calloc (1 + h->size / 32, 1); - if (h->bitmap == NULL) - goto error; - - /* Header checksum. */ - uint32_t sum = header_checksum (h); - if (sum != le32toh (h->hdr->csum)) { - SET_ERRNO (EINVAL, "%s: bad checksum in hive header", filename); - goto error; - } - - /* Last modified time. */ - h->last_modified = le64toh ((int64_t) h->hdr->last_modified); - - 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" - " last modified %" PRIu64 "\n" - " (Windows filetime, x 100 ns since 1601-01-01)\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), - h->last_modified, - 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; - - DEBUG (2, "root offset = 0x%zx", 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 - * we read to a hash table so pointers will only reference the start - * of valid blocks. - */ - size_t off; - struct ntreg_hbin_page *page; - for (off = 0x1000; off < h->size; off += le32toh (page->page_size)) { - if (off >= h->endpages) - break; - - page = (struct ntreg_hbin_page *) ((char *) h->addr + off); - if (page->magic[0] != 'h' || - page->magic[1] != 'b' || - page->magic[2] != 'i' || - page->magic[3] != 'n') { - SET_ERRNO (ENOTSUP, - "%s: trailing garbage at end of file " - "(at 0x%zx, after %zu pages)", - filename, off, pages); - goto error; - } - - size_t page_size = le32toh (page->page_size); - DEBUG (2, "page at 0x%zx, size %zu", 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) { - SET_ERRNO (ENOTSUP, - "%s: page size %zu at 0x%zx, bad registry", - filename, page_size, off); - goto error; - } - - /* Read the blocks in this page. */ - size_t blkoff; - struct ntreg_hbin_block *block; - size_t seg_len; - for (blkoff = off + 0x20; - blkoff < off + page_size; - blkoff += seg_len) { - blocks++; - - int is_root = blkoff == h->rootoffs; - if (is_root) - seen_root_block = 1; - - block = (struct ntreg_hbin_block *) ((char *) h->addr + blkoff); - int used; - seg_len = block_len (h, blkoff, &used); - if (seg_len <= 4 || (seg_len & 3) != 0) { - SET_ERRNO (ENOTSUP, - "%s: block size %" PRIu32 " at 0x%zx, bad registry", - filename, le32toh (block->seg_len), blkoff); - goto error; - } - - DEBUG (2, "%s block id %d,%d at 0x%zx size %zu%s", - 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) { - used_blocks++; - used_size += seg_len; - - /* Root block must be an nk-block. */ - if (is_root && (block->id[0] != 'n' || block->id[1] != 'k')) - bad_root_block = 1; - - /* Note this blkoff is a valid address. */ - BITMAP_SET (h->bitmap, blkoff); - } - } - } - - if (!seen_root_block) { - SET_ERRNO (ENOTSUP, "%s: no root block found", filename); - goto error; - } - - if (bad_root_block) { - SET_ERRNO (ENOTSUP, "%s: bad root block (free or not nk)", filename); - goto error; - } - - DEBUG (1, "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", - pages, smallest_page, largest_page, - blocks, smallest_block, blocks_bytes / blocks, largest_block, - used_blocks, used_size); - - return h; - - error:; - int err = errno; - if (h) { - free (h->bitmap); - 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; - return NULL; -} - -int -hivex_close (hive_h *h) -{ - int r; - - DEBUG (1, "hivex_close"); - - free (h->bitmap); - if (!h->writable) - munmap (h->addr, h->size); - else - free (h->addr); - if (h->fd >= 0) - r = close (h->fd); - else - r = 0; - free (h->filename); - free (h); - - return r; -} - -/*---------------------------------------------------------------------- - * Reading. - */ - -hive_node_h -hivex_root (hive_h *h) -{ - hive_node_h ret = h->rootoffs; - if (!IS_VALID_BLOCK (h, ret)) { - SET_ERRNO (HIVEX_NO_KEY, "no root key"); - return 0; - } - return ret; -} - -size_t -hivex_node_struct_length (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 name_len = le16toh (nk->name_len); - /* -1 to avoid double-counting the first name character */ - size_t ret = name_len + sizeof (struct ntreg_nk_record) - 1; - int used; - size_t seg_len = block_len (h, node, &used); - if (ret > seg_len) { - SET_ERRNO (EFAULT, "node name is too long (%zu, %zu)", name_len, seg_len); - return 0; - } - return ret; -} - -char * -hivex_node_name (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 NULL; - } - - struct ntreg_nk_record *nk - (struct ntreg_nk_record *) ((char *) h->addr + node); - - /* AFAIK the node name is always plain ASCII, so no conversion - * to UTF-8 is necessary. However we do need to nul-terminate - * the string. - */ - - /* nk->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 (nk->name_len); - size_t seg_len = block_len (h, node, NULL); - if (sizeof (struct ntreg_nk_record) + len - 1 > seg_len) { - SET_ERRNO (EFAULT, "node name is too long (%zu, %zu)", len, seg_len); - return NULL; - } - - char *ret = malloc (len + 1); - if (ret == NULL) - return NULL; - memcpy (ret, nk->name, len); - ret[len] = '\0'; - return ret; -} - -static int64_t -timestamp_check (hive_h *h, hive_node_h node, int64_t timestamp) -{ - if (timestamp < 0) { - SET_ERRNO (EINVAL, - "negative time reported at %zu: %" PRIi64, node, timestamp); - return -1; - } - - return timestamp; -} - -int64_t -hivex_last_modified (hive_h *h) -{ - return timestamp_check (h, 0, h->last_modified); -} - -int64_t -hivex_node_timestamp (hive_h *h, hive_node_h node) -{ - int64_t 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); - - ret = le64toh (nk->timestamp); - return timestamp_check (h, node, ret); -} - -#if 0 -/* I think the documentation for the sk and classname fields in the nk - * record is wrong, or else the offset field is in the wrong place. - * Otherwise this makes no sense. Disabled this for now -- it's not - * useful for reading the registry anyway. - */ - -hive_security_h -hivex_node_security (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 *) (h->addr + node); - - hive_node_h ret = le32toh (nk->sk); - ret += 0x1000; - if (!IS_VALID_BLOCK (h, ret)) { - SET_ERRNO (EFAULT, "invalid block"); - return 0; - } - return ret; -} - -hive_classname_h -hivex_node_classname (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 *) (h->addr + node); - - hive_node_h ret = le32toh (nk->classname); - ret += 0x1000; - if (!IS_VALID_BLOCK (h, ret)) { - SET_ERRNO (EFAULT, "invalid block"); - return 0; - } - return ret; -} -#endif - -/* 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")) { - 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_subkeys_in_nk = le32toh (nk->nr_subkeys); - - offset_list children, blocks; - _hivex_init_offset_list (h, &children); - _hivex_init_offset_list (h, &blocks); - - /* Deal with the common "no subkeys" case quickly. */ - if (nr_subkeys_in_nk == 0) - goto ok; - - /* Arbitrarily limit the number of subkeys we will ever deal with. */ - if (nr_subkeys_in_nk > HIVEX_MAX_SUBKEYS) { - SET_ERRNO (ERANGE, - "nr_subkeys_in_nk > HIVEX_MAX_SUBKEYS (%zu > %d)", - nr_subkeys_in_nk, HIVEX_MAX_SUBKEYS); - goto error; - } - - /* Preallocate space for the children. */ - if (_hivex_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. - */ - size_t subkey_lf = le32toh (nk->subkey_lf); - subkey_lf += 0x1000; - if (!IS_VALID_BLOCK (h, subkey_lf)) { - SET_ERRNO (EFAULT, - "subkey_lf is not a valid block (0x%zx)", subkey_lf); - goto error; - } - - if (_hivex_add_to_offset_list (&blocks, subkey_lf) == -1) - goto error; - - struct ntreg_hbin_block *block - (struct ntreg_hbin_block *) ((char *) h->addr + subkey_lf); - - /* Points to lf-record? (Note, also "lh" but that is basically the - * same as "lf" as far as we are concerned here). - */ - if (block->id[0] == 'l' && (block->id[1] == 'f' || block->id[1] == 'h')) { - struct ntreg_lf_record *lf = (struct ntreg_lf_record *) block; - - /* Check number of subkeys in the nk-record matches number of subkeys - * in the lf-record. - */ - size_t nr_subkeys_in_lf = le16toh (lf->nr_keys); - - if (nr_subkeys_in_nk != nr_subkeys_in_lf) { - SET_ERRNO (ENOTSUP, - "nr_subkeys_in_nk = %zu is not equal to nr_subkeys_in_lf = %zu", - nr_subkeys_in_nk, nr_subkeys_in_lf); - goto error; - } - - size_t len = block_len (h, subkey_lf, NULL); - if (8 + nr_subkeys_in_lf * 8 > len) { - SET_ERRNO (EFAULT, "too many subkeys (%zu, %zu)", nr_subkeys_in_lf, len); - goto error; - } - - size_t i; - for (i = 0; i < nr_subkeys_in_lf; ++i) { - hive_node_h subkey = le32toh (lf->keys[i].offset); - subkey += 0x1000; - if (!(flags & GET_CHILDREN_NO_CHECK_NK)) { - if (!IS_VALID_BLOCK (h, subkey)) { - SET_ERRNO (EFAULT, "subkey is not a valid block (0x%zx)", subkey); - goto error; - } - } - if (_hivex_add_to_offset_list (&children, subkey) == -1) - goto error; - } - goto ok; - } - /* Points to ri-record? */ - else if (block->id[0] == 'r' && block->id[1] == 'i') { - struct ntreg_ri_record *ri = (struct ntreg_ri_record *) block; - - size_t nr_offsets = le16toh (ri->nr_offsets); - - /* Count total number of children. */ - size_t i, count = 0; - for (i = 0; i < nr_offsets; ++i) { - hive_node_h offset = le32toh (ri->offset[i]); - offset += 0x1000; - if (!IS_VALID_BLOCK (h, offset)) { - SET_ERRNO (EFAULT, "ri-offset is not a valid block (0x%zx)", offset); - goto error; - } - if (!BLOCK_ID_EQ (h, offset, "lf") && !BLOCK_ID_EQ (h, offset, "lh")) { - struct ntreg_lf_record *block - (struct ntreg_lf_record *) ((char *) h->addr + offset); - SET_ERRNO (ENOTSUP, - "ri-record offset does not point to lf/lh (0x%zx, %d, %d)", - offset, block->id[0], block->id[1]); - goto error; - } - - if (_hivex_add_to_offset_list (&blocks, offset) == -1) - goto error; - - struct ntreg_lf_record *lf - (struct ntreg_lf_record *) ((char *) h->addr + offset); - - count += le16toh (lf->nr_keys); - } - - if (nr_subkeys_in_nk != count) { - SET_ERRNO (ENOTSUP, - "nr_subkeys_in_nk = %zu is not equal to counted = %zu", - nr_subkeys_in_nk, count); - goto error; - } - - /* Copy list of children. Note nr_subkeys_in_nk is limited to - * something reasonable above. - */ - for (i = 0; i < nr_offsets; ++i) { - hive_node_h offset = le32toh (ri->offset[i]); - offset += 0x1000; - if (!IS_VALID_BLOCK (h, offset)) { - SET_ERRNO (EFAULT, "ri-offset is not a valid block (0x%zx)", offset); - goto error; - } - if (!BLOCK_ID_EQ (h, offset, "lf") && !BLOCK_ID_EQ (h, offset, "lh")) { - struct ntreg_lf_record *block - (struct ntreg_lf_record *) ((char *) h->addr + offset); - SET_ERRNO (ENOTSUP, - "ri-record offset does not point to lf/lh (0x%zx, %d, %d)", - offset, block->id[0], block->id[1]); - goto error; - } - - struct ntreg_lf_record *lf - (struct ntreg_lf_record *) ((char *) h->addr + offset); - - size_t j; - for (j = 0; j < le16toh (lf->nr_keys); ++j) { - hive_node_h subkey = le32toh (lf->keys[j].offset); - subkey += 0x1000; - if (!(flags & GET_CHILDREN_NO_CHECK_NK)) { - if (!IS_VALID_BLOCK (h, subkey)) { - SET_ERRNO (EFAULT, - "indirect subkey is not a valid block (0x%zx)", - subkey); - goto error; - } - } - if (_hivex_add_to_offset_list (&children, subkey) == -1) - goto error; - } - } - goto ok; - } - /* else not supported, set errno and fall through */ - SET_ERRNO (ENOTSUP, - "subkey block is not lf/lh/ri (0x%zx, %d, %d)", - subkey_lf, block->id[0], block->id[1]); - error: - _hivex_free_offset_list (&children); - _hivex_free_offset_list (&blocks); - return -1; - - ok: - *children_ret = _hivex_return_offset_list (&children); - *blocks_ret = _hivex_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 - * allows us to make it more efficient in future. - */ -hive_node_h -hivex_node_get_child (hive_h *h, hive_node_h node, const char *nname) -{ - hive_node_h *children = NULL; - char *name = NULL; - hive_node_h ret = 0; - - children = hivex_node_children (h, node); - if (!children) goto error; - - size_t i; - for (i = 0; children[i] != 0; ++i) { - name = hivex_node_name (h, children[i]); - if (!name) goto error; - if (STRCASEEQ (name, nname)) { - ret = children[i]; - break; - } - free (name); name = NULL; - } - - error: - free (children); - free (name); - return ret; -} - -hive_node_h -hivex_node_parent (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); - - hive_node_h ret = le32toh (nk->parent); - ret += 0x1000; - if (!IS_VALID_BLOCK (h, ret)) { - SET_ERRNO (EFAULT, "parent is not a valid block (0x%zx)", ret); - return 0; - } - return ret; -} - -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")) { - 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; -} - -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 - * allows us to make it more efficient in future. - */ -hive_value_h -hivex_node_get_value (hive_h *h, hive_node_h node, const char *key) -{ - hive_value_h *values = NULL; - char *name = NULL; - hive_value_h ret = 0; - - values = hivex_node_values (h, node); - if (!values) goto error; - - size_t i; - for (i = 0; values[i] != 0; ++i) { - name = hivex_value_key (h, values[i]); - if (!name) goto error; - if (STRCASEEQ (name, key)) { - ret = values[i]; - break; - } - free (name); name = NULL; - } - - error: - free (values); - free (name); - return ret; -} - -size_t -hivex_value_struct_length (hive_h *h, hive_value_h value) -{ - size_t key_len; - - errno = 0; - key_len = hivex_value_key_len (h, value); - if (key_len == 0 && errno != 0) - return 0; - - /* -1 to avoid double-counting the first name character */ - return key_len + sizeof (struct ntreg_vk_record) - 1; -} - -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 ret = le16toh (vk->name_len); - size_t seg_len = block_len (h, value, NULL); - if (sizeof (struct ntreg_vk_record) + ret - 1 > seg_len) { - SET_ERRNO (EFAULT, "key length is too long (%zu, %zu)", ret, seg_len); - return 0; - } - return ret; -} - -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); - - /* AFAIK the key is always plain ASCII, so no conversion to UTF-8 is - * necessary. However we do need to nul-terminate the string. - */ - errno = 0; - size_t len = hivex_value_key_len (h, value); - if (len == 0 && errno != 0) - return NULL; - - char *ret = malloc (len + 1); - if (ret == NULL) - return NULL; - memcpy (ret, vk->name, len); - ret[len] = '\0'; - return ret; -} - -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; -} - -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; - 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_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=%d, 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_SUBKEYS); - 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; - } -} - -static char * -windows_utf16_to_utf8 (/* const */ char *input, size_t len) -{ - iconv_t ic = iconv_open ("UTF-8", "UTF-16"); - if (ic == (iconv_t) -1) - return NULL; - - /* iconv(3) has an insane interface ... */ - - /* Mostly UTF-8 will be smaller, so this is a good initial guess. */ - size_t outalloc = len; - - again:; - size_t inlen = len; - size_t outlen = outalloc; - char *out = malloc (outlen + 1); - if (out == NULL) { - int err = errno; - iconv_close (ic); - errno = err; - return NULL; - } - char *inp = input; - char *outp = out; - - size_t r = iconv (ic, &inp, &inlen, &outp, &outlen); - if (r == (size_t) -1) { - if (errno == E2BIG) { - int err = errno; - size_t prev = outalloc; - /* Try again with a larger output buffer. */ - free (out); - outalloc *= 2; - if (outalloc < prev) { - iconv_close (ic); - errno = err; - return NULL; - } - goto again; - } - else { - /* Else some conversion failure, eg. EILSEQ, EINVAL. */ - int err = errno; - iconv_close (ic); - free (out); - errno = err; - return NULL; - } - } - - *outp = '\0'; - iconv_close (ic); - - return out; -} - -char * -hivex_value_string (hive_h *h, hive_value_h value) -{ - hive_type t; - size_t len; - char *data = hivex_value_value (h, value, &t, &len); - - if (data == NULL) - return NULL; - - if (t != hive_t_string && t != hive_t_expand_string && t != hive_t_link) { - free (data); - SET_ERRNO (EINVAL, "type is not string/expand_string/link"); - return NULL; - } - - /* Deal with the case where Windows has allocated a large buffer - * full of random junk, and only the first few bytes of the buffer - * contain a genuine UTF-16 string. - * - * In this case, iconv would try to process the junk bytes as UTF-16 - * and inevitably find an illegal sequence (EILSEQ). Instead, stop - * after we find the first \0\0. - * - * (Found by Hilko Bengen in a fresh Windows XP SOFTWARE hive). - */ - size_t slen = utf16_string_len_in_bytes_max (data, len); - if (slen < len) - len = slen; - - char *ret = windows_utf16_to_utf8 (data, len); - free (data); - if (ret == NULL) - return NULL; - - return ret; -} - -static void -free_strings (char **argv) -{ - if (argv) { - size_t i; - - for (i = 0; argv[i] != NULL; ++i) - free (argv[i]); - free (argv); - } -} - -/* Get the length of a UTF-16 format string. Handle the string as - * pairs of bytes, looking for the first \0\0 pair. Only read up to - * 'len' maximum bytes. - */ -static size_t -utf16_string_len_in_bytes_max (const char *str, size_t len) -{ - size_t ret = 0; - - while (len >= 2 && (str[0] || str[1])) { - str += 2; - ret += 2; - len -= 2; - } - - return ret; -} - -/* http://blogs.msdn.com/oldnewthing/archive/2009/10/08/9904646.aspx */ -char ** -hivex_value_multiple_strings (hive_h *h, hive_value_h value) -{ - hive_type t; - size_t len; - char *data = hivex_value_value (h, value, &t, &len); - - if (data == NULL) - return NULL; - - if (t != hive_t_multiple_strings) { - free (data); - SET_ERRNO (EINVAL, "type is not multiple_strings"); - return NULL; - } - - size_t nr_strings = 0; - char **ret = malloc ((1 + nr_strings) * sizeof (char *)); - if (ret == NULL) { - free (data); - return NULL; - } - ret[0] = NULL; - - char *p = data; - size_t plen; - - while (p < data + len && - (plen = utf16_string_len_in_bytes_max (p, data + len - p)) > 0) { - nr_strings++; - char **ret2 = realloc (ret, (1 + nr_strings) * sizeof (char *)); - if (ret2 == NULL) { - free_strings (ret); - free (data); - return NULL; - } - ret = ret2; - - ret[nr_strings-1] = windows_utf16_to_utf8 (p, plen); - ret[nr_strings] = NULL; - if (ret[nr_strings-1] == NULL) { - free_strings (ret); - free (data); - return NULL; - } - - p += plen + 2 /* skip over UTF-16 \0\0 at the end of this string */; - } - - free (data); - return ret; -} - -int32_t -hivex_value_dword (hive_h *h, hive_value_h value) -{ - hive_type t; - size_t len; - void *data = hivex_value_value (h, value, &t, &len); - - if (data == NULL) - return -1; - - if ((t != hive_t_dword && t != hive_t_dword_be) || len < 4) { - free (data); - SET_ERRNO (EINVAL, "type is not dword/dword_be"); - return -1; - } - - int32_t ret = * (int32_t *) data; - free (data); - if (t == hive_t_dword) /* little endian */ - ret = le32toh (ret); - else - ret = be32toh (ret); - - return ret; -} - -int64_t -hivex_value_qword (hive_h *h, hive_value_h value) -{ - hive_type t; - size_t len; - void *data = hivex_value_value (h, value, &t, &len); - - if (data == NULL) - return -1; - - if (t != hive_t_qword || len < 8) { - free (data); - SET_ERRNO (EINVAL, "type is not qword or length < 8"); - return -1; - } - - int64_t ret = * (int64_t *) data; - free (data); - ret = le64toh (ret); /* always little endian */ - - return ret; -} - -/*---------------------------------------------------------------------- - * Visiting. - */ - -int -hivex_visit (hive_h *h, const struct hivex_visitor *visitor, size_t len, - void *opaque, int flags) -{ - return hivex_visit_node (h, hivex_root (h), visitor, len, opaque, flags); -} - -static int hivex__visit_node (hive_h *h, hive_node_h node, - const struct hivex_visitor *vtor, - char *unvisited, void *opaque, int flags); - -int -hivex_visit_node (hive_h *h, hive_node_h node, - const struct hivex_visitor *visitor, size_t len, void *opaque, - int flags) -{ - struct hivex_visitor vtor; - memset (&vtor, 0, sizeof vtor); - - /* Note that len might be larger *or smaller* than the expected size. */ - size_t copysize = len <= sizeof vtor ? len : sizeof vtor; - memcpy (&vtor, visitor, copysize); - - /* This bitmap records unvisited nodes, so we don't loop if the - * registry contains cycles. - */ - char *unvisited = malloc (1 + h->size / 32); - if (unvisited == NULL) - return -1; - memcpy (unvisited, h->bitmap, 1 + h->size / 32); - - int r = hivex__visit_node (h, node, &vtor, unvisited, opaque, flags); - free (unvisited); - return r; -} - -static int -hivex__visit_node (hive_h *h, hive_node_h node, - const struct hivex_visitor *vtor, char *unvisited, - void *opaque, int flags) -{ - int skip_bad = flags & HIVEX_VISIT_SKIP_BAD; - char *name = NULL; - hive_value_h *values = NULL; - hive_node_h *children = NULL; - char *key = NULL; - char *str = NULL; - char **strs = NULL; - int i; - - /* Return -1 on all callback errors. However on internal errors, - * check if skip_bad is set and suppress those errors if so. - */ - int ret = -1; - - if (!BITMAP_TST (unvisited, node)) { - SET_ERRNO (ELOOP, "contains cycle: visited node 0x%zx already", node); - return skip_bad ? 0 : -1; - } - BITMAP_CLR (unvisited, node); - - name = hivex_node_name (h, node); - if (!name) return skip_bad ? 0 : -1; - if (vtor->node_start && vtor->node_start (h, opaque, node, name) == -1) - goto error; - - values = hivex_node_values (h, node); - if (!values) { - ret = skip_bad ? 0 : -1; - goto error; - } - - for (i = 0; values[i] != 0; ++i) { - hive_type t; - size_t len; - - if (hivex_value_type (h, values[i], &t, &len) == -1) { - ret = skip_bad ? 0 : -1; - goto error; - } - - key = hivex_value_key (h, values[i]); - if (key == NULL) { - ret = skip_bad ? 0 : -1; - goto error; - } - - if (vtor->value_any) { - str = hivex_value_value (h, values[i], &t, &len); - if (str == NULL) { - ret = skip_bad ? 0 : -1; - goto error; - } - if (vtor->value_any (h, opaque, node, values[i], t, len, key, str) == -1) - goto error; - free (str); str = NULL; - } - 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 (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; - - 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_dword: - case hive_t_dword_be: { - int32_t i32 = hivex_value_dword (h, values[i]); - if (vtor->value_dword && - vtor->value_dword (h, opaque, node, values[i], - t, len, key, i32) == -1) - goto error; - break; - } - - case hive_t_qword: { - int64_t i64 = hivex_value_qword (h, values[i]); - if (vtor->value_qword && - vtor->value_qword (h, opaque, node, values[i], - t, len, key, i64) == -1) - goto error; - break; - } - - case hive_t_binary: - str = hivex_value_value (h, values[i], &t, &len); - if (str == NULL) { - ret = skip_bad ? 0 : -1; - goto error; - } - if (t != hive_t_binary) { - ret = skip_bad ? 0 : -1; - goto error; - } - if (vtor->value_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; - } - if (vtor->value_string_invalid_utf16) { - str = hivex_value_value (h, values[i], &t, &len); - if (vtor->value_string_invalid_utf16 (h, opaque, node, values[i], - t, len, key, str) == -1) - goto error; - free (str); str = NULL; - } - break; - } - if (vtor->value_multiple_strings && - vtor->value_multiple_strings (h, opaque, node, values[i], - t, len, key, strs) == -1) - goto error; - free_strings (strs); strs = NULL; - break; - - case hive_t_resource_list: - case hive_t_full_resource_description: - case hive_t_resource_requirements_list: - default: - str = hivex_value_value (h, values[i], &t, &len); - if (str == NULL) { - ret = skip_bad ? 0 : -1; - goto error; - } - if (vtor->value_other && - vtor->value_other (h, opaque, node, values[i], - t, len, key, str) == -1) - goto error; - free (str); str = NULL; - break; - } - } - - free (key); key = NULL; - } - - children = hivex_node_children (h, node); - if (children == NULL) { - ret = skip_bad ? 0 : -1; - goto error; - } - - for (i = 0; children[i] != 0; ++i) { - DEBUG (2, "%s: visiting subkey %d (0x%zx)", name, i, children[i]); - - if (hivex__visit_node (h, children[i], vtor, unvisited, opaque, flags) == -1) - goto error; - } - - if (vtor->node_end && vtor->node_end (h, opaque, node, name) == -1) - goto error; - - ret = 0; - - error: - free (name); - free (values); - free (children); - free (key); - free (str); - 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; - - DEBUG (2, "current endpages = 0x%zx, current size = 0x%zx", - h->endpages, h->size); - DEBUG (2, "extending file by %zd bytes (<= 0 if no extension)", - 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 ((char *) h->addr + oldsize, 0, newsize - oldsize); - memset (h->bitmap + oldbitmapsize, 0, newbitmapsize - oldbitmapsize); - } - - size_t offset = h->endpages; - h->endpages += nr_4k_pages * 4096; - - DEBUG (2, "new endpages = 0x%zx, new size = 0x%zx", h->endpages, h->size); - - /* Write the hbin header. */ - struct ntreg_hbin_page *page - (struct ntreg_hbin_page *) ((char *) 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)); - - DEBUG (2, "new page at 0x%zx", 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. - * - * **NB** Because allocate_block may reallocate the memory, all - * pointers into the memory become potentially invalid. I really - * love writing in C, can't you tell? - * - * 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]) -{ - CHECK_WRITABLE (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. - */ - SET_ERRNO (ERANGE, "refusing too small allocation (%zu)", seg_len); - return 0; - } - - /* Refuse really large allocations. */ - if (seg_len > HIVEX_MAX_ALLOCATION) { - SET_ERRNO (ERANGE, "refusing too large allocation (%zu)", seg_len); - 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; - - DEBUG (2, "new block at 0x%zx, size %zu", offset, seg_len); - - struct ntreg_hbin_block *blockhdr - (struct ntreg_hbin_block *) ((char *) h->addr + offset); - - memset (blockhdr, 0, seg_len); - - 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) { - DEBUG (2, "marking remainder of page free" - " starting at 0x%zx, size %zd", h->endblocks, rem); - - assert (rem >= 4); - - blockhdr = (struct ntreg_hbin_block *) ((char *) 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)); - - 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); -} - -/* 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 *) ((char *) 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 *) ((char *) 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) -{ - int fd; - - if (flags != 0) { - SET_ERRNO (EINVAL, "flags != 0"); - return -1; - } - - CHECK_WRITABLE (-1); - - filename = filename ? : h->filename; -#ifdef O_CLOEXEC - fd = open (filename, O_WRONLY|O_CREAT|O_TRUNC|O_NOCTTY|O_CLOEXEC|O_BINARY, - 0666); -#else - fd = open (filename, O_WRONLY|O_CREAT|O_TRUNC|O_NOCTTY|O_BINARY, 0666); -#endif - if (fd == -1) - return -1; -#ifndef O_CLOEXEC - fcntl (fd, F_SETFD, FD_CLOEXEC); -#endif - - /* 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); - - DEBUG (2, "hivex_commit: new header checksum: 0x%x", 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, void *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 *) ((char *) 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 *) ((char *) 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; - - /* 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; -} - -/* 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) -{ - 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; - } - - /* 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; - - DEBUG (2, "allocated new nk-record for child at 0x%zx", node); - - struct ntreg_nk_record *nk - (struct ntreg_nk_record *) ((char *) 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 *) ((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 - * 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; - } - - /* Recalculate pointers that could have been invalidated by - * previous call to allocate_block (via new_lh_record). - */ - nk = (struct ntreg_nk_record *) ((char *) h->addr + node); - 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. */ - 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 *) ((char *) 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: - DEBUG (2, "insert key in existing lh-record at 0x%zx, posn %zu", - old_offs, j); - - new_offs = insert_lf_record (h, old_offs, j, name, node); - if (new_offs == 0) { - free (blocks); - return 0; - } - - /* Recalculate pointers that could have been invalidated by - * previous call to allocate_block (via insert_lf_record). - */ - nk = (struct ntreg_nk_record *) ((char *) h->addr + node); - parent_nk = (struct ntreg_nk_record *) ((char *) h->addr + parent); - - DEBUG (2, "new lh-record at 0x%zx", 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 *) ((char *) 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. */ - SET_ERRNO (ENOTSUP, "could not find ri->lf link"); - 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")) { - 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; -} - -/* 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 *) ((char *) 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) -{ - 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 (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; - } - } - } - SET_ERRNO (ENOTSUP, "could not find parent to child link"); - return -1; - - found:; - 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; -} - -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); - - struct ntreg_value_list *vallist - (struct ntreg_value_list *) ((char *) 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; - - /* Recalculate pointers that could have been invalidated by - * previous call to allocate_block. - */ - nk = (struct ntreg_nk_record *) ((char *) h->addr + node); - 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); - 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; - - /* Recalculate pointers that could have been invalidated by - * previous call to allocate_block. - */ - nk = (struct ntreg_nk_record *) ((char *) h->addr + node); - vallist = (struct ntreg_value_list *) ((char *) h->addr + vallist_offs); - 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); - } - - 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; -} - -int -hivex_node_set_value (hive_h *h, hive_node_h node, - const hive_set_value *val, int flags) -{ - int retval = -1; - hive_value_h *prev_values; - hive_set_value *new_values; - size_t nr_values; - size_t i; - ssize_t idx_of_val; - - prev_values = hivex_node_values (h, node); - if (prev_values == NULL) - return -1; - - /* Count number of existing values in this node. */ - nr_values = 0; - for (i = 0; prev_values[i] != 0; i++) - nr_values++; - - /* Allocate a new hive_set_value list, with space for all existing - * values, plus 1 (for the new key if we're not replacing an - * existing key). - */ - new_values = calloc (nr_values + 1, sizeof (hive_set_value)); - if (new_values == NULL) - goto out1; - - /* Copy the old values to the new values. If we find the key along - * the way, note its index in 'idx_of_val'. - */ - idx_of_val = -1; - for (i = 0; prev_values[i] != 0; i++) { - size_t len; - hive_type t; - char *valkey, *valval; - - valval = hivex_value_value (h, prev_values[i], &t, &len); - if (valval == NULL) goto out2; - - new_values[i].value = valval; - new_values[i].t = t; - new_values[i].len = len; - - valkey = hivex_value_key (h, prev_values[i]); - if (valkey == NULL) goto out2; - - new_values[i].key = valkey; - - if (STRCASEEQ (valkey, val->key)) - idx_of_val = i; - } - - if (idx_of_val > -1) { - free (new_values[idx_of_val].key); - free (new_values[idx_of_val].value); - } else { /* insert it at the end */ - idx_of_val = nr_values; - nr_values++; - } - - new_values[idx_of_val].key = strdup (val->key); - new_values[idx_of_val].value = malloc (val->len); - new_values[idx_of_val].len = val->len; - new_values[idx_of_val].t = val->t; - - if (new_values[idx_of_val].key == NULL || - new_values[idx_of_val].value == NULL) - goto out2; - memcpy (new_values[idx_of_val].value, val->value, val->len); - - retval = hivex_node_set_values (h, node, nr_values, new_values, 0); - - out2: - for (i = 0; i < nr_values; ++i) { - free (new_values[i].key); - free (new_values[i].value); - } - free (new_values); - - out1: - free (prev_values); - - return retval; -} diff --git a/lib/node.c b/lib/node.c new file mode 100644 index 0000000..ad7817f --- /dev/null +++ b/lib/node.c @@ -0,0 +1,438 @@ +/* hivex - Windows Registry "hive" extraction library. + * Copyright (C) 2009-2011 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: + * Copyright (c) 2000-2004, Markus Stephany. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * See file LICENSE for the full license. + */ + +#include <config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <stddef.h> +#include <inttypes.h> +#include <string.h> +#include <unistd.h> +#include <errno.h> +#include <assert.h> + +#include "c-ctype.h" + +#include "hivex.h" +#include "hivex-internal.h" +#include "byte_conversions.h" + +hive_node_h +hivex_root (hive_h *h) +{ + hive_node_h ret = h->rootoffs; + if (!IS_VALID_BLOCK (h, ret)) { + SET_ERRNO (HIVEX_NO_KEY, "no root key"); + return 0; + } + return ret; +} + +size_t +hivex_node_struct_length (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 name_len = le16toh (nk->name_len); + /* -1 to avoid double-counting the first name character */ + size_t ret = name_len + sizeof (struct ntreg_nk_record) - 1; + int used; + size_t seg_len = block_len (h, node, &used); + if (ret > seg_len) { + SET_ERRNO (EFAULT, "node name is too long (%zu, %zu)", name_len, seg_len); + return 0; + } + return ret; +} + +char * +hivex_node_name (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 NULL; + } + + struct ntreg_nk_record *nk + (struct ntreg_nk_record *) ((char *) h->addr + node); + + /* AFAIK the node name is always plain ASCII, so no conversion + * to UTF-8 is necessary. However we do need to nul-terminate + * the string. + */ + + /* nk->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 (nk->name_len); + size_t seg_len = block_len (h, node, NULL); + if (sizeof (struct ntreg_nk_record) + len - 1 > seg_len) { + SET_ERRNO (EFAULT, "node name is too long (%zu, %zu)", len, seg_len); + return NULL; + } + + char *ret = malloc (len + 1); + if (ret == NULL) + return NULL; + memcpy (ret, nk->name, len); + ret[len] = '\0'; + return ret; +} + +static int64_t +timestamp_check (hive_h *h, hive_node_h node, int64_t timestamp) +{ + if (timestamp < 0) { + SET_ERRNO (EINVAL, + "negative time reported at %zu: %" PRIi64, node, timestamp); + return -1; + } + + return timestamp; +} + +int64_t +hivex_last_modified (hive_h *h) +{ + return timestamp_check (h, 0, h->last_modified); +} + +int64_t +hivex_node_timestamp (hive_h *h, hive_node_h node) +{ + int64_t 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); + + ret = le64toh (nk->timestamp); + return timestamp_check (h, node, ret); +} + +#if 0 +/* I think the documentation for the sk and classname fields in the nk + * record is wrong, or else the offset field is in the wrong place. + * Otherwise this makes no sense. Disabled this for now -- it's not + * useful for reading the registry anyway. + */ + +hive_security_h +hivex_node_security (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 *) (h->addr + node); + + hive_node_h ret = le32toh (nk->sk); + ret += 0x1000; + if (!IS_VALID_BLOCK (h, ret)) { + SET_ERRNO (EFAULT, "invalid block"); + return 0; + } + return ret; +} + +hive_classname_h +hivex_node_classname (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 *) (h->addr + node); + + hive_node_h ret = le32toh (nk->classname); + ret += 0x1000; + if (!IS_VALID_BLOCK (h, ret)) { + SET_ERRNO (EFAULT, "invalid block"); + return 0; + } + return ret; +} +#endif + +/* Iterate over children, returning child nodes and intermediate blocks. */ +int +_hivex_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")) { + 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_subkeys_in_nk = le32toh (nk->nr_subkeys); + + offset_list children, blocks; + _hivex_init_offset_list (h, &children); + _hivex_init_offset_list (h, &blocks); + + /* Deal with the common "no subkeys" case quickly. */ + if (nr_subkeys_in_nk == 0) + goto ok; + + /* Arbitrarily limit the number of subkeys we will ever deal with. */ + if (nr_subkeys_in_nk > HIVEX_MAX_SUBKEYS) { + SET_ERRNO (ERANGE, + "nr_subkeys_in_nk > HIVEX_MAX_SUBKEYS (%zu > %d)", + nr_subkeys_in_nk, HIVEX_MAX_SUBKEYS); + goto error; + } + + /* Preallocate space for the children. */ + if (_hivex_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. + */ + size_t subkey_lf = le32toh (nk->subkey_lf); + subkey_lf += 0x1000; + if (!IS_VALID_BLOCK (h, subkey_lf)) { + SET_ERRNO (EFAULT, + "subkey_lf is not a valid block (0x%zx)", subkey_lf); + goto error; + } + + if (_hivex_add_to_offset_list (&blocks, subkey_lf) == -1) + goto error; + + struct ntreg_hbin_block *block + (struct ntreg_hbin_block *) ((char *) h->addr + subkey_lf); + + /* Points to lf-record? (Note, also "lh" but that is basically the + * same as "lf" as far as we are concerned here). + */ + if (block->id[0] == 'l' && (block->id[1] == 'f' || block->id[1] == 'h')) { + struct ntreg_lf_record *lf = (struct ntreg_lf_record *) block; + + /* Check number of subkeys in the nk-record matches number of subkeys + * in the lf-record. + */ + size_t nr_subkeys_in_lf = le16toh (lf->nr_keys); + + if (nr_subkeys_in_nk != nr_subkeys_in_lf) { + SET_ERRNO (ENOTSUP, + "nr_subkeys_in_nk = %zu is not equal to nr_subkeys_in_lf = %zu", + nr_subkeys_in_nk, nr_subkeys_in_lf); + goto error; + } + + size_t len = block_len (h, subkey_lf, NULL); + if (8 + nr_subkeys_in_lf * 8 > len) { + SET_ERRNO (EFAULT, "too many subkeys (%zu, %zu)", nr_subkeys_in_lf, len); + goto error; + } + + size_t i; + for (i = 0; i < nr_subkeys_in_lf; ++i) { + hive_node_h subkey = le32toh (lf->keys[i].offset); + subkey += 0x1000; + if (!(flags & GET_CHILDREN_NO_CHECK_NK)) { + if (!IS_VALID_BLOCK (h, subkey)) { + SET_ERRNO (EFAULT, "subkey is not a valid block (0x%zx)", subkey); + goto error; + } + } + if (_hivex_add_to_offset_list (&children, subkey) == -1) + goto error; + } + goto ok; + } + /* Points to ri-record? */ + else if (block->id[0] == 'r' && block->id[1] == 'i') { + struct ntreg_ri_record *ri = (struct ntreg_ri_record *) block; + + size_t nr_offsets = le16toh (ri->nr_offsets); + + /* Count total number of children. */ + size_t i, count = 0; + for (i = 0; i < nr_offsets; ++i) { + hive_node_h offset = le32toh (ri->offset[i]); + offset += 0x1000; + if (!IS_VALID_BLOCK (h, offset)) { + SET_ERRNO (EFAULT, "ri-offset is not a valid block (0x%zx)", offset); + goto error; + } + if (!BLOCK_ID_EQ (h, offset, "lf") && !BLOCK_ID_EQ (h, offset, "lh")) { + struct ntreg_lf_record *block + (struct ntreg_lf_record *) ((char *) h->addr + offset); + SET_ERRNO (ENOTSUP, + "ri-record offset does not point to lf/lh (0x%zx, %d, %d)", + offset, block->id[0], block->id[1]); + goto error; + } + + if (_hivex_add_to_offset_list (&blocks, offset) == -1) + goto error; + + struct ntreg_lf_record *lf + (struct ntreg_lf_record *) ((char *) h->addr + offset); + + count += le16toh (lf->nr_keys); + } + + if (nr_subkeys_in_nk != count) { + SET_ERRNO (ENOTSUP, + "nr_subkeys_in_nk = %zu is not equal to counted = %zu", + nr_subkeys_in_nk, count); + goto error; + } + + /* Copy list of children. Note nr_subkeys_in_nk is limited to + * something reasonable above. + */ + for (i = 0; i < nr_offsets; ++i) { + hive_node_h offset = le32toh (ri->offset[i]); + offset += 0x1000; + if (!IS_VALID_BLOCK (h, offset)) { + SET_ERRNO (EFAULT, "ri-offset is not a valid block (0x%zx)", offset); + goto error; + } + if (!BLOCK_ID_EQ (h, offset, "lf") && !BLOCK_ID_EQ (h, offset, "lh")) { + struct ntreg_lf_record *block + (struct ntreg_lf_record *) ((char *) h->addr + offset); + SET_ERRNO (ENOTSUP, + "ri-record offset does not point to lf/lh (0x%zx, %d, %d)", + offset, block->id[0], block->id[1]); + goto error; + } + + struct ntreg_lf_record *lf + (struct ntreg_lf_record *) ((char *) h->addr + offset); + + size_t j; + for (j = 0; j < le16toh (lf->nr_keys); ++j) { + hive_node_h subkey = le32toh (lf->keys[j].offset); + subkey += 0x1000; + if (!(flags & GET_CHILDREN_NO_CHECK_NK)) { + if (!IS_VALID_BLOCK (h, subkey)) { + SET_ERRNO (EFAULT, + "indirect subkey is not a valid block (0x%zx)", + subkey); + goto error; + } + } + if (_hivex_add_to_offset_list (&children, subkey) == -1) + goto error; + } + } + goto ok; + } + /* else not supported, set errno and fall through */ + SET_ERRNO (ENOTSUP, + "subkey block is not lf/lh/ri (0x%zx, %d, %d)", + subkey_lf, block->id[0], block->id[1]); + error: + _hivex_free_offset_list (&children); + _hivex_free_offset_list (&blocks); + return -1; + + ok: + *children_ret = _hivex_return_offset_list (&children); + *blocks_ret = _hivex_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 (_hivex_get_children (h, node, &children, &blocks, 0) == -1) + return NULL; + + free (blocks); + return children; +} + +/* Very inefficient, but at least having a separate API call + * allows us to make it more efficient in future. + */ +hive_node_h +hivex_node_get_child (hive_h *h, hive_node_h node, const char *nname) +{ + hive_node_h *children = NULL; + char *name = NULL; + hive_node_h ret = 0; + + children = hivex_node_children (h, node); + if (!children) goto error; + + size_t i; + for (i = 0; children[i] != 0; ++i) { + name = hivex_node_name (h, children[i]); + if (!name) goto error; + if (STRCASEEQ (name, nname)) { + ret = children[i]; + break; + } + free (name); name = NULL; + } + + error: + free (children); + free (name); + return ret; +} + +hive_node_h +hivex_node_parent (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); + + hive_node_h ret = le32toh (nk->parent); + ret += 0x1000; + if (!IS_VALID_BLOCK (h, ret)) { + SET_ERRNO (EFAULT, "parent is not a valid block (0x%zx)", ret); + return 0; + } + return ret; +} diff --git a/lib/utf16.c b/lib/utf16.c new file mode 100644 index 0000000..4115d30 --- /dev/null +++ b/lib/utf16.c @@ -0,0 +1,104 @@ +/* hivex - Windows Registry "hive" extraction library. + * Copyright (C) 2009-2011 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: + * Copyright (c) 2000-2004, Markus Stephany. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * See file LICENSE for the full license. + */ + +#include <config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <iconv.h> + +#include "hivex.h" +#include "hivex-internal.h" +#include "byte_conversions.h" + +char * +_hivex_windows_utf16_to_utf8 (/* const */ char *input, size_t len) +{ + iconv_t ic = iconv_open ("UTF-8", "UTF-16"); + if (ic == (iconv_t) -1) + return NULL; + + /* iconv(3) has an insane interface ... */ + + /* Mostly UTF-8 will be smaller, so this is a good initial guess. */ + size_t outalloc = len; + + again:; + size_t inlen = len; + size_t outlen = outalloc; + char *out = malloc (outlen + 1); + if (out == NULL) { + int err = errno; + iconv_close (ic); + errno = err; + return NULL; + } + char *inp = input; + char *outp = out; + + size_t r = iconv (ic, &inp, &inlen, &outp, &outlen); + if (r == (size_t) -1) { + if (errno == E2BIG) { + int err = errno; + size_t prev = outalloc; + /* Try again with a larger output buffer. */ + free (out); + outalloc *= 2; + if (outalloc < prev) { + iconv_close (ic); + errno = err; + return NULL; + } + goto again; + } + else { + /* Else some conversion failure, eg. EILSEQ, EINVAL. */ + int err = errno; + iconv_close (ic); + free (out); + errno = err; + return NULL; + } + } + + *outp = '\0'; + iconv_close (ic); + + return out; +} + +/* Get the length of a UTF-16 format string. Handle the string as + * pairs of bytes, looking for the first \0\0 pair. Only read up to + * 'len' maximum bytes. + */ +size_t +_hivex_utf16_string_len_in_bytes_max (const char *str, size_t len) +{ + size_t ret = 0; + + while (len >= 2 && (str[0] || str[1])) { + str += 2; + ret += 2; + len -= 2; + } + + return ret; +} diff --git a/lib/util.c b/lib/util.c new file mode 100644 index 0000000..d8f3042 --- /dev/null +++ b/lib/util.c @@ -0,0 +1,39 @@ +/* hivex - Windows Registry "hive" extraction library. + * Copyright (C) 2009-2011 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: + * Copyright (c) 2000-2004, Markus Stephany. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * See file LICENSE for the full license. + */ + +#include <config.h> + +#include <stdio.h> +#include <stdlib.h> + +#include "hivex.h" +#include "hivex-internal.h" + +void +_hivex_free_strings (char **argv) +{ + if (argv) { + size_t i; + + for (i = 0; argv[i] != NULL; ++i) + free (argv[i]); + free (argv); + } +} diff --git a/lib/value.c b/lib/value.c new file mode 100644 index 0000000..d713cdc --- /dev/null +++ b/lib/value.c @@ -0,0 +1,567 @@ +/* hivex - Windows Registry "hive" extraction library. + * Copyright (C) 2009-2011 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: + * Copyright (c) 2000-2004, Markus Stephany. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * See file LICENSE for the full license. + */ + +#include <config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <stddef.h> +#include <inttypes.h> +#include <string.h> +#include <unistd.h> +#include <errno.h> +#include <assert.h> + +#include "hivex.h" +#include "hivex-internal.h" +#include "byte_conversions.h" + +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; +} + +hive_value_h * +hivex_node_values (hive_h *h, hive_node_h node) +{ + hive_value_h *values; + size_t *blocks; + + if (_hivex_get_values (h, node, &values, &blocks) == -1) + return NULL; + + free (blocks); + return values; +} + +/* Very inefficient, but at least having a separate API call + * allows us to make it more efficient in future. + */ +hive_value_h +hivex_node_get_value (hive_h *h, hive_node_h node, const char *key) +{ + hive_value_h *values = NULL; + char *name = NULL; + hive_value_h ret = 0; + + values = hivex_node_values (h, node); + if (!values) goto error; + + size_t i; + for (i = 0; values[i] != 0; ++i) { + name = hivex_value_key (h, values[i]); + if (!name) goto error; + if (STRCASEEQ (name, key)) { + ret = values[i]; + break; + } + free (name); name = NULL; + } + + error: + free (values); + free (name); + return ret; +} + +size_t +hivex_value_struct_length (hive_h *h, hive_value_h value) +{ + size_t key_len; + + errno = 0; + key_len = hivex_value_key_len (h, value); + if (key_len == 0 && errno != 0) + return 0; + + /* -1 to avoid double-counting the first name character */ + return key_len + sizeof (struct ntreg_vk_record) - 1; +} + +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 ret = le16toh (vk->name_len); + size_t seg_len = block_len (h, value, NULL); + if (sizeof (struct ntreg_vk_record) + ret - 1 > seg_len) { + SET_ERRNO (EFAULT, "key length is too long (%zu, %zu)", ret, seg_len); + return 0; + } + return ret; +} + +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); + + /* AFAIK the key is always plain ASCII, so no conversion to UTF-8 is + * necessary. However we do need to nul-terminate the string. + */ + errno = 0; + size_t len = hivex_value_key_len (h, value); + if (len == 0 && errno != 0) + return NULL; + + char *ret = malloc (len + 1); + if (ret == NULL) + return NULL; + memcpy (ret, vk->name, len); + ret[len] = '\0'; + return ret; +} + +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; +} + +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; + 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_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=%d, 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_SUBKEYS); + 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; + } +} + +char * +hivex_value_string (hive_h *h, hive_value_h value) +{ + hive_type t; + size_t len; + char *data = hivex_value_value (h, value, &t, &len); + + if (data == NULL) + return NULL; + + if (t != hive_t_string && t != hive_t_expand_string && t != hive_t_link) { + free (data); + SET_ERRNO (EINVAL, "type is not string/expand_string/link"); + return NULL; + } + + /* Deal with the case where Windows has allocated a large buffer + * full of random junk, and only the first few bytes of the buffer + * contain a genuine UTF-16 string. + * + * In this case, iconv would try to process the junk bytes as UTF-16 + * and inevitably find an illegal sequence (EILSEQ). Instead, stop + * after we find the first \0\0. + * + * (Found by Hilko Bengen in a fresh Windows XP SOFTWARE hive). + */ + size_t slen = _hivex_utf16_string_len_in_bytes_max (data, len); + if (slen < len) + len = slen; + + char *ret = _hivex_windows_utf16_to_utf8 (data, len); + free (data); + if (ret == NULL) + return NULL; + + return ret; +} + +/* http://blogs.msdn.com/oldnewthing/archive/2009/10/08/9904646.aspx */ +char ** +hivex_value_multiple_strings (hive_h *h, hive_value_h value) +{ + hive_type t; + size_t len; + char *data = hivex_value_value (h, value, &t, &len); + + if (data == NULL) + return NULL; + + if (t != hive_t_multiple_strings) { + free (data); + SET_ERRNO (EINVAL, "type is not multiple_strings"); + return NULL; + } + + size_t nr_strings = 0; + char **ret = malloc ((1 + nr_strings) * sizeof (char *)); + if (ret == NULL) { + free (data); + return NULL; + } + ret[0] = NULL; + + char *p = data; + size_t plen; + + while (p < data + len && + (plen = _hivex_utf16_string_len_in_bytes_max (p, data + len - p)) > 0) { + nr_strings++; + char **ret2 = realloc (ret, (1 + nr_strings) * sizeof (char *)); + if (ret2 == NULL) { + _hivex_free_strings (ret); + free (data); + return NULL; + } + ret = ret2; + + ret[nr_strings-1] = _hivex_windows_utf16_to_utf8 (p, plen); + ret[nr_strings] = NULL; + if (ret[nr_strings-1] == NULL) { + _hivex_free_strings (ret); + free (data); + return NULL; + } + + p += plen + 2 /* skip over UTF-16 \0\0 at the end of this string */; + } + + free (data); + return ret; +} + +int32_t +hivex_value_dword (hive_h *h, hive_value_h value) +{ + hive_type t; + size_t len; + void *data = hivex_value_value (h, value, &t, &len); + + if (data == NULL) + return -1; + + if ((t != hive_t_dword && t != hive_t_dword_be) || len < 4) { + free (data); + SET_ERRNO (EINVAL, "type is not dword/dword_be"); + return -1; + } + + int32_t ret = * (int32_t *) data; + free (data); + if (t == hive_t_dword) /* little endian */ + ret = le32toh (ret); + else + ret = be32toh (ret); + + return ret; +} + +int64_t +hivex_value_qword (hive_h *h, hive_value_h value) +{ + hive_type t; + size_t len; + void *data = hivex_value_value (h, value, &t, &len); + + if (data == NULL) + return -1; + + if (t != hive_t_qword || len < 8) { + free (data); + SET_ERRNO (EINVAL, "type is not qword or length < 8"); + return -1; + } + + int64_t ret = * (int64_t *) data; + free (data); + ret = le64toh (ret); /* always little endian */ + + return ret; +} diff --git a/lib/visit.c b/lib/visit.c new file mode 100644 index 0000000..bd95b6c --- /dev/null +++ b/lib/visit.c @@ -0,0 +1,283 @@ +/* hivex - Windows Registry "hive" extraction library. + * Copyright (C) 2009-2011 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: + * Copyright (c) 2000-2004, Markus Stephany. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * See file LICENSE for the full license. + */ + +#include <config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <stddef.h> +#include <inttypes.h> +#include <string.h> +#include <unistd.h> +#include <errno.h> +#include <assert.h> + +#include "hivex.h" +#include "hivex-internal.h" +#include "byte_conversions.h" + +int +hivex_visit (hive_h *h, const struct hivex_visitor *visitor, size_t len, + void *opaque, int flags) +{ + return hivex_visit_node (h, hivex_root (h), visitor, len, opaque, flags); +} + +static int hivex__visit_node (hive_h *h, hive_node_h node, + const struct hivex_visitor *vtor, + char *unvisited, void *opaque, int flags); + +int +hivex_visit_node (hive_h *h, hive_node_h node, + const struct hivex_visitor *visitor, size_t len, void *opaque, + int flags) +{ + struct hivex_visitor vtor; + memset (&vtor, 0, sizeof vtor); + + /* Note that len might be larger *or smaller* than the expected size. */ + size_t copysize = len <= sizeof vtor ? len : sizeof vtor; + memcpy (&vtor, visitor, copysize); + + /* This bitmap records unvisited nodes, so we don't loop if the + * registry contains cycles. + */ + char *unvisited = malloc (1 + h->size / 32); + if (unvisited == NULL) + return -1; + memcpy (unvisited, h->bitmap, 1 + h->size / 32); + + int r = hivex__visit_node (h, node, &vtor, unvisited, opaque, flags); + free (unvisited); + return r; +} + +static int +hivex__visit_node (hive_h *h, hive_node_h node, + const struct hivex_visitor *vtor, char *unvisited, + void *opaque, int flags) +{ + int skip_bad = flags & HIVEX_VISIT_SKIP_BAD; + char *name = NULL; + hive_value_h *values = NULL; + hive_node_h *children = NULL; + char *key = NULL; + char *str = NULL; + char **strs = NULL; + int i; + + /* Return -1 on all callback errors. However on internal errors, + * check if skip_bad is set and suppress those errors if so. + */ + int ret = -1; + + if (!BITMAP_TST (unvisited, node)) { + SET_ERRNO (ELOOP, "contains cycle: visited node 0x%zx already", node); + return skip_bad ? 0 : -1; + } + BITMAP_CLR (unvisited, node); + + name = hivex_node_name (h, node); + if (!name) return skip_bad ? 0 : -1; + if (vtor->node_start && vtor->node_start (h, opaque, node, name) == -1) + goto error; + + values = hivex_node_values (h, node); + if (!values) { + ret = skip_bad ? 0 : -1; + goto error; + } + + for (i = 0; values[i] != 0; ++i) { + hive_type t; + size_t len; + + if (hivex_value_type (h, values[i], &t, &len) == -1) { + ret = skip_bad ? 0 : -1; + goto error; + } + + key = hivex_value_key (h, values[i]); + if (key == NULL) { + ret = skip_bad ? 0 : -1; + goto error; + } + + if (vtor->value_any) { + str = hivex_value_value (h, values[i], &t, &len); + if (str == NULL) { + ret = skip_bad ? 0 : -1; + goto error; + } + if (vtor->value_any (h, opaque, node, values[i], t, len, key, str) == -1) + goto error; + free (str); str = NULL; + } + 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 (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; + + 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_dword: + case hive_t_dword_be: { + int32_t i32 = hivex_value_dword (h, values[i]); + if (vtor->value_dword && + vtor->value_dword (h, opaque, node, values[i], + t, len, key, i32) == -1) + goto error; + break; + } + + case hive_t_qword: { + int64_t i64 = hivex_value_qword (h, values[i]); + if (vtor->value_qword && + vtor->value_qword (h, opaque, node, values[i], + t, len, key, i64) == -1) + goto error; + break; + } + + case hive_t_binary: + str = hivex_value_value (h, values[i], &t, &len); + if (str == NULL) { + ret = skip_bad ? 0 : -1; + goto error; + } + if (t != hive_t_binary) { + ret = skip_bad ? 0 : -1; + goto error; + } + if (vtor->value_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; + } + 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; + _hivex_free_strings (strs); strs = NULL; + break; + + case hive_t_resource_list: + case hive_t_full_resource_description: + case hive_t_resource_requirements_list: + default: + str = hivex_value_value (h, values[i], &t, &len); + if (str == NULL) { + ret = skip_bad ? 0 : -1; + goto error; + } + if (vtor->value_other && + vtor->value_other (h, opaque, node, values[i], + t, len, key, str) == -1) + goto error; + free (str); str = NULL; + break; + } + } + + free (key); key = NULL; + } + + children = hivex_node_children (h, node); + if (children == NULL) { + ret = skip_bad ? 0 : -1; + goto error; + } + + for (i = 0; children[i] != 0; ++i) { + DEBUG (2, "%s: visiting subkey %d (0x%zx)", name, i, children[i]); + + if (hivex__visit_node (h, children[i], vtor, unvisited, opaque, flags) == -1) + goto error; + } + + if (vtor->node_end && vtor->node_end (h, opaque, node, name) == -1) + goto error; + + ret = 0; + + error: + free (name); + free (values); + free (children); + free (key); + free (str); + _hivex_free_strings (strs); + return ret; +} diff --git a/lib/write.c b/lib/write.c new file mode 100644 index 0000000..236ff3c --- /dev/null +++ b/lib/write.c @@ -0,0 +1,955 @@ +/* hivex - Windows Registry "hive" extraction library. + * Copyright (C) 2009-2011 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: + * Copyright (c) 2000-2004, Markus Stephany. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * See file LICENSE for the full license. + */ + +#include <config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <stddef.h> +#include <inttypes.h> +#include <string.h> +#include <unistd.h> +#include <errno.h> +#include <assert.h> + +#ifdef HAVE_MMAP +#include <sys/mman.h> +#else +/* On systems without mmap (and munmap), use a replacement function. */ +#include "mmap.h" +#endif + +#include "c-ctype.h" + +#include "hivex.h" +#include "hivex-internal.h" +#include "byte_conversions.h" + +/*---------------------------------------------------------------------- + * 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; + + DEBUG (2, "current endpages = 0x%zx, current size = 0x%zx", + h->endpages, h->size); + DEBUG (2, "extending file by %zd bytes (<= 0 if no extension)", + 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 ((char *) h->addr + oldsize, 0, newsize - oldsize); + memset (h->bitmap + oldbitmapsize, 0, newbitmapsize - oldbitmapsize); + } + + size_t offset = h->endpages; + h->endpages += nr_4k_pages * 4096; + + DEBUG (2, "new endpages = 0x%zx, new size = 0x%zx", h->endpages, h->size); + + /* Write the hbin header. */ + struct ntreg_hbin_page *page + (struct ntreg_hbin_page *) ((char *) 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)); + + DEBUG (2, "new page at 0x%zx", 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. + * + * **NB** Because allocate_block may reallocate the memory, all + * pointers into the memory become potentially invalid. I really + * love writing in C, can't you tell? + * + * 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]) +{ + CHECK_WRITABLE (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. + */ + SET_ERRNO (ERANGE, "refusing too small allocation (%zu)", seg_len); + return 0; + } + + /* Refuse really large allocations. */ + if (seg_len > HIVEX_MAX_ALLOCATION) { + SET_ERRNO (ERANGE, "refusing too large allocation (%zu)", seg_len); + 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; + + DEBUG (2, "new block at 0x%zx, size %zu", offset, seg_len); + + struct ntreg_hbin_block *blockhdr + (struct ntreg_hbin_block *) ((char *) h->addr + offset); + + memset (blockhdr, 0, seg_len); + + 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) { + DEBUG (2, "marking remainder of page free" + " starting at 0x%zx, size %zd", h->endblocks, rem); + + assert (rem >= 4); + + blockhdr = (struct ntreg_hbin_block *) ((char *) 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)); + + 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); +} + +/* 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 (_hivex_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 *) ((char *) 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 *) ((char *) h->addr + node); + nk->nr_values = htole32 (0); + nk->vallist = htole32 (0xffffffff); + + return 0; +} + +/* Calculate the hash for a lf or lh record offset. + */ +static void +calc_hash (const char *type, const char *name, void *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 *) ((char *) 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 *) ((char *) 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; + + /* 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; +} + +/* 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) +{ + 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; + } + + /* 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; + + DEBUG (2, "allocated new nk-record for child at 0x%zx", node); + + struct ntreg_nk_record *nk + (struct ntreg_nk_record *) ((char *) 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 *) ((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 + * 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 (_hivex_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; + } + + /* Recalculate pointers that could have been invalidated by + * previous call to allocate_block (via new_lh_record). + */ + nk = (struct ntreg_nk_record *) ((char *) h->addr + node); + 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. */ + 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 *) ((char *) 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: + DEBUG (2, "insert key in existing lh-record at 0x%zx, posn %zu", + old_offs, j); + + new_offs = insert_lf_record (h, old_offs, j, name, node); + if (new_offs == 0) { + free (blocks); + return 0; + } + + /* Recalculate pointers that could have been invalidated by + * previous call to allocate_block (via insert_lf_record). + */ + nk = (struct ntreg_nk_record *) ((char *) h->addr + node); + parent_nk = (struct ntreg_nk_record *) ((char *) h->addr + parent); + + DEBUG (2, "new lh-record at 0x%zx", 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 *) ((char *) 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. */ + SET_ERRNO (ENOTSUP, "could not find ri->lf link"); + 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")) { + 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; +} + +/* 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 (_hivex_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 *) ((char *) 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) +{ + 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; + } + } + } + SET_ERRNO (ENOTSUP, "could not find parent to child link"); + return -1; + + found:; + 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; +} + +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); + + struct ntreg_value_list *vallist + (struct ntreg_value_list *) ((char *) 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; + + /* Recalculate pointers that could have been invalidated by + * previous call to allocate_block. + */ + nk = (struct ntreg_nk_record *) ((char *) h->addr + node); + 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); + 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; + + /* Recalculate pointers that could have been invalidated by + * previous call to allocate_block. + */ + nk = (struct ntreg_nk_record *) ((char *) h->addr + node); + vallist = (struct ntreg_value_list *) ((char *) h->addr + vallist_offs); + 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); + } + + 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; +} + +int +hivex_node_set_value (hive_h *h, hive_node_h node, + const hive_set_value *val, int flags) +{ + int retval = -1; + hive_value_h *prev_values; + hive_set_value *new_values; + size_t nr_values; + size_t i; + ssize_t idx_of_val; + + prev_values = hivex_node_values (h, node); + if (prev_values == NULL) + return -1; + + /* Count number of existing values in this node. */ + nr_values = 0; + for (i = 0; prev_values[i] != 0; i++) + nr_values++; + + /* Allocate a new hive_set_value list, with space for all existing + * values, plus 1 (for the new key if we're not replacing an + * existing key). + */ + new_values = calloc (nr_values + 1, sizeof (hive_set_value)); + if (new_values == NULL) + goto out1; + + /* Copy the old values to the new values. If we find the key along + * the way, note its index in 'idx_of_val'. + */ + idx_of_val = -1; + for (i = 0; prev_values[i] != 0; i++) { + size_t len; + hive_type t; + char *valkey, *valval; + + valval = hivex_value_value (h, prev_values[i], &t, &len); + if (valval == NULL) goto out2; + + new_values[i].value = valval; + new_values[i].t = t; + new_values[i].len = len; + + valkey = hivex_value_key (h, prev_values[i]); + if (valkey == NULL) goto out2; + + new_values[i].key = valkey; + + if (STRCASEEQ (valkey, val->key)) + idx_of_val = i; + } + + if (idx_of_val > -1) { + free (new_values[idx_of_val].key); + free (new_values[idx_of_val].value); + } else { /* insert it at the end */ + idx_of_val = nr_values; + nr_values++; + } + + new_values[idx_of_val].key = strdup (val->key); + new_values[idx_of_val].value = malloc (val->len); + new_values[idx_of_val].len = val->len; + new_values[idx_of_val].t = val->t; + + if (new_values[idx_of_val].key == NULL || + new_values[idx_of_val].value == NULL) + goto out2; + memcpy (new_values[idx_of_val].value, val->value, val->len); + + retval = hivex_node_set_values (h, node, nr_values, new_values, 0); + + out2: + for (i = 0; i < nr_values; ++i) { + free (new_values[i].key); + free (new_values[i].value); + } + free (new_values); + + out1: + free (prev_values); + + return retval; +} -- 1.8.3.1
Richard W.M. Jones
2013-Jul-25 10:38 UTC
[Libguestfs] [PATCH hivex 10/19] lib: get_children: Document the function, and small non-functional cleanups.
From: "Richard W.M. Jones" <rjones@redhat.com> --- lib/node.c | 40 +++++++++++++++++++++++++++------------- 1 file changed, 27 insertions(+), 13 deletions(-) diff --git a/lib/node.c b/lib/node.c index ad7817f..326b913 100644 --- a/lib/node.c +++ b/lib/node.c @@ -184,7 +184,20 @@ hivex_node_classname (hive_h *h, hive_node_h node) } #endif -/* Iterate over children, returning child nodes and intermediate blocks. */ +/* Iterate over children (ie. subkeys of a node), returning child + * nodes and intermediate blocks. + * + * 'node' is the nk block. + * + * 'flags' can be 0, or GET_CHILDREN_NO_CHECK_NK which bypasses a + * check that each child is a valid block. + * + * The list of child nodes (all nk blocks) is returned in + * 'children_ret'. + * + * The list of intermediate nodes (a mix of lf/lh/ri/li blocks) is + * returned in 'blocks_ret'. + */ int _hivex_get_children (hive_h *h, hive_node_h node, hive_node_h **children_ret, size_t **blocks_ret, @@ -206,7 +219,7 @@ _hivex_get_children (hive_h *h, hive_node_h node, /* Deal with the common "no subkeys" case quickly. */ if (nr_subkeys_in_nk == 0) - goto ok; + goto out; /* Arbitrarily limit the number of subkeys we will ever deal with. */ if (nr_subkeys_in_nk > HIVEX_MAX_SUBKEYS) { @@ -275,7 +288,6 @@ _hivex_get_children (hive_h *h, hive_node_h node, if (_hivex_add_to_offset_list (&children, subkey) == -1) goto error; } - goto ok; } /* Points to ri-record? */ else if (block->id[0] == 'r' && block->id[1] == 'i') { @@ -355,23 +367,25 @@ _hivex_get_children (hive_h *h, hive_node_h node, goto error; } } - goto ok; } - /* else not supported, set errno and fall through */ - SET_ERRNO (ENOTSUP, - "subkey block is not lf/lh/ri (0x%zx, %d, %d)", - subkey_lf, block->id[0], block->id[1]); - error: - _hivex_free_offset_list (&children); - _hivex_free_offset_list (&blocks); - return -1; + else { + SET_ERRNO (ENOTSUP, + "subkey block is not lf/lh/ri (0x%zx, %d, %d)", + subkey_lf, block->id[0], block->id[1]); + goto error; + } - ok: + out: *children_ret = _hivex_return_offset_list (&children); *blocks_ret = _hivex_return_offset_list (&blocks); if (!*children_ret || !*blocks_ret) goto error; return 0; + + error: + _hivex_free_offset_list (&children); + _hivex_free_offset_list (&blocks); + return -1; } hive_node_h * -- 1.8.3.1
Richard W.M. Jones
2013-Jul-25 10:38 UTC
[Libguestfs] [PATCH hivex 11/19] lib: get_children: Use offset_list limits to limit length of returned lists.
From: "Richard W.M. Jones" <rjones@redhat.com> This will allow us to change the function to work recursively, and still enforce these limits. --- lib/node.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/lib/node.c b/lib/node.c index 326b913..1255a84 100644 --- a/lib/node.c +++ b/lib/node.c @@ -229,6 +229,16 @@ _hivex_get_children (hive_h *h, hive_node_h node, goto error; } + /* Don't read mode child nodes than the declared number of subkeys. */ + _hivex_set_offset_list_limit (&children, nr_subkeys_in_nk); + + /* Pre-1.3.8 hivex did not limit the number of intermediate blocks + * it would return, and there is no obvious limit to use. However + * if we ever exceeded HIVEX_MAX_SUBKEYS then there's something + * fishy going on. + */ + _hivex_set_offset_list_limit (&blocks, HIVEX_MAX_SUBKEYS); + /* Preallocate space for the children. */ if (_hivex_grow_offset_list (&children, nr_subkeys_in_nk) == -1) goto error; -- 1.8.3.1
Richard W.M. Jones
2013-Jul-25 10:38 UTC
[Libguestfs] [PATCH hivex 12/19] lib: get_children: Refactor this into two functions.
From: "Richard W.M. Jones" <rjones@redhat.com> Although this change seems rather large, including removing a large block of dead code, there should be no functional change. --- lib/node.c | 122 +++++++++++++++++++++++++++---------------------------------- 1 file changed, 54 insertions(+), 68 deletions(-) diff --git a/lib/node.c b/lib/node.c index 1255a84..91c85e6 100644 --- a/lib/node.c +++ b/lib/node.c @@ -184,6 +184,10 @@ hivex_node_classname (hive_h *h, hive_node_h node) } #endif +static int _get_children (hive_h *h, hive_node_h blkoff, + offset_list *children, offset_list *blocks, + int flags); + /* Iterate over children (ie. subkeys of a node), returning child * nodes and intermediate blocks. * @@ -255,11 +259,45 @@ _hivex_get_children (hive_h *h, hive_node_h node, goto error; } - if (_hivex_add_to_offset_list (&blocks, subkey_lf) == -1) + if (_get_children (h, subkey_lf, &children, &blocks, flags) == -1) goto error; + /* Check the number of children we ended up reading matches + * nr_subkeys_in_nk. + */ + size_t nr_children = _hivex_get_offset_list_length (&children); + if (nr_subkeys_in_nk != nr_children) { + SET_ERRNO (ENOTSUP, + "nr_subkeys_in_nk = %zu " + "is not equal to number of children read %zu", + nr_subkeys_in_nk, nr_children); + goto error; + } + + out: + *children_ret = _hivex_return_offset_list (&children); + *blocks_ret = _hivex_return_offset_list (&blocks); + if (!*children_ret || !*blocks_ret) + goto error; + return 0; + + error: + _hivex_free_offset_list (&children); + _hivex_free_offset_list (&blocks); + return -1; +} + +static int +_get_children (hive_h *h, hive_node_h blkoff, + offset_list *children, offset_list *blocks, + int flags) +{ + /* Add this intermediate block. */ + if (_hivex_add_to_offset_list (blocks, blkoff) == -1) + return -1; + struct ntreg_hbin_block *block - (struct ntreg_hbin_block *) ((char *) h->addr + subkey_lf); + (struct ntreg_hbin_block *) ((char *) h->addr + blkoff); /* Points to lf-record? (Note, also "lh" but that is basically the * same as "lf" as far as we are concerned here). @@ -272,17 +310,10 @@ _hivex_get_children (hive_h *h, hive_node_h node, */ size_t nr_subkeys_in_lf = le16toh (lf->nr_keys); - if (nr_subkeys_in_nk != nr_subkeys_in_lf) { - SET_ERRNO (ENOTSUP, - "nr_subkeys_in_nk = %zu is not equal to nr_subkeys_in_lf = %zu", - nr_subkeys_in_nk, nr_subkeys_in_lf); - goto error; - } - - size_t len = block_len (h, subkey_lf, NULL); + size_t len = block_len (h, blkoff, NULL); if (8 + nr_subkeys_in_lf * 8 > len) { SET_ERRNO (EFAULT, "too many subkeys (%zu, %zu)", nr_subkeys_in_lf, len); - goto error; + return -1; } size_t i; @@ -292,11 +323,11 @@ _hivex_get_children (hive_h *h, hive_node_h node, if (!(flags & GET_CHILDREN_NO_CHECK_NK)) { if (!IS_VALID_BLOCK (h, subkey)) { SET_ERRNO (EFAULT, "subkey is not a valid block (0x%zx)", subkey); - goto error; + return -1; } } - if (_hivex_add_to_offset_list (&children, subkey) == -1) - goto error; + if (_hivex_add_to_offset_list (children, subkey) == -1) + return -1; } } /* Points to ri-record? */ @@ -305,14 +336,14 @@ _hivex_get_children (hive_h *h, hive_node_h node, size_t nr_offsets = le16toh (ri->nr_offsets); - /* Count total number of children. */ - size_t i, count = 0; + /* Copy list of children. */ + size_t i; for (i = 0; i < nr_offsets; ++i) { hive_node_h offset = le32toh (ri->offset[i]); offset += 0x1000; if (!IS_VALID_BLOCK (h, offset)) { SET_ERRNO (EFAULT, "ri-offset is not a valid block (0x%zx)", offset); - goto error; + return -1; } if (!BLOCK_ID_EQ (h, offset, "lf") && !BLOCK_ID_EQ (h, offset, "lh")) { struct ntreg_lf_record *block @@ -320,42 +351,7 @@ _hivex_get_children (hive_h *h, hive_node_h node, SET_ERRNO (ENOTSUP, "ri-record offset does not point to lf/lh (0x%zx, %d, %d)", offset, block->id[0], block->id[1]); - goto error; - } - - if (_hivex_add_to_offset_list (&blocks, offset) == -1) - goto error; - - struct ntreg_lf_record *lf - (struct ntreg_lf_record *) ((char *) h->addr + offset); - - count += le16toh (lf->nr_keys); - } - - if (nr_subkeys_in_nk != count) { - SET_ERRNO (ENOTSUP, - "nr_subkeys_in_nk = %zu is not equal to counted = %zu", - nr_subkeys_in_nk, count); - goto error; - } - - /* Copy list of children. Note nr_subkeys_in_nk is limited to - * something reasonable above. - */ - for (i = 0; i < nr_offsets; ++i) { - hive_node_h offset = le32toh (ri->offset[i]); - offset += 0x1000; - if (!IS_VALID_BLOCK (h, offset)) { - SET_ERRNO (EFAULT, "ri-offset is not a valid block (0x%zx)", offset); - goto error; - } - if (!BLOCK_ID_EQ (h, offset, "lf") && !BLOCK_ID_EQ (h, offset, "lh")) { - struct ntreg_lf_record *block - (struct ntreg_lf_record *) ((char *) h->addr + offset); - SET_ERRNO (ENOTSUP, - "ri-record offset does not point to lf/lh (0x%zx, %d, %d)", - offset, block->id[0], block->id[1]); - goto error; + return -1; } struct ntreg_lf_record *lf @@ -370,32 +366,22 @@ _hivex_get_children (hive_h *h, hive_node_h node, SET_ERRNO (EFAULT, "indirect subkey is not a valid block (0x%zx)", subkey); - goto error; + return -1; } } - if (_hivex_add_to_offset_list (&children, subkey) == -1) - goto error; + if (_hivex_add_to_offset_list (children, subkey) == -1) + return -1; } } } else { SET_ERRNO (ENOTSUP, "subkey block is not lf/lh/ri (0x%zx, %d, %d)", - subkey_lf, block->id[0], block->id[1]); - goto error; + blkoff, block->id[0], block->id[1]); + return -1; } - out: - *children_ret = _hivex_return_offset_list (&children); - *blocks_ret = _hivex_return_offset_list (&blocks); - if (!*children_ret || !*blocks_ret) - goto error; return 0; - - error: - _hivex_free_offset_list (&children); - _hivex_free_offset_list (&blocks); - return -1; } hive_node_h * -- 1.8.3.1
Richard W.M. Jones
2013-Jul-25 10:38 UTC
[Libguestfs] [PATCH hivex 13/19] lib: get_children: Factor out valid block test into separate function.
From: "Richard W.M. Jones" <rjones@redhat.com> Just refactoring, no functional change. --- lib/node.c | 34 ++++++++++++++++++++-------------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/lib/node.c b/lib/node.c index 91c85e6..08b8914 100644 --- a/lib/node.c +++ b/lib/node.c @@ -187,6 +187,7 @@ hivex_node_classname (hive_h *h, hive_node_h node) static int _get_children (hive_h *h, hive_node_h blkoff, offset_list *children, offset_list *blocks, int flags); +static int check_child_is_nk_block (hive_h *h, hive_node_h child, int flags); /* Iterate over children (ie. subkeys of a node), returning child * nodes and intermediate blocks. @@ -320,12 +321,8 @@ _get_children (hive_h *h, hive_node_h blkoff, for (i = 0; i < nr_subkeys_in_lf; ++i) { hive_node_h subkey = le32toh (lf->keys[i].offset); subkey += 0x1000; - if (!(flags & GET_CHILDREN_NO_CHECK_NK)) { - if (!IS_VALID_BLOCK (h, subkey)) { - SET_ERRNO (EFAULT, "subkey is not a valid block (0x%zx)", subkey); - return -1; - } - } + if (check_child_is_nk_block (h, subkey, flags) == -1) + return -1; if (_hivex_add_to_offset_list (children, subkey) == -1) return -1; } @@ -361,14 +358,8 @@ _get_children (hive_h *h, hive_node_h blkoff, for (j = 0; j < le16toh (lf->nr_keys); ++j) { hive_node_h subkey = le32toh (lf->keys[j].offset); subkey += 0x1000; - if (!(flags & GET_CHILDREN_NO_CHECK_NK)) { - if (!IS_VALID_BLOCK (h, subkey)) { - SET_ERRNO (EFAULT, - "indirect subkey is not a valid block (0x%zx)", - subkey); - return -1; - } - } + if (check_child_is_nk_block (h, subkey, flags) == -1) + return -1; if (_hivex_add_to_offset_list (children, subkey) == -1) return -1; } @@ -384,6 +375,21 @@ _get_children (hive_h *h, hive_node_h blkoff, return 0; } +static int +check_child_is_nk_block (hive_h *h, hive_node_h child, int flags) +{ + /* Bypass the check if flag set. */ + if (flags & GET_CHILDREN_NO_CHECK_NK) + return 0; + + if (!IS_VALID_BLOCK (h, child)) { + SET_ERRNO (EFAULT, "subkey is not a valid block (0x%zx)", child); + return -1; + } + + return 0; +} + hive_node_h * hivex_node_children (hive_h *h, hive_node_h node) { -- 1.8.3.1
Richard W.M. Jones
2013-Jul-25 10:38 UTC
[Libguestfs] [PATCH hivex 14/19] lib: get_children: Add additional check that each child is an nk block.
From: "Richard W.M. Jones" <rjones@redhat.com> --- lib/node.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/lib/node.c b/lib/node.c index 08b8914..55dde13 100644 --- a/lib/node.c +++ b/lib/node.c @@ -387,6 +387,15 @@ check_child_is_nk_block (hive_h *h, hive_node_h child, int flags) return -1; } + struct ntreg_hbin_block *block + (struct ntreg_hbin_block *) ((char *) h->addr + child); + + if (!BLOCK_ID_EQ (h, child, "nk")) { + SET_ERRNO (EFAULT, "subkey is not an 'nk' block (0x%zx, %d, %d)", + child, block->id[0], block->id[1]); + return -1; + } + return 0; } -- 1.8.3.1
Richard W.M. Jones
2013-Jul-25 10:38 UTC
[Libguestfs] [PATCH hivex 15/19] lib: get_children: Handle ri-record by just recursing.
From: "Richard W.M. Jones" <rjones@redhat.com> No functional change. --- lib/node.c | 22 ++-------------------- 1 file changed, 2 insertions(+), 20 deletions(-) diff --git a/lib/node.c b/lib/node.c index 55dde13..02da483 100644 --- a/lib/node.c +++ b/lib/node.c @@ -342,27 +342,9 @@ _get_children (hive_h *h, hive_node_h blkoff, SET_ERRNO (EFAULT, "ri-offset is not a valid block (0x%zx)", offset); return -1; } - if (!BLOCK_ID_EQ (h, offset, "lf") && !BLOCK_ID_EQ (h, offset, "lh")) { - struct ntreg_lf_record *block - (struct ntreg_lf_record *) ((char *) h->addr + offset); - SET_ERRNO (ENOTSUP, - "ri-record offset does not point to lf/lh (0x%zx, %d, %d)", - offset, block->id[0], block->id[1]); + + if (_get_children (h, offset, children, blocks, flags) == -1) return -1; - } - - struct ntreg_lf_record *lf - (struct ntreg_lf_record *) ((char *) h->addr + offset); - - size_t j; - for (j = 0; j < le16toh (lf->nr_keys); ++j) { - hive_node_h subkey = le32toh (lf->keys[j].offset); - subkey += 0x1000; - if (check_child_is_nk_block (h, subkey, flags) == -1) - return -1; - if (_hivex_add_to_offset_list (children, subkey) == -1) - return -1; - } } } else { -- 1.8.3.1
Richard W.M. Jones
2013-Jul-25 10:38 UTC
[Libguestfs] [PATCH hivex 16/19] lib: get_children: Add a check that we don't overrun ri-record when reading.
From: "Richard W.M. Jones" <rjones@redhat.com> --- lib/node.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/node.c b/lib/node.c index 02da483..9127251 100644 --- a/lib/node.c +++ b/lib/node.c @@ -300,6 +300,8 @@ _get_children (hive_h *h, hive_node_h blkoff, struct ntreg_hbin_block *block (struct ntreg_hbin_block *) ((char *) h->addr + blkoff); + size_t len = block_len (h, blkoff, NULL); + /* Points to lf-record? (Note, also "lh" but that is basically the * same as "lf" as far as we are concerned here). */ @@ -311,7 +313,6 @@ _get_children (hive_h *h, hive_node_h blkoff, */ size_t nr_subkeys_in_lf = le16toh (lf->nr_keys); - size_t len = block_len (h, blkoff, NULL); if (8 + nr_subkeys_in_lf * 8 > len) { SET_ERRNO (EFAULT, "too many subkeys (%zu, %zu)", nr_subkeys_in_lf, len); return -1; @@ -333,6 +334,11 @@ _get_children (hive_h *h, hive_node_h blkoff, size_t nr_offsets = le16toh (ri->nr_offsets); + if (8 + nr_offsets * 4 > len) { + SET_ERRNO (EFAULT, "too many offsets (%zu, %zu)", nr_offsets, len); + return -1; + } + /* Copy list of children. */ size_t i; for (i = 0; i < nr_offsets; ++i) { -- 1.8.3.1
Richard W.M. Jones
2013-Jul-25 10:38 UTC
[Libguestfs] [PATCH hivex 17/19] lib: get_children: Handle li-records as intermediate nodes (RHBZ#717583).
From: "Richard W.M. Jones" <rjones@redhat.com> This is a full fix for https://bugzilla.redhat.com/show_bug.cgi?id=717583 --- lib/node.c | 88 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 87 insertions(+), 1 deletion(-) diff --git a/lib/node.c b/lib/node.c index 9127251..4824df2 100644 --- a/lib/node.c +++ b/lib/node.c @@ -202,6 +202,64 @@ static int check_child_is_nk_block (hive_h *h, hive_node_h child, int flags); * * The list of intermediate nodes (a mix of lf/lh/ri/li blocks) is * returned in 'blocks_ret'. + * + * ---------------------------------------- + * + * The format of the intermediate blocks is not documented, but + * appears to be this: + * + * +-------------+ This is the parent registry key. + * | nk | + * |-------------| + * | subkey_lf ----> Points to either lf/lh, li or ri + * | | (all 3 cases have to be dealt with + * | | separately, see below) + * +-------------+ + * + * The subkey_lf field can point to one of three things, which are + * all subtly different: + * + * (1) lf/lh record. (It's not clear what the precise difference + * is but we treat them as the same thing) + * + * +-------------+ + * | lf/lh | + * |-------------| + * | nr_keys | + * | keys[0].offset ------> points to nk blocks (the children) + * | keys[1].offset ------> + * | keys[2].offset ------> + * +-------------+ + * + * (2) li record. + * + * Although the format of an li-record is the same as the format of an + * ri-record, the difference is that the offsets point directly to the + * nk blocks (children). + * + * +-------------+ + * | li | + * |-------------| + * | nr_offsets | + * | offset[0] ------> points to nk blocks (the children) + * | offset[1] ------> + * | offset[2] ------> + * +-------------+ + * + * (3) ri record. + * + * The format of the block is the same as the li-record, BUT ri-record + * offsets *never* point directly to nk blocks. They only point to + * other lf/lh/li/ri-records, thus forming a tree of arbitrary depth. + * + * +-------------+ + * | ri | + * |-------------| + * | nr_offsets | + * | offset[0] ------> points to another lf/lh/li/ri block + * | offset[1] ------> + * | offset[2] ------> + * +-------------+ */ int _hivex_get_children (hive_h *h, hive_node_h node, @@ -328,6 +386,34 @@ _get_children (hive_h *h, hive_node_h blkoff, return -1; } } + /* Points to li-record? */ + else if (block->id[0] == 'l' && block->id[1] == 'i') { + /* li-records are formatted the same as ri-records, but they + * contain direct links to child records (same as lf/lh), so + * we treat them the same way as lf/lh. + */ + struct ntreg_ri_record *ri = (struct ntreg_ri_record *) block; + + /* Check number of subkeys in the nk-record matches number of subkeys + * in the li-record. + */ + size_t nr_offsets = le16toh (ri->nr_offsets); + + if (8 + nr_offsets * 4 > len) { + SET_ERRNO (EFAULT, "too many offsets (%zu, %zu)", nr_offsets, len); + return -1; + } + + size_t i; + for (i = 0; i < nr_offsets; ++i) { + hive_node_h subkey = le32toh (ri->offset[i]); + subkey += 0x1000; + if (check_child_is_nk_block (h, subkey, flags) == -1) + return -1; + if (_hivex_add_to_offset_list (children, subkey) == -1) + return -1; + } + } /* Points to ri-record? */ else if (block->id[0] == 'r' && block->id[1] == 'i') { struct ntreg_ri_record *ri = (struct ntreg_ri_record *) block; @@ -355,7 +441,7 @@ _get_children (hive_h *h, hive_node_h blkoff, } else { SET_ERRNO (ENOTSUP, - "subkey block is not lf/lh/ri (0x%zx, %d, %d)", + "subkey block is not lf/lh/li/ri (0x%zx, %d, %d)", blkoff, block->id[0], block->id[1]); return -1; } -- 1.8.3.1
Richard W.M. Jones
2013-Jul-25 10:39 UTC
[Libguestfs] [PATCH hivex 18/19] lib: node_add_child: Factor out subkey insertion into separate function.
From: "Richard W.M. Jones" <rjones@redhat.com> No functional change. --- lib/write.c | 160 +++++++++++++++++++++++++++++++++--------------------------- 1 file changed, 87 insertions(+), 73 deletions(-) diff --git a/lib/write.c b/lib/write.c index 236ff3c..371b1ab 100644 --- a/lib/write.c +++ b/lib/write.c @@ -407,6 +407,82 @@ compare_name_with_nk_name (hive_h *h, const char *name, hive_node_h nk_offs) return r; } +static int +insert_subkey (hive_h *h, const char *name, + size_t parent, size_t nkoffset, size_t *blocks) +{ + size_t old_offs = 0, new_offs = 0; + size_t i, j; + 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 *) ((char *) 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: + DEBUG (2, "insert key in existing lh-record at 0x%zx, posn %zu", + old_offs, j); + + new_offs = insert_lf_record (h, old_offs, j, name, nkoffset); + if (new_offs == 0) + return -1; + + DEBUG (2, "new lh-record at 0x%zx", new_offs); + + /* Recalculate pointers that could have been invalidated by + * previous call to allocate_block (via new_lh_record). + */ + struct ntreg_nk_record *parent_nk + (struct ntreg_nk_record *) ((char *) h->addr + parent); + + /* 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 *) ((char *) 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. */ + SET_ERRNO (ENOTSUP, "could not find ri->lf link"); + return -1; + + found_it:; + } + + return 0; +} + hive_node_h hivex_node_add_child (hive_h *h, hive_node_h parent, const char *name) { @@ -430,14 +506,14 @@ hivex_node_add_child (hive_h *h, hive_node_h parent, const char *name) /* 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) + hive_node_h nkoffset = allocate_block (h, seg_len, nk_id); + if (nkoffset == 0) return 0; - DEBUG (2, "allocated new nk-record for child at 0x%zx", node); + DEBUG (2, "allocated new nk-record for child at 0x%zx", nkoffset); struct ntreg_nk_record *nk - (struct ntreg_nk_record *) ((char *) h->addr + node); + (struct ntreg_nk_record *) ((char *) h->addr + nkoffset); nk->flags = htole16 (0x0020); /* key is ASCII. */ nk->parent = htole32 (parent - 0x1000); nk->subkey_lf = htole32 (0xffffffff); @@ -488,13 +564,13 @@ hivex_node_add_child (hive_h *h, hive_node_h parent, const char *name) return 0; free (unused); - size_t i, j; + size_t i; 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); + size_t lh_offs = new_lh_record (h, name, nkoffset); if (lh_offs == 0) { free (blocks); return 0; @@ -503,7 +579,7 @@ hivex_node_add_child (hive_h *h, hive_node_h parent, const char *name) /* Recalculate pointers that could have been invalidated by * previous call to allocate_block (via new_lh_record). */ - nk = (struct ntreg_nk_record *) ((char *) h->addr + node); + nk = (struct ntreg_nk_record *) ((char *) h->addr + nkoffset); parent_nk = (struct ntreg_nk_record *) ((char *) h->addr + parent); DEBUG (2, "no keys, allocated new lh-record at 0x%zx", lh_offs); @@ -511,78 +587,16 @@ hivex_node_add_child (hive_h *h, hive_node_h parent, const char *name) 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 *) ((char *) 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: - DEBUG (2, "insert key in existing lh-record at 0x%zx, posn %zu", - old_offs, j); - - new_offs = insert_lf_record (h, old_offs, j, name, node); - if (new_offs == 0) { + 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 insert_lf_record). + * previous call to allocate_block (via new_lh_record). */ - nk = (struct ntreg_nk_record *) ((char *) h->addr + node); + nk = (struct ntreg_nk_record *) ((char *) h->addr + nkoffset); parent_nk = (struct ntreg_nk_record *) ((char *) h->addr + parent); - - DEBUG (2, "new lh-record at 0x%zx", 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 *) ((char *) 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. */ - SET_ERRNO (ENOTSUP, "could not find ri->lf link"); - free (blocks); - return 0; - - found_it: - ; - } } free (blocks); @@ -596,7 +610,7 @@ hivex_node_add_child (hive_h *h, hive_node_h parent, const char *name) if (max < strlen (name) * 2) /* *2 because "recoded" in UTF16-LE. */ parent_nk->max_subkey_name_len = htole16 (strlen (name) * 2); - return node; + return nkoffset; } /* Decrement the refcount of an sk-record, and if it reaches zero, -- 1.8.3.1
Richard W.M. Jones
2013-Jul-25 10:39 UTC
[Libguestfs] [PATCH hivex 19/19] lib: node_add_child: Handle li-records as intermediate notes (RHBZ#987463).
From: "Richard W.M. Jones" <rjones@redhat.com> This commit implements adding a child to a registry key that uses li-records for intermediate nodes. --- lib/write.c | 139 +++++++++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 114 insertions(+), 25 deletions(-) diff --git a/lib/write.c b/lib/write.c index 371b1ab..076cf8c 100644 --- a/lib/write.c +++ b/lib/write.c @@ -385,6 +385,57 @@ insert_lf_record (hive_h *h, size_t old_offs, size_t posn, 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); + + 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; +} + /* 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) @@ -407,19 +458,34 @@ compare_name_with_nk_name (hive_h *h, const char *name, hive_node_h nk_offs) return r; } +/* See comment about keeping things sorted in hivex_node_add_child. */ static int insert_subkey (hive_h *h, const char *name, size_t parent, size_t nkoffset, size_t *blocks) { size_t old_offs = 0, new_offs = 0; - size_t i, j; + size_t i, j = SIZE_MAX; struct ntreg_lf_record *old_lf = NULL; + struct ntreg_ri_record *old_li = NULL; - /* Find lf/lh key name just after the one we are inserting. */ + /* The caller already dealt with the no subkeys case, so this should + * be true. + */ + assert (blocks[0] != 0); + + /* Find the intermediate block which contains a link to the key that + * is just after the one we are inserting. This intermediate block + * might be an lf/lh-record or an li-record (but it won't be a + * ri-record so we can ignore those). The lf/lh- and li-records + * have different formats. If we cannot find the key after, then we + * end up at the final block and we have to insert the new key at + * the end. + */ 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 *) ((char *) h->addr + old_offs); + old_li = NULL; for (j = 0; j < le16toh (old_lf->nr_keys); ++j) { hive_node_h nk_offs = le32toh (old_lf->keys[j].offset); nk_offs += 0x1000; @@ -427,39 +493,62 @@ insert_subkey (hive_h *h, const char *name, goto insert_it; } } + else if (BLOCK_ID_EQ (h, blocks[i], "li")) { + old_offs = blocks[i]; + old_lf = NULL; + old_li = (struct ntreg_ri_record *) ((char *) h->addr + old_offs); + for (j = 0; j < le16toh (old_li->nr_offsets); ++j) { + hive_node_h nk_offs = le32toh (old_li->offset[j]); + 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); + /* To insert it at the end, we fall through here. */ + assert (j != SIZE_MAX); - /* Insert it. */ insert_it: - DEBUG (2, "insert key in existing lh-record at 0x%zx, posn %zu", - old_offs, j); + /* Verify that the search worked. */ + assert (old_lf || old_li); + assert (!(old_lf && old_li)); - new_offs = insert_lf_record (h, old_offs, j, name, nkoffset); - if (new_offs == 0) - return -1; + if (old_lf) { + DEBUG (2, "insert key in existing lf/lh-record at 0x%zx, posn %zu", + old_offs, j); - DEBUG (2, "new lh-record at 0x%zx", new_offs); + new_offs = insert_lf_record (h, old_offs, j, name, nkoffset); + if (new_offs == 0) + return -1; + + DEBUG (2, "new lf/lh-record at 0x%zx", new_offs); + } + else /* old_li */ { + DEBUG (2, "insert key in existing li-record at 0x%zx, posn %zu", + old_offs, j); + + new_offs = insert_li_record (h, old_offs, j, name, nkoffset); + if (new_offs == 0) + return -1; + + DEBUG (2, "new li-record at 0x%zx", new_offs); + } /* Recalculate pointers that could have been invalidated by - * previous call to allocate_block (via new_lh_record). + * previous call to allocate_block (via new_{lf,li}_record). */ struct ntreg_nk_record *parent_nk (struct ntreg_nk_record *) ((char *) h->addr + parent); - /* If the lf/lh-record was directly referenced by the parent nk, - * then update the parent nk. + /* Since the lf/lh/li-record has moved, now we have to find the old + * reference to it and update it. It might be referenced directly + * from the parent_nk->subkey_lf, or it might be referenced + * indirectly from some ri-record in blocks[]. Since we can update + * either of these in-place, we don't need to do this recursively. */ 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")) { @@ -548,11 +637,11 @@ hivex_node_add_child (hive_h *h, hive_node_h parent, const char *name) * 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. + * 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. -- 1.8.3.1
Apparently Analagous Threads
- [PATCH 01/14] hivexsh: Document some peculiarities of the "cd" command.
- [PATCH 0/2] Fix errors found by Clang static analyzer
- [PATCH hivex] maint: split long lines
- hivex lib: Add function hivex_node_num_children
- [PATCH v4 0/5] hivex: handle corrupted hives better.