[PATCH 0/12] Add support for writing to hive files
This patch series adds support for some simple operations on hive files, and I've now tested and verified that those operations work correctly. All except for the last patch (12/12) are ready to be committed. The last patch is WIP. Rich. -- Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones virt-df lists disk usage of guests without needing to install any software inside the virtual machine. Supports Linux and Windows. http://et.redhat.com/~rjones/virt-df/
-- Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones New in Fedora 11: Fedora Windows cross-compiler. Compile Windows programs, test, and build Windows installers. Over 70 libraries supprt'd http://fedoraproject.org/wiki/MinGW http://www.annexia.org/fedora_mingw
On 03/02/10 18:31, Richard W.M. Jones wrote:
Subject: [PATCH 01/12] Move htole*/le*toh macros into a separate header file.
This allows us to reuse these macros in hivexsh later. --- hivex/Makefile.am | 3 +- hivex/byte_conversions.h | 89 ++++++++++++++++++++++++++++++++++++++++++++++ hivex/hivex.c | 43 +--------------------- 3 files changed, 93 insertions(+), 42 deletions(-) create mode 100644 hivex/byte_conversions.h
ACK. Matt -- Matthew Booth, RHCA, RHCSS Red Hat Engineering, Virtualisation Team M: +44 (0)7977 267231 GPG ID: D33C3490 GPG FPR: 3733 612D 2D05 5458 8A8A 1600 3441 EA19 D33C 3490
-- Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones virt-p2v converts physical machines to virtual machines. Boot with a live CD or over the network (PXE) and turn machines into Xen guests. http://et.redhat.com/~rjones/virt-p2v
On 03/02/10 18:31, Richard W.M. Jones wrote:
Subject: [PATCH 02/12] Misc documentation and gitignore update.
--- hivex/README | 4 +++- m4/.gitignore | 1 + 2 files changed, 4 insertions(+), 1 deletions(-)
Trivial. ACK. Matt -- Matthew Booth, RHCA, RHCSS Red Hat Engineering, Virtualisation Team M: +44 (0)7977 267231 GPG ID: D33C3490 GPG FPR: 3733 612D 2D05 5458 8A8A 1600 3441 EA19 D33C 3490
-- Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones virt-df lists disk usage of guests without needing to install any software inside the virtual machine. Supports Linux and Windows. http://et.redhat.com/~rjones/virt-df/
On 03/02/10 18:32, Richard W.M. Jones wrote:
Subject: [PATCH 03/12] Document that this flag is clear for default keys.
--- hivex/hivex.c | 3 ++- 1 files changed, 2 insertions(+), 1 deletions(-)
ACK. Matt -- Matthew Booth, RHCA, RHCSS Red Hat Engineering, Virtualisation Team M: +44 (0)7977 267231 GPG ID: D33C3490 GPG FPR: 3733 612D 2D05 5458 8A8A 1600 3441 EA19 D33C 3490
-- Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones virt-df lists disk usage of guests without needing to install any software inside the virtual machine. Supports Linux and Windows. http://et.redhat.com/~rjones/virt-df/
On 03/02/10 18:32, Richard W.M. Jones wrote:
Subject: [PATCH 04/12] hivexsh: Change handling of prompt argument to rl_gets()
Make the result of isatty into a global variable (is_tty).
Change the rl_gets() function so it takes the prompt string instead of a "display prompt?" flag. rl_gets() then consults the global to find out if it should display the prompt at all. --- hivex/hivexsh.c | 19 ++++++++++--------- 1 files changed, 10 insertions(+), 9 deletions(-)
ACK. Matt -- Matthew Booth, RHCA, RHCSS Red Hat Engineering, Virtualisation Team M: +44 (0)7977 267231 GPG ID: D33C3490 GPG FPR: 3733 612D 2D05 5458 8A8A 1600 3441 EA19 D33C 3490
-- Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones virt-df lists disk usage of guests without needing to install any software inside the virtual machine. Supports Linux and Windows. http://et.redhat.com/~rjones/virt-df/
On 03/02/10 18:33, Richard W.M. Jones wrote:
Subject: [PATCH 05/12] hivexsh: Only print final \n when interactive.
When hivexsh was called non-interactively, it would print an annoying extra line. Only print this line if we are being used interactively. --- hivex/hivexsh.c | 3 ++- 1 files changed, 2 insertions(+), 1 deletions(-)
ACK. Matt -- Matthew Booth, RHCA, RHCSS Red Hat Engineering, Virtualisation Team M: +44 (0)7977 267231 GPG ID: D33C3490 GPG FPR: 3733 612D 2D05 5458 8A8A 1600 3441 EA19 D33C 3490
-- Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones virt-df lists disk usage of guests without needing to install any software inside the virtual machine. Supports Linux and Windows. http://et.redhat.com/~rjones/virt-df/
On 03/02/10 18:33, Richard W.M. Jones wrote:
Subject: [PATCH 06/12] hivexsh: Change some exit(1) -> exit(EXIT_FAILURE)
--- hivex/hivexsh.c | 4 ++-- 1 files changed, 2 insertions(+), 2 deletions(-)
ACK. Incidentally, why? I hate magic numbers, but still use 0 and 1 for exit codes. Matt -- Matthew Booth, RHCA, RHCSS Red Hat Engineering, Virtualisation Team M: +44 (0)7977 267231 GPG ID: D33C3490 GPG FPR: 3733 612D 2D05 5458 8A8A 1600 3441 EA19 D33C 3490
On Thu, Feb 04, 2010 at 12:04:04PM +0000, Matthew Booth wrote:
On 03/02/10 18:33, Richard W.M. Jones wrote:
Subject: [PATCH 06/12] hivexsh: Change some exit(1) -> exit(EXIT_FAILURE)
--- hivex/hivexsh.c | 4 ++-- 1 files changed, 2 insertions(+), 2 deletions(-)
ACK.
Incidentally, why? I hate magic numbers, but still use 0 and 1 for exit codes.
IIRC we check for it in 'make syntax-check'. Are there platforms which exit with non-standard codes? VMS apparently ... Rich. -- Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones virt-df lists disk usage of guests without needing to install any software inside the virtual machine. Supports Linux and Windows. http://et.redhat.com/~rjones/virt-df/
-- Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones libguestfs lets you edit virtual machines. Supports shell scripting, bindings from many languages. http://et.redhat.com/~rjones/libguestfs/ See what it can do: http://et.redhat.com/~rjones/libguestfs/recipes.html
On 03/02/10 18:33, Richard W.M. Jones wrote:
Subject: [PATCH 07/12] Tools for analyzing and reverse engineering hive files.
This commit is not of general interest. It contains the tools which I used to reverse engineer the hive format and to test changes. Keeping these with the rest of the code is useful in case in future we encounter a hive file that we fail to modify.
Note that the tools are not compiled by default. You have to compile each explicitly with:
make -C hivex/tools <toolname>.opt --- .gitignore | 1 + configure.ac | 1 + hivex/Makefile.am | 2 + hivex/tools/Makefile.am | 54 +++ hivex/tools/clearheaderfields.ml | 112 +++++ hivex/tools/fillemptyhbins.ml | 74 ++++ hivex/tools/truncatefile.ml | 112 +++++ hivex/tools/visualizer.ml | 845 +++++++++++++++++++++++++++++++++++++ hivex/tools/visualizer_NT_time.ml | 30 ++ hivex/tools/visualizer_utils.ml | 160 +++++++ 10 files changed, 1391 insertions(+), 0 deletions(-) create mode 100644 hivex/tools/Makefile.am create mode 100644 hivex/tools/clearheaderfields.ml create mode 100644 hivex/tools/fillemptyhbins.ml create mode 100644 hivex/tools/truncatefile.ml create mode 100644 hivex/tools/visualizer.ml create mode 100644 hivex/tools/visualizer_NT_time.ml create mode 100644 hivex/tools/visualizer_utils.ml
Ok, I haven't looked at the contents, but agree that keeping them around is useful. The only bits which might break anything are the build changes. Have you confirmed that this genuinely does nothing during a normal build? I note you've included tools as a SUBDIR in hivex/Makefile.am which, as I understand it, will cause make to be executed with the same target as the parent directory on every invocation. Does 'make' without arguments genuinely do nothing in that directory? I vaguely recall that it will default to building the first target, which would be visualizer.opt here. It might be better not to specify tools as a SUBDIR in hivex/Makefile.am. Matt -- Matthew Booth, RHCA, RHCSS Red Hat Engineering, Virtualisation Team M: +44 (0)7977 267231 GPG ID: D33C3490 GPG FPR: 3733 612D 2D05 5458 8A8A 1600 3441 EA19 D33C 3490
On Thu, Feb 04, 2010 at 12:11:11PM +0000, Matthew Booth wrote:
Have you confirmed that this genuinely does nothing during a normal build? I note you've included tools as a SUBDIR in hivex/Makefile.am which, as I understand it, will cause make to be executed with the same target as the parent directory on every invocation. Does 'make' without arguments genuinely do nothing in that directory? I vaguely recall that it will default to building the first target, which would be visualizer.opt here.
I confirmed it does nothing. automake adds its own 'all:' rule at the beginning of the generated Makefile which in this case does nothing. Rich. -- Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones New in Fedora 11: Fedora Windows cross-compiler. Compile Windows programs, test, and build Windows installers. Over 70 libraries supprt'd http://fedoraproject.org/wiki/MinGW http://www.annexia.org/fedora_mingw
On 04/02/10 12:43, Richard W.M. Jones wrote:
On Thu, Feb 04, 2010 at 12:11:11PM +0000, Matthew Booth wrote:
Have you confirmed that this genuinely does nothing during a normal build? I note you've included tools as a SUBDIR in hivex/Makefile.am which, as I understand it, will cause make to be executed with the same target as the parent directory on every invocation. Does 'make' without arguments genuinely do nothing in that directory? I vaguely recall that it will default to building the first target, which would be visualizer.opt here.
I confirmed it does nothing. automake adds its own 'all:' rule at the beginning of the generated Makefile which in this case does nothing.
Ok. ACK. Matt -- Matthew Booth, RHCA, RHCSS Red Hat Engineering, Virtualisation Team M: +44 (0)7977 267231 GPG ID: D33C3490 GPG FPR: 3733 612D 2D05 5458 8A8A 1600 3441 EA19 D33C 3490
-- Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones Read my programming blog: http://rwmj.wordpress.com Fedora now supports 80 OCaml packages (the OPEN alternative to F#) http://cocan.org/getting_started_with_ocaml_on_red_hat_and_fedora
On 03/02/10 18:34, Richard W.M. Jones wrote:
Subject: [PATCH 08/12] hivex: Add HIVEX_OPEN_WRITE flag to allow hive to be opened for writing.
If this flag is omitted (as in the case for all existing callers) then the hive is still opened read-only.
We add a 'writable' flag to the hive handle, and we change the way that the hive file (data) is stored. The data is still mmapped if the file is opened read-only, since that is more efficient and allows us to handle larger hives. However if we need to write to the file then we have to read it all into memory, since if we had to extend the file we need to realloc that data.
Note the manpage section L</WRITING TO HIVE FILES> comes in a later commit.
I'm not convinced this is a good idea. It introduces an asymmetry which is like to lead to head-scratching bugs down the line. Why can't we mremap where realloc would be necessary? Matt -- Matthew Booth, RHCA, RHCSS Red Hat Engineering, Virtualisation Team M: +44 (0)7977 267231 GPG ID: D33C3490 GPG FPR: 3733 612D 2D05 5458 8A8A 1600 3441 EA19 D33C 3490
On Thu, Feb 04, 2010 at 01:34:10PM +0000, Matthew Booth wrote:
On 03/02/10 18:34, Richard W.M. Jones wrote:
Subject: [PATCH 08/12] hivex: Add HIVEX_OPEN_WRITE flag to allow hive to be opened for writing.
If this flag is omitted (as in the case for all existing callers) then the hive is still opened read-only.
We add a 'writable' flag to the hive handle, and we change the way that the hive file (data) is stored. The data is still mmapped if the file is opened read-only, since that is more efficient and allows us to handle larger hives. However if we need to write to the file then we have to read it all into memory, since if we had to extend the file we need to realloc that data.
Note the manpage section L</WRITING TO HIVE FILES> comes in a later commit.
I'm not convinced this is a good idea. It introduces an asymmetry which is like to lead to head-scratching bugs down the line.
Why can't we mremap where realloc would be necessary?
Actually I had no idea about mremap. It's a _GNU_SOURCE function so not portable ... But the reason we want a private copy with a separate 'commit' operation is because our write functions are not careful to clean up after themselves in the case where they fail (because doing that would introduce a huge, rarely tested set of error-path cases). So you need a separate commit operation which you only call when the previous write calls were all successful. At this point I'd prefer not to rewrite this code to use mremap, even though there would be some small saving in memory usage to have a private mapping. Rich. -- Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones Read my programming blog: http://rwmj.wordpress.com Fedora now supports 80 OCaml packages (the OPEN alternative to F#) http://cocan.org/getting_started_with_ocaml_on_red_hat_and_fedora
-- Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones New in Fedora 11: Fedora Windows cross-compiler. Compile Windows programs, test, and build Windows installers. Over 70 libraries supprt'd http://fedoraproject.org/wiki/MinGW http://www.annexia.org/fedora_mingw
On 03/02/10 18:34, Richard W.M. Jones wrote:
Subject: [PATCH 09/12] hivex: Begin implementation of writing to hives.
This implements hivex_node_set_values which is used to delete the (key, value) pairs at a node and optionally replace them with a new set.
This also implements hivex_commit which is used to commit changes to hives back to disk. --- hivex/hivex.c | 384 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ hivex/hivex.h | 12 ++ hivex/hivex.pod | 125 ++++++++++++++++++ 3 files changed, 521 insertions(+), 0 deletions(-)
This looks generally ok. However, it is *extremely* dense. I've indicated a few places where I feel there are insufficient comments, or magic numbers making the code less clear than it could be.
+/* 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) +{
Every usage of nr_4k_pages in this function multiplies it by 4096. It would be more readable to choose a different name which reflects that it's simply a rounding.
+ /* 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);
I think this assert is impossible to trigger.
+ /* 'extend' is the number of bytes to extend the file by. Note that + * hives found in the wild often contain slack between 'endpages' + * and the actual end of the file, so we don't always need to make + * the file larger. + */ + ssize_t extend = h->endpages + nr_4k_pages * 4096 - h->size; + + if (h->msglvl >= 2) { + fprintf (stderr, "allocate_page: current endpages = 0x%zx, current size = 0x%zx\n", + h->endpages, h->size); + fprintf (stderr, "allocate_page: extending file by %zd bytes (<= 0 if no extension)\n", + extend); + } + + if (extend > 0) { + size_t oldsize = h->size; + size_t newsize = h->size + extend;
In general, are we worrying about overflow?
+ char *newaddr = realloc (h->addr, newsize); + if (newaddr == NULL) + return 0; + + size_t oldbitmapsize = 1 + oldsize / 32; + size_t newbitmapsize = 1 + newsize / 32; + char *newbitmap = realloc (h->bitmap, newbitmapsize); + if (newbitmap == NULL) { + free (newaddr); + return 0; + } + + h->addr = newaddr; + h->size = newsize; + h->bitmap = newbitmap; + + memset (h->addr + oldsize, 0, newsize - oldsize); + memset (h->bitmap + oldbitmapsize, 0, newbitmapsize - oldbitmapsize); + } + + size_t offset = h->endpages; + h->endpages += nr_4k_pages * 4096; + + if (h->msglvl >= 2) + fprintf (stderr, "allocate_page: new endpages = 0x%zx, new size = 0x%zx\n", + h->endpages, h->size); + + /* Write the hbin header. */ + struct ntreg_hbin_page *page = + (struct ntreg_hbin_page *) (h->addr + offset); + page->magic[0] = 'h'; + page->magic[1] = 'b'; + page->magic[2] = 'i'; + page->magic[3] = 'n'; + page->offset_first = htole32 (offset - 0x1000);
What's 0x1000? Can you comment this?
+ page->page_size = htole32 (nr_4k_pages * 4096); + memset (page->unknown, 0, sizeof (page->unknown)); + + if (h->msglvl >= 2) + fprintf (stderr, "allocate_page: new page at 0x%zx\n", offset); + + /* Offset of first usable byte after the header. */ + return offset + sizeof (struct ntreg_hbin_page); +} + +/* Allocate a single block, first allocating an hbin (page) at the end + * of the current file if necessary. NB. To keep the implementation + * simple and more likely to be correct, we do not reuse existing free + * blocks. + * + * seg_len is the size of the block (this INCLUDES the block header). + * The header of the block is initialized to -seg_len (negative to + * indicate used). id[2] is the block ID (type), eg. "nk" for nk- + * record. The block bitmap is updated to show this block as valid. + * The rest of the contents of the block will be zero. + * + * Returns: + * > 0 : offset of new block + * 0 : error (errno set) + */ +static size_t +allocate_block (hive_h *h, size_t seg_len, const char id[2]) +{ + if (!h->writable) { + errno = EROFS; + return 0; + } + + if (seg_len < 4) {
Can you replace this '4' with a sizeof(ntreg_hbin_block.seg_len)?
+ /* The caller probably forgot to include the header. Note that + * value lists have no ID field, so seg_len == 4 would be possible + * for them, albeit unusual. + */ + if (h->msglvl >= 2) + fprintf (stderr, "allocate_block: refusing too small allocation (%zu), returning ERANGE\n", + seg_len); + errno = ERANGE; + return 0; + } + + /* Refuse really large allocations. */ + if (seg_len > 1000000) {
Can you replace 1000000 with a #define?
+ if (h->msglvl >= 2) + fprintf (stderr, "allocate_block: refusing large allocation (%zu), returning ERANGE\n", + seg_len); + errno = ERANGE; + return 0; + } + + /* Round up allocation to multiple of 8 bytes. All blocks must be + * on an 8 byte boundary. + */ + seg_len = (seg_len + 7) & ~7; + + /* Allocate a new page if necessary. */ + if (h->endblocks == 0 || h->endblocks + seg_len > h->endpages) { + size_t newendblocks = allocate_page (h, seg_len); + if (newendblocks == 0) + return 0; + h->endblocks = newendblocks; + } + + size_t offset = h->endblocks; + + if (h->msglvl >= 2) + fprintf (stderr, "allocate_block: new block at 0x%zx, size %zu\n", + offset, seg_len); + + struct ntreg_hbin_block *blockhdr = + (struct ntreg_hbin_block *) (h->addr + offset); + + blockhdr->seg_len = htole32 (- (int32_t) seg_len); + if (id[0] && id[1] && seg_len >= 6) {
Can you replace 6 with sizeof(ntreg_hbin_block)?
+ blockhdr->id[0] = id[0]; + blockhdr->id[1] = id[1]; + } + + h->endblocks += seg_len; + + /* If there is space after the last block in the last page, then we + * have to put a dummy free block header here to mark the rest of + * the page as free. + */ + ssize_t rem = h->endpages - h->endblocks; + if (rem > 0) { + if (h->msglvl >= 2) + fprintf (stderr, "allocate_block: marking remainder of page free starting at 0x%zx, size %zd\n", + h->endblocks, rem); + + assert (rem >= 4);
This assertion doesn't look safe. Why must this be the case? In the event that rem < 4, would you allocate a new page for the dummy free block?
+ blockhdr = (struct ntreg_hbin_block *) (h->addr + h->endblocks); + blockhdr->seg_len = htole32 ((int32_t) rem);
Will blockhdr->id ever be read here? Is 0x00 valid?
+ } + + return offset; +} +
+ +/* Delete all existing values at this node. */ +static int +delete_values (hive_h *h, hive_node_h node) +{ + assert (h->writable); + + hive_value_h *values; + size_t *blocks; + if (get_values (h, node, &values, &blocks) == -1) + return -1; + + size_t i; + for (i = 0; blocks[i] != 0; ++i) + mark_block_unused (h, blocks[i]); + + free (blocks); + + for (i = 0; values[i] != 0; ++i) { + struct ntreg_vk_record *vk = + (struct ntreg_vk_record *) (h->addr + values[i]); + + size_t len; + len = le32toh (vk->data_len); + if (len == 0x80000000) /* special case */ + len = 4; + len &= 0x7fffffff;
Comment, please. What's going on with this record length?
+ + if (len > 4) { /* non-inline, so remove data block */ + size_t data_offset = le32toh (vk->data_offset); + data_offset += 0x1000; + mark_block_unused (h, data_offset);
More magic numbers :) Why is this necessary?
+ } + + /* remove vk record */ + mark_block_unused (h, values[i]); + } + + free (values); + + struct ntreg_nk_record *nk = (struct ntreg_nk_record *) (h->addr + node); + nk->nr_values = htole32 (0); + nk->vallist = htole32 (0xffffffff); + + return 0; +} + +int +hivex_commit (hive_h *h, const char *filename, int flags) +{ + if (flags != 0) { + errno = EINVAL; + return -1; + }
What are you intending to use the flags for?
+ if (!h->writable) { + errno = EROFS; + return -1; + } + + filename = filename ? : h->filename; + int fd = open (filename, O_WRONLY|O_CREAT|O_TRUNC|O_NOCTTY, 0666); + if (fd == -1) + return -1; + + /* Update the header fields. */ + uint32_t sequence = le32toh (h->hdr->sequence1); + sequence++; + h->hdr->sequence1 = htole32 (sequence); + h->hdr->sequence2 = htole32 (sequence); + /* XXX Ought to update h->hdr->last_modified. */ + h->hdr->blocks = htole32 (h->endpages - 0x1000); + + /* Recompute header checksum. */ + uint32_t sum = header_checksum (h); + h->hdr->csum = htole32 (sum); + + if (h->msglvl >= 2) + fprintf (stderr, "hivex_commit: new header checksum: 0x%x\n", sum); + + if (full_write (fd, h->addr, h->size) != h->size) { + int err = errno; + close (fd); + errno = err; + return -1; + } + + if (close (fd) == -1) + return -1; + + return 0; +} + +int +hivex_node_set_values (hive_h *h, hive_node_h node, + size_t nr_values, const hive_set_value *values, + int flags) +{ + if (!h->writable) { + errno = EROFS; + return -1; + } + + if (!IS_VALID_BLOCK (h, node) || !BLOCK_ID_EQ (h, node, "nk")) { + errno = EINVAL; + return -1;
This would be an internal inconsistency error, surely. If so, you probably want an assert here instead.
+ } + + /* 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);
Why are you subtracting 1 from nr_values above?
+ size_t vallist_offs = allocate_block (h, seg_len, nul_id); + if (vallist_offs == 0) + return -1; + + struct ntreg_nk_record *nk = (struct ntreg_nk_record *) (h->addr + node); + nk->nr_values = htole32 (nr_values); + nk->vallist = htole32 (vallist_offs - 0x1000);
Whatever the purpose of adding or subtracting 0x1000 to offsets is, it could probably do with a descriptively-named macro, which also does htole32 or le32toh as appropriate.
+ struct ntreg_value_list *vallist = + (struct ntreg_value_list *) (h->addr + vallist_offs); + + size_t i; + for (i = 0; i < nr_values; ++i) { + /* Allocate vk record to store this (key, value) pair. */ + static const char vk_id[2] = { 'v', 'k' }; + seg_len = sizeof (struct ntreg_vk_record) + strlen (values[i].key); + size_t vk_offs = allocate_block (h, seg_len, vk_id); + if (vk_offs == 0) + return -1; + + vallist->offset[i] = htole32 (vk_offs - 0x1000); + + struct ntreg_vk_record *vk = (struct ntreg_vk_record *) (h->addr + vk_offs); + size_t name_len = strlen (values[i].key);
If you did this further up you could re-use the result of strlen.
+ vk->name_len = htole16 (name_len); + strcpy (vk->name, values[i].key); + vk->data_type = htole32 (values[i].t); + vk->data_len = htole16 (values[i].len); + vk->flags = name_len == 0 ? 0 : 1; + + if (values[i].len <= 4) /* Store data inline. */
sizeof.
+ memcpy (&vk->data_offset, values[i].value, values[i].len); + else { + size_t offs = allocate_block (h, values[i].len + 4, nul_id);
sizeof.
+ if (offs == 0) + return -1; + memcpy (h->addr + offs + 4, values[i].value, values[i].len);
sizeof, or the address of a struct member.
+ vk->data_offset = htole32 (offs - 0x1000); + } + + if (name_len * 2 > le32toh (nk->max_vk_name_len)) + nk->max_vk_name_len = htole32 (name_len * 2);
Why is name_len multiplied by 2? Comment, please.
+ if (values[i].len > le32toh (nk->max_vk_data_len)) + nk->max_vk_data_len = htole32 (values[i].len); + } + + return 0; +} diff --git a/hivex/hivex.h b/hivex/hivex.h index 56718b4..6a3cb3a 100644 --- a/hivex/hivex.h +++ b/hivex/hivex.h @@ -110,6 +110,18 @@ struct hivex_visitor { extern int hivex_visit (hive_h *h, const struct hivex_visitor *visitor, size_t len, void *opaque, int flags); extern int hivex_visit_node (hive_h *h, hive_node_h node, const struct hivex_visitor *visitor, size_t len, void *opaque, int flags);
+extern int hivex_commit (hive_h *h, const char *filename, int flags); + +struct hive_set_value { + char *key; + hive_type t; + size_t len; + char *value; +}; +typedef struct hive_set_value hive_set_value; + +extern int hivex_node_set_values (hive_h *h, hive_node_h node, size_t nr_values, const hive_set_value *values, int flags); + #ifdef __cplusplus } #endif diff --git a/hivex/hivex.pod b/hivex/hivex.pod index 5a58144..5df75aa 100644 --- a/hivex/hivex.pod +++ b/hivex/hivex.pod @@ -326,6 +326,127 @@ starts at C<node>.
=back
+=head2 WRITING TO HIVE FILES + +The hivex library supports making limited modifications to hive files. +We have tried to implement this very conservatively in order to reduce +the chance of corrupting your registry. However you should be careful +and take back-ups, since Microsoft has never documented the hive +format, and so it is possible there are nuances in the +reverse-engineered format that we do not understand. + +To be able to modify a hive, you must pass the C<HIVEX_OPEN_WRITE> +flag to C<hivex_open>, otherwise any write operation will return with +errno C<EROFS>. + +The write operations shown below do not modify the on-disk file +immediately. You must call C<hivex_commit> in order to write the +changes to disk. If you call C<hivex_close> without committing then +any writes are discarded. + +Hive files internally consist of a "memory dump" of binary blocks +(like the C heap), and some of these blocks can be unused. The hivex +library never reuses these unused blocks. Instead, to ensure +robustness in the face of the partially understood on-disk format, +hivex only allocates new blocks after the end of the file, and makes +minimal modifications to existing structures in the file to point to +these new blocks. This makes hivex slightly less disk-efficient than +it could be, but disk is cheap, and registry modifications tend to be +very small. + +When deleting nodes, it is possible that this library may leave +unreachable live blocks in the hive. This is because certain parts of +the hive disk format such as security (sk) records and big data (db) +records and classname fields are not well understood (and not +documented at all) and we play it safe by not attempting to modify +them. Apart from wasting a little bit of disk space, it is not +thought that unreachable blocks are a problem. + +=over 4 + +=item int hivex_commit (hive_h *h, const char *filename, int flags); + +Commit (write) any changes which have been made. + +C<filename> is the new file to write. If C<filename == NULL> then we +overwrite the original file (ie. the file name that was passed to +C<hivex_open>). C<flags> is not used, always pass 0. + +Returns 0 on success. On error this returns -1 and sets errno. + +Note this does not close the hive handle. You can perform further +operations on the hive after committing, including making more +modifications. If you no longer wish to use the hive, call +C<hivex_close> after this. + +=item hive_set_value + +The typedef C<hive_set_value> is used in conjunction with the +C<hivex_node_set_values> call described below. + + struct hive_set_value { + char *key; /* key - a UTF-8 encoded ASCIIZ string */ + hive_type t; /* type of value field */ + size_t len; /* length of value field in bytes */ + char *value; /* value field */ + }; + typedef struct hive_set_value hive_set_value; + +To set the default value for a node, you have to pass C<key = "">. + +Note that the C<value> field is just treated as a list of bytes, and +is stored directly in the hive. The caller has to ensure correct +encoding and endianness, for example converting dwords to little +endian. + +The correct type and encoding for values depends on the node and key +in the registry, the version of Windows, and sometimes even changes +between versions of Windows for the same key. We don't document it +here. Often it's not documented at all. + +=item int hivex_node_set_values (hive_h *h, hive_node_h node, size_t nr_values, const hive_set_value *values, int flags); + +This call can be used to set all the (key, value) pairs stored in C<node>. + +C<node> is the node to modify. C<values> is an array of (key, value) +pairs. There should be C<nr_values> elements in this array. C<flags> +is not used, always pass 0. + +Any existing values stored at the node are discarded, and their +C<hive_value_h> handles become invalid. Thus you can remove all +values stored at C<node> by passing C<nr_values = 0>. + +Returns 0 on success. On error this returns -1 and sets errno. + +Note that this library does not offer a way to modify just a single +key at a node. We don't implement a way to do this efficiently. + +=back + +=head3 WRITE OPERATIONS WHICH ARE NOT SUPPORTED + +=over 4 + +=item * + +Changing the root node. + +=item * + +Creating a new hive file from scratch. This is impossible at present +because not all fields in the header are understood. + +=item * + +Modifying or deleting single values at a node. + +=item * + +Modifying security key (sk) records or classnames. These are not +well understood. + +=back + =head1 THE STRUCTURE OF THE WINDOWS REGISTRY
Note: To understand the relationship between hives and the common @@ -452,6 +573,10 @@ Registry contains cycles.
Field in the registry out of range.
+=item EROFS + +Tried to write to a registry which is not opened for writing. + =back
=head1 ENVIRONMENT VARIABLES -- 1.6.5.2
-- Matthew Booth, RHCA, RHCSS Red Hat Engineering, Virtualisation Team M: +44 (0)7977 267231 GPG ID: D33C3490 GPG FPR: 3733 612D 2D05 5458 8A8A 1600 3441 EA19 D33C 3490
On Thu, Feb 04, 2010 at 04:54:53PM +0000, Matthew Booth wrote: [...] Thanks for this review. Because I'm about 20 commits ahead of the public git repo at the moment, what I will do is to add another commit which addresses documentation issues in the code retrospectively. More comments inline.
+static size_t +allocate_page (hive_h *h, size_t allocation_hint) +{
Every usage of nr_4k_pages in this function multiplies it by 4096. It would be more readable to choose a different name which reflects that it's simply a rounding.
It's the number of 4K pages, so it seems like a reasonable name to me ...
+ /* 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);
I think this assert is impossible to trigger.
I hope so too, but the assertion is there to remind me and others that this value can't be zero. [in allocate_page]
In general, are we worrying about overflow?
In general yes, but in this function it shouldn't be possible to trigger any overflow. (Having said that, who knows what strange combination of inputs might trigger an overflow ...)
+ page->offset_first = htole32 (offset - 0x1000);
What's 0x1000? Can you comment this?
The first 4096 bytes of the file are the header. Internal pointers within the file are relative to the first block, which is to say, relative to the file start + 4096 bytes. Unfortunately the representation I chose for the file is a bit stupid in that we store the header + blocks as a single allocation, instead of storing those separately. (I didn't make the same mistake in the OCaml analysis tools). I intend to go back and correct this, but first I want a comprehensive test suite so that we don't introduce regressions.
+ if (seg_len < 4) {
Can you replace this '4' with a sizeof(ntreg_hbin_block.seg_len)?
No because that's a syntax error ... Apparently it's not possible to portably get the size of a structure member.
+ blockhdr->seg_len = htole32 (- (int32_t) seg_len); + if (id[0] && id[1] && seg_len >= 6) {
Can you replace 6 with sizeof(ntreg_hbin_block)?
Or more correctly, offsetof (ntreg_hbin_block, <the field after id[1]>), but of course you can't do that in C ... Stupid language.
+ assert (rem >= 4);
This assertion doesn't look safe. Why must this be the case? In the event that rem < 4, would you allocate a new page for the dummy free block?
Allocations are aligned to and rounded up to 8 bytes, so rem should be 0 or >= 8. We know rem > 0 because we're in a conditional. We need to write 4 bytes here. Hence the assertion.
+ blockhdr = (struct ntreg_hbin_block *) (h->addr + h->endblocks); + blockhdr->seg_len = htole32 ((int32_t) rem);
Will blockhdr->id ever be read here? Is 0x00 valid?
blockhdr->id is not read for unused blocks, so this is safe.
+ size_t len; + len = le32toh (vk->data_len); + if (len == 0x80000000) /* special case */ + len = 4; + len &= 0x7fffffff;
Comment, please. What's going on with this record length?
This is a mysterious and undocumented part of the format. I don't claim to understand why, but it is necessary.
+int +hivex_commit (hive_h *h, const char *filename, int flags) +{ + if (flags != 0) { + errno = EINVAL; + return -1; + }
What are you intending to use the flags for?
Future expansion.
+int +hivex_node_set_values (hive_h *h, hive_node_h node, + size_t nr_values, const hive_set_value *values, + int flags) +{ + if (!h->writable) { + errno = EROFS; + return -1; + } + + if (!IS_VALID_BLOCK (h, node) || !BLOCK_ID_EQ (h, node, "nk")) { + errno = EINVAL; + return -1;
This would be an internal inconsistency error, surely. If so, you probably want an assert here instead.
No, it could mean that someone passed in an offset which is a block of some sort, but is not an nk-record. eg: size_t *values = hivex_node_values (h, node); hivex_node_set_values (h, values[0], ...); Unfortunately (and another stupidity about C) size_t and hive_node_h are compatible types so this won't generate any compile-time error.
+ size_t seg_len = + sizeof (struct ntreg_value_list) + (nr_values - 1) * sizeof (uint32_t);
Why are you subtracting 1 from nr_values above?
There is already 1 value in the struct: struct ntreg_value_list { int32_t seg_len; uint32_t offset[1]; /* list of pointers to vk records */ } __attribute__((__packed__));
+ if (values[i].len <= 4) /* Store data inline. */
sizeof.
No, it's a fixed magic number in the format. If the length is <= 4 it stores 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);
sizeof.
There's no struct to take the sizeof here. These are special id-less blocks.
+ if (offs == 0) + return -1; + memcpy (h->addr + offs + 4, values[i].value, values[i].len);
sizeof, or the address of a struct member.
As above. Rich. -- Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones virt-p2v converts physical machines to virtual machines. Boot with a live CD or over the network (PXE) and turn machines into Xen guests. http://et.redhat.com/~rjones/virt-p2v
On 05/02/10 12:46, Richard W.M. Jones wrote:
On Thu, Feb 04, 2010 at 04:54:53PM +0000, Matthew Booth wrote: [...]
Thanks for this review. Because I'm about 20 commits ahead of the public git repo at the moment, what I will do is to add another commit which addresses documentation issues in the code retrospectively.
More comments inline.
+static size_t +allocate_page (hive_h *h, size_t allocation_hint) +{
Every usage of nr_4k_pages in this function multiplies it by 4096. It would be more readable to choose a different name which reflects that it's simply a rounding.
It's the number of 4K pages, so it seems like a reasonable name to me ...
I meant storing a value that is already multiplied by 4096, because that's how it's used. Not important.
+ page->offset_first = htole32 (offset - 0x1000);
What's 0x1000? Can you comment this?
The first 4096 bytes of the file are the header. Internal pointers within the file are relative to the first block, which is to say, relative to the file start + 4096 bytes. Unfortunately the representation I chose for the file is a bit stupid in that we store the header + blocks as a single allocation, instead of storing those separately. (I didn't make the same mistake in the OCaml analysis tools). I intend to go back and correct this, but first I want a comprehensive test suite so that we don't introduce regressions.
Ok.
+ if (seg_len < 4) {
Can you replace this '4' with a sizeof(ntreg_hbin_block.seg_len)?
No because that's a syntax error ... Apparently it's not possible to portably get the size of a structure member.
The above wasn't intended to be syntactically correct. I think, however, you can do something like: sizeof(((struct ntreg_hbin_block *) void)->seg_len) This would be best implemented as a macro.
+ blockhdr->seg_len = htole32 (- (int32_t) seg_len); + if (id[0] && id[1] && seg_len >= 6) {
Can you replace 6 with sizeof(ntreg_hbin_block)?
Or more correctly, offsetof (ntreg_hbin_block, <the field after id[1]>), but of course you can't do that in C ... Stupid language.
If you redefine struct ntreg_hbin_block as: struct ntreg_hbin_block { int32_t seg_len; char id[2]; char data[]; } __attribute__((package__)); You can use: offsetof(struct ntreg_hbin_block, data) I haven't looked, but I'll bet gnulib contains a portable offsetof.
+ assert (rem >= 4);
This assertion doesn't look safe. Why must this be the case? In the event that rem < 4, would you allocate a new page for the dummy free block?
Allocations are aligned to and rounded up to 8 bytes, so rem should be 0 or >= 8. We know rem > 0 because we're in a conditional. We need to write 4 bytes here. Hence the assertion.
Ok.
+ blockhdr = (struct ntreg_hbin_block *) (h->addr + h->endblocks); + blockhdr->seg_len = htole32 ((int32_t) rem);
Will blockhdr->id ever be read here? Is 0x00 valid?
blockhdr->id is not read for unused blocks, so this is safe.
Ok.
+ size_t len; + len = le32toh (vk->data_len); + if (len == 0x80000000) /* special case */ + len = 4; + len &= 0x7fffffff;
Comment, please. What's going on with this record length?
This is a mysterious and undocumented part of the format. I don't claim to understand why, but it is necessary.
Hehe, ok. Can you at least put in a comment to that effect, preferrably with a pointer to your source.
+int +hivex_node_set_values (hive_h *h, hive_node_h node, + size_t nr_values, const hive_set_value *values, + int flags) +{ + if (!h->writable) { + errno = EROFS; + return -1; + } + + if (!IS_VALID_BLOCK (h, node) || !BLOCK_ID_EQ (h, node, "nk")) { + errno = EINVAL; + return -1;
This would be an internal inconsistency error, surely. If so, you probably want an assert here instead.
No, it could mean that someone passed in an offset which is a block of some sort, but is not an nk-record. eg:
size_t *values = hivex_node_values (h, node); hivex_node_set_values (h, values[0], ...);
Unfortunately (and another stupidity about C) size_t and hive_node_h are compatible types so this won't generate any compile-time error.
This is a stylistic choice, I guess. I would say that incorrect use of this function is a bug, therefore not something the user can do anything about. The best outcome for the user is that it gets fixed :) I'd use an assert. Not important.
+ size_t seg_len = + sizeof (struct ntreg_value_list) + (nr_values - 1) * sizeof (uint32_t);
Why are you subtracting 1 from nr_values above?
There is already 1 value in the struct:
struct ntreg_value_list { int32_t seg_len; uint32_t offset[1]; /* list of pointers to vk records */ } __attribute__((__packed__));
That's nasty. Is there a format reason for doing this, or could you redefine this struct as: struct ntreg_value_list { int32_t seg_len; uint32_t offset[]; /* list of pointers to vk records */ } __attribute__((__packed__));
+ if (values[i].len <= 4) /* Store data inline. */
sizeof.
No, it's a fixed magic number in the format. If the length is <= 4 it stores it inline.
Can you give the magic number a name and #define it up the top to be 4?
+ memcpy (&vk->data_offset, values[i].value, values[i].len); + else { + size_t offs = allocate_block (h, values[i].len + 4, nul_id);
sizeof.
There's no struct to take the sizeof here. These are special id-less blocks.
offsetof(struct ntreg_hbin_block, id) and include a comment about overwriting id.
+ if (offs == 0) + return -1; + memcpy (h->addr + offs + 4, values[i].value, values[i].len);
sizeof, or the address of a struct member.
As above.
Ditto ;) Matt -- Matthew Booth, RHCA, RHCSS Red Hat Engineering, Virtualisation Team M: +44 (0)7977 267231 GPG ID: D33C3490 GPG FPR: 3733 612D 2D05 5458 8A8A 1600 3441 EA19 D33C 3490
On Fri, Feb 05, 2010 at 01:20:44PM +0000, Matthew Booth wrote:
On 05/02/10 12:46, Richard W.M. Jones wrote:
On Thu, Feb 04, 2010 at 04:54:53PM +0000, Matthew Booth wrote:
+ size_t len; + len = le32toh (vk->data_len); + if (len == 0x80000000) /* special case */ + len = 4; + len &= 0x7fffffff;
Comment, please. What's going on with this record length?
This is a mysterious and undocumented part of the format. I don't claim to understand why, but it is necessary.
Hehe, ok. Can you at least put in a comment to that effect, preferrably with a pointer to your source.
Alright I've looked into this further: The MSB (vk->data_len & 0x80000000) indicates whether the data is inline. In all existing registry hives, the data is always stored inline if the data length <= 4 (ie. the data_len field would be stored in those cases as 0x80000004 or whatever). I am updating the code accordingly ... Rich. -- Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones libguestfs lets you edit virtual machines. Supports shell scripting, bindings from many languages. http://et.redhat.com/~rjones/libguestfs/ See what it can do: http://et.redhat.com/~rjones/libguestfs/recipes.html
-- Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones New in Fedora 11: Fedora Windows cross-compiler. Compile Windows programs, test, and build Windows installers. Over 70 libraries supprt'd http://fedoraproject.org/wiki/MinGW http://www.annexia.org/fedora_mingw
On 03/02/10 18:35, Richard W.M. Jones wrote:
From c6fa2b912e47df21bc6a64b2f1b0bcf17cbd0a0f Mon Sep 17 00:00:00 2001 From: Richard Jones <rjones@redhat.com> Date: Wed, 3 Feb 2010 18:04:31 +0000 Subject: [PATCH 10/12] hivexsh: Add 'setval' and 'commit' commands.
This adds the 'setval' and 'commit' commands to the hivex shell.
Also adds some example scripts showing use of these. --- hivex/Makefile.am | 5 +- hivex/example1 | 40 ++++++++ hivex/example2 | 47 +++++++++ hivex/example3 | 53 +++++++++++ hivex/hivexsh.c | 271 ++++++++++++++++++++++++++++++++++++++++++++++++++++- hivex/hivexsh.pod | 79 ++++++++++++++++ 6 files changed, 491 insertions(+), 4 deletions(-) create mode 100755 hivex/example1 create mode 100755 hivex/example2 create mode 100755 hivex/example3
A few nits inline. Matt
diff --git a/hivex/hivexsh.c b/hivex/hivexsh.c index 01a5ddc..6f33f41 100644 --- a/hivex/hivexsh.c +++ b/hivex/hivexsh.c @@ -779,3 +816,231 @@ cmd_lsval (char *key) perror ("hivexsh: lsval"); return -1; } + +static int +cmd_setval (char *nrvals_str) +{ + strtol_error xerr; + + /* Parse number of values. */ + long nrvals; + xerr = xstrtol (nrvals_str, NULL, 0, &nrvals, ""); + if (xerr != LONGINT_OK) { + fprintf (stderr, _("%s: %s: invalid integer parameter (%s returned %d)\n"), + "setval", "nrvals", "xstrtol", xerr); + return -1; + } + if (nrvals < 0 || nrvals > 1000) {
IIRC, this 1000 was an arbitrary constant in the previous patch. Could you replace both with a #define?
+ fprintf (stderr, _("%s: %s: integer out of range\n"), + "setval", "nrvals"); + return -1; + } + + struct hive_set_value *values = + calloc (nrvals, sizeof (struct hive_set_value)); + if (values == NULL) { + perror ("calloc"); + exit (EXIT_FAILURE); + } + + int ret = -1; + + /* Read nrvals * 2 lines of input, nrvals * (key, value) pairs, as + * explained in the man page. + */ + int prompt = isatty (0) ? 2 : 0;
You've already got this in a global. In fact, I can't see it used anywhere. Doesn't this generate a warning?
+ int i, j; + for (i = 0; i < nrvals; ++i) { + /* Read key. */ + char *buf = rl_gets (" key> "); + if (!buf) { + fprintf (stderr, _("hivexsh: setval: unexpected end of input\n")); + quit = 1; + goto error; + } + + /* Note that buf will be overwritten by the next call to rl_gets. */ + if (STREQ (buf, "@")) + values[i].key = strdup (""); + else + values[i].key = strdup (buf); + if (values[i].key == NULL) { + perror ("strdup"); + exit (EXIT_FAILURE); + } + + /* Read value. */ + buf = rl_gets ("value> ");
Why no indentation on this prompt, as for key?
+ if (!buf) { + fprintf (stderr, _("hivexsh: setval: unexpected end of input\n")); + quit = 1; + goto error; + } + + if (STREQ (buf, "none")) { + values[i].t = hive_t_none; + values[i].len = 0; + } + else if (STRPREFIX (buf, "string:")) { + buf += 7; + values[i].t = hive_t_string; + int nr_chars = strlen (buf); + values[i].len = 2 * (nr_chars + 1); + values[i].value = malloc (values[i].len); + if (!values[i].value) { + perror ("malloc"); + exit (EXIT_FAILURE); + } + for (j = 0; j <= /* sic */ nr_chars; ++j) { + if (buf[j] & 0x80) { + fprintf (stderr, _("hivexsh: string(utf16le): only 7 bit ASCII strings are supported for input\n")); + goto error; + } + values[i].value[2*j] = buf[j]; + values[i].value[2*j+1] = '\0';
There must be a library function to do the above. Where does the 7 bit ASCII restriction come from?
+ } + }
This doesn't look like regedit's expandstring format. What's the purpose of it?
+ else if (STRPREFIX (buf, "expandstring:")) { + buf += 13; + values[i].t = hive_t_string; + int nr_chars = strlen (buf); + values[i].len = 2 * (nr_chars + 1); + values[i].value = malloc (values[i].len); + if (!values[i].value) { + perror ("malloc"); + exit (EXIT_FAILURE); + } + for (j = 0; j <= /* sic */ nr_chars; ++j) { + if (buf[j] & 0x80) { + fprintf (stderr, _("hivexsh: string(utf16le): only 7 bit ASCII strings are supported for input\n")); + goto error; + } + values[i].value[2*j] = buf[j]; + values[i].value[2*j+1] = '\0'; + } + } + else if (STRPREFIX (buf, "dword:")) { + buf += 6; + values[i].t = hive_t_dword; + values[i].len = 4; + values[i].value = malloc (4); + if (!values[i].value) { + perror ("malloc"); + exit (EXIT_FAILURE); + } + long n; + xerr = xstrtol (buf, NULL, 0, &n, "");
Does xstrtol support 0x notation?
+ if (xerr != LONGINT_OK) { + fprintf (stderr, _("%s: %s: invalid integer parameter (%s returned %d)\n"), + "setval", "dword", "xstrtol", xerr); + goto error; + } + if (n < 0 || n > UINT32_MAX) { + fprintf (stderr, _("%s: %s: integer out of range\n"), + "setval", "dword"); + goto error; + } + uint32_t u32 = htole32 (n); + memcpy (values[i].value, &u32, 4); + } + else if (STRPREFIX (buf, "qword:")) { + buf += 6; + values[i].t = hive_t_qword; + values[i].len = 8; + values[i].value = malloc (8); + if (!values[i].value) { + perror ("malloc"); + exit (EXIT_FAILURE); + } + long long n; + xerr = xstrtoll (buf, NULL, 0, &n, ""); + if (xerr != LONGINT_OK) { + fprintf (stderr, _("%s: %s: invalid integer parameter (%s returned %d)\n"), + "setval", "dword", "xstrtoll", xerr); + goto error; + } +#if 0 + if (n < 0 || n > UINT64_MAX) { + fprintf (stderr, _("%s: %s: integer out of range\n"), + "setval", "dword"); + goto error; + } +#endif
Why have you commented this out?
+ uint64_t u64 = htole64 (n); + memcpy (values[i].value, &u64, 4); + } + else if (STRPREFIX (buf, "hex:")) { + /* Read the type. */ + buf += 4; + size_t len = strcspn (buf, ":"); + char *nextbuf; + if (buf[len] == '\0') /* "hex:t" */ + nextbuf = &buf[len]; + else { /* "hex:t:..." */ + buf[len] = '\0'; + nextbuf = &buf[len+1]; + } + + long t; + xerr = xstrtol (buf, NULL, 0, &t, ""); + if (xerr != LONGINT_OK) { + fprintf (stderr, _("%s: %s: invalid integer parameter (%s returned %d)\n"), + "setval", "hex", "xstrtol", xerr); + goto error; + } + if (t < 0 || t > UINT32_MAX) { + fprintf (stderr, _("%s: %s: integer out of range\n"), + "setval", "hex"); + goto error; + } + values[i].t = t; + + /* Read the hex data. */ + buf = nextbuf; + + /* The allocation length is an overestimate, but it doesn't matter. */ + values[i].value = malloc (1 + strlen (buf) / 2); + if (!values[i].value) { + perror ("malloc"); + exit (EXIT_FAILURE); + } + values[i].len = 0; + + while (*buf) { + int c = 0; + + for (j = 0; *buf && j < 2; buf++) { + if (c_isxdigit (*buf)) { /* NB: ignore non-hex digits. */
The documentation defines the limiter to be a comma. I'd stick to this strictly, making it more likely to catch typos.
+ c <<= 4; + c |= get_xdigit (*buf); + j++; + } + } + + if (j == 2) values[i].value[values[i].len++] = c; + else if (j == 1) { + fprintf (stderr, _("hivexsh: setval: trailing garbage after hex string\n")); + goto error; + } + } + } + else { + fprintf (stderr, + _("hivexsh: setval: cannot parse value string, please refer to the man page hivexsh(1) for help: %s\n"), + buf); + goto error; + } + } + + ret = hivex_node_set_values (h, cwd, nrvals, values, 0); + + error: + /* Free values array. */ + for (i = 0; i < nrvals; ++i) { + free (values[i].key); + free (values[i].value); + } + free (values); + + return ret; +}
-- Matthew Booth, RHCA, RHCSS Red Hat Engineering, Virtualisation Team M: +44 (0)7977 267231 GPG ID: D33C3490 GPG FPR: 3733 612D 2D05 5458 8A8A 1600 3441 EA19 D33C 3490
On Thu, Feb 04, 2010 at 05:34:36PM +0000, Matthew Booth wrote:
+ int prompt = isatty (0) ? 2 : 0;
You've already got this in a global. In fact, I can't see it used anywhere. Doesn't this generate a warning?
Yup, this is a bug -- removed -- thanks.
+ buf = rl_gets (" key> "); + buf = rl_gets ("value> ");
Why no indentation on this prompt, as for key?
So they line up ^^^
+ if (!buf) { + fprintf (stderr, _("hivexsh: setval: unexpected end of input\n")); + quit = 1; + goto error; + } + + if (STREQ (buf, "none")) { + values[i].t = hive_t_none; + values[i].len = 0; + } + else if (STRPREFIX (buf, "string:")) { + buf += 7; + values[i].t = hive_t_string; + int nr_chars = strlen (buf); + values[i].len = 2 * (nr_chars + 1); + values[i].value = malloc (values[i].len); + if (!values[i].value) { + perror ("malloc"); + exit (EXIT_FAILURE); + } + for (j = 0; j <= /* sic */ nr_chars; ++j) { + if (buf[j] & 0x80) { + fprintf (stderr, _("hivexsh: string(utf16le): only 7 bit ASCII strings are supported for input\n")); + goto error; + } + values[i].value[2*j] = buf[j]; + values[i].value[2*j+1] = '\0';
There must be a library function to do the above. Where does the 7 bit ASCII restriction come from?
There's iconv, but that's even crazier than doing it by hand. This is fine for 7 bit ASCII, but would break if you pass in UTF-8 (hence the check that no high bits are set).
This doesn't look like regedit's expandstring format. What's the purpose of it?
+ else if (STRPREFIX (buf, "expandstring:")) {
Not sure what you mean -- expandstring is a separate type in the hive. We don't care about what regedit may or may not do.
+ xerr = xstrtol (buf, NULL, 0, &n, "");
Does xstrtol support 0x notation?
Yes.
+#if 0 + if (n < 0 || n > UINT64_MAX) { + fprintf (stderr, _("%s: %s: integer out of range\n"), + "setval", "dword"); + goto error; + } +#endif
Why have you commented this out?
You can't test a 64 bit int against UINT64_MAX. In any case it's just documentation of what the limits are.
+ for (j = 0; *buf && j < 2; buf++) { + if (c_isxdigit (*buf)) { /* NB: ignore non-hex digits. */
The documentation defines the limiter to be a comma. I'd stick to this strictly, making it more likely to catch typos.
The documentation just says that non-hex digits are ignored, and I can't be bothered to code more complex parsing here. Rich. -- Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones New in Fedora 11: Fedora Windows cross-compiler. Compile Windows programs, test, and build Windows installers. Over 70 libraries supprt'd http://fedoraproject.org/wiki/MinGW http://www.annexia.org/fedora_mingw
On 05/02/10 12:54, Richard W.M. Jones wrote:
+ if (!buf) { + fprintf (stderr, _("hivexsh: setval: unexpected end of input\n")); + quit = 1; + goto error; + } + + if (STREQ (buf, "none")) { + values[i].t = hive_t_none; + values[i].len = 0; + } + else if (STRPREFIX (buf, "string:")) { + buf += 7; + values[i].t = hive_t_string; + int nr_chars = strlen (buf); + values[i].len = 2 * (nr_chars + 1); + values[i].value = malloc (values[i].len); + if (!values[i].value) { + perror ("malloc"); + exit (EXIT_FAILURE); + } + for (j = 0; j <= /* sic */ nr_chars; ++j) { + if (buf[j] & 0x80) { + fprintf (stderr, _("hivexsh: string(utf16le): only 7 bit ASCII strings are supported for input\n")); + goto error; + } + values[i].value[2*j] = buf[j]; + values[i].value[2*j+1] = '\0';
There must be a library function to do the above. Where does the 7 bit ASCII restriction come from?
There's iconv, but that's even crazier than doing it by hand. This is fine for 7 bit ASCII, but would break if you pass in UTF-8 (hence the check that no high bits are set).
I'll take your word for it :)
This doesn't look like regedit's expandstring format. What's the purpose of it?
+ else if (STRPREFIX (buf, "expandstring:")) {
Not sure what you mean -- expandstring is a separate type in the hive. We don't care about what regedit may or may not do.
Ah, I assumed they were just input formatting differences, because both string: and expandstring: set values[i].t = hive_t_string. Is that a bug?
+ for (j = 0; *buf && j < 2; buf++) { + if (c_isxdigit (*buf)) { /* NB: ignore non-hex digits. */
The documentation defines the limiter to be a comma. I'd stick to this strictly, making it more likely to catch typos.
The documentation just says that non-hex digits are ignored, and I can't be bothered to code more complex parsing here.
Fair enough. Matt -- Matthew Booth, RHCA, RHCSS Red Hat Engineering, Virtualisation Team M: +44 (0)7977 267231 GPG ID: D33C3490 GPG FPR: 3733 612D 2D05 5458 8A8A 1600 3441 EA19 D33C 3490
On Fri, Feb 05, 2010 at 01:29:54PM +0000, Matthew Booth wrote:
Ah, I assumed they were just input formatting differences, because both string: and expandstring: set values[i].t = hive_t_string. Is that a bug?
Yeah, that's in fact a bug. Rich. -- Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones virt-top is 'top' for virtual machines. Tiny program with many powerful monitoring features, net stats, disk stats, logging, etc. http://et.redhat.com/~rjones/virt-top
Although this is an 8K binary file that has no source as such, I think it is worth including this file because it will allow us to write automated tests of hivexsh. The reason it is necessary for testing is because we cannot create a hive from scratch (the format is not sufficiently documented to allow us to do that). Therefore all tests have to start from an existing hive, and this means we either have to supply some minimal hive like this, or get the user to supply a hive file. Rich. -- Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones virt-top is 'top' for virtual machines. Tiny program with many powerful monitoring features, net stats, disk stats, logging, etc. http://et.redhat.com/~rjones/virt-top
On 03/02/10 18:37, Richard W.M. Jones wrote:
Subject: [PATCH 11/12] hivex: Minimal registry example.
This is the smallest registry you can make and still have it load correctly in Windows regedit. --- configure.ac | 1 + hivex/Makefile.am | 2 +- hivex/t/Makefile.am | 18 ++++++++++++++++++ hivex/t/README | 9 +++++++++ hivex/t/minimal | Bin 0 -> 8192 bytes 5 files changed, 29 insertions(+), 1 deletions(-) create mode 100644 hivex/t/Makefile.am create mode 100644 hivex/t/README create mode 100755 hivex/t/minimal
I don't read binary. I guess it's ok ;) Matt -- Matthew Booth, RHCA, RHCSS Red Hat Engineering, Virtualisation Team M: +44 (0)7977 267231 GPG ID: D33C3490 GPG FPR: 3733 612D 2D05 5458 8A8A 1600 3441 EA19 D33C 3490
WIP ... -- Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones virt-top is 'top' for virtual machines. Tiny program with many powerful monitoring features, net stats, disk stats, logging, etc. http://et.redhat.com/~rjones/virt-top
participants (2)
-
Matthew Booth -
Richard W.M. Jones