2009-01-15 Rodrigo Kumpera <rkumpera@novell.com>
[mono.git] / mono / metadata / metadata-verify.c
1 #include <mono/metadata/object-internals.h>
2 #include <mono/metadata/verify.h>
3 #include <mono/metadata/verify-internals.h>
4 #include <mono/metadata/opcodes.h>
5 #include <mono/metadata/tabledefs.h>
6 #include <mono/metadata/reflection.h>
7 #include <mono/metadata/debug-helpers.h>
8 #include <mono/metadata/mono-endian.h>
9 #include <mono/metadata/metadata.h>
10 #include <mono/metadata/metadata-internals.h>
11 #include <mono/metadata/class-internals.h>
12 #include <mono/metadata/tokentype.h>
13 #include <string.h>
14 #include <signal.h>
15 #include <ctype.h>
16
17 /*
18  TODO add fail fast mode
19  TODO add PE32+ support
20  TODO verify the entry point RVA and content.
21 */
22
23 typedef struct {
24         const char *data;
25         guint32 size;
26         GSList *errors;
27         int valid;
28
29         guint32 data_dir_count;
30 } VerifyContext;
31
32 #define ADD_VERIFY_INFO(__ctx, __msg, __status, __exception)    \
33         do {    \
34                 MonoVerifyInfoExtended *vinfo = g_new (MonoVerifyInfoExtended, 1);      \
35                 vinfo->info.status = __status;  \
36                 vinfo->info.message = ( __msg); \
37                 vinfo->exception_type = (__exception);  \
38                 (__ctx)->errors = g_slist_prepend ((__ctx)->errors, vinfo);     \
39         } while (0)
40
41
42 #define ADD_ERROR(__ctx, __msg) \
43         do {    \
44                 ADD_VERIFY_INFO(__ctx, __msg, MONO_VERIFY_ERROR, MONO_EXCEPTION_INVALID_PROGRAM); \
45                 (__ctx)->valid = 0; \
46         } while (0)
47
48 #define CHECK_STATE() do { if (!ctx.valid) goto cleanup; } while (0)
49
50 static guint32
51 pe_signature_offset (VerifyContext *ctx)
52 {
53         return read32 (ctx->data + 0x3c);
54 }
55
56 static void
57 verify_msdos_header (VerifyContext *ctx)
58 {
59         guint32 lfanew;
60         if (ctx->size < 128) {
61                 ADD_ERROR (ctx, g_strdup ("Not enough space for the MS-DOS header"));
62                 return;
63         }
64         if (ctx->data [0] != 0x4d || ctx->data [1] != 0x5a)
65                 ADD_ERROR (ctx,  g_strdup ("Invalid MS-DOS watermark"));
66         lfanew = pe_signature_offset (ctx);
67         if (lfanew > ctx->size - 4)
68                 ADD_ERROR (ctx, g_strdup ("MS-DOS lfanew offset points to outside of the file"));
69 }
70
71 static void
72 verify_pe_header (VerifyContext *ctx)
73 {
74         guint32 offset = pe_signature_offset (ctx);
75         const char *pe_header = ctx->data + offset;
76         if (pe_header [0] != 'P' || pe_header [1] != 'E' ||pe_header [2] != 0 ||pe_header [3] != 0)
77                 ADD_ERROR (ctx,  g_strdup ("Invalid PE header watermark"));
78         pe_header += 4;
79         offset += 4;
80
81         if (offset > ctx->size - 20)
82                 ADD_ERROR (ctx, g_strdup ("File with truncated pe header"));
83         if (read16 (pe_header) != 0x14c)
84                 ADD_ERROR (ctx, g_strdup ("Invalid PE header Machine value"));
85
86 }
87
88 static void
89 verify_pe_optional_header (VerifyContext *ctx)
90 {
91         guint32 offset = pe_signature_offset (ctx) + 4;
92         guint32 header_size;
93         const char *pe_header = ctx->data + offset;
94         const char *pe_optional_header = pe_header + 20;
95
96         header_size = read16 (pe_header + 16);
97         offset += 20;
98
99         if (header_size < 2) /*must be at least 2 or we won't be able to read magic*/
100                 ADD_ERROR (ctx, g_strdup ("Invalid PE optional header size"));
101
102         if (offset > ctx->size - header_size || header_size > ctx->size) {
103                 ADD_ERROR (ctx, g_strdup ("Invalid PE optional header size"));
104                 return;
105         }
106
107         if (read16 (pe_optional_header) == 0x10b) {
108                 if (header_size != 224)
109                         ADD_ERROR (ctx, g_strdup_printf ("Invalid optional header size %d", header_size));
110
111                 if (read32 (pe_optional_header + 28) != 0x400000)
112                         ADD_ERROR (ctx, g_strdup_printf ("Invalid Image base %x", read32 (pe_optional_header + 28)));
113                 if (read32 (pe_optional_header + 32) != 0x2000)
114                         ADD_ERROR (ctx, g_strdup_printf ("Invalid Section Aligmment %x", read32 (pe_optional_header + 32)));
115                 if (read32 (pe_optional_header + 36) != 0x200)
116                         ADD_ERROR (ctx, g_strdup_printf ("Invalid file Aligmment %x", read32 (pe_optional_header + 36)));
117                 /* All the junk in the middle is irrelevant, specially for mono. */
118                 ctx->data_dir_count = read32 (pe_optional_header + 92);
119                 if (ctx->data_dir_count > 0x10)
120                         ADD_ERROR (ctx, g_strdup_printf ("Too many data directories %x", ctx->data_dir_count));
121         } else {
122                 if (read16 (pe_optional_header) == 0x20B)
123                         ADD_ERROR (ctx, g_strdup ("Metadata verifier doesn't handle PE32+"));
124                 else
125                         ADD_ERROR (ctx, g_strdup_printf ("Invalid optional header magic %d", read16 (pe_optional_header)));
126         }
127
128
129 }
130
131 GSList*
132 mono_image_verify (const char *data, guint32 size)
133 {
134         VerifyContext ctx;
135         memset (&ctx, 0, sizeof (VerifyContext));
136         ctx.data = data;
137         ctx.size = size;
138         ctx.valid = 1;
139
140         verify_msdos_header (&ctx);
141         CHECK_STATE();
142         verify_pe_header (&ctx);
143         CHECK_STATE();
144         verify_pe_optional_header (&ctx);
145         CHECK_STATE();
146
147 cleanup:
148         return ctx.errors;
149 }