1 : /*
2 : * Copyright (c) 2012 The Native Client Authors. All rights reserved.
3 : * Use of this source code is governed by a BSD-style license that can be
4 : * found in the LICENSE file.
5 : */
6 :
7 : /* Implement the Validator API for the x86-32 architecture. */
8 : #include <errno.h>
9 : #include <string.h>
10 :
11 : #include "native_client/src/shared/platform/nacl_check.h"
12 : #include "native_client/src/trusted/validator/validation_cache.h"
13 : #include "native_client/src/trusted/validator_ragel/bitmap.h"
14 : #include "native_client/src/trusted/validator_ragel/dfa_validate_common.h"
15 : #include "native_client/src/trusted/validator_ragel/validator.h"
16 :
17 : /*
18 : * Be sure the correct compile flags are defined for this.
19 : * TODO(khim): Try to figure out if these checks actually make any sense.
20 : * I don't foresee any use in cross-environment, but it should work
21 : * and may be useful in some case so why not?
22 : */
23 : #if NACL_ARCH(NACL_TARGET_ARCH) != NACL_x86 || NACL_TARGET_SUBARCH != 32
24 : # error "Can't compile, target is for x86-32"
25 : #endif
26 :
27 2463 : NaClValidationStatus ApplyDfaValidator_x86_32(
28 : uintptr_t guest_addr,
29 : uint8_t *data,
30 : size_t size,
31 : int stubout_mode,
32 : int readonly_text,
33 : const NaClCPUFeatures *f,
34 : const struct NaClValidationMetadata *metadata,
35 : struct NaClValidationCache *cache) {
36 : /* TODO(jfb) Use a safe cast here. */
37 2463 : NaClCPUFeaturesX86 *cpu_features = (NaClCPUFeaturesX86 *) f;
38 2463 : enum NaClValidationStatus status = NaClValidationFailed;
39 2463 : int did_stubout = 0;
40 2463 : void *query = NULL;
41 : UNREFERENCED_PARAMETER(guest_addr);
42 :
43 2463 : if (stubout_mode)
44 0 : return NaClValidationFailedNotImplemented;
45 2463 : if (!NaClArchSupportedX86(cpu_features))
46 0 : return NaClValidationFailedCpuNotSupported;
47 2463 : if (size & kBundleMask)
48 0 : return NaClValidationFailed;
49 :
50 : /*
51 : * If the validation caching interface is available and it would be
52 : * inexpensive to do so, perform a query.
53 : */
54 2463 : if (cache != NULL && NaClCachingIsInexpensive(cache, metadata))
55 11 : query = cache->CreateQuery(cache->handle);
56 2463 : if (query != NULL) {
57 11 : const char validator_id[] = "x86-32 dfa";
58 11 : cache->AddData(query, (uint8_t *) validator_id, sizeof(validator_id));
59 11 : cache->AddData(query, (uint8_t *) cpu_features, sizeof(*cpu_features));
60 11 : NaClAddCodeIdentity(data, size, metadata, cache, query);
61 11 : if (cache->QueryKnownToValidate(query)) {
62 3 : cache->DestroyQuery(query);
63 3 : return NaClValidationSucceeded;
64 : }
65 : }
66 :
67 2460 : if (readonly_text) {
68 8 : if (ValidateChunkIA32(data, size, 0 /*options*/, cpu_features,
69 : NaClDfaProcessValidationError,
70 : NULL))
71 6 : status = NaClValidationSucceeded;
72 : } else {
73 2452 : if (ValidateChunkIA32(data, size, 0 /*options*/, cpu_features,
74 : NaClDfaStubOutCPUUnsupportedInstruction,
75 : &did_stubout))
76 2411 : status = NaClValidationSucceeded;
77 : }
78 2460 : if (status != NaClValidationSucceeded && errno == ENOMEM)
79 0 : status = NaClValidationFailedOutOfMemory;
80 :
81 : /* Cache the result if validation succeeded and the code was not modified. */
82 2460 : if (query != NULL) {
83 8 : if (status == NaClValidationSucceeded && did_stubout == 0)
84 6 : cache->SetKnownToValidate(query);
85 8 : cache->DestroyQuery(query);
86 : }
87 :
88 2460 : return status;
89 : }
90 :
91 :
92 7 : static NaClValidationStatus ValidatorCopy_x86_32(
93 : uintptr_t guest_addr,
94 : uint8_t *data_existing,
95 : uint8_t *data_new,
96 : size_t size,
97 : const NaClCPUFeatures *f,
98 : NaClCopyInstructionFunc copy_func) {
99 : /* TODO(jfb) Use a safe cast here. */
100 7 : NaClCPUFeaturesX86 *cpu_features = (NaClCPUFeaturesX86 *) f;
101 : struct CodeCopyCallbackData callback_data;
102 : UNREFERENCED_PARAMETER(guest_addr);
103 :
104 7 : if (size & kBundleMask)
105 0 : return NaClValidationFailed;
106 7 : callback_data.copy_func = copy_func;
107 7 : callback_data.existing_minus_new = data_existing - data_new;
108 7 : if (ValidateChunkIA32(data_new, size, CALL_USER_CALLBACK_ON_EACH_INSTRUCTION,
109 : cpu_features, NaClDfaProcessCodeCopyInstruction,
110 : &callback_data))
111 7 : return NaClValidationSucceeded;
112 0 : if (errno == ENOMEM)
113 0 : return NaClValidationFailedOutOfMemory;
114 0 : return NaClValidationFailed;
115 : }
116 :
117 : /* This structure is used by callbacks ProcessCodeReplacementInstruction
118 : and ProcessOriginalCodeInstruction during code replacement validation
119 : in ValidatorCodeReplacement_x86_32. */
120 : struct CodeReplacementCallbackData {
121 : /* Bitmap with boundaries of the instructions in the old code bundle. */
122 : bitmap_word instruction_boundaries_existing;
123 : /* Bitmap with boundaries of the instructions in the new code bundle. */
124 : bitmap_word instruction_boundaries_new;
125 : /* cpu_features - needed for the call to ValidateChunkIA32. */
126 : const NaClCPUFeaturesX86 *cpu_features;
127 : /* Pointer to the start of the current bundle in the old code. */
128 : const uint8_t *bundle_existing;
129 : /* Pointer to the start of the new code. */
130 : const uint8_t *data_new;
131 : /* Difference between addresses: data_existing - data_new. This is needed for
132 : fast comparison between existing and new code. */
133 : ptrdiff_t existing_minus_new;
134 : };
135 :
136 383 : static Bool ProcessOriginalCodeInstruction(const uint8_t *begin_existing,
137 : const uint8_t *end_existing,
138 : uint32_t info_existing,
139 : void *callback_data) {
140 383 : struct CodeReplacementCallbackData *data = callback_data;
141 383 : size_t instruction_length = end_existing - begin_existing;
142 383 : const uint8_t *begin_new = begin_existing - data->existing_minus_new;
143 :
144 : /* Sanity check: instruction must be no longer than 17 bytes. */
145 383 : CHECK(instruction_length <= MAX_INSTRUCTION_LENGTH);
146 :
147 : /* Sanity check: old code must be valid... except for jumps. */
148 383 : CHECK(!(info_existing &
149 : (VALIDATION_ERRORS_MASK & ~DIRECT_JUMP_OUT_OF_RANGE)));
150 :
151 : /*
152 : * Return failure if code replacement attempts to delete or modify a special
153 : * instruction such as naclcall or nacljmp.
154 : */
155 391 : if ((info_existing & SPECIAL_INSTRUCTION) &&
156 8 : memcmp(begin_new, begin_existing, instruction_length) != 0)
157 4 : return FALSE;
158 :
159 379 : BitmapSetBit(&data->instruction_boundaries_existing,
160 379 : begin_existing - data->bundle_existing);
161 :
162 379 : return TRUE;
163 : }
164 :
165 424 : static INLINE Bool ProcessCodeReplacementInstructionInfoFlags(
166 : const uint8_t *begin_existing,
167 : const uint8_t *begin_new,
168 : size_t instruction_length,
169 : uint32_t info_new,
170 : struct CodeReplacementCallbackData *data) {
171 :
172 : /* Unsupported instruction must have been replaced with HLTs. */
173 424 : if ((info_new & VALIDATION_ERRORS_MASK) == CPUID_UNSUPPORTED_INSTRUCTION) {
174 : /*
175 : * The new instruction will be replaced with a bunch of HLTs by
176 : * ValidatorCopy_x86_32 and they are single-byte instructions.
177 : * Mark all the bytes as instruction boundaries.
178 : */
179 0 : if (NaClDfaCodeReplacementIsStubouted(begin_existing, instruction_length)) {
180 0 : BitmapSetBits(&data->instruction_boundaries_new,
181 0 : (begin_new - data->data_new) & kBundleMask,
182 : instruction_length);
183 0 : return TRUE;
184 : }
185 0 : return FALSE;
186 : }
187 :
188 : /* If we have jump which jumps out of its range... */
189 424 : if (info_new & DIRECT_JUMP_OUT_OF_RANGE) {
190 : /* then everything is fine if it's the only error and jump is unchanged! */
191 4 : if ((info_new & VALIDATION_ERRORS_MASK) == DIRECT_JUMP_OUT_OF_RANGE &&
192 2 : memcmp(begin_new, begin_existing, instruction_length) == 0)
193 1 : return TRUE;
194 1 : return FALSE;
195 : }
196 :
197 : /* If instruction is not accepted then we have nothing to do here. */
198 422 : if (info_new & (VALIDATION_ERRORS_MASK | BAD_JUMP_TARGET))
199 3 : return FALSE;
200 :
201 : /* Instruction is untouched: we are done. */
202 419 : if (memcmp(begin_new, begin_existing, instruction_length) == 0)
203 376 : return TRUE;
204 :
205 : /*
206 : * Return failure if code replacement attempts to create or modify a special
207 : * instruction such as naclcall or nacljmp.
208 : */
209 43 : if (info_new & SPECIAL_INSTRUCTION)
210 3 : return FALSE;
211 :
212 : /* No problems found. */
213 40 : return TRUE;
214 : }
215 :
216 :
217 424 : static Bool ProcessCodeReplacementInstruction(const uint8_t *begin_new,
218 : const uint8_t *end_new,
219 : uint32_t info_new,
220 : void *callback_data) {
221 424 : struct CodeReplacementCallbackData *data = callback_data;
222 424 : size_t instruction_length = end_new - begin_new;
223 424 : const uint8_t *begin_existing = begin_new + data->existing_minus_new;
224 :
225 : /* Sanity check: instruction must be no longer than 17 bytes. */
226 424 : CHECK(instruction_length <= MAX_INSTRUCTION_LENGTH);
227 :
228 424 : if (!ProcessCodeReplacementInstructionInfoFlags(begin_existing, begin_new,
229 : instruction_length,
230 : info_new, data))
231 7 : return FALSE;
232 :
233 : /* We mark the end of the instruction as valid jump target here. */
234 417 : BitmapSetBit(&data->instruction_boundaries_new,
235 417 : (begin_new - data->data_new) & kBundleMask);
236 :
237 : /* If we at the end of a bundle then check boundaries. */
238 417 : if (((end_new - data->data_new) & kBundleMask) == 0) {
239 : Bool rc;
240 16 : data->bundle_existing = end_new - kBundleSize + data->existing_minus_new;
241 :
242 : /* Check existing code and mark its instruction boundaries. */
243 16 : rc = ValidateChunkIA32(data->bundle_existing, kBundleSize,
244 : CALL_USER_CALLBACK_ON_EACH_INSTRUCTION,
245 : data->cpu_features, ProcessOriginalCodeInstruction,
246 : callback_data);
247 : /* Attempt to delete superinstruction is detected. */
248 16 : if (!rc)
249 4 : return FALSE;
250 :
251 24 : if (data->instruction_boundaries_existing !=
252 12 : data->instruction_boundaries_new)
253 3 : return FALSE;
254 : /* Mark all boundaries in the next bundle invalid. */
255 9 : data->instruction_boundaries_existing = 0;
256 9 : data->instruction_boundaries_new = 0;
257 : }
258 :
259 410 : return TRUE;
260 : }
261 :
262 19 : static NaClValidationStatus ValidatorCodeReplacement_x86_32(
263 : uintptr_t guest_addr,
264 : uint8_t *data_existing,
265 : uint8_t *data_new,
266 : size_t size,
267 : const NaClCPUFeatures *f) {
268 : /* TODO(jfb) Use a safe cast here. */
269 19 : NaClCPUFeaturesX86 *cpu_features = (NaClCPUFeaturesX86 *) f;
270 : struct CodeReplacementCallbackData callback_data;
271 : UNREFERENCED_PARAMETER(guest_addr);
272 :
273 19 : if (size & kBundleMask)
274 0 : return NaClValidationFailed;
275 : /* Mark all boundaries in the bundle invalid. */
276 19 : callback_data.instruction_boundaries_existing = 0;
277 19 : callback_data.instruction_boundaries_new = 0;
278 19 : callback_data.cpu_features = cpu_features;
279 : /* Note: bundle_existing is used when we call second validator. */
280 19 : callback_data.data_new = data_new;
281 19 : callback_data.existing_minus_new = data_existing - data_new;
282 19 : if (ValidateChunkIA32(data_new, size, CALL_USER_CALLBACK_ON_EACH_INSTRUCTION,
283 : cpu_features, ProcessCodeReplacementInstruction,
284 : &callback_data))
285 7 : return NaClValidationSucceeded;
286 12 : if (errno == ENOMEM)
287 0 : return NaClValidationFailedOutOfMemory;
288 12 : return NaClValidationFailed;
289 : }
290 :
291 : static const struct NaClValidatorInterface validator = {
292 : FALSE, /* Optional stubout_mode is not implemented. */
293 : TRUE, /* Optional readonly_text mode is implemented. */
294 : TRUE, /* Optional code replacement functions are implemented. */
295 : ApplyDfaValidator_x86_32,
296 : ValidatorCopy_x86_32,
297 : ValidatorCodeReplacement_x86_32,
298 : sizeof(NaClCPUFeaturesX86),
299 : NaClSetAllCPUFeaturesX86,
300 : NaClGetCurrentCPUFeaturesX86,
301 : NaClFixCPUFeaturesX86,
302 : };
303 :
304 321 : const struct NaClValidatorInterface *NaClDfaValidatorCreate_x86_32(void) {
305 321 : return &validator;
306 : }
|