OLD | NEW |
1 /* | 1 /* |
2 * Copyright (c) 2012 The Native Client Authors. All rights reserved. | 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 | 3 * Use of this source code is governed by a BSD-style license that can be |
4 * found in the LICENSE file. | 4 * found in the LICENSE file. |
5 */ | 5 */ |
6 | 6 |
7 /* Implement the functions common for ia32 and x86-64 architectures. */ | 7 /* Implement the functions common for ia32 and x86-64 architectures. */ |
8 #include "native_client/src/trusted/validator_ragel/dfa_validate_common.h" | 8 #include "native_client/src/trusted/validator_ragel/dfa_validate_common.h" |
9 | 9 |
10 #include <string.h> | 10 #include <string.h> |
11 | 11 |
12 #include "native_client/src/shared/platform/nacl_check.h" | 12 #include "native_client/src/shared/platform/nacl_check.h" |
13 #include "native_client/src/trusted/service_runtime/nacl_config.h" | 13 #include "native_client/src/trusted/service_runtime/nacl_config.h" |
14 #include "native_client/src/trusted/validator_ragel/validator.h" | 14 #include "native_client/src/include/build_config.h" |
15 | 15 |
16 /* Used as an argument to copy_func when unsupported instruction must be | 16 /* Used as an argument to copy_func when unsupported instruction must be |
17 replaced with HLTs. */ | 17 replaced with HLTs. */ |
18 static const uint8_t kStubOutMem[MAX_INSTRUCTION_LENGTH] = { | 18 static const uint8_t kStubOutMem[MAX_INSTRUCTION_LENGTH] = { |
19 NACL_HALT_OPCODE, NACL_HALT_OPCODE, NACL_HALT_OPCODE, NACL_HALT_OPCODE, | 19 NACL_HALT_OPCODE, NACL_HALT_OPCODE, NACL_HALT_OPCODE, NACL_HALT_OPCODE, |
20 NACL_HALT_OPCODE, NACL_HALT_OPCODE, NACL_HALT_OPCODE, NACL_HALT_OPCODE, | 20 NACL_HALT_OPCODE, NACL_HALT_OPCODE, NACL_HALT_OPCODE, NACL_HALT_OPCODE, |
21 NACL_HALT_OPCODE, NACL_HALT_OPCODE, NACL_HALT_OPCODE, NACL_HALT_OPCODE, | 21 NACL_HALT_OPCODE, NACL_HALT_OPCODE, NACL_HALT_OPCODE, NACL_HALT_OPCODE, |
22 NACL_HALT_OPCODE, NACL_HALT_OPCODE, NACL_HALT_OPCODE, NACL_HALT_OPCODE, | 22 NACL_HALT_OPCODE, NACL_HALT_OPCODE, NACL_HALT_OPCODE, NACL_HALT_OPCODE, |
23 NACL_HALT_OPCODE | 23 NACL_HALT_OPCODE |
24 }; | 24 }; |
25 | 25 |
26 Bool NaClDfaProcessValidationError(const uint8_t *begin, const uint8_t *end, | 26 Bool NaClDfaProcessValidationError(const uint8_t *begin, const uint8_t *end, |
27 uint32_t info, void *callback_data) { | 27 uint32_t info, void *callback_data) { |
28 UNREFERENCED_PARAMETER(begin); | 28 UNREFERENCED_PARAMETER(begin); |
29 UNREFERENCED_PARAMETER(end); | 29 UNREFERENCED_PARAMETER(end); |
30 UNREFERENCED_PARAMETER(info); | 30 UNREFERENCED_PARAMETER(info); |
31 UNREFERENCED_PARAMETER(callback_data); | 31 UNREFERENCED_PARAMETER(callback_data); |
32 | 32 |
33 return FALSE; | 33 return FALSE; |
34 } | 34 } |
35 | 35 |
| 36 Bool NaClDfaProcessPostRewriteValidationError(const uint8_t *begin, |
| 37 const uint8_t *end, |
| 38 uint32_t info, |
| 39 void *callback_data) { |
| 40 UNREFERENCED_PARAMETER(begin); |
| 41 UNREFERENCED_PARAMETER(end); |
| 42 UNREFERENCED_PARAMETER(callback_data); |
| 43 /* |
| 44 * Similar to NaClDfaRewriteUnsupportedInstruction(), we don't consider |
| 45 * DIRECT_JUMP_OUT_OF_RANGE as an error. But if we still get |
| 46 * CPUID_UNSUPPORTED_INSTRUCTION or UNSUPPORTED_INSTRUCTION, the validation |
| 47 * should fail, because these errors should have already been fixed |
| 48 * by NaClDfaRewriteUnsupportedInstruction(). |
| 49 */ |
| 50 return (info & VALIDATION_ERRORS_MASK) == DIRECT_JUMP_OUT_OF_RANGE; |
| 51 } |
| 52 |
| 53 #if NACL_BUILD_SUBARCH == 64 |
| 54 static Bool IsREX(uint8_t byte) { |
| 55 return byte >= 0x40 && byte <= 0x4f; |
| 56 } |
| 57 #endif |
| 58 |
| 59 static Bool NaClDfaRewriteUnsupportedInstruction(const uint8_t *begin, |
| 60 const uint8_t *end, |
| 61 uint32_t info, |
| 62 void *callback_data) { |
| 63 uint8_t *ptr = (uint8_t *) begin; |
| 64 struct StubOutCallbackData *data = callback_data; |
| 65 /* |
| 66 * Clear DIRECT_JUMP_OUT_OF_RANGE error, because it may be introduced by |
| 67 * validating a bundle which is smaller than the original chunk size. Even if |
| 68 * the orignal chunk has this error, it can be detected when validating the |
| 69 * whole chunk. |
| 70 */ |
| 71 info &= ~DIRECT_JUMP_OUT_OF_RANGE; |
| 72 if ((info & VALIDATION_ERRORS_MASK) == 0) { |
| 73 return TRUE; |
| 74 } else if ((info & VALIDATION_ERRORS_MASK) == CPUID_UNSUPPORTED_INSTRUCTION) { |
| 75 /* Stub-out instructions unsupported on this CPU, but valid on other CPUs.*/ |
| 76 data->did_rewrite = 1; |
| 77 memset((uint8_t *)begin, NACL_HALT_OPCODE, end - begin); |
| 78 return TRUE; |
| 79 } else if ((info & VALIDATION_ERRORS_MASK) != UNSUPPORTED_INSTRUCTION) { |
| 80 return FALSE; |
| 81 } |
| 82 /* |
| 83 * Instruction rewriting. Note that we only rewrite non-temporal instructions |
| 84 * found in current webstore nexes so that validation succeeds and we don't |
| 85 * break them. If future nexes use other non-temporal instructions, they will |
| 86 * fail validation. |
| 87 * |
| 88 * We usually only check and rewrite the first few bytes without examining |
| 89 * further because this function is only called when the validator tells us |
| 90 * that it is an 'unsupported instruction' and there are no other validation |
| 91 * failures. |
| 92 */ |
| 93 #if NACL_BUILD_SUBARCH == 32 |
| 94 UNREFERENCED_PARAMETER(end); |
| 95 if (memcmp(ptr, "\x0f\xe7", 2) == 0) { |
| 96 /* movntq => movq */ |
| 97 ptr[1] = 0x7f; |
| 98 data->did_rewrite = 1; |
| 99 return TRUE; |
| 100 } else if (memcmp(ptr, "\x66\x0f\xe7", 3) == 0) { |
| 101 /* movntdq => movdqa */ |
| 102 ptr[2] = 0x7f; |
| 103 data->did_rewrite = 1; |
| 104 return TRUE; |
| 105 } |
| 106 #elif NACL_BUILD_SUBARCH == 64 |
| 107 if (IsREX(ptr[0]) && ptr[1] == 0x0f) { |
| 108 uint8_t opcode_byte2 = ptr[2]; |
| 109 switch (opcode_byte2) { |
| 110 case 0x2b: |
| 111 /* movntps => movaps */ |
| 112 ptr[2] = 0x29; |
| 113 data->did_rewrite = 1; |
| 114 return TRUE; |
| 115 case 0xc3: |
| 116 /* movnti => mov, nop */ |
| 117 if (info & RESTRICTED_REGISTER_USED) { |
| 118 /* |
| 119 * The rewriting for movnti is special because it changes instruction |
| 120 * boundary: movnti is replaced by a mov and a nop so that the total |
| 121 * size does not change. Therefore, special care needs to be taken: |
| 122 * if restricted register is used in this instruction, we have to put |
| 123 * nop at the end so that the rewritten restricted register consuming |
| 124 * instruction follows closely with the restricted register producing |
| 125 * instruction (if there is one). |
| 126 */ |
| 127 ptr[1] = 0x89; |
| 128 memmove(ptr + 2, ptr + 3, end - ptr - 3); |
| 129 ptr[end - ptr - 1] = 0x90; /* NOP */ |
| 130 } else { |
| 131 /* |
| 132 * There are cases where we need to preserve instruction end position, |
| 133 * for example, when RIP-relative address is used. Fortunately, RIP- |
| 134 * relative addressing cannot use an index register, and therefore |
| 135 * RESTRICTED_REGISTER_USED cannot be set. Therefore, no matter |
| 136 * whether RIP-relative addressing is used, as long as restricted |
| 137 * register is not used, we are safe to put nop in the beginning and |
| 138 * preserve instruction end position. |
| 139 */ |
| 140 ptr[2] = 0x89; |
| 141 ptr[1] = ptr[0]; |
| 142 ptr[0] = 0x90; /* NOP */ |
| 143 } |
| 144 data->did_rewrite = 1; |
| 145 return TRUE; |
| 146 case 0x18: |
| 147 /* prefetchnta => nop...nop */ |
| 148 memset(ptr, 0x90, end - ptr); |
| 149 data->did_rewrite = 1; |
| 150 return TRUE; |
| 151 default: |
| 152 return FALSE; |
| 153 } |
| 154 } else if (ptr[0] == 0x66 && IsREX(ptr[1]) && |
| 155 memcmp(ptr + 2, "\x0f\xe7", 2) == 0) { |
| 156 /* movntdq => movdqa */ |
| 157 ptr[3] = 0x7f; |
| 158 data->did_rewrite = 1; |
| 159 return TRUE; |
| 160 } |
| 161 #else |
| 162 #error "Unknown architecture" |
| 163 #endif |
| 164 return FALSE; |
| 165 } |
| 166 |
36 Bool NaClDfaStubOutUnsupportedInstruction(const uint8_t *begin, | 167 Bool NaClDfaStubOutUnsupportedInstruction(const uint8_t *begin, |
37 const uint8_t *end, | 168 const uint8_t *end, |
38 uint32_t info, | 169 uint32_t info, |
39 void *callback_data) { | 170 void *callback_data) { |
40 struct StubOutCallbackData *data = callback_data; | 171 struct StubOutCallbackData *data = callback_data; |
41 /* Stub-out instructions unsupported on this CPU, but valid on other CPUs. */ | 172 uintptr_t temp_addr; |
42 if ((info & VALIDATION_ERRORS_MASK) == CPUID_UNSUPPORTED_INSTRUCTION) { | 173 uint8_t *bundle_begin; |
43 data->did_rewrite = 1; | 174 Bool rc; |
44 memset((uint8_t *)begin, NACL_HALT_OPCODE, end - begin); | 175 UNREFERENCED_PARAMETER(end); |
45 return TRUE; | 176 |
46 } else if ((info & VALIDATION_ERRORS_MASK) == UNSUPPORTED_INSTRUCTION) { | 177 /* |
47 if (data->flags & NACL_DISABLE_NONTEMPORALS_X86) { | 178 * We can only handle two types of errors by rewriting: |
48 return FALSE; | 179 * CPUID_UNSUPPORTED_INSTRUCTION and UNSUPPORTED_INSTRUCTION. |
49 } else { | 180 */ |
50 /* TODO(ruiq): rewrite instruction. For now, we keep the original | 181 if ((info & VALIDATION_ERRORS_MASK) != CPUID_UNSUPPORTED_INSTRUCTION && |
51 * instruction and indicate validation success, which is consistent | 182 (info & VALIDATION_ERRORS_MASK) != UNSUPPORTED_INSTRUCTION) |
52 * with current validation results. */ | 183 return FALSE; |
53 data->did_rewrite = 0; | 184 if ((info & VALIDATION_ERRORS_MASK) == UNSUPPORTED_INSTRUCTION && |
54 return TRUE; | 185 (data->flags & NACL_DISABLE_NONTEMPORALS_X86)) |
55 } | 186 return FALSE; |
56 } else { | 187 |
57 return FALSE; | 188 CHECK(!data->chunk_processed_as_a_contiguous_stream); |
58 } | 189 /* |
| 190 * Compute bundle begin. Note that the validator does not enforce the |
| 191 * validated chunk to start at a 32-byte aligned address, although it checks |
| 192 * the chunk size to be a multiple of 32 (kBundleSize). Therefore, we cannot |
| 193 * simply compute bundle_begin from "begin & ~kBundleMask", but have to |
| 194 * consider to bundle_begin_offset. Alternatively, bundle begin can be passed |
| 195 * to the user callback by the validator. However, the callback interface |
| 196 * needs to be changed for this additional parameter, because "callback_data" |
| 197 * is opaque to the validator and should not be used for this purpose. |
| 198 */ |
| 199 temp_addr = ((uintptr_t) begin & ~kBundleMask) + data->bundle_begin_offset; |
| 200 if (temp_addr <= (uintptr_t) begin) |
| 201 bundle_begin = (uint8_t *) temp_addr; |
| 202 else |
| 203 bundle_begin = (uint8_t *) (temp_addr - kBundleSize); |
| 204 |
| 205 /* |
| 206 * Rewrite the bundle containing current instruction. We want to rewrite the |
| 207 * whole bundle primarily because this eases revalidation. We should not |
| 208 * revalidate a single instruction because it may use a restricted register, |
| 209 * and is prefixed by a restricted register producing instruction in the |
| 210 * bundle. If we just revalidate this restricted register consuming |
| 211 * instruction, we will erroneously get an UNRESTRICTED_INDEX_REGISTER error. |
| 212 * On the other hand, we don't want to revalidate the whole chunk because it |
| 213 * can be expensive. Therefore, the only choice left is to revalidate the |
| 214 * current bundle. This requires us to rewrite the whole bundle first, because |
| 215 * there could be to-be-rewritten instructions after current instruction, and |
| 216 * the revalidation of current bundle would fail if we just rewrite current |
| 217 * instruction without rewriting those after. |
| 218 */ |
| 219 #if NACL_BUILD_SUBARCH == 32 |
| 220 rc = ValidateChunkIA32(bundle_begin, kBundleSize, 0 /*options*/, |
| 221 data->cpu_features, |
| 222 NaClDfaRewriteUnsupportedInstruction, |
| 223 callback_data); |
| 224 #elif NACL_BUILD_SUBARCH == 64 |
| 225 rc = ValidateChunkAMD64(bundle_begin, kBundleSize, 0 /*options*/, |
| 226 data->cpu_features, |
| 227 NaClDfaRewriteUnsupportedInstruction, |
| 228 callback_data); |
| 229 #endif |
| 230 |
| 231 if (!rc) |
| 232 return FALSE; |
| 233 |
| 234 /* Revalidate the bundle after rewriting. */ |
| 235 #if NACL_BUILD_SUBARCH == 32 |
| 236 rc = ValidateChunkIA32(bundle_begin, kBundleSize, 0 /*options*/, |
| 237 data->cpu_features, |
| 238 NaClDfaProcessPostRewriteValidationError, |
| 239 NULL); |
| 240 #elif NACL_BUILD_SUBARCH == 64 |
| 241 rc = ValidateChunkAMD64(bundle_begin, kBundleSize, 0 /*options*/, |
| 242 data->cpu_features, |
| 243 NaClDfaProcessPostRewriteValidationError, |
| 244 NULL); |
| 245 #endif |
| 246 return rc; |
59 } | 247 } |
60 | 248 |
61 Bool NaClDfaProcessCodeCopyInstruction(const uint8_t *begin_new, | 249 Bool NaClDfaProcessCodeCopyInstruction(const uint8_t *begin_new, |
62 const uint8_t *end_new, | 250 const uint8_t *end_new, |
63 uint32_t info_new, | 251 uint32_t info_new, |
64 void *callback_data) { | 252 void *callback_data) { |
65 struct CodeCopyCallbackData *data = callback_data; | 253 struct CodeCopyCallbackData *data = callback_data; |
66 size_t instruction_length = end_new - begin_new; | 254 size_t instruction_length = end_new - begin_new; |
67 | 255 |
68 /* Sanity check: instruction must be no longer than 17 bytes. */ | 256 /* Sanity check: instruction must be no longer than 17 bytes. */ |
69 CHECK(instruction_length <= MAX_INSTRUCTION_LENGTH); | 257 CHECK(instruction_length <= MAX_INSTRUCTION_LENGTH); |
70 | 258 |
71 return data->copy_func( | 259 return data->copy_func( |
72 (uint8_t *)begin_new + data->existing_minus_new, /* begin_existing */ | 260 (uint8_t *)begin_new + data->existing_minus_new, /* begin_existing */ |
73 (info_new & VALIDATION_ERRORS_MASK) == CPUID_UNSUPPORTED_INSTRUCTION ? | 261 (info_new & VALIDATION_ERRORS_MASK) == CPUID_UNSUPPORTED_INSTRUCTION ? |
74 (uint8_t *)kStubOutMem : | 262 (uint8_t *)kStubOutMem : |
75 (uint8_t *)begin_new, | 263 (uint8_t *)begin_new, |
76 (uint8_t)instruction_length); | 264 (uint8_t)instruction_length); |
77 } | 265 } |
78 | 266 |
79 Bool NaClDfaCodeReplacementIsStubouted(const uint8_t *begin_existing, | 267 Bool NaClDfaCodeReplacementIsStubouted(const uint8_t *begin_existing, |
80 size_t instruction_length) { | 268 size_t instruction_length) { |
81 | 269 |
82 /* Unsupported instruction must have been replaced with HLTs. */ | 270 /* Unsupported instruction must have been replaced with HLTs. */ |
83 if (memcmp(kStubOutMem, begin_existing, instruction_length) == 0) | 271 if (memcmp(kStubOutMem, begin_existing, instruction_length) == 0) |
84 return TRUE; | 272 return TRUE; |
85 else | 273 else |
86 return FALSE; | 274 return FALSE; |
87 } | 275 } |
OLD | NEW |