Coverage Report

Created: 2026-07-10 07:32

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libmsiecf/libmsiecf/libmsiecf_hash_table.c
Line
Count
Source
1
/*
2
 * Hash table functions
3
 *
4
 * Copyright (C) 2009-2026, Joachim Metz <joachim.metz@gmail.com>
5
 *
6
 * Refer to AUTHORS for acknowledgements.
7
 *
8
 * This program is free software: you can redistribute it and/or modify
9
 * it under the terms of the GNU Lesser General Public License as published by
10
 * the Free Software Foundation, either version 3 of the License, or
11
 * (at your option) any later version.
12
 *
13
 * This program is distributed in the hope that it will be useful,
14
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16
 * GNU General Public License for more details.
17
 *
18
 * You should have received a copy of the GNU Lesser General Public License
19
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
20
 */
21
22
#include <common.h>
23
#include <byte_stream.h>
24
#include <memory.h>
25
#include <types.h>
26
27
#include "libmsiecf_definitions.h"
28
#include "libmsiecf_hash_table.h"
29
#include "libmsiecf_item_descriptor.h"
30
#include "libmsiecf_libbfio.h"
31
#include "libmsiecf_libcdata.h"
32
#include "libmsiecf_libcerror.h"
33
#include "libmsiecf_libcnotify.h"
34
35
#include "msiecf_hash_record.h"
36
37
/* Reads a hash table
38
 * Returns 1 if successful or -1 on error
39
 */
40
int libmsiecf_hash_table_read_file_io_handle(
41
     libcdata_array_t *hash_table,
42
     off64_t *next_hash_table_offset,
43
     libbfio_handle_t *file_io_handle,
44
     off64_t hash_table_offset,
45
     size32_t block_size,
46
     libcerror_error_t **error )
47
4.00k
{
48
4.00k
  msiecf_hash_record_header_t hash_record_header;
49
50
4.00k
  uint8_t *hash_record_data                    = NULL;
51
4.00k
  uint8_t *entry_data                          = NULL;
52
4.00k
  static char *function                        = "libmsiecf_hash_table_read_file_io_handle";
53
4.00k
  size_t read_size                             = 0;
54
4.00k
  size_t table_iterator                        = 0;
55
4.00k
  ssize_t read_count                           = 0;
56
4.00k
  uint32_t entry_hash                          = 0;
57
4.00k
  uint32_t entry_offset                        = 0;
58
4.00k
  uint32_t number_of_blocks                    = 0;
59
60
#if defined( HAVE_DEBUG_OUTPUT )
61
  uint32_t value_32bit                         = 0;
62
  int number_of_items                          = 0;
63
#endif
64
65
4.00k
  if( hash_table == NULL )
66
0
  {
67
0
    libcerror_error_set(
68
0
     error,
69
0
     LIBCERROR_ERROR_DOMAIN_ARGUMENTS,
70
0
     LIBCERROR_ARGUMENT_ERROR_INVALID_VALUE,
71
0
     "%s: invalid hash table.",
72
0
     function );
73
74
0
    return( -1 );
75
0
  }
76
4.00k
  if( next_hash_table_offset == NULL )
77
0
  {
78
0
    libcerror_error_set(
79
0
     error,
80
0
     LIBCERROR_ERROR_DOMAIN_ARGUMENTS,
81
0
     LIBCERROR_ARGUMENT_ERROR_INVALID_VALUE,
82
0
     "%s: invalid next hash table offset.",
83
0
     function );
84
85
0
    return( -1 );
86
0
  }
87
4.00k
  if( file_io_handle == NULL )
88
0
  {
89
0
    libcerror_error_set(
90
0
     error,
91
0
     LIBCERROR_ERROR_DOMAIN_ARGUMENTS,
92
0
     LIBCERROR_ARGUMENT_ERROR_INVALID_VALUE,
93
0
     "%s: invalid file IO handle.",
94
0
     function );
95
96
0
    return( -1 );
97
0
  }
98
4.00k
  if( block_size == 0 )
99
0
  {
100
0
    libcerror_error_set(
101
0
     error,
102
0
     LIBCERROR_ERROR_DOMAIN_ARGUMENTS,
103
0
     LIBCERROR_ARGUMENT_ERROR_VALUE_ZERO_OR_LESS,
104
0
     "%s: invalid block size value zero or less.",
105
0
     function );
106
107
0
    return( -1 );
108
0
  }
109
#if defined( HAVE_DEBUG_OUTPUT )
110
  if( libcnotify_verbose != 0 )
111
  {
112
    libcnotify_printf(
113
     "%s: reading HASH record at offset: %" PRIi64 " (0x%08" PRIx64 ")\n",
114
     function,
115
     hash_table_offset,
116
     hash_table_offset );
117
  }
118
#endif
119
4.00k
  read_count = libbfio_handle_read_buffer_at_offset(
120
4.00k
                file_io_handle,
121
4.00k
                (uint8_t *) &hash_record_header,
122
4.00k
                sizeof( msiecf_hash_record_header_t ),
123
4.00k
                hash_table_offset,
124
4.00k
                error );
125
126
4.00k
  if( read_count != (ssize_t) sizeof( msiecf_hash_record_header_t ) )
127
275
  {
128
275
    libcerror_error_set(
129
275
     error,
130
275
     LIBCERROR_ERROR_DOMAIN_IO,
131
275
     LIBCERROR_IO_ERROR_READ_FAILED,
132
275
     "%s: unable to read HASH record header at offset: %" PRIi64 " (0x%08" PRIx64 ").",
133
275
     function,
134
275
     hash_table_offset,
135
275
     hash_table_offset );
136
137
275
    goto on_error;
138
275
  }
139
#if defined( HAVE_DEBUG_OUTPUT )
140
  if( libcnotify_verbose != 0 )
141
  {
142
    libcnotify_printf(
143
     "%s: HASH record header:\n",
144
     function );
145
    libcnotify_print_data(
146
     (uint8_t *) &hash_record_header,
147
     sizeof( msiecf_hash_record_header_t ),
148
     0 );
149
  }
150
#endif
151
3.72k
  if( memory_compare(
152
3.72k
       hash_record_header.signature,
153
3.72k
       "HASH",
154
3.72k
       4 ) != 0 )
155
53
  {
156
53
    libcerror_error_set(
157
53
     error,
158
53
     LIBCERROR_ERROR_DOMAIN_RUNTIME,
159
53
     LIBCERROR_RUNTIME_ERROR_UNSUPPORTED_VALUE,
160
53
     "%s: unsupported signature.",
161
53
     function );
162
163
53
    goto on_error;
164
53
  }
165
3.67k
  byte_stream_copy_to_uint32_little_endian(
166
3.67k
   hash_record_header.number_of_blocks,
167
3.67k
   number_of_blocks );
168
169
3.67k
  byte_stream_copy_to_uint32_little_endian(
170
3.67k
   hash_record_header.next_offset,
171
3.67k
   *next_hash_table_offset );
172
173
#if defined( HAVE_DEBUG_OUTPUT )
174
  if( libcnotify_verbose != 0 )
175
  {
176
    libcnotify_printf(
177
     "%s: signature\t\t\t\t\t: %c%c%c%c\n",
178
     function,
179
     hash_record_header.signature[ 0 ],
180
     hash_record_header.signature[ 1 ],
181
     hash_record_header.signature[ 2 ],
182
     hash_record_header.signature[ 3 ] );
183
184
    libcnotify_printf(
185
     "%s: number of blocks\t\t\t\t: %" PRIu32 "\n",
186
     function,
187
     number_of_blocks );
188
189
    libcnotify_printf(
190
     "%s: next offset\t\t\t\t\t: %" PRIi64 " (0x%08" PRIx64 ")\n",
191
     function,
192
     *next_hash_table_offset,
193
     *next_hash_table_offset );
194
195
    byte_stream_copy_to_uint32_little_endian(
196
     hash_record_header.sequence_number,
197
     value_32bit );
198
    libcnotify_printf(
199
     "%s: sequence number\t\t\t\t: %" PRIu32 "\n",
200
     function,
201
     value_32bit );
202
203
    libcnotify_printf(
204
     "\n" );
205
  }
206
#endif /* defined( HAVE_DEBUG_OUTPUT ) */
207
208
3.67k
  read_size = ( number_of_blocks * block_size ) - sizeof( msiecf_hash_record_header_t );
209
210
3.67k
  if( read_size > (size_t) MEMORY_MAXIMUM_ALLOCATION_SIZE )
211
21
  {
212
21
    libcerror_error_set(
213
21
     error,
214
21
     LIBCERROR_ERROR_DOMAIN_RUNTIME,
215
21
     LIBCERROR_RUNTIME_ERROR_VALUE_EXCEEDS_MAXIMUM,
216
21
     "%s: invalid HASH record data size value exceeds maximum allocation size.",
217
21
     function );
218
219
21
    goto on_error;
220
21
  }
221
3.65k
  if( ( read_size == 0 )
222
3.65k
   || ( ( read_size % 8 ) != 0 ) )
223
0
  {
224
0
    libcerror_error_set(
225
0
     error,
226
0
     LIBCERROR_ERROR_DOMAIN_RUNTIME,
227
0
     LIBCERROR_RUNTIME_ERROR_UNSUPPORTED_VALUE,
228
0
     "%s: unsupported HASH record data size.",
229
0
     function );
230
231
0
    goto on_error;
232
0
  }
233
3.65k
  hash_record_data = (uint8_t *) memory_allocate(
234
3.65k
                                  read_size );
235
236
3.65k
  if( hash_record_data == NULL )
237
0
  {
238
0
    libcerror_error_set(
239
0
     error,
240
0
     LIBCERROR_ERROR_DOMAIN_MEMORY,
241
0
     LIBCERROR_MEMORY_ERROR_INSUFFICIENT,
242
0
     "%s: unable to create HASH record data.",
243
0
     function );
244
245
0
    goto on_error;
246
0
  }
247
3.65k
  read_count = libbfio_handle_read_buffer(
248
3.65k
                file_io_handle,
249
3.65k
                hash_record_data,
250
3.65k
                read_size,
251
3.65k
                error );
252
253
3.65k
  if( read_count != (ssize_t) read_size )
254
115
  {
255
115
    libcerror_error_set(
256
115
     error,
257
115
     LIBCERROR_ERROR_DOMAIN_IO,
258
115
     LIBCERROR_IO_ERROR_READ_FAILED,
259
115
     "%s: unable to read HASH record data.",
260
115
     function );
261
262
115
    goto on_error;
263
115
  }
264
#if defined( HAVE_DEBUG_OUTPUT )
265
  if( libcnotify_verbose != 0 )
266
  {
267
    libcnotify_printf(
268
     "%s: HASH record data:\n",
269
     function );
270
    libcnotify_print_data(
271
     hash_record_data,
272
     read_size,
273
     LIBCNOTIFY_PRINT_DATA_FLAG_GROUP_DATA );
274
  }
275
#endif
276
3.53k
  read_size /= 8;
277
278
3.53k
  entry_data = hash_record_data;
279
280
3.53k
  for( table_iterator = 0;
281
73.2k
       table_iterator < read_size;
282
69.7k
       table_iterator++ )
283
69.7k
  {
284
69.7k
    byte_stream_copy_to_uint32_little_endian(
285
69.7k
     entry_data,
286
69.7k
     entry_hash );
287
288
69.7k
    entry_data += 4;
289
290
69.7k
    byte_stream_copy_to_uint32_little_endian(
291
69.7k
     entry_data,
292
69.7k
     entry_offset );
293
294
69.7k
    entry_data += 4;
295
296
#if defined( HAVE_DEBUG_OUTPUT )
297
    if( libcnotify_verbose != 0 )
298
    {
299
      libcnotify_printf(
300
       "%s: entry %03d hash\t\t\t\t: 0x%08" PRIx32 "\n",
301
       function,
302
       table_iterator,
303
       entry_hash );
304
      libcnotify_printf(
305
       "%s: entry %03d offset\t\t\t\t: 0x%08" PRIx32 "\n",
306
       function,
307
       table_iterator,
308
       entry_offset );
309
    }
310
#endif
311
    /* Skip empty entries
312
     */
313
69.7k
    if( entry_hash == entry_offset )
314
15.9k
    {
315
15.9k
      continue;
316
15.9k
    }
317
    /* Skip uninitialized entries
318
     * These should only appear at the end of the HASH record data
319
     */
320
53.7k
    if( ( entry_hash == 0x0badf00d )
321
53.7k
     || ( entry_hash == 0xdeadbeef ) )
322
4
    {
323
4
      continue;
324
4
    }
325
    /* Skip invalid URL entries
326
     */
327
53.7k
    if( ( entry_hash & 0x0f ) == 0x01 )
328
3.22k
    {
329
3.22k
      continue;
330
3.22k
    }
331
    /* Check if the entry record offset is block aligned
332
     */
333
50.5k
    if( ( entry_offset % block_size ) != 0 )
334
34.1k
    {
335
34.1k
      continue;
336
34.1k
    }
337
#if defined( HAVE_DEBUG_OUTPUT )
338
    number_of_items++;
339
#endif
340
    /* TODO flag hashed items
341
     */
342
343
50.5k
  }
344
3.53k
  memory_free(
345
3.53k
   hash_record_data );
346
347
3.53k
  hash_record_data = NULL;
348
349
#if defined( HAVE_DEBUG_OUTPUT )
350
  if( libcnotify_verbose != 0 )
351
  {
352
    libcnotify_printf(
353
     "%s: found %d hashed items.\n",
354
     function,
355
     number_of_items );
356
    libcnotify_printf(
357
     "\n" );
358
  }
359
#endif
360
3.53k
  return( 1 );
361
362
464
on_error:
363
464
  if( hash_record_data != NULL )
364
115
  {
365
115
    memory_free(
366
115
     hash_record_data );
367
115
  }
368
464
  return( -1 );
369
3.65k
}
370