hivex: Add metadata length functions for nodes and values
[hivex.git] / lib / hivex.c
index 946ecf3..3063c45 100644 (file)
@@ -93,6 +93,7 @@ struct hive_h {
   /* Fields from the header, extracted from little-endianness hell. */
   size_t rootoffs;              /* Root key offset (always an nk-block). */
   size_t endpages;              /* Offset of end of pages. */
+  int64_t last_modified;        /* mtime of base block. */
 
   /* For writing. */
   size_t endblocks;             /* Offset to next block allocation (0
@@ -104,7 +105,7 @@ struct ntreg_header {
   char magic[4];                /* "regf" */
   uint32_t sequence1;
   uint32_t sequence2;
-  char last_modified[8];
+  int64_t last_modified;
   uint32_t major_ver;           /* 1 */
   uint32_t minor_ver;           /* 3 */
   uint32_t unknown5;            /* 0 */
@@ -173,7 +174,7 @@ struct ntreg_nk_record {
   int32_t seg_len;              /* length (always -ve because used) */
   char id[2];                   /* "nk" */
   uint16_t flags;
-  char timestamp[8];
+  int64_t timestamp;
   uint32_t unknown1;
   uint32_t parent;              /* offset of owner/parent */
   uint32_t nr_subkeys;          /* number of subkeys */
@@ -293,9 +294,16 @@ hivex_open (const char *filename, int flags)
   if (h->filename == NULL)
     goto error;
 
+#ifdef O_CLOEXEC
   h->fd = open (filename, O_RDONLY | O_CLOEXEC);
+#else
+  h->fd = open (filename, O_RDONLY);
+#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)
@@ -317,6 +325,13 @@ hivex_open (const char *filename, int flags)
 
     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. */
@@ -352,6 +367,9 @@ hivex_open (const char *filename, int flags)
     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);
 
@@ -360,6 +378,8 @@ hivex_open (const char *filename, int flags)
              "  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"
@@ -367,6 +387,7 @@ hivex_open (const char *filename, int flags)
              "  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,
@@ -539,7 +560,10 @@ hivex_close (hive_h *h)
     munmap (h->addr, h->size);
   else
     free (h->addr);
-  r = close (h->fd);
+  if (h->fd >= 0)
+    r = close (h->fd);
+  else
+    r = 0;
   free (h->filename);
   free (h);
 
@@ -561,6 +585,30 @@ hivex_root (hive_h *h)
   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")) {
+    errno = EINVAL;
+    return 0;
+  }
+
+  struct ntreg_nk_record *nk = (struct ntreg_nk_record *) (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) {
+    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);
+    errno = EFAULT;
+    return 0;
+  }
+  return ret;
+}
+
 char *
 hivex_node_name (hive_h *h, hive_node_h node)
 {
@@ -598,6 +646,43 @@ hivex_node_name (hive_h *h, hive_node_h node)
   return ret;
 }
 
+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);
+    errno = EINVAL;
+    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")) {
+    errno = EINVAL;
+    return -1;
+  }
+
+  struct ntreg_nk_record *nk = (struct ntreg_nk_record *) (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.
@@ -1128,8 +1213,22 @@ hivex_node_get_value (hive_h *h, hive_node_h node, const char *key)
   return ret;
 }
 
-char *
-hivex_value_key (hive_h *h, hive_value_h value)
+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")) {
     errno = EINVAL;
@@ -1138,23 +1237,39 @@ hivex_value_key (hive_h *h, hive_value_h value)
 
   struct ntreg_vk_record *vk = (struct ntreg_vk_record *) (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.
-   */
-
   /* vk->name_len is unsigned, 16 bit, so this is safe ...  However
    * we have to make sure the length doesn't exceed the block length.
    */
-  size_t len = le16toh (vk->name_len);
+  size_t ret = le16toh (vk->name_len);
   size_t seg_len = block_len (h, value, NULL);
-  if (sizeof (struct ntreg_vk_record) + len - 1 > seg_len) {
+  if (sizeof (struct ntreg_vk_record) + ret - 1 > seg_len) {
     if (h->msglvl >= 2)
-      fprintf (stderr, "hivex_value_key: returning EFAULT"
+      fprintf (stderr, "hivex_value_key_len: returning EFAULT"
                " because key length is too long (%zu, %zu)\n",
-               len, seg_len);
+               ret, seg_len);
     errno = EFAULT;
-    return NULL;
+    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")) {
+    errno = EINVAL;
+    return 0;
+  }
+
+  struct ntreg_vk_record *vk = (struct ntreg_vk_record *) (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)
@@ -2254,7 +2369,7 @@ hivex_node_add_child (hive_h *h, hive_node_h parent, const char *name)
   nk->sk = htole32 (parent_sk_offset - 0x1000);
 
   /* Inherit parent timestamp. */
-  memcpy (nk->timestamp, parent_nk->timestamp, sizeof (parent_nk->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