| Index: src/trusted/validator_ragel/dfa_validate_common.c
|
| diff --git a/src/trusted/validator_ragel/dfa_validate_common.c b/src/trusted/validator_ragel/dfa_validate_common.c
|
| index 4972cbd654b3fcd0d884dec0099db63750991802..4d489808e4de8eb278c1bdabde51246a32813399 100644
|
| --- a/src/trusted/validator_ragel/dfa_validate_common.c
|
| +++ b/src/trusted/validator_ragel/dfa_validate_common.c
|
| @@ -11,7 +11,7 @@
|
|
|
| #include "native_client/src/shared/platform/nacl_check.h"
|
| #include "native_client/src/trusted/service_runtime/nacl_config.h"
|
| -#include "native_client/src/trusted/validator_ragel/validator.h"
|
| +#include "native_client/src/include/build_config.h"
|
|
|
| /* Used as an argument to copy_func when unsupported instruction must be
|
| replaced with HLTs. */
|
| @@ -33,29 +33,217 @@ Bool NaClDfaProcessValidationError(const uint8_t *begin, const uint8_t *end,
|
| return FALSE;
|
| }
|
|
|
| -Bool NaClDfaStubOutUnsupportedInstruction(const uint8_t *begin,
|
| - const uint8_t *end,
|
| - uint32_t info,
|
| - void *callback_data) {
|
| +Bool NaClDfaProcessPostRewriteValidationError(const uint8_t *begin,
|
| + const uint8_t *end,
|
| + uint32_t info,
|
| + void *callback_data) {
|
| + UNREFERENCED_PARAMETER(begin);
|
| + UNREFERENCED_PARAMETER(end);
|
| + UNREFERENCED_PARAMETER(callback_data);
|
| + /*
|
| + * Similar to NaClDfaRewriteUnsupportedInstruction(), we don't consider
|
| + * DIRECT_JUMP_OUT_OF_RANGE as an error. But if we still get
|
| + * CPUID_UNSUPPORTED_INSTRUCTION or UNSUPPORTED_INSTRUCTION, the validation
|
| + * should fail, because these errors should have already been fixed
|
| + * by NaClDfaRewriteUnsupportedInstruction().
|
| + */
|
| + return (info & VALIDATION_ERRORS_MASK) == DIRECT_JUMP_OUT_OF_RANGE;
|
| +}
|
| +
|
| +#if NACL_BUILD_SUBARCH == 64
|
| +static Bool IsREX(uint8_t byte) {
|
| + return byte >= 0x40 && byte <= 0x4f;
|
| +}
|
| +#endif
|
| +
|
| +static Bool NaClDfaRewriteUnsupportedInstruction(const uint8_t *begin,
|
| + const uint8_t *end,
|
| + uint32_t info,
|
| + void *callback_data) {
|
| + uint8_t *ptr = (uint8_t *) begin;
|
| struct StubOutCallbackData *data = callback_data;
|
| - /* Stub-out instructions unsupported on this CPU, but valid on other CPUs. */
|
| - if ((info & VALIDATION_ERRORS_MASK) == CPUID_UNSUPPORTED_INSTRUCTION) {
|
| + /*
|
| + * Clear DIRECT_JUMP_OUT_OF_RANGE error, because it may be introduced by
|
| + * validating a bundle which is smaller than the original chunk size. Even if
|
| + * the orignal chunk has this error, it can be detected when validating the
|
| + * whole chunk.
|
| + */
|
| + info &= ~DIRECT_JUMP_OUT_OF_RANGE;
|
| + if ((info & VALIDATION_ERRORS_MASK) == 0) {
|
| + return TRUE;
|
| + } else if ((info & VALIDATION_ERRORS_MASK) == CPUID_UNSUPPORTED_INSTRUCTION) {
|
| + /* Stub-out instructions unsupported on this CPU, but valid on other CPUs.*/
|
| data->did_rewrite = 1;
|
| memset((uint8_t *)begin, NACL_HALT_OPCODE, end - begin);
|
| return TRUE;
|
| - } else if ((info & VALIDATION_ERRORS_MASK) == UNSUPPORTED_INSTRUCTION) {
|
| - if (data->flags & NACL_DISABLE_NONTEMPORALS_X86) {
|
| - return FALSE;
|
| - } else {
|
| - /* TODO(ruiq): rewrite instruction. For now, we keep the original
|
| - * instruction and indicate validation success, which is consistent
|
| - * with current validation results. */
|
| - data->did_rewrite = 0;
|
| - return TRUE;
|
| - }
|
| - } else {
|
| + } else if ((info & VALIDATION_ERRORS_MASK) != UNSUPPORTED_INSTRUCTION) {
|
| return FALSE;
|
| }
|
| + /*
|
| + * Instruction rewriting. Note that we only rewrite non-temporal instructions
|
| + * found in current webstore nexes so that validation succeeds and we don't
|
| + * break them. If future nexes use other non-temporal instructions, they will
|
| + * fail validation.
|
| + *
|
| + * We usually only check and rewrite the first few bytes without examining
|
| + * further because this function is only called when the validator tells us
|
| + * that it is an 'unsupported instruction' and there are no other validation
|
| + * failures.
|
| + */
|
| +#if NACL_BUILD_SUBARCH == 32
|
| + UNREFERENCED_PARAMETER(end);
|
| + if (memcmp(ptr, "\x0f\xe7", 2) == 0) {
|
| + /* movntq => movq */
|
| + ptr[1] = 0x7f;
|
| + data->did_rewrite = 1;
|
| + return TRUE;
|
| + } else if (memcmp(ptr, "\x66\x0f\xe7", 3) == 0) {
|
| + /* movntdq => movdqa */
|
| + ptr[2] = 0x7f;
|
| + data->did_rewrite = 1;
|
| + return TRUE;
|
| + }
|
| +#elif NACL_BUILD_SUBARCH == 64
|
| + if (IsREX(ptr[0]) && ptr[1] == 0x0f) {
|
| + uint8_t opcode_byte2 = ptr[2];
|
| + switch (opcode_byte2) {
|
| + case 0x2b:
|
| + /* movntps => movaps */
|
| + ptr[2] = 0x29;
|
| + data->did_rewrite = 1;
|
| + return TRUE;
|
| + case 0xc3:
|
| + /* movnti => mov, nop */
|
| + if (info & RESTRICTED_REGISTER_USED) {
|
| + /*
|
| + * The rewriting for movnti is special because it changes instruction
|
| + * boundary: movnti is replaced by a mov and a nop so that the total
|
| + * size does not change. Therefore, special care needs to be taken:
|
| + * if restricted register is used in this instruction, we have to put
|
| + * nop at the end so that the rewritten restricted register consuming
|
| + * instruction follows closely with the restricted register producing
|
| + * instruction (if there is one).
|
| + */
|
| + ptr[1] = 0x89;
|
| + memmove(ptr + 2, ptr + 3, end - ptr - 3);
|
| + ptr[end - ptr - 1] = 0x90; /* NOP */
|
| + } else {
|
| + /*
|
| + * There are cases where we need to preserve instruction end position,
|
| + * for example, when RIP-relative address is used. Fortunately, RIP-
|
| + * relative addressing cannot use an index register, and therefore
|
| + * RESTRICTED_REGISTER_USED cannot be set. Therefore, no matter
|
| + * whether RIP-relative addressing is used, as long as restricted
|
| + * register is not used, we are safe to put nop in the beginning and
|
| + * preserve instruction end position.
|
| + */
|
| + ptr[2] = 0x89;
|
| + ptr[1] = ptr[0];
|
| + ptr[0] = 0x90; /* NOP */
|
| + }
|
| + data->did_rewrite = 1;
|
| + return TRUE;
|
| + case 0x18:
|
| + /* prefetchnta => nop...nop */
|
| + memset(ptr, 0x90, end - ptr);
|
| + data->did_rewrite = 1;
|
| + return TRUE;
|
| + default:
|
| + return FALSE;
|
| + }
|
| + } else if (ptr[0] == 0x66 && IsREX(ptr[1]) &&
|
| + memcmp(ptr + 2, "\x0f\xe7", 2) == 0) {
|
| + /* movntdq => movdqa */
|
| + ptr[3] = 0x7f;
|
| + data->did_rewrite = 1;
|
| + return TRUE;
|
| + }
|
| +#else
|
| + #error "Unknown architecture"
|
| +#endif
|
| + return FALSE;
|
| +}
|
| +
|
| +Bool NaClDfaStubOutUnsupportedInstruction(const uint8_t *begin,
|
| + const uint8_t *end,
|
| + uint32_t info,
|
| + void *callback_data) {
|
| + struct StubOutCallbackData *data = callback_data;
|
| + uintptr_t temp_addr;
|
| + uint8_t *bundle_begin;
|
| + Bool rc;
|
| + UNREFERENCED_PARAMETER(end);
|
| +
|
| + /*
|
| + * We can only handle two types of errors by rewriting:
|
| + * CPUID_UNSUPPORTED_INSTRUCTION and UNSUPPORTED_INSTRUCTION.
|
| + */
|
| + if ((info & VALIDATION_ERRORS_MASK) != CPUID_UNSUPPORTED_INSTRUCTION &&
|
| + (info & VALIDATION_ERRORS_MASK) != UNSUPPORTED_INSTRUCTION)
|
| + return FALSE;
|
| + if ((info & VALIDATION_ERRORS_MASK) == UNSUPPORTED_INSTRUCTION &&
|
| + (data->flags & NACL_DISABLE_NONTEMPORALS_X86))
|
| + return FALSE;
|
| +
|
| + CHECK(!data->chunk_processed_as_a_contiguous_stream);
|
| + /*
|
| + * Compute bundle begin. Note that the validator does not enforce the
|
| + * validated chunk to start at a 32-byte aligned address, although it checks
|
| + * the chunk size to be a multiple of 32 (kBundleSize). Therefore, we cannot
|
| + * simply compute bundle_begin from "begin & ~kBundleMask", but have to
|
| + * consider to bundle_begin_offset. Alternatively, bundle begin can be passed
|
| + * to the user callback by the validator. However, the callback interface
|
| + * needs to be changed for this additional parameter, because "callback_data"
|
| + * is opaque to the validator and should not be used for this purpose.
|
| + */
|
| + temp_addr = ((uintptr_t) begin & ~kBundleMask) + data->bundle_begin_offset;
|
| + if (temp_addr <= (uintptr_t) begin)
|
| + bundle_begin = (uint8_t *) temp_addr;
|
| + else
|
| + bundle_begin = (uint8_t *) (temp_addr - kBundleSize);
|
| +
|
| + /*
|
| + * Rewrite the bundle containing current instruction. We want to rewrite the
|
| + * whole bundle primarily because this eases revalidation. We should not
|
| + * revalidate a single instruction because it may use a restricted register,
|
| + * and is prefixed by a restricted register producing instruction in the
|
| + * bundle. If we just revalidate this restricted register consuming
|
| + * instruction, we will erroneously get an UNRESTRICTED_INDEX_REGISTER error.
|
| + * On the other hand, we don't want to revalidate the whole chunk because it
|
| + * can be expensive. Therefore, the only choice left is to revalidate the
|
| + * current bundle. This requires us to rewrite the whole bundle first, because
|
| + * there could be to-be-rewritten instructions after current instruction, and
|
| + * the revalidation of current bundle would fail if we just rewrite current
|
| + * instruction without rewriting those after.
|
| + */
|
| +#if NACL_BUILD_SUBARCH == 32
|
| + rc = ValidateChunkIA32(bundle_begin, kBundleSize, 0 /*options*/,
|
| + data->cpu_features,
|
| + NaClDfaRewriteUnsupportedInstruction,
|
| + callback_data);
|
| +#elif NACL_BUILD_SUBARCH == 64
|
| + rc = ValidateChunkAMD64(bundle_begin, kBundleSize, 0 /*options*/,
|
| + data->cpu_features,
|
| + NaClDfaRewriteUnsupportedInstruction,
|
| + callback_data);
|
| +#endif
|
| +
|
| + if (!rc)
|
| + return FALSE;
|
| +
|
| + /* Revalidate the bundle after rewriting. */
|
| +#if NACL_BUILD_SUBARCH == 32
|
| + rc = ValidateChunkIA32(bundle_begin, kBundleSize, 0 /*options*/,
|
| + data->cpu_features,
|
| + NaClDfaProcessPostRewriteValidationError,
|
| + NULL);
|
| +#elif NACL_BUILD_SUBARCH == 64
|
| + rc = ValidateChunkAMD64(bundle_begin, kBundleSize, 0 /*options*/,
|
| + data->cpu_features,
|
| + NaClDfaProcessPostRewriteValidationError,
|
| + NULL);
|
| +#endif
|
| + return rc;
|
| }
|
|
|
| Bool NaClDfaProcessCodeCopyInstruction(const uint8_t *begin_new,
|
|
|