2 * metadata-verify.c: Metadata verfication support
5 * Mono Project (http://www.mono-project.com)
7 * Copyright (C) 2005-2008 Novell, Inc. (http://www.novell.com)
10 #include <mono/metadata/object-internals.h>
11 #include <mono/metadata/verify.h>
12 #include <mono/metadata/verify-internals.h>
13 #include <mono/metadata/opcodes.h>
14 #include <mono/metadata/tabledefs.h>
15 #include <mono/metadata/reflection.h>
16 #include <mono/metadata/debug-helpers.h>
17 #include <mono/metadata/mono-endian.h>
18 #include <mono/metadata/metadata.h>
19 #include <mono/metadata/metadata-internals.h>
20 #include <mono/metadata/class-internals.h>
21 #include <mono/metadata/tokentype.h>
27 TODO add fail fast mode
28 TODO add PE32+ support
29 TODO verify the entry point RVA and content.
30 TODO load_section_table and load_data_directories must take PE32+ into account
31 TODO add section relocation support
32 TODO verify the relocation table, since we really don't use, no need so far.
33 TODO do full PECOFF resources verification
34 TODO verify in the CLI header entry point and resources
37 #define INVALID_OFFSET ((guint32)-1)
41 RESOURCE_TABLE_IDX = 2,
42 RELOCATION_TABLE_IDX = 5,
58 guint32 translated_offset;
70 guint32 rellocationsRVA;
71 guint16 numberOfRelocations;
83 guint32 section_count;
84 SectionHeader *sections;
85 gboolean wide_strings, wide_guid, wide_blob;
87 DataDirectory data_directories [16];
88 OffsetAndSize metadata_streams [5]; //offset from begin of the image
89 TableInfo tables [MONO_TABLE_NUM];
92 #define ADD_VERIFY_INFO(__ctx, __msg, __status, __exception) \
94 MonoVerifyInfoExtended *vinfo = g_new (MonoVerifyInfoExtended, 1); \
95 vinfo->info.status = __status; \
96 vinfo->info.message = ( __msg); \
97 vinfo->exception_type = (__exception); \
98 (__ctx)->errors = g_slist_prepend ((__ctx)->errors, vinfo); \
102 #define ADD_ERROR(__ctx, __msg) \
104 ADD_VERIFY_INFO(__ctx, __msg, MONO_VERIFY_ERROR, MONO_EXCEPTION_INVALID_PROGRAM); \
105 (__ctx)->valid = 0; \
109 #define CHECK_STATE() do { if (!ctx.valid) goto cleanup; } while (0)
111 #define CHECK_ERROR() do { if (!ctx->valid) return; } while (0)
114 pe_signature_offset (VerifyContext *ctx)
116 return read32 (ctx->data + 0x3c);
120 pe_header_offset (VerifyContext *ctx)
122 return read32 (ctx->data + 0x3c) + 4;
126 bounds_check_virtual_address (VerifyContext *ctx, guint32 rva, guint32 size)
133 for (i = 0; i < ctx->section_count; ++i) {
134 guint32 base = ctx->sections [i].baseRVA;
135 guint32 end = ctx->sections [i].baseRVA + ctx->sections [i].size;
136 if (rva >= base && rva + size <= end)
143 bounds_check_offset (DataDirectory *dir, guint32 offset, guint32 size)
145 if (dir->translated_offset > offset)
147 if (dir->size < size)
149 return offset + size <= dir->translated_offset + dir->size;
154 translate_rva (VerifyContext *ctx, guint32 rva)
161 for (i = 0; i < ctx->section_count; ++i) {
162 guint32 base = ctx->sections [i].baseRVA;
163 guint32 end = ctx->sections [i].baseRVA + ctx->sections [i].size;
164 if (rva >= base && rva <= end) {
165 guint32 res = (rva - base) + ctx->sections [i].baseOffset;
167 return res >= ctx->size ? INVALID_OFFSET : res;
171 return INVALID_OFFSET;
175 verify_msdos_header (VerifyContext *ctx)
179 ADD_ERROR (ctx, g_strdup ("Not enough space for the MS-DOS header"));
180 if (ctx->data [0] != 0x4d || ctx->data [1] != 0x5a)
181 ADD_ERROR (ctx, g_strdup ("Invalid MS-DOS watermark"));
182 lfanew = pe_signature_offset (ctx);
183 if (lfanew > ctx->size - 4)
184 ADD_ERROR (ctx, g_strdup ("MS-DOS lfanew offset points to outside of the file"));
188 verify_pe_header (VerifyContext *ctx)
190 guint32 offset = pe_signature_offset (ctx);
191 const char *pe_header = ctx->data + offset;
192 if (pe_header [0] != 'P' || pe_header [1] != 'E' ||pe_header [2] != 0 ||pe_header [3] != 0)
193 ADD_ERROR (ctx, g_strdup ("Invalid PE header watermark"));
197 if (offset > ctx->size - 20)
198 ADD_ERROR (ctx, g_strdup ("File with truncated pe header"));
199 if (read16 (pe_header) != 0x14c)
200 ADD_ERROR (ctx, g_strdup ("Invalid PE header Machine value"));
204 verify_pe_optional_header (VerifyContext *ctx)
206 guint32 offset = pe_header_offset (ctx);
207 guint32 header_size, file_alignment;
208 const char *pe_header = ctx->data + offset;
209 const char *pe_optional_header = pe_header + 20;
211 header_size = read16 (pe_header + 16);
214 if (header_size < 2) /*must be at least 2 or we won't be able to read magic*/
215 ADD_ERROR (ctx, g_strdup ("Invalid PE optional header size"));
217 if (offset > ctx->size - header_size || header_size > ctx->size)
218 ADD_ERROR (ctx, g_strdup ("Invalid PE optional header size"));
220 if (read16 (pe_optional_header) == 0x10b) {
221 if (header_size != 224)
222 ADD_ERROR (ctx, g_strdup_printf ("Invalid optional header size %d", header_size));
224 /*if (read32 (pe_optional_header + 28) != 0x400000)
225 ADD_ERROR (ctx, g_strdup_printf ("Invalid Image base %x", read32 (pe_optional_header + 28)));*/
226 if (read32 (pe_optional_header + 32) != 0x2000)
227 ADD_ERROR (ctx, g_strdup_printf ("Invalid Section Aligmnent %x", read32 (pe_optional_header + 32)));
228 file_alignment = read32 (pe_optional_header + 36);
229 if (file_alignment != 0x200 && file_alignment != 0x1000)
230 ADD_ERROR (ctx, g_strdup_printf ("Invalid file Aligmnent %x", file_alignment));
231 /* All the junk in the middle is irrelevant, specially for mono. */
232 if (read32 (pe_optional_header + 92) > 0x10)
233 ADD_ERROR (ctx, g_strdup_printf ("Too many data directories %x", read32 (pe_optional_header + 92)));
235 if (read16 (pe_optional_header) == 0x20B)
236 ADD_ERROR (ctx, g_strdup ("Metadata verifier doesn't handle PE32+"));
238 ADD_ERROR (ctx, g_strdup_printf ("Invalid optional header magic %d", read16 (pe_optional_header)));
243 load_section_table (VerifyContext *ctx)
246 SectionHeader *sections;
247 guint32 offset = pe_header_offset (ctx);
248 const char *ptr = ctx->data + offset;
249 guint16 num_sections = ctx->section_count = read16 (ptr + 2);
251 offset += 244;/*FIXME, this constant is different under PE32+*/
254 if (num_sections * 40 > ctx->size - offset)
255 ADD_ERROR (ctx, g_strdup ("Invalid PE optional header size"));
257 sections = ctx->sections = g_new0 (SectionHeader, num_sections);
258 for (i = 0; i < num_sections; ++i) {
259 sections [i].size = read32 (ptr + 8);
260 sections [i].baseRVA = read32 (ptr + 12);
261 sections [i].baseOffset = read32 (ptr + 20);
262 sections [i].rellocationsRVA = read32 (ptr + 24);
263 sections [i].numberOfRelocations = read16 (ptr + 32);
267 ptr = ctx->data + offset; /*reset it to the beggining*/
268 for (i = 0; i < num_sections; ++i) {
269 guint32 raw_size, flags;
270 if (sections [i].baseOffset == 0)
271 ADD_ERROR (ctx, g_strdup ("Metadata verifier doesn't handle sections with intialized data only"));
272 if (sections [i].baseOffset >= ctx->size)
273 ADD_ERROR (ctx, g_strdup_printf ("Invalid PointerToRawData %x points beyond EOF", sections [i].baseOffset));
274 if (sections [i].size > ctx->size - sections [i].baseOffset)
275 ADD_ERROR (ctx, g_strdup ("Invalid VirtualSize points beyond EOF"));
277 raw_size = read32 (ptr + 16);
278 if (raw_size < sections [i].size)
279 ADD_ERROR (ctx, g_strdup ("Metadata verifier doesn't handle sections with SizeOfRawData < VirtualSize"));
281 if (raw_size > ctx->size - sections [i].baseOffset)
282 ADD_ERROR (ctx, g_strdup_printf ("Invalid SizeOfRawData %x points beyond EOF", raw_size));
284 if (sections [i].rellocationsRVA || sections [i].numberOfRelocations)
285 ADD_ERROR (ctx, g_strdup_printf ("Metadata verifier doesn't handle section relocation"));
287 flags = read32 (ptr + 36);
288 /*TODO 0xFE0000E0 is all flags from cil-coff.h OR'd. Make it a less magical number*/
289 if (flags == 0 || (flags & ~0xFE0000E0) != 0)
290 ADD_ERROR (ctx, g_strdup_printf ("Invalid section flags %x", flags));
297 is_valid_data_directory (int i)
299 /*LAMESPEC 4 == certificate 6 == debug, MS uses both*/
300 return i == 1 || i == 2 || i == 5 || i == 12 || i == 14 || i == 4 || i == 6;
304 load_data_directories (VerifyContext *ctx)
306 guint32 offset = pe_header_offset (ctx) + 116; /*FIXME, this constant is different under PE32+*/
307 const char *ptr = ctx->data + offset;
310 for (i = 0; i < 16; ++i) {
311 guint32 rva = read32 (ptr);
312 guint32 size = read32 (ptr + 4);
314 if ((rva != 0 || size != 0) && !is_valid_data_directory (i))
315 ADD_ERROR (ctx, g_strdup_printf ("Invalid data directory %d", i));
317 if (rva != 0 && !bounds_check_virtual_address (ctx, rva, size))
318 ADD_ERROR (ctx, g_strdup_printf ("Invalid data directory %d rva/size pair %x/%x", i, rva, size));
320 ctx->data_directories [i].rva = rva;
321 ctx->data_directories [i].size = size;
322 ctx->data_directories [i].translated_offset = translate_rva (ctx, rva);
328 #define SIZE_OF_MSCOREE (sizeof ("mscoree.dll"))
330 #define SIZE_OF_CORMAIN (sizeof ("_CorExeMain"))
333 verify_hint_name_table (VerifyContext *ctx, guint32 import_rva, const char *table_name)
336 guint32 hint_table_rva;
338 import_rva = translate_rva (ctx, import_rva);
339 g_assert (import_rva != INVALID_OFFSET);
341 hint_table_rva = read32 (ctx->data + import_rva);
342 if (!bounds_check_virtual_address (ctx, hint_table_rva, SIZE_OF_CORMAIN + 2))
343 ADD_ERROR (ctx, g_strdup_printf ("Invalid Hint/Name rva %d for %s", hint_table_rva, table_name));
345 hint_table_rva = translate_rva (ctx, hint_table_rva);
346 g_assert (hint_table_rva != INVALID_OFFSET);
347 ptr = ctx->data + hint_table_rva + 2;
349 if (memcmp ("_CorExeMain", ptr, SIZE_OF_CORMAIN) && memcmp ("_CorDllMain", ptr, SIZE_OF_CORMAIN)) {
350 char name[SIZE_OF_CORMAIN];
351 memcpy (name, ptr, SIZE_OF_CORMAIN);
352 name [SIZE_OF_CORMAIN - 1] = 0;
353 ADD_ERROR (ctx, g_strdup_printf ("Invalid Hint / Name: '%s'", name));
358 verify_import_table (VerifyContext *ctx)
360 DataDirectory it = ctx->data_directories [IMPORT_TABLE_IDX];
361 guint32 offset = it.translated_offset;
362 const char *ptr = ctx->data + offset;
363 guint32 name_rva, ilt_rva, iat_rva;
365 g_assert (offset != INVALID_OFFSET);
368 ADD_ERROR (ctx, g_strdup_printf ("Import table size %d is smaller than 40", it.size));
370 ilt_rva = read32 (ptr);
371 if (!bounds_check_virtual_address (ctx, ilt_rva, 8))
372 ADD_ERROR (ctx, g_strdup_printf ("Invalid Import Lookup Table rva %x", ilt_rva));
374 name_rva = read32 (ptr + 12);
375 if (!bounds_check_virtual_address (ctx, name_rva, SIZE_OF_MSCOREE))
376 ADD_ERROR (ctx, g_strdup_printf ("Invalid Import Table Name rva %x", name_rva));
378 iat_rva = read32 (ptr + 16);
379 if (!bounds_check_virtual_address (ctx, iat_rva, 8))
380 ADD_ERROR (ctx, g_strdup_printf ("Invalid Import Address Table rva %x", iat_rva));
382 if (iat_rva != ctx->data_directories [IAT_IDX].rva)
383 ADD_ERROR (ctx, g_strdup_printf ("Import Address Table rva %x different from data directory entry %x", read32 (ptr + 16), ctx->data_directories [IAT_IDX].rva));
385 name_rva = translate_rva (ctx, name_rva);
386 g_assert (name_rva != INVALID_OFFSET);
387 ptr = ctx->data + name_rva;
389 if (memcmp ("mscoree.dll", ptr, SIZE_OF_MSCOREE)) {
390 char name[SIZE_OF_MSCOREE];
391 memcpy (name, ptr, SIZE_OF_MSCOREE);
392 name [SIZE_OF_MSCOREE - 1] = 0;
393 ADD_ERROR (ctx, g_strdup_printf ("Invalid Import Table Name: '%s'", name));
396 verify_hint_name_table (ctx, ilt_rva, "Import Lookup Table");
398 verify_hint_name_table (ctx, iat_rva, "Import Address Table");
402 verify_resources_table (VerifyContext *ctx)
404 DataDirectory it = ctx->data_directories [RESOURCE_TABLE_IDX];
406 guint16 named_entries, id_entries;
407 const char *ptr, *root, *end;
413 ADD_ERROR (ctx, g_strdup_printf ("Resource section is too small, must be at least 16 bytes long but it's %d long", it.size));
415 offset = it.translated_offset;
416 root = ptr = ctx->data + offset;
417 end = root + it.size;
419 g_assert (offset != INVALID_OFFSET);
421 named_entries = read16 (ptr + 12);
422 id_entries = read16 (ptr + 14);
424 printf ("named %d id_entries %d\n", named_entries, id_entries);
425 if ((named_entries + id_entries) * 8 + 16 > it.size)
426 ADD_ERROR (ctx, g_strdup_printf ("Resource section is too small, the number of entries (%d) doesn't fit on it's size %d", named_entries + id_entries, it.size));
428 if (named_entries || id_entries)
429 ADD_ERROR (ctx, g_strdup_printf ("The metadata verifier doesn't support full verification of PECOFF resources"));
433 verify_cli_header (VerifyContext *ctx)
435 DataDirectory it = ctx->data_directories [CLI_HEADER_IDX];
441 ADD_ERROR (ctx, g_strdup_printf ("CLI header missing"));
444 ADD_ERROR (ctx, g_strdup_printf ("Invalid cli header size in data directory %d must be 72", it.size));
446 offset = it.translated_offset;
447 ptr = ctx->data + offset;
449 g_assert (offset != INVALID_OFFSET);
451 if (read16 (ptr) != 72)
452 ADD_ERROR (ctx, g_strdup_printf ("Invalid cli header size %d must be 72", read16 (ptr)));
454 if (!bounds_check_virtual_address (ctx, read32 (ptr + 8), read32 (ptr + 12)))
455 ADD_ERROR (ctx, g_strdup_printf ("Invalid medatata section rva/size pair %x/%x", read32 (ptr + 8), read32 (ptr + 12)));
457 if (!read32 (ptr + 8) || !read32 (ptr + 12))
458 ADD_ERROR (ctx, g_strdup_printf ("Missing medatata section in the CLI header"));
460 if ((read32 (ptr + 16) & ~0x0001000B) != 0)
461 ADD_ERROR (ctx, g_strdup_printf ("Invalid CLI header flags"));
464 for (i = 0; i < 6; ++i) {
465 guint32 rva = read32 (ptr);
466 guint32 size = read32 (ptr + 4);
468 if (rva != 0 && !bounds_check_virtual_address (ctx, rva, size))
469 ADD_ERROR (ctx, g_strdup_printf ("Invalid cli section %i rva/size pair %x/%x", i, rva, size));
474 ADD_ERROR (ctx, g_strdup_printf ("Metadata verifier doesn't support cli header section %d", i));
479 pad4 (guint32 offset)
481 if (offset & 0x3) //pad to the next 4 byte boundary
482 offset = (offset & ~0x3) + 4;
487 verify_metadata_header (VerifyContext *ctx)
490 DataDirectory it = ctx->data_directories [CLI_HEADER_IDX];
494 offset = it.translated_offset;
495 ptr = ctx->data + offset;
496 g_assert (offset != INVALID_OFFSET);
498 //build a directory entry for the metadata root
500 it.rva = read32 (ptr);
502 it.size = read32 (ptr);
503 it.translated_offset = offset = translate_rva (ctx, it.rva);
505 ptr = ctx->data + offset;
506 g_assert (offset != INVALID_OFFSET);
509 ADD_ERROR (ctx, g_strdup_printf ("Metadata root section is too small %d (at least 20 bytes required for initial decoding)", it.size));
511 if (read32 (ptr) != 0x424A5342)
512 ADD_ERROR (ctx, g_strdup_printf ("Invalid metadata signature, expected 0x424A5342 but got %08x", read32 (ptr)));
514 offset = pad4 (offset + 16 + read32 (ptr + 12));
516 if (!bounds_check_offset (&it, offset, 4))
517 ADD_ERROR (ctx, g_strdup_printf ("Metadata root section is too small %d (at least %d bytes required for flags decoding)", it.size, offset + 4 - it.translated_offset));
519 ptr = ctx->data + offset; //move to streams header
521 if (read16 (ptr + 2) != 5)
522 ADD_ERROR (ctx, g_strdup_printf ("Metadata root section have %d streams (it must have exactly 5)", read16 (ptr + 2)));
527 for (i = 0; i < 5; ++i) {
528 guint32 stream_off, stream_size;
529 int string_size, stream_idx;
531 if (!bounds_check_offset (&it, offset, 8))
532 ADD_ERROR (ctx, g_strdup_printf ("Metadata root section is too small for initial decode of stream header %d, missing %d bytes", i, offset + 9 - it.translated_offset));
534 stream_off = it.translated_offset + read32 (ptr);
535 stream_size = read32 (ptr + 4);
537 if (!bounds_check_offset (&it, stream_off, stream_size))
538 ADD_ERROR (ctx, g_strdup_printf ("Invalid stream header %d offset/size pair %x/%x", 0, stream_off, stream_size));
543 for (string_size = 0; string_size < 32; ++string_size) {
544 if (!bounds_check_offset (&it, offset++, 1))
545 ADD_ERROR (ctx, g_strdup_printf ("Metadata root section is too small to decode stream header %d name", i));
546 if (!ptr [string_size])
550 if (ptr [string_size])
551 ADD_ERROR (ctx, g_strdup_printf ("Metadata stream header %d name larger than 32 bytes", i));
553 if (!strncmp ("#Strings", ptr, 9))
554 stream_idx = STRINGS_STREAM;
555 else if (!strncmp ("#US", ptr, 4))
556 stream_idx = USER_STRINGS_STREAM;
557 else if (!strncmp ("#Blob", ptr, 6))
558 stream_idx = BLOB_STREAM;
559 else if (!strncmp ("#GUID", ptr, 6))
560 stream_idx = GUID_STREAM;
561 else if (!strncmp ("#~", ptr, 3))
562 stream_idx = TILDE_STREAM;
564 ADD_ERROR (ctx, g_strdup_printf ("Metadata stream header %d invalid name %s", i, ptr));
566 if (ctx->metadata_streams [stream_idx].offset != 0)
567 ADD_ERROR (ctx, g_strdup_printf ("Duplicated metadata stream header %s", ptr));
569 ctx->metadata_streams [stream_idx].offset = stream_off;
570 ctx->metadata_streams [stream_idx].size = stream_size;
572 offset = pad4 (offset);
573 ptr = ctx->data + offset;
578 verify_tables_schema (VerifyContext *ctx)
580 OffsetAndSize tables_area = ctx->metadata_streams [TILDE_STREAM];
581 unsigned offset = tables_area.offset;
582 const char *ptr = ctx->data + offset;
583 guint64 valid_tables;
587 if (tables_area.size < 24)
588 ADD_ERROR (ctx, g_strdup_printf ("Table schemata size (%d) too small to for initial decoding (requires 24 bytes)", tables_area.size));
591 ADD_ERROR (ctx, g_strdup_printf ("Invalid table schemata major version %d, expected 2", ptr [4]));
593 ADD_ERROR (ctx, g_strdup_printf ("Invalid table schemata minor version %d, expected 0", ptr [5]));
595 if ((ptr [6] & ~0x7) != 0)
596 ADD_ERROR (ctx, g_strdup_printf ("Invalid table schemata heap sizes 0x%02x, only bits 0, 1 and 2 can be set", ((unsigned char *) ptr) [6]));
598 ctx->wide_strings = ptr [6] & 0x1;
599 ctx->wide_guid = ptr [6] & 0x2;
600 ctx->wide_blob = ptr [6] & 04;
602 valid_tables = read64 (ptr + 8);
604 for (i = 0; i < 64; ++i) {
605 if (!(valid_tables & ((guint64)1 << i)))
608 /*MS Extensions: 0x3 0x5 0x7 0x13 0x16
609 Unused: 0x1E 0x1F 0x2D-0x3F
610 We don't care about the MS extensions.*/
611 if (i == 0x3 || i == 0x5 || i == 0x7 || i == 0x13 || i == 0x16)
612 ADD_ERROR (ctx, g_strdup_printf ("The metadata verifies doesn't support MS specific table %x", i));
613 if (i == 0x1E || i == 0x1F || i >= 0x2D)
614 ADD_ERROR (ctx, g_strdup_printf ("Invalid table %x", i));
618 if (tables_area.size < 24 + count * 4)
619 ADD_ERROR (ctx, g_strdup_printf ("Table schemata size (%d) too small to for decoding row counts (requires %d bytes)", tables_area.size, 24 + count * 4));
625 mono_image_verify (const char *data, guint32 size)
628 memset (&ctx, 0, sizeof (VerifyContext));
633 verify_msdos_header (&ctx);
635 verify_pe_header (&ctx);
637 verify_pe_optional_header (&ctx);
639 load_section_table (&ctx);
641 load_data_directories (&ctx);
643 verify_import_table (&ctx);
645 /*No need to check the IAT directory entry, it's content is indirectly verified by verify_import_table*/
646 verify_resources_table (&ctx);
648 verify_cli_header (&ctx);
650 verify_metadata_header (&ctx);
652 verify_tables_schema (&ctx);
655 g_free (ctx.sections);