blob: ec77cad4fa59e9c739c848ed86f82237649dd25b [file] [log] [blame]
/*
* Copyright (c) 2012 The Native Client Authors. All rights reserved.
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
/* Implement the functions common for ia32 and x86-64 architectures. */
#include "native_client/src/trusted/validator_ragel/dfa_validate_common.h"
#include <string.h>
#include "native_client/src/include/build_config.h"
#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"
/* Used as an argument to copy_func when unsupported instruction must be
replaced with HLTs. */
static const uint8_t kStubOutMem[MAX_INSTRUCTION_LENGTH] = {
NACL_HALT_OPCODE, NACL_HALT_OPCODE, NACL_HALT_OPCODE, NACL_HALT_OPCODE,
NACL_HALT_OPCODE, NACL_HALT_OPCODE, NACL_HALT_OPCODE, NACL_HALT_OPCODE,
NACL_HALT_OPCODE, NACL_HALT_OPCODE, NACL_HALT_OPCODE, NACL_HALT_OPCODE,
NACL_HALT_OPCODE, NACL_HALT_OPCODE, NACL_HALT_OPCODE, NACL_HALT_OPCODE,
NACL_HALT_OPCODE
};
Bool NaClDfaProcessValidationError(const uint8_t *begin, const uint8_t *end,
uint32_t info, void *callback_data) {
UNREFERENCED_PARAMETER(begin);
UNREFERENCED_PARAMETER(end);
UNREFERENCED_PARAMETER(info);
UNREFERENCED_PARAMETER(callback_data);
return FALSE;
}
/*
* Returns whether a validation error should be ignored by
* RewriteAndRevalidateBundle()'s two validation passes, which
* validate individual instruction bundles.
*/
static Bool AllowErrorDuringBundleValidation(uint32_t info) {
if ((info & VALIDATION_ERRORS_MASK) == DIRECT_JUMP_OUT_OF_RANGE) {
/*
* This error can occur on valid jumps because we are validating
* an instruction bundle that is a subset of a code chunk.
*/
return TRUE;
}
return FALSE;
}
#if NACL_BUILD_SUBARCH == 64
static Bool IsREXPrefix(uint8_t byte) {
return byte >= 0x40 && byte <= 0x4f;
}
#endif
static Bool RewriteNonTemporal(uint8_t *ptr, uint8_t *end, uint32_t info) {
/*
* Instruction rewriting. Note that we only rewrite non-temporal
* instructions found in nexes and DSOs that are currently found in the
* Chrome Web Store. 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.
*/
ptrdiff_t size = end - ptr;
if (size >= 2 && memcmp(ptr, "\x0f\x18", 2) == 0) {
/* prefetchnta => nop...nop */
memset(ptr, 0x90, size);
return TRUE;
}
#if NACL_BUILD_SUBARCH == 32
UNREFERENCED_PARAMETER(info);
if (size >= 2 && memcmp(ptr, "\x0f\xe7", 2) == 0) {
/* movntq => movq */
ptr[1] = 0x7f;
return TRUE;
} else if (size >= 2 && memcmp(ptr, "\x0f\x2b", 2) == 0) {
/* movntps => movaps */
ptr[1] = 0x29;
return TRUE;
} else if (size >= 3 && memcmp(ptr, "\x66\x0f\xe7", 3) == 0) {
/* movntdq => movdqa */
ptr[2] = 0x7f;
return TRUE;
}
#elif NACL_BUILD_SUBARCH == 64
if (size >= 3 && IsREXPrefix(ptr[0]) && ptr[1] == 0x0f) {
uint8_t opcode_byte2 = ptr[2];
switch (opcode_byte2) {
case 0xe7:
/* movntq => movq */
ptr[2] = 0x7f;
return TRUE;
case 0x2b:
/* movntps => movaps */
ptr[2] = 0x29;
return TRUE;
case 0xc3:
/* movnti => mov, nop */
if ((info & RESTRICTED_REGISTER_USED) != 0) {
/*
* 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, size - 3);
ptr[size - 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 */
}
return TRUE;
case 0x18:
/* prefetchnta => nop...nop */
memset(ptr, 0x90, size);
return TRUE;
default:
return FALSE;
}
} else if (size >= 4 &&
ptr[0] == 0x66 &&
IsREXPrefix(ptr[1]) &&
memcmp(ptr + 2, "\x0f\xe7", 2) == 0) {
/* movntdq => movdqa */
ptr[3] = 0x7f;
return TRUE;
}
#else
# error "Unknown architecture"
#endif
return FALSE;
}
/*
* First pass of RewriteAndRevalidateBundle(): Rewrite any
* instructions that need rewriting.
*/
static Bool BundleValidationApplyRewrite(const uint8_t *begin,
const uint8_t *end,
uint32_t info,
void *callback_data) {
struct StubOutCallbackData *data = callback_data;
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;
}
if ((info & VALIDATION_ERRORS_MASK) == UNSUPPORTED_INSTRUCTION &&
(data->flags & NACL_DISABLE_NONTEMPORALS_X86) == 0) {
if (RewriteNonTemporal((uint8_t *) begin, (uint8_t *) end, info)) {
data->did_rewrite = 1;
return TRUE;
}
return FALSE;
}
return AllowErrorDuringBundleValidation(info);
}
/*
* Second pass of RewriteAndRevalidateBundle(): Revalidate, checking
* that no further instruction rewrites are needed.
*/
static Bool BundleValidationCheckAfterRewrite(const uint8_t *begin,
const uint8_t *end,
uint32_t info,
void *callback_data) {
UNREFERENCED_PARAMETER(begin);
UNREFERENCED_PARAMETER(end);
UNREFERENCED_PARAMETER(callback_data);
return AllowErrorDuringBundleValidation(info);
}
/*
* As an extra safety check, when the validator modifies an
* instruction, we want to revalidate the rewritten code.
*
* For performance, we don't want to revalidate the whole code chunk,
* because that would double the overall validation time. However,
* it's not practical to revalidate the individual rewritten
* instruction, because for x86-64 we need to account for previous
* instructions that can truncate registers to 32 bits (otherwise we'd
* get an UNRESTRICTED_INDEX_REGISTER error).
*
* Therefore, we revalidate the whole bundle instead. This means we
* must apply all rewrites to the bundle first.
*
* So, when we hit an instruction that needs rewriting, we recursively
* invoke the validator two times on the instruction bundle:
* * First pass: Rewrite all instructions within the bundle that need
* rewriting.
* * Second pass: Ensure that the rewritten bundle passes the
* validator, with no instructions requiring rewrites.
*/
static Bool RewriteAndRevalidateBundle(const uint8_t *instruction_begin,
const uint8_t *instruction_end,
struct StubOutCallbackData *data) {
uint8_t *bundle_begin;
/* Find the start of the bundle that this instruction is part of. */
CHECK(instruction_begin >= data->chunk_begin);
bundle_begin = data->chunk_begin + ((instruction_begin - data->chunk_begin)
& ~kBundleMask);
CHECK(instruction_end <= bundle_begin + kBundleSize);
CHECK(bundle_begin + kBundleSize <= data->chunk_end);
/* First pass: Rewrite instructions within bundle that need rewriting. */
if (!data->validate_chunk_func(bundle_begin, kBundleSize, 0 /*options*/,
data->cpu_features,
BundleValidationApplyRewrite,
data)) {
return FALSE;
}
/* Second pass: Revalidate the bundle. */
return data->validate_chunk_func(bundle_begin, kBundleSize, 0 /*options*/,
data->cpu_features,
BundleValidationCheckAfterRewrite,
data);
}
Bool NaClDfaStubOutUnsupportedInstruction(const uint8_t *begin,
const uint8_t *end,
uint32_t info,
void *callback_data) {
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) {
return RewriteAndRevalidateBundle(begin, end, data);
}
if ((info & VALIDATION_ERRORS_MASK) == UNSUPPORTED_INSTRUCTION &&
(data->flags & NACL_DISABLE_NONTEMPORALS_X86) == 0) {
return RewriteAndRevalidateBundle(begin, end, data);
}
return FALSE;
}
Bool NaClDfaProcessCodeCopyInstruction(const uint8_t *begin_new,
const uint8_t *end_new,
uint32_t info_new,
void *callback_data) {
struct CodeCopyCallbackData *data = callback_data;
size_t instruction_length = end_new - begin_new;
/* Sanity check: instruction must be no longer than 17 bytes. */
CHECK(instruction_length <= MAX_INSTRUCTION_LENGTH);
return data->copy_func(
(uint8_t *)begin_new + data->existing_minus_new, /* begin_existing */
(info_new & VALIDATION_ERRORS_MASK) == CPUID_UNSUPPORTED_INSTRUCTION ?
(uint8_t *)kStubOutMem :
(uint8_t *)begin_new,
(uint8_t)instruction_length);
}
Bool NaClDfaCodeReplacementIsStubouted(const uint8_t *begin_existing,
size_t instruction_length) {
/* Unsupported instruction must have been replaced with HLTs. */
if (memcmp(kStubOutMem, begin_existing, instruction_length) == 0)
return TRUE;
else
return FALSE;
}