Commit 73f88b5f authored by Georgia Kouveli's avatar Georgia Kouveli Committed by Commit Bot

Reland "[arm64] Protect return addresses stored on stack"

This is a reland of 137bfe47

Original change's description:
> [arm64] Protect return addresses stored on stack
> 
> This change uses the Arm v8.3 pointer authentication instructions in
> order to protect return addresses stored on the stack.  The generated
> code signs the return address before storing on the stack and
> authenticates it after loading it. This also changes the stack frame
> iterator in order to authenticate stored return addresses and re-sign
> them when needed, as well as the deoptimizer in order to sign saved
> return addresses when creating new frames. This offers a level of
> protection against ROP attacks.
> 
> This functionality is enabled with the v8_control_flow_integrity flag
> that this CL introduces.
> 
> The code size effect of this change is small for Octane (up to 2% in
> some cases but mostly much lower) and negligible for larger benchmarks,
> however code size measurements are rather noisy. The performance impact
> on current cores (where the instructions are NOPs) is single digit,
> around 1-2% for ARES-6 and Octane, and tends to be smaller for big
> cores than for little cores.
> 
> Bug: v8:10026
> Change-Id: I0081f3938c56e2f24d8227e4640032749f4f8368
> Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1373782
> Commit-Queue: Georgia Kouveli <georgia.kouveli@arm.com>
> Reviewed-by: Ross McIlroy <rmcilroy@chromium.org>
> Reviewed-by: Georg Neis <neis@chromium.org>
> Cr-Commit-Position: refs/heads/master@{#66239}

Bug: v8:10026
Change-Id: Id1adfa2e6c713f6977d69aa467986e48fe67b3c2
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2051958Reviewed-by: 's avatarGeorg Neis <neis@chromium.org>
Reviewed-by: 's avatarRoss McIlroy <rmcilroy@chromium.org>
Commit-Queue: Georgia Kouveli <georgia.kouveli@arm.com>
Cr-Commit-Position: refs/heads/master@{#66254}
parent a046c5fc
......@@ -214,6 +214,10 @@ declare_args() {
# Disable all snapshot compression.
v8_enable_snapshot_compression = true
# Enable control-flow integrity features, such as pointer authentication for
# ARM64.
v8_control_flow_integrity = false
}
# Derived defaults.
......@@ -273,6 +277,9 @@ assert(!v8_disable_write_barriers || v8_enable_single_generation,
assert(v8_current_cpu != "x86" || !v8_untrusted_code_mitigations,
"Untrusted code mitigations are unsupported on ia32")
assert(v8_current_cpu == "arm64" || !v8_control_flow_integrity,
"Control-flow integrity is only supported on arm64")
assert(
!v8_enable_pointer_compression || !v8_enable_shared_ro_heap,
"Pointer compression is not supported with shared read-only heap enabled")
......@@ -507,6 +514,9 @@ config("features") {
if (v8_enable_snapshot_compression) {
defines += [ "V8_SNAPSHOT_COMPRESSION" ]
}
if (v8_control_flow_integrity) {
defines += [ "V8_ENABLE_CONTROL_FLOW_INTEGRITY" ]
}
}
config("toolchain") {
......@@ -549,6 +559,12 @@ config("toolchain") {
}
if (v8_current_cpu == "arm64") {
defines += [ "V8_TARGET_ARCH_ARM64" ]
if (v8_control_flow_integrity) {
# TODO(v8:10026): Enable this in src/build.
if (current_cpu == "arm64") {
cflags += [ "-mbranch-protection=pac-ret" ]
}
}
}
# Mips64el/mipsel simulators.
......@@ -2243,6 +2259,7 @@ v8_source_set("v8_base_without_compiler") {
"src/execution/microtask-queue.h",
"src/execution/off-thread-isolate.cc",
"src/execution/off-thread-isolate.h",
"src/execution/pointer-authentication.h",
"src/execution/protectors-inl.h",
"src/execution/protectors.cc",
"src/execution/protectors.h",
......@@ -3024,6 +3041,10 @@ v8_source_set("v8_base_without_compiler") {
"src/zone/zone.h",
]
if (!v8_control_flow_integrity) {
sources += [ "src/execution/pointer-authentication-dummy.h" ]
}
if (v8_enable_third_party_heap) {
sources += v8_third_party_heap_files
}
......@@ -3181,6 +3202,9 @@ v8_source_set("v8_base_without_compiler") {
"src/regexp/arm64/regexp-macro-assembler-arm64.h",
"src/wasm/baseline/arm64/liftoff-assembler-arm64.h",
]
if (v8_control_flow_integrity) {
sources += [ "src/execution/arm64/pointer-authentication-arm64.h" ]
}
if (is_win) {
sources += [
"src/diagnostics/unwinding-info-win64.cc",
......
......@@ -1187,7 +1187,7 @@ void Builtins::Generate_InterpreterEntryTrampoline(MacroAssembler* masm) {
// the frame (that is done below).
__ Bind(&push_stack_frame);
FrameScope frame_scope(masm, StackFrame::MANUAL);
__ Push(lr, fp, cp, closure);
__ Push<TurboAssembler::kSignLR>(lr, fp, cp, closure);
__ Add(fp, sp, StandardFrameConstants::kFixedFrameSizeFromFp);
// Reset code age.
......@@ -1672,7 +1672,7 @@ void Generate_ContinueToBuiltinHelper(MacroAssembler* masm,
// Restore fp, lr.
__ Mov(sp, fp);
__ Pop(fp, lr);
__ Pop<TurboAssembler::kAuthLR>(fp, lr);
__ LoadEntryFromBuiltinIndex(builtin);
__ Jump(builtin);
......@@ -2053,7 +2053,7 @@ void Builtins::Generate_ReflectConstruct(MacroAssembler* masm) {
namespace {
void EnterArgumentsAdaptorFrame(MacroAssembler* masm) {
__ Push(lr, fp);
__ Push<TurboAssembler::kSignLR>(lr, fp);
__ Mov(x11, StackFrame::TypeToMarker(StackFrame::ARGUMENTS_ADAPTOR));
__ Push(x11, x1); // x1: function
__ SmiTag(x11, x0); // x0: number of arguments.
......@@ -2069,7 +2069,7 @@ void LeaveArgumentsAdaptorFrame(MacroAssembler* masm) {
// then drop the parameters and the receiver.
__ Ldr(x10, MemOperand(fp, ArgumentsAdaptorFrameConstants::kLengthOffset));
__ Mov(sp, fp);
__ Pop(fp, lr);
__ Pop<TurboAssembler::kAuthLR>(fp, lr);
// Drop actual parameters and receiver.
__ SmiUntag(x10);
......@@ -3675,9 +3675,9 @@ void Builtins::Generate_DirectCEntry(MacroAssembler* masm) {
// DirectCEntry places the return address on the stack (updated by the GC),
// making the call GC safe. The irregexp backend relies on this.
__ Poke(lr, 0); // Store the return address.
__ Poke<TurboAssembler::kSignLR>(lr, 0); // Store the return address.
__ Blr(x10); // Call the C++ function.
__ Peek(lr, 0); // Return to calling code.
__ Peek<TurboAssembler::kAuthLR>(lr, 0); // Return to calling code.
__ AssertFPCRState();
__ Ret();
}
......
......@@ -1075,6 +1075,166 @@ void MacroAssembler::JumpIfNotSmi(Register value, Label* not_smi_label) {
void TurboAssembler::jmp(Label* L) { B(L); }
template <TurboAssembler::StoreLRMode lr_mode>
void TurboAssembler::Push(const CPURegister& src0, const CPURegister& src1,
const CPURegister& src2, const CPURegister& src3) {
DCHECK(AreSameSizeAndType(src0, src1, src2, src3));
DCHECK_IMPLIES((lr_mode == kSignLR), ((src0 == lr) || (src1 == lr) ||
(src2 == lr) || (src3 == lr)));
DCHECK_IMPLIES((lr_mode == kDontStoreLR), ((src0 != lr) && (src1 != lr) &&
(src2 != lr) && (src3 != lr)));
#ifdef V8_ENABLE_CONTROL_FLOW_INTEGRITY
if (lr_mode == kSignLR) {
Paciasp();
}
#endif
int count = 1 + src1.is_valid() + src2.is_valid() + src3.is_valid();
int size = src0.SizeInBytes();
DCHECK_EQ(0, (size * count) % 16);
PushHelper(count, size, src0, src1, src2, src3);
}
template <TurboAssembler::StoreLRMode lr_mode>
void TurboAssembler::Push(const Register& src0, const VRegister& src1) {
DCHECK_IMPLIES((lr_mode == kSignLR), ((src0 == lr) || (src1 == lr)));
DCHECK_IMPLIES((lr_mode == kDontStoreLR), ((src0 != lr) && (src1 != lr)));
#ifdef V8_ENABLE_CONTROL_FLOW_INTEGRITY
if (lr_mode == kSignLR) {
Paciasp();
}
#endif
int size = src0.SizeInBytes() + src1.SizeInBytes();
DCHECK_EQ(0, size % 16);
// Reserve room for src0 and push src1.
str(src1, MemOperand(sp, -size, PreIndex));
// Fill the gap with src0.
str(src0, MemOperand(sp, src1.SizeInBytes()));
}
template <TurboAssembler::LoadLRMode lr_mode>
void TurboAssembler::Pop(const CPURegister& dst0, const CPURegister& dst1,
const CPURegister& dst2, const CPURegister& dst3) {
// It is not valid to pop into the same register more than once in one
// instruction, not even into the zero register.
DCHECK(!AreAliased(dst0, dst1, dst2, dst3));
DCHECK(AreSameSizeAndType(dst0, dst1, dst2, dst3));
DCHECK(dst0.is_valid());
int count = 1 + dst1.is_valid() + dst2.is_valid() + dst3.is_valid();
int size = dst0.SizeInBytes();
DCHECK_EQ(0, (size * count) % 16);
PopHelper(count, size, dst0, dst1, dst2, dst3);
DCHECK_IMPLIES((lr_mode == kAuthLR), ((dst0 == lr) || (dst1 == lr) ||
(dst2 == lr) || (dst3 == lr)));
DCHECK_IMPLIES((lr_mode == kDontLoadLR), ((dst0 != lr) && (dst1 != lr)) &&
(dst2 != lr) && (dst3 != lr));
#ifdef V8_ENABLE_CONTROL_FLOW_INTEGRITY
if (lr_mode == kAuthLR) {
Autiasp();
}
#endif
}
template <TurboAssembler::StoreLRMode lr_mode>
void TurboAssembler::Poke(const CPURegister& src, const Operand& offset) {
DCHECK_IMPLIES((lr_mode == kSignLR), (src == lr));
DCHECK_IMPLIES((lr_mode == kDontStoreLR), (src != lr));
#ifdef V8_ENABLE_CONTROL_FLOW_INTEGRITY
if (lr_mode == kSignLR) {
Paciasp();
}
#endif
if (offset.IsImmediate()) {
DCHECK_GE(offset.ImmediateValue(), 0);
} else if (emit_debug_code()) {
Cmp(xzr, offset);
Check(le, AbortReason::kStackAccessBelowStackPointer);
}
Str(src, MemOperand(sp, offset));
}
template <TurboAssembler::LoadLRMode lr_mode>
void TurboAssembler::Peek(const CPURegister& dst, const Operand& offset) {
if (offset.IsImmediate()) {
DCHECK_GE(offset.ImmediateValue(), 0);
} else if (emit_debug_code()) {
Cmp(xzr, offset);
Check(le, AbortReason::kStackAccessBelowStackPointer);
}
Ldr(dst, MemOperand(sp, offset));
DCHECK_IMPLIES((lr_mode == kAuthLR), (dst == lr));
DCHECK_IMPLIES((lr_mode == kDontLoadLR), (dst != lr));
#ifdef V8_ENABLE_CONTROL_FLOW_INTEGRITY
if (lr_mode == kAuthLR) {
Autiasp();
}
#endif
}
template <TurboAssembler::StoreLRMode lr_mode>
void TurboAssembler::PushCPURegList(CPURegList registers) {
DCHECK_IMPLIES((lr_mode == kDontStoreLR), !registers.IncludesAliasOf(lr));
#ifdef V8_ENABLE_CONTROL_FLOW_INTEGRITY
if (lr_mode == kSignLR && registers.IncludesAliasOf(lr)) {
Paciasp();
}
#endif
int size = registers.RegisterSizeInBytes();
DCHECK_EQ(0, (size * registers.Count()) % 16);
// Push up to four registers at a time.
while (!registers.IsEmpty()) {
int count_before = registers.Count();
const CPURegister& src0 = registers.PopHighestIndex();
const CPURegister& src1 = registers.PopHighestIndex();
const CPURegister& src2 = registers.PopHighestIndex();
const CPURegister& src3 = registers.PopHighestIndex();
int count = count_before - registers.Count();
PushHelper(count, size, src0, src1, src2, src3);
}
}
template <TurboAssembler::LoadLRMode lr_mode>
void TurboAssembler::PopCPURegList(CPURegList registers) {
int size = registers.RegisterSizeInBytes();
DCHECK_EQ(0, (size * registers.Count()) % 16);
#ifdef V8_ENABLE_CONTROL_FLOW_INTEGRITY
bool contains_lr = registers.IncludesAliasOf(lr);
DCHECK_IMPLIES((lr_mode == kDontLoadLR), !contains_lr);
#endif
// Pop up to four registers at a time.
while (!registers.IsEmpty()) {
int count_before = registers.Count();
const CPURegister& dst0 = registers.PopLowestIndex();
const CPURegister& dst1 = registers.PopLowestIndex();
const CPURegister& dst2 = registers.PopLowestIndex();
const CPURegister& dst3 = registers.PopLowestIndex();
int count = count_before - registers.Count();
PopHelper(count, size, dst0, dst1, dst2, dst3);
}
#ifdef V8_ENABLE_CONTROL_FLOW_INTEGRITY
if (lr_mode == kAuthLR && contains_lr) {
Autiasp();
}
#endif
}
void TurboAssembler::Push(Handle<HeapObject> handle) {
UseScratchRegisterScope temps(this);
Register tmp = temps.AcquireX();
......
This diff is collapsed.
......@@ -783,20 +783,33 @@ class V8_EXPORT_PRIVATE TurboAssembler : public TurboAssemblerBase {
// The stack pointer must be aligned to 16 bytes on entry and the total size
// of the specified registers must also be a multiple of 16 bytes.
//
// Other than the registers passed into Pop, the stack pointer and (possibly)
// the system stack pointer, these methods do not modify any other registers.
// Other than the registers passed into Pop, the stack pointer, (possibly)
// the system stack pointer and (possibly) the link register, these methods
// do not modify any other registers.
//
// Some of the methods take an optional LoadLRMode or StoreLRMode template
// argument, which specifies whether we need to sign the link register at the
// start of the operation, or authenticate it at the end of the operation,
// when control flow integrity measures are enabled.
// When the mode is kDontLoadLR or kDontStoreLR, LR must not be passed as an
// argument to the operation.
enum LoadLRMode { kAuthLR, kDontAuthLR, kDontLoadLR };
enum StoreLRMode { kSignLR, kDontSignLR, kDontStoreLR };
template <StoreLRMode lr_mode = kDontStoreLR>
void Push(const CPURegister& src0, const CPURegister& src1 = NoReg,
const CPURegister& src2 = NoReg, const CPURegister& src3 = NoReg);
void Push(const CPURegister& src0, const CPURegister& src1,
const CPURegister& src2, const CPURegister& src3,
const CPURegister& src4, const CPURegister& src5 = NoReg,
const CPURegister& src6 = NoReg, const CPURegister& src7 = NoReg);
template <LoadLRMode lr_mode = kDontLoadLR>
void Pop(const CPURegister& dst0, const CPURegister& dst1 = NoReg,
const CPURegister& dst2 = NoReg, const CPURegister& dst3 = NoReg);
void Pop(const CPURegister& dst0, const CPURegister& dst1,
const CPURegister& dst2, const CPURegister& dst3,
const CPURegister& dst4, const CPURegister& dst5 = NoReg,
const CPURegister& dst6 = NoReg, const CPURegister& dst7 = NoReg);
template <StoreLRMode lr_mode = kDontStoreLR>
void Push(const Register& src0, const VRegister& src1);
// This is a convenience method for pushing a single Handle<Object>.
......@@ -838,7 +851,15 @@ class V8_EXPORT_PRIVATE TurboAssembler : public TurboAssemblerBase {
// kSRegSizeInBits are supported.
//
// Otherwise, (Push|Pop)(CPU|X|W|D|S)RegList is preferred.
//
// The methods take an optional LoadLRMode or StoreLRMode template argument.
// When control flow integrity measures are enabled and the link register is
// included in 'registers', passing kSignLR to PushCPURegList will sign the
// link register before pushing the list, and passing kAuthLR to
// PopCPURegList will authenticate it after popping the list.
template <StoreLRMode lr_mode = kDontStoreLR>
void PushCPURegList(CPURegList registers);
template <LoadLRMode lr_mode = kDontLoadLR>
void PopCPURegList(CPURegList registers);
// Calculate how much stack space (in bytes) are required to store caller
......@@ -1042,10 +1063,18 @@ class V8_EXPORT_PRIVATE TurboAssembler : public TurboAssemblerBase {
// Poke 'src' onto the stack. The offset is in bytes. The stack pointer must
// be 16 byte aligned.
// When the optional template argument is kSignLR and control flow integrity
// measures are enabled, we sign the link register before poking it onto the
// stack. 'src' must be lr in this case.
template <StoreLRMode lr_mode = kDontStoreLR>
void Poke(const CPURegister& src, const Operand& offset);
// Peek at a value on the stack, and put it in 'dst'. The offset is in bytes.
// The stack pointer must be aligned to 16 bytes.
// When the optional template argument is kAuthLR and control flow integrity
// measures are enabled, we authenticate the link register after peeking the
// value. 'dst' must be lr in this case.
template <LoadLRMode lr_mode = kDontLoadLR>
void Peek(const CPURegister& dst, const Operand& offset);
// Poke 'src1' and 'src2' onto the stack. The values written will be adjacent
......@@ -1297,6 +1326,12 @@ class V8_EXPORT_PRIVATE TurboAssembler : public TurboAssemblerBase {
void DecompressAnyTagged(const Register& destination,
const MemOperand& field_operand);
// Restore FP and LR from the values stored in the current frame. This will
// authenticate the LR when pointer authentication is enabled.
void RestoreFPAndLR();
void StoreReturnAddressInWasmExitFrame(Label* return_location);
protected:
// The actual Push and Pop implementations. These don't generate any code
// other than that required for the push or pop. This allows
......@@ -1625,21 +1660,27 @@ class V8_EXPORT_PRIVATE MacroAssembler : public TurboAssembler {
tbx(vd, vn, vn2, vn3, vn4, vm);
}
// For the 'lr_mode' template argument of the following methods, see
// PushCPURegList/PopCPURegList.
template <StoreLRMode lr_mode = kDontStoreLR>
inline void PushSizeRegList(
RegList registers, unsigned reg_size,
CPURegister::RegisterType type = CPURegister::kRegister) {
PushCPURegList(CPURegList(type, reg_size, registers));
PushCPURegList<lr_mode>(CPURegList(type, reg_size, registers));
}
template <LoadLRMode lr_mode = kDontLoadLR>
inline void PopSizeRegList(
RegList registers, unsigned reg_size,
CPURegister::RegisterType type = CPURegister::kRegister) {
PopCPURegList(CPURegList(type, reg_size, registers));
PopCPURegList<lr_mode>(CPURegList(type, reg_size, registers));
}
template <StoreLRMode lr_mode = kDontStoreLR>
inline void PushXRegList(RegList regs) {
PushSizeRegList(regs, kXRegSizeInBits);
PushSizeRegList<lr_mode>(regs, kXRegSizeInBits);
}
template <LoadLRMode lr_mode = kDontLoadLR>
inline void PopXRegList(RegList regs) {
PopSizeRegList(regs, kXRegSizeInBits);
PopSizeRegList<lr_mode>(regs, kXRegSizeInBits);
}
inline void PushWRegList(RegList regs) {
PushSizeRegList(regs, kWRegSizeInBits);
......@@ -1681,6 +1722,9 @@ class V8_EXPORT_PRIVATE MacroAssembler : public TurboAssembler {
// Floating-point registers are pushed before general-purpose registers, and
// thus get higher addresses.
//
// When control flow integrity measures are enabled, this method signs the
// link register before pushing it.
//
// Note that registers are not checked for invalid values. Use this method
// only if you know that the GC won't try to examine the values on the stack.
void PushCalleeSavedRegisters();
......@@ -1691,6 +1735,9 @@ class V8_EXPORT_PRIVATE MacroAssembler : public TurboAssembler {
// thus come from higher addresses.
// Floating-point registers are popped after general-purpose registers, and
// thus come from higher addresses.
//
// When control flow integrity measures are enabled, this method
// authenticates the link register after popping it.
void PopCalleeSavedRegisters();
// Helpers ------------------------------------------------------------------
......
......@@ -291,7 +291,7 @@ class OutOfLineRecordWrite final : public OutOfLineCode {
frame()->DidAllocateDoubleRegisters() ? kSaveFPRegs : kDontSaveFPRegs;
if (must_save_lr_) {
// We need to save and restore lr if the frame was elided.
__ Push(lr, padreg);
__ Push<TurboAssembler::kSignLR>(lr, padreg);
unwinding_info_writer_->MarkLinkRegisterOnTopOfStack(__ pc_offset(), sp);
}
if (mode_ == RecordWriteMode::kValueIsEphemeronKey) {
......@@ -307,7 +307,7 @@ class OutOfLineRecordWrite final : public OutOfLineCode {
save_fp_mode);
}
if (must_save_lr_) {
__ Pop(padreg, lr);
__ Pop<TurboAssembler::kAuthLR>(padreg, lr);
unwinding_info_writer_->MarkPopLinkRegisterFromTopOfStack(__ pc_offset());
}
}
......@@ -496,18 +496,14 @@ void EmitMaybePoisonedFPLoad(CodeGenerator* codegen, InstructionCode opcode,
void CodeGenerator::AssembleDeconstructFrame() {
__ Mov(sp, fp);
__ Pop(fp, lr);
__ Pop<TurboAssembler::kAuthLR>(fp, lr);
unwinding_info_writer_.MarkFrameDeconstructed(__ pc_offset());
}
void CodeGenerator::AssemblePrepareTailCall() {
if (frame_access_state()->has_frame()) {
static_assert(
StandardFrameConstants::kCallerFPOffset + kSystemPointerSize ==
StandardFrameConstants::kCallerPCOffset,
"Offsets must be consecutive for ldp!");
__ Ldp(fp, lr, MemOperand(fp, StandardFrameConstants::kCallerFPOffset));
__ RestoreFPAndLR();
}
frame_access_state()->SetFrameAccessToSP();
}
......@@ -779,10 +775,7 @@ CodeGenerator::CodeGenResult CodeGenerator::AssembleArchInstruction(
Label return_location;
if (linkage()->GetIncomingDescriptor()->IsWasmCapiFunction()) {
// Put the return address in a stack slot.
Register scratch = x8;
__ Adr(scratch, &return_location);
__ Str(scratch,
MemOperand(fp, WasmExitFrameConstants::kCallingPCOffset));
__ StoreReturnAddressInWasmExitFrame(&return_location);
}
if (instr->InputAt(0)->IsImmediate()) {
......@@ -2812,7 +2805,7 @@ void CodeGenerator::AssembleConstructFrame() {
if (call_descriptor->IsJSFunctionCall()) {
__ Prologue();
} else {
__ Push(lr, fp);
__ Push<TurboAssembler::kSignLR>(lr, fp);
__ Mov(fp, sp);
}
unwinding_info_writer_.MarkFrameConstructed(__ pc_offset());
......@@ -2962,7 +2955,7 @@ void CodeGenerator::AssembleConstructFrame() {
// TODO(palfia): TF save list is not in sync with
// CPURegList::GetCalleeSaved(): x30 is missing.
// DCHECK(saves.list() == CPURegList::GetCalleeSaved().list());
__ PushCPURegList(saves);
__ PushCPURegList<TurboAssembler::kSignLR>(saves);
if (returns != 0) {
__ Claim(returns);
......@@ -2981,7 +2974,7 @@ void CodeGenerator::AssembleReturn(InstructionOperand* pop) {
// Restore registers.
CPURegList saves = CPURegList(CPURegister::kRegister, kXRegSizeInBits,
call_descriptor->CalleeSavedRegisters());
__ PopCPURegList(saves);
__ PopCPURegList<TurboAssembler::kAuthLR>(saves);
// Restore fp registers.
CPURegList saves_fp = CPURegList(CPURegister::kVRegister, kDRegSizeInBits,
......
......@@ -9,6 +9,9 @@ namespace v8 {
namespace internal {
namespace compiler {
// TODO(v8:10026): When using CFI, we need to generate unwinding info to tell
// the unwinder that return addresses are signed.
void UnwindingInfoWriter::BeginInstructionBlock(int pc_offset,
const InstructionBlock* block) {
if (!enabled()) return;
......
......@@ -38,7 +38,7 @@ void DebugCodegen::GenerateFrameDropperTrampoline(MacroAssembler* masm) {
__ Ldr(x1, MemOperand(fp, StandardFrameConstants::kFunctionOffset));
__ Mov(sp, fp);
__ Pop(fp, lr); // Frame, Return address.
__ Pop<TurboAssembler::kAuthLR>(fp, lr);
__ LoadTaggedPointerField(
x0, FieldMemOperand(x1, JSFunction::kSharedFunctionInfoOffset));
......
......@@ -262,6 +262,8 @@ void FrameDescription::SetCallerConstantPool(unsigned offset, intptr_t value) {
UNREACHABLE();
}
void FrameDescription::SetPc(intptr_t pc) { pc_ = pc; }
#undef __
} // namespace internal
......
......@@ -9,6 +9,7 @@
#include "src/codegen/safepoint-table.h"
#include "src/deoptimizer/deoptimizer.h"
#include "src/execution/frame-constants.h"
#include "src/execution/pointer-authentication.h"
namespace v8 {
namespace internal {
......@@ -288,6 +289,9 @@ void Deoptimizer::GenerateDeoptimizationEntries(MacroAssembler* masm,
__ Ldr(continuation, MemOperand(last_output_frame,
FrameDescription::continuation_offset()));
__ Ldr(lr, MemOperand(last_output_frame, FrameDescription::pc_offset()));
#ifdef V8_ENABLE_CONTROL_FLOW_INTEGRITY
__ Autiasp();
#endif
__ Br(continuation);
}
......@@ -297,6 +301,14 @@ Float32 RegisterValues::GetFloatRegister(unsigned n) const {
}
void FrameDescription::SetCallerPc(unsigned offset, intptr_t value) {
// TODO(v8:10026): check that the pointer is still in the list of allowed
// builtins.
Address new_context =
static_cast<Address>(GetTop()) + offset + kPCOnStackSize;
uint64_t old_context = GetTop() + GetFrameSize();
PointerAuthentication::ReplaceContext(reinterpret_cast<Address*>(&value),
old_context, new_context);
SetFrameSlot(offset, value);
}
......@@ -309,6 +321,12 @@ void FrameDescription::SetCallerConstantPool(unsigned offset, intptr_t value) {
UNREACHABLE();
}
void FrameDescription::SetPc(intptr_t pc) {
// TODO(v8:10026): we should only accept a specific list of allowed builtins
// here.
pc_ = PointerAuthentication::SignPCWithSP(pc, GetTop());
}
#undef __
} // namespace internal
......
......@@ -14,6 +14,7 @@
#include "src/codegen/register-configuration.h"
#include "src/diagnostics/disasm.h"
#include "src/execution/frames-inl.h"
#include "src/execution/pointer-authentication.h"
#include "src/execution/v8threads.h"
#include "src/handles/global-handles.h"
#include "src/heap/heap-inl.h"
......@@ -252,7 +253,10 @@ class ActivationsFinder : public ThreadVisitor {
int trampoline_pc = safepoint.trampoline_pc();
DCHECK_IMPLIES(code == topmost_, safe_to_deopt_);
// Replace the current pc on the stack with the trampoline.
it.frame()->set_pc(code.raw_instruction_start() + trampoline_pc);
// TODO(v8:10026): avoid replacing a signed pointer.
Address* pc_addr = it.frame()->pc_address();
Address new_pc = code.raw_instruction_start() + trampoline_pc;
PointerAuthentication::ReplacePC(pc_addr, new_pc, kSystemPointerSize);
}
}
}
......@@ -690,6 +694,13 @@ void Deoptimizer::DoComputeOutputFrames() {
caller_fp_ = Memory<intptr_t>(fp_address);
caller_pc_ =
Memory<intptr_t>(fp_address + CommonFrameConstants::kCallerPCOffset);
// Sign caller_pc_ with caller_frame_top_ to be consistent with everything
// else here.
uint64_t sp = stack_fp_ + StandardFrameConstants::kCallerSPOffset;
// TODO(v8:10026): avoid replacing a signed pointer.
PointerAuthentication::ReplaceContext(
reinterpret_cast<Address*>(&caller_pc_), sp, caller_frame_top_);
input_frame_context_ = Memory<intptr_t>(
fp_address + CommonFrameConstants::kContextOrFrameTypeOffset);
......
......@@ -712,7 +712,7 @@ class FrameDescription {
void SetTop(intptr_t top) { top_ = top; }
intptr_t GetPc() const { return pc_; }
void SetPc(intptr_t pc) { pc_ = pc; }
void SetPc(intptr_t pc);
intptr_t GetFp() const { return fp_; }
void SetFp(intptr_t fp) { fp_ = fp; }
......
......@@ -217,6 +217,8 @@ void FrameDescription::SetCallerConstantPool(unsigned offset, intptr_t value) {
UNREACHABLE();
}
void FrameDescription::SetPc(intptr_t pc) { pc_ = pc; }
#undef __
} // namespace internal
......
......@@ -236,6 +236,8 @@ void FrameDescription::SetCallerConstantPool(unsigned offset, intptr_t value) {
UNREACHABLE();
}
void FrameDescription::SetPc(intptr_t pc) { pc_ = pc; }
#undef __
} // namespace internal
......
......@@ -236,6 +236,8 @@ void FrameDescription::SetCallerConstantPool(unsigned offset, intptr_t value) {
UNREACHABLE();
}
void FrameDescription::SetPc(intptr_t pc) { pc_ = pc; }
#undef __
} // namespace internal
......
......@@ -258,6 +258,8 @@ void FrameDescription::SetCallerConstantPool(unsigned offset, intptr_t value) {
SetFrameSlot(offset, value);
}
void FrameDescription::SetPc(intptr_t pc) { pc_ = pc; }
#undef __
} // namespace internal
} // namespace v8
......@@ -254,6 +254,8 @@ void FrameDescription::SetCallerConstantPool(unsigned offset, intptr_t value) {
UNREACHABLE();
}
void FrameDescription::SetPc(intptr_t pc) { pc_ = pc; }
#undef __
} // namespace internal
......
......@@ -221,18 +221,10 @@ Float32 RegisterValues::GetFloatRegister(unsigned n) const {
}
void FrameDescription::SetCallerPc(unsigned offset, intptr_t value) {
if (kPCOnStackSize == 2 * kSystemPointerSize) {
// Zero out the high-32 bit of PC for x32 port.
SetFrameSlot(offset + kSystemPointerSize, 0);
}
SetFrameSlot(offset, value);
}
void FrameDescription::SetCallerFp(unsigned offset, intptr_t value) {
if (kFPOnStackSize == 2 * kSystemPointerSize) {
// Zero out the high-32 bit of FP for x32 port.
SetFrameSlot(offset + kSystemPointerSize, 0);
}
SetFrameSlot(offset, value);
}
......@@ -241,6 +233,8 @@ void FrameDescription::SetCallerConstantPool(unsigned offset, intptr_t value) {
UNREACHABLE();
}
void FrameDescription::SetPc(intptr_t pc) { pc_ = pc; }
#undef __
} // namespace internal
......
......@@ -7,6 +7,7 @@
#include "include/v8.h"
#include "src/common/globals.h"
#include "src/execution/frame-constants.h"
#include "src/execution/pointer-authentication.h"
namespace v8 {
......@@ -87,8 +88,9 @@ void* GetReturnAddressFromFP(void* fp, void* pc,
caller_pc_offset = i::EntryFrameConstants::kDirectCallerPCOffset;
}
#endif
return reinterpret_cast<void*>(
Load(reinterpret_cast<i::Address>(fp) + caller_pc_offset));
i::Address ret_addr =
Load(reinterpret_cast<i::Address>(fp) + caller_pc_offset);
return reinterpret_cast<void*>(i::PointerAuthentication::StripPAC(ret_addr));
}
void* GetReturnAddressFromFP(void* fp, void* pc,
......@@ -99,8 +101,9 @@ void* GetReturnAddressFromFP(void* fp, void* pc,
caller_pc_offset = i::EntryFrameConstants::kDirectCallerPCOffset;
}
#endif
return reinterpret_cast<void*>(
Load(reinterpret_cast<i::Address>(fp) + caller_pc_offset));
i::Address ret_addr =
Load(reinterpret_cast<i::Address>(fp) + caller_pc_offset);
return reinterpret_cast<void*>(i::PointerAuthentication::StripPAC(ret_addr));
}
void* GetCallerFPFromFP(void* fp, void* pc,
......
// Copyright 2019 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef V8_EXECUTION_ARM64_POINTER_AUTHENTICATION_ARM64_H_
#define V8_EXECUTION_ARM64_POINTER_AUTHENTICATION_ARM64_H_
#include "src/execution/pointer-authentication.h"
#include "src/common/globals.h"
#include "src/execution/arm64/simulator-arm64.h"
// TODO(v8:10026): Replace hints with instruction aliases, when supported.
#define AUTIA1716 "hint #12"
#define PACIA1716 "hint #8"
#define XPACLRI "hint #7"
namespace v8 {
namespace internal {
// The following functions execute on the host and therefore need a different
// path based on whether we are simulating arm64 or not.
// clang-format fails to detect this file as C++, turn it off.
// clang-format off
// Authenticate the address stored in {pc_address}. {offset_from_sp} is the
// offset between {pc_address} and the pointer used as a context for signing.
V8_INLINE Address PointerAuthentication::AuthenticatePC(
Address* pc_address, unsigned offset_from_sp) {
uint64_t sp = reinterpret_cast<uint64_t>(pc_address) + offset_from_sp;
uint64_t pc = reinterpret_cast<uint64_t>(*pc_address);
#ifdef USE_SIMULATOR
pc = Simulator::AuthPAC(pc, sp, Simulator::kPACKeyIA,
Simulator::kInstructionPointer);
#else
asm volatile(
" mov x17, %[pc]\n"
" mov x16, %[stack_ptr]\n"
" " AUTIA1716 "\n"
" ldr xzr, [x17]\n"
" mov %[pc], x17\n"
: [pc] "+r"(pc)
: [stack_ptr] "r"(sp)
: "x16", "x17");
#endif
return pc;
}
// Strip Pointer Authentication Code (PAC) from {pc} and return the raw value.
V8_INLINE Address PointerAuthentication::StripPAC(Address pc) {
#ifdef USE_SIMULATOR
return Simulator::StripPAC(pc, Simulator::kInstructionPointer);
#else
asm volatile(
" mov x16, lr\n"
" mov lr, %[pc]\n"
" " XPACLRI "\n"
" mov %[pc], lr\n"
" mov lr, x16\n"
: [pc] "+r"(pc)
:
: "x16", "lr");
return pc;
#endif
}
// Sign {pc} using {sp}.
V8_INLINE Address PointerAuthentication::SignPCWithSP(Address pc, Address sp) {
#ifdef USE_SIMULATOR
return Simulator::AddPAC(pc, sp, Simulator::kPACKeyIA,
Simulator::kInstructionPointer);
#else
asm volatile(
" mov x17, %[pc]\n"
" mov x16, %[sp]\n"
" " PACIA1716 "\n"
" mov %[pc], x17\n"
: [pc] "+r"(pc)
: [sp] "r"(sp)
: "x16", "x17");
return pc;
#endif
}
// Authenticate the address stored in {pc_address} and replace it with
// {new_pc}, after signing it. {offset_from_sp} is the offset between
// {pc_address} and the pointer used as a context for signing.
V8_INLINE void PointerAuthentication::ReplacePC(Address* pc_address,
Address new_pc,
int offset_from_sp) {
uint64_t sp = reinterpret_cast<uint64_t>(pc_address) + offset_from_sp;
uint64_t old_pc = reinterpret_cast<uint64_t>(*pc_address);
#ifdef USE_SIMULATOR
uint64_t auth_old_pc = Simulator::AuthPAC(old_pc, sp, Simulator::kPACKeyIA,
Simulator::kInstructionPointer);
uint64_t raw_old_pc =
Simulator::StripPAC(old_pc, Simulator::kInstructionPointer);
// Verify that the old address is authenticated.
CHECK_EQ(auth_old_pc, raw_old_pc);
new_pc = Simulator::AddPAC(new_pc, sp, Simulator::kPACKeyIA,
Simulator::kInstructionPointer);
#else
// Only store newly signed address after we have verified that the old
// address is authenticated.
asm volatile(
" mov x17, %[new_pc]\n"
" mov x16, %[sp]\n"
" " PACIA1716 "\n"
" mov %[new_pc], x17\n"
" mov x17, %[old_pc]\n"
" " AUTIA1716 "\n"
" ldr xzr, [x17]\n"
: [new_pc] "+&r"(new_pc)
: [sp] "r"(sp), [old_pc] "r"(old_pc)
: "x16", "x17");
#endif
*pc_address = new_pc;
}
// Authenticate the address stored in {pc_address} based on {old_context} and
// replace it with the same address signed with {new_context} instead.
V8_INLINE void PointerAuthentication::ReplaceContext(Address* pc_address,
Address old_context,
Address new_context) {
uint64_t old_signed_pc = static_cast<uint64_t>(*pc_address);
uint64_t new_pc;
#ifdef USE_SIMULATOR
uint64_t auth_pc =
Simulator::AuthPAC(old_signed_pc, old_context, Simulator::kPACKeyIA,
Simulator::kInstructionPointer);
uint64_t raw_pc =
Simulator::StripPAC(auth_pc, Simulator::kInstructionPointer);
// Verify that the old address is authenticated.
CHECK_EQ(raw_pc, auth_pc);
new_pc = Simulator::AddPAC(raw_pc, new_context, Simulator::kPACKeyIA,
Simulator::kInstructionPointer);
#else
// Only store newly signed address after we have verified that the old
// address is authenticated.
asm volatile(
" mov x17, %[old_pc]\n"
" mov x16, %[old_ctx]\n"
" " AUTIA1716 "\n"
" mov x16, %[new_ctx]\n"
" " PACIA1716 "\n"
" mov %[new_pc], x17\n"
" mov x17, %[old_pc]\n"
" mov x16, %[old_ctx]\n"
" " AUTIA1716 "\n"
" ldr xzr, [x17]\n"
: [new_pc] "=&r"(new_pc)
: [old_pc] "r"(old_signed_pc), [old_ctx] "r"(old_context),
[new_ctx] "r"(new_context)
: "x16", "x17");
#endif
*pc_address = new_pc;
}
// clang-format on
} // namespace internal
} // namespace v8
#endif // V8_EXECUTION_ARM64_POINTER_AUTHENTICATION_ARM64_H_
......@@ -9,6 +9,7 @@
#include "src/execution/frame-constants.h"
#include "src/execution/frames.h"
#include "src/execution/isolate.h"
#include "src/execution/pointer-authentication.h"
#include "src/objects/objects-inl.h"
namespace v8 {
......@@ -69,6 +70,16 @@ inline StackHandler* StackFrame::top_handler() const {
return iterator_->handler();
}
inline Address StackFrame::callee_pc() const {
return state_.callee_pc_address ? ReadPC(state_.callee_pc_address)
: kNullAddress;
}
inline Address StackFrame::pc() const { return ReadPC(pc_address()); }
inline Address StackFrame::ReadPC(Address* pc_address) {
return PointerAuthentication::AuthenticatePC(pc_address, kSystemPointerSize);
}
inline Address* StackFrame::ResolveReturnAddressLocation(Address* pc_address) {
if (return_address_location_resolver_ == nullptr) {
......
......@@ -491,16 +491,18 @@ Code StackFrame::LookupCode() const {
void StackFrame::IteratePc(RootVisitor* v, Address* pc_address,
Address* constant_pool_address, Code holder) {
Address pc = *pc_address;
Address old_pc = ReadPC(pc_address);
DCHECK(ReadOnlyHeap::Contains(holder) ||
holder.GetHeap()->GcSafeCodeContains(holder, pc));
unsigned pc_offset = static_cast<unsigned>(pc - holder.InstructionStart());
holder.GetHeap()->GcSafeCodeContains(holder, old_pc));
unsigned pc_offset =
static_cast<unsigned>(old_pc - holder.InstructionStart());
Object code = holder;
v->VisitRootPointer(Root::kTop, nullptr, FullObjectSlot(&code));
if (code == holder) return;
holder = Code::unchecked_cast(code);
pc = holder.InstructionStart() + pc_offset;
*pc_address = pc;
Address pc = holder.InstructionStart() + pc_offset;
// TODO(v8:10026): avoid replacing a signed pointer.
PointerAuthentication::ReplacePC(pc_address, pc, kSystemPointerSize);
if (FLAG_enable_embedded_constant_pool && constant_pool_address) {
*constant_pool_address = holder.constant_pool();
}
......@@ -521,6 +523,7 @@ StackFrame::Type StackFrame::ComputeType(const StackFrameIteratorBase* iterator,
kSystemPointerSize);
intptr_t marker = Memory<intptr_t>(
state->fp + CommonFrameConstants::kContextOrFrameTypeOffset);
Address pc = StackFrame::ReadPC(state->pc_address);
if (!iterator->can_access_heap_objects_) {
// TODO(titzer): "can_access_heap_objects" is kind of bogus. It really
// means that we are being called from the profiler, which can interrupt
......@@ -535,15 +538,13 @@ StackFrame::Type StackFrame::ComputeType(const StackFrameIteratorBase* iterator,
if (!StackFrame::IsTypeMarker(marker)) {
if (maybe_function.IsSmi()) {
return NATIVE;
} else if (IsInterpreterFramePc(iterator->isolate(), *(state->pc_address),
state)) {
} else if (IsInterpreterFramePc(iterator->isolate(), pc, state)) {
return INTERPRETED;
} else {
return OPTIMIZED;
}
}
} else {
Address pc = *(state->pc_address);
// If the {pc} does not point into WebAssembly code we can rely on the
// returned {wasm_code} to be null and fall back to {GetContainingCode}.
wasm::WasmCodeRefScope code_ref_scope;
......
......@@ -215,9 +215,7 @@ class StackFrame {
// Accessors.
Address sp() const { return state_.sp; }
Address fp() const { return state_.fp; }
Address callee_pc() const {
return state_.callee_pc_address ? *state_.callee_pc_address : kNullAddress;
}
inline Address callee_pc() const;
Address caller_sp() const { return GetCallerStackPointer(); }
// If this frame is optimized and was dynamically aligned return its old
......@@ -225,8 +223,7 @@ class StackFrame {
// up one word and become unaligned.
Address UnpaddedFP() const;
Address pc() const { return *pc_address(); }
void set_pc(Address pc) { *pc_address() = pc; }
inline Address pc() const;
Address constant_pool() const { return *constant_pool_address(); }
void set_constant_pool(Address constant_pool) {
......@@ -265,6 +262,8 @@ class StackFrame {
static void SetReturnAddressLocationResolver(
ReturnAddressLocationResolver resolver);
static inline Address ReadPC(Address* pc_address);
// Resolves pc_address through the resolution address function if one is set.
static inline Address* ResolveReturnAddressLocation(Address* pc_address);
......
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef V8_EXECUTION_POINTER_AUTHENTICATION_DUMMY_H_
#define V8_EXECUTION_POINTER_AUTHENTICATION_DUMMY_H_
#include "src/execution/pointer-authentication.h"
#include "include/v8.h"
#include "src/base/macros.h"
#include "src/common/globals.h"
namespace v8 {
namespace internal {
// Dummy implementation of the PointerAuthentication class methods, to be used
// when CFI is not enabled.
// Load return address from {pc_address} and return it.
V8_INLINE Address PointerAuthentication::AuthenticatePC(
Address* pc_address, unsigned offset_from_sp) {
USE(offset_from_sp);
return *pc_address;
}
// Return {pc} unmodified.
V8_INLINE Address PointerAuthentication::StripPAC(Address pc) { return pc; }
// Return {pc} unmodified.
V8_INLINE Address PointerAuthentication::SignPCWithSP(Address pc, Address sp) {
USE(sp);
return pc;
}
// Store {new_pc} to {pc_address} without signing.
V8_INLINE void PointerAuthentication::ReplacePC(Address* pc_address,
Address new_pc,
int offset_from_sp) {
USE(offset_from_sp);
*pc_address = new_pc;
}
// Do nothing.
V8_INLINE void PointerAuthentication::ReplaceContext(Address* pc_address,
Address old_context,
Address new_context) {
USE(pc_address);
USE(old_context);
USE(new_context);
}
} // namespace internal
} // namespace v8
#endif // V8_EXECUTION_POINTER_AUTHENTICATION_DUMMY_H_
// Copyright 2019 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef V8_EXECUTION_POINTER_AUTHENTICATION_H_
#define V8_EXECUTION_POINTER_AUTHENTICATION_H_
#include "include/v8.h"
#include "src/base/macros.h"
#include "src/common/globals.h"
namespace v8 {
namespace internal {
class PointerAuthentication : public AllStatic {
public:
// When CFI is enabled, authenticate the address stored in {pc_address} and
// return the authenticated address. {offset_from_sp} is the offset between
// {pc_address} and the pointer used as a context for signing.
// When CFI is not enabled, simply load return address from {pc_address} and
// return it.
V8_INLINE static Address AuthenticatePC(Address* pc_address,
unsigned offset_from_sp);
// When CFI is enabled, strip Pointer Authentication Code (PAC) from {pc} and
// return the raw value.
// When CFI is not enabled, return {pc} unmodified.
V8_INLINE static Address StripPAC(Address pc);
// When CFI is enabled, sign {pc} using {sp} and return the signed value.
// When CFI is not enabled, return {pc} unmodified.
V8_INLINE static Address SignPCWithSP(Address pc, Address sp);
// When CFI is enabled, authenticate the address stored in {pc_address} and
// replace it with {new_pc}, after signing it. {offset_from_sp} is the offset
// between {pc_address} and the pointer used as a context for signing.
// When CFI is not enabled, store {new_pc} to {pc_address} without signing.
V8_INLINE static void ReplacePC(Address* pc_address, Address new_pc,
int offset_from_sp);
// When CFI is enabled, authenticate the address stored in {pc_address} based
// on {old_context} and replace it with the same address signed with
// {new_context} instead.
// When CFI is not enabled, do nothing.
V8_INLINE static void ReplaceContext(Address* pc_address, Address old_context,
Address new_context);
};
} // namespace internal
} // namespace v8
#ifdef V8_ENABLE_CONTROL_FLOW_INTEGRITY
#ifndef V8_TARGET_ARCH_ARM64
#error "V8_ENABLE_CONTROL_FLOW_INTEGRITY should imply V8_TARGET_ARCH_ARM64"
#endif
#include "src/execution/arm64/pointer-authentication-arm64.h"
#else
#include "src/execution/pointer-authentication-dummy.h"
#endif
#endif // V8_EXECUTION_POINTER_AUTHENTICATION_H_
......@@ -740,7 +740,8 @@ Handle<HeapObject> RegExpMacroAssemblerARM64::GetCode(Handle<String> source) {
DCHECK_EQ(11, kCalleeSaved.Count());
registers_to_retain.Combine(lr);
__ PushCPURegList(registers_to_retain);
DCHECK(registers_to_retain.IncludesAliasOf(lr));
__ PushCPURegList<TurboAssembler::kSignLR>(registers_to_retain);
__ PushCPURegList(argument_registers);
// Set frame pointer in place.
......@@ -1035,7 +1036,7 @@ Handle<HeapObject> RegExpMacroAssemblerARM64::GetCode(Handle<String> source) {
__ Mov(sp, fp);
// Restore registers.
__ PopCPURegList(registers_to_retain);
__ PopCPURegList<TurboAssembler::kAuthLR>(registers_to_retain);
__ Ret();
......@@ -1585,14 +1586,14 @@ void RegExpMacroAssemblerARM64::CallIf(Label* to, Condition condition) {
void RegExpMacroAssemblerARM64::RestoreLinkRegister() {
__ Pop(lr, xzr);
__ Pop<TurboAssembler::kAuthLR>(padreg, lr);
__ Add(lr, lr, Operand(masm_->CodeObject()));
}
void RegExpMacroAssemblerARM64::SaveLinkRegister() {
__ Sub(lr, lr, Operand(masm_->CodeObject()));
__ Push(xzr, lr);
__ Push<TurboAssembler::kSignLR>(lr, padreg);
}
......
......@@ -6,6 +6,7 @@
#include "src/codegen/assembler.h"
#include "src/execution/isolate-inl.h"
#include "src/execution/pointer-authentication.h"
#include "src/execution/simulator.h"
#include "src/regexp/regexp-stack.h"
#include "src/strings/unicode-inl.h"
......@@ -149,9 +150,10 @@ int NativeRegExpMacroAssembler::CheckStackGuardState(
Address* return_address, Code re_code, Address* subject,
const byte** input_start, const byte** input_end) {
DisallowHeapAllocation no_gc;
Address old_pc = PointerAuthentication::AuthenticatePC(return_address, 0);
DCHECK_LE(re_code.raw_instruction_start(), old_pc);
DCHECK_LE(old_pc, re_code.raw_instruction_end());
DCHECK(re_code.raw_instruction_start() <= *return_address);
DCHECK(*return_address <= re_code.raw_instruction_end());
StackLimitCheck check(isolate);
bool js_has_overflowed = check.JsHasOverflowed();
......@@ -193,9 +195,11 @@ int NativeRegExpMacroAssembler::CheckStackGuardState(
}
if (*code_handle != re_code) { // Return address no longer valid
intptr_t delta = code_handle->address() - re_code.address();
// Overwrite the return address on the stack.
*return_address += delta;
intptr_t delta = code_handle->address() - re_code.address();
Address new_pc = old_pc + delta;
// TODO(v8:10026): avoid replacing a signed pointer.
PointerAuthentication::ReplacePC(return_address, new_pc, 0);
}
// If we continue, we need to update the subject string addresses.
......
......@@ -36,6 +36,7 @@
#include "src/codegen/register-configuration.h"
#include "src/debug/debug-interface.h"
#include "src/execution/isolate.h"
#include "src/execution/simulator.h"
#include "src/flags/flags.h"
#include "src/heap/factory.h"
#include "src/init/v8.h"
......@@ -735,4 +736,65 @@ class TestPlatform : public v8::Platform {
DISALLOW_COPY_AND_ASSIGN(TestPlatform);
};
#if defined(USE_SIMULATOR)
class SimulatorHelper {
public:
inline bool Init(v8::Isolate* isolate) {
simulator_ = reinterpret_cast<v8::internal::Isolate*>(isolate)
->thread_local_top()
->simulator_;
// Check if there is active simulator.
return simulator_ != nullptr;
}
inline void FillRegisters(v8::RegisterState* state) {
#if V8_TARGET_ARCH_ARM
state->pc = reinterpret_cast<void*>(simulator_->get_pc());
state->sp = reinterpret_cast<void*>(
simulator_->get_register(v8::internal::Simulator::sp));
state->fp = reinterpret_cast<void*>(
simulator_->get_register(v8::internal::Simulator::r11));
state->lr = reinterpret_cast<void*>(
simulator_->get_register(v8::internal::Simulator::lr));
#elif V8_TARGET_ARCH_ARM64
if (simulator_->sp() == 0 || simulator_->fp() == 0) {
// It's possible that the simulator is interrupted while it is updating
// the sp or fp register. ARM64 simulator does this in two steps:
// first setting it to zero and then setting it to a new value.
// Bailout if sp/fp doesn't contain the new value.
return;
}
state->pc = reinterpret_cast<void*>(simulator_->pc());
state->sp = reinterpret_cast<void*>(simulator_->sp());
state->fp = reinterpret_cast<void*>(simulator_->fp());
state->lr = reinterpret_cast<void*>(simulator_->lr());
#elif V8_TARGET_ARCH_MIPS || V8_TARGET_ARCH_MIPS64
state->pc = reinterpret_cast<void*>(simulator_->get_pc());
state->sp = reinterpret_cast<void*>(
simulator_->get_register(v8::internal::Simulator::sp));
state->fp = reinterpret_cast<void*>(
simulator_->get_register(v8::internal::Simulator::fp));
#elif V8_TARGET_ARCH_PPC || V8_TARGET_ARCH_PPC64
state->pc = reinterpret_cast<void*>(simulator_->get_pc());
state->sp = reinterpret_cast<void*>(
simulator_->get_register(v8::internal::Simulator::sp));
state->fp = reinterpret_cast<void*>(
simulator_->get_register(v8::internal::Simulator::fp));
state->lr = reinterpret_cast<void*>(simulator_->get_lr());
#elif V8_TARGET_ARCH_S390 || V8_TARGET_ARCH_S390X
state->pc = reinterpret_cast<void*>(simulator_->get_pc());
state->sp = reinterpret_cast<void*>(
simulator_->get_register(v8::internal::Simulator::sp));
state->fp = reinterpret_cast<void*>(
simulator_->get_register(v8::internal::Simulator::fp));
state->lr = reinterpret_cast<void*>(
simulator_->get_register(v8::internal::Simulator::ra));
#endif
}
private:
v8::internal::Simulator* simulator_;
};
#endif // USE_SIMULATOR
#endif // ifndef CCTEST_H_
......@@ -12227,7 +12227,8 @@ static void PushPopSimpleHelper(int reg_count, int reg_size,
case PushPopByFour:
// Push high-numbered registers first (to the highest addresses).
for (i = reg_count; i >= 4; i -= 4) {
__ Push(r[i-1], r[i-2], r[i-3], r[i-4]);
__ Push<TurboAssembler::kDontSignLR>(r[i - 1], r[i - 2], r[i - 3],
r[i - 4]);
}
// Finish off the leftovers.
switch (i) {
......@@ -12240,7 +12241,7 @@ static void PushPopSimpleHelper(int reg_count, int reg_size,
}
break;
case PushPopRegList:
__ PushSizeRegList(list, reg_size);
__ PushSizeRegList<TurboAssembler::kDontSignLR>(list, reg_size);
break;
}
......@@ -12251,7 +12252,8 @@ static void PushPopSimpleHelper(int reg_count, int reg_size,
case PushPopByFour:
// Pop low-numbered registers first (from the lowest addresses).
for (i = 0; i <= (reg_count-4); i += 4) {
__ Pop(r[i], r[i+1], r[i+2], r[i+3]);
__ Pop<TurboAssembler::kDontAuthLR>(r[i], r[i + 1], r[i + 2],
r[i + 3]);
}
// Finish off the leftovers.
switch (reg_count - i) {
......@@ -12264,7 +12266,7 @@ static void PushPopSimpleHelper(int reg_count, int reg_size,
}
break;
case PushPopRegList:
__ PopSizeRegList(list, reg_size);
__ PopSizeRegList<TurboAssembler::kDontAuthLR>(list, reg_size);
break;
}
}
......@@ -12597,8 +12599,8 @@ TEST(push_pop) {
__ PopXRegList(0);
// Don't push/pop x18 (platform register) or xzr (for alignment)
RegList all_regs = 0xFFFFFFFF & ~(x18.bit() | x31.bit());
__ PushXRegList(all_regs);
__ PopXRegList(all_regs);
__ PushXRegList<TurboAssembler::kDontSignLR>(all_regs);
__ PopXRegList<TurboAssembler::kDontAuthLR>(all_regs);
__ Drop(12);
END();
......@@ -14597,13 +14599,13 @@ TEST(near_call_no_relocation) {
__ Bind(&test);
__ Mov(x0, 0x0);
__ Push(lr, xzr);
__ Push<TurboAssembler::kDontSignLR>(lr, xzr);
{
Assembler::BlockConstPoolScope scope(&masm);
int offset = (function.pos() - __ pc_offset()) / kInstrSize;
__ near_call(offset, RelocInfo::NONE);
}
__ Pop(xzr, lr);
__ Pop<TurboAssembler::kDontAuthLR>(xzr, lr);
END();
RUN();
......
......@@ -7,7 +7,6 @@
#include <map>
#include <string>
#include "include/v8.h"
#include "src/execution/simulator.h"
#include "src/flags/flags.h"
#include "test/cctest/cctest.h"
......@@ -31,68 +30,6 @@ class Sample {
};
#if defined(USE_SIMULATOR)
class SimulatorHelper {
public:
inline bool Init(v8::Isolate* isolate) {
simulator_ = reinterpret_cast<v8::internal::Isolate*>(isolate)
->thread_local_top()
->simulator_;
// Check if there is active simulator.
return simulator_ != nullptr;
}
inline void FillRegisters(v8::RegisterState* state) {
#if V8_TARGET_ARCH_ARM
state->pc = reinterpret_cast<void*>(simulator_->get_pc());
state->sp = reinterpret_cast<void*>(
simulator_->get_register(v8::internal::Simulator::sp));
state->fp = reinterpret_cast<void*>(
simulator_->get_register(v8::internal::Simulator::r11));
state->lr = reinterpret_cast<void*>(
simulator_->get_register(v8::internal::Simulator::lr));
#elif V8_TARGET_ARCH_ARM64
if (simulator_->sp() == 0 || simulator_->fp() == 0) {
// It's possible that the simulator is interrupted while it is updating
// the sp or fp register. ARM64 simulator does this in two steps:
// first setting it to zero and then setting it to a new value.
// Bailout if sp/fp doesn't contain the new value.
return;
}
state->pc = reinterpret_cast<void*>(simulator_->pc());
state->sp = reinterpret_cast<void*>(simulator_->sp());
state->fp = reinterpret_cast<void*>(simulator_->fp());
state->lr = reinterpret_cast<void*>(simulator_->lr());
#elif V8_TARGET_ARCH_MIPS || V8_TARGET_ARCH_MIPS64
state->pc = reinterpret_cast<void*>(simulator_->get_pc());
state->sp = reinterpret_cast<void*>(
simulator_->get_register(v8::internal::Simulator::sp));
state->fp = reinterpret_cast<void*>(
simulator_->get_register(v8::internal::Simulator::fp));
#elif V8_TARGET_ARCH_PPC || V8_TARGET_ARCH_PPC64
state->pc = reinterpret_cast<void*>(simulator_->get_pc());
state->sp = reinterpret_cast<void*>(
simulator_->get_register(v8::internal::Simulator::sp));
state->fp = reinterpret_cast<void*>(
simulator_->get_register(v8::internal::Simulator::fp));
state->lr = reinterpret_cast<void*>(simulator_->get_lr());
#elif V8_TARGET_ARCH_S390 || V8_TARGET_ARCH_S390X
state->pc = reinterpret_cast<void*>(simulator_->get_pc());
state->sp = reinterpret_cast<void*>(
simulator_->get_register(v8::internal::Simulator::sp));
state->fp = reinterpret_cast<void*>(
simulator_->get_register(v8::internal::Simulator::fp));
state->lr = reinterpret_cast<void*>(
simulator_->get_register(v8::internal::Simulator::ra));
#endif
}
private:
v8::internal::Simulator* simulator_;
};
#endif // USE_SIMULATOR
class SamplingTestHelper {
public:
struct CodeEventEntry {
......
......@@ -591,6 +591,80 @@ TEST(PCIsInV8_LargeCodeObject_CodePagesAPI) {
CHECK(v8::Unwinder::PCIsInV8(pages_length, code_pages, pc));
}
#ifdef USE_SIMULATOR
// TODO(v8:10026): Make this also work without the simulator. The part that
// needs modifications is getting the RegisterState.
class UnwinderTestHelper {
public:
explicit UnwinderTestHelper(const std::string& test_function)
: isolate_(CcTest::isolate()) {
CHECK(!instance_);
instance_ = this;
v8::HandleScope scope(isolate_);
v8::Local<v8::ObjectTemplate> global = v8::ObjectTemplate::New(isolate_);
global->Set(v8_str("TryUnwind"),
v8::FunctionTemplate::New(isolate_, TryUnwind));
LocalContext env(isolate_, nullptr, global);
CompileRun(v8_str(test_function.c_str()));
}
~UnwinderTestHelper() { instance_ = nullptr; }
private:
static void TryUnwind(const v8::FunctionCallbackInfo<v8::Value>& args) {
instance_->DoTryUnwind();
}
void DoTryUnwind() {
// Set up RegisterState.
v8::RegisterState register_state;
SimulatorHelper simulator_helper;
if (!simulator_helper.Init(isolate_)) return;
simulator_helper.FillRegisters(&register_state);
// At this point, the PC will point to a Redirection object, which is not
// in V8 as far as the unwinder is concerned. To make this work, point to
// the return address, which is in V8, instead.
register_state.pc = register_state.lr;
JSEntryStubs entry_stubs = isolate_->GetJSEntryStubs();
MemoryRange code_pages[v8::Isolate::kMinCodePagesBufferSize];
size_t pages_length =
isolate_->CopyCodePages(arraysize(code_pages), code_pages);
CHECK_LE(pages_length, arraysize(code_pages));
void* stack_base = reinterpret_cast<void*>(0xffffffffffffffffL);
bool unwound = v8::Unwinder::TryUnwindV8Frames(
entry_stubs, pages_length, code_pages, &register_state, stack_base);
// Check that we have successfully unwound past js_entry_sp.
CHECK(unwound);
CHECK_GT(register_state.sp,
reinterpret_cast<void*>(CcTest::i_isolate()->js_entry_sp()));
}
v8::Isolate* isolate_;
static UnwinderTestHelper* instance_;
};
UnwinderTestHelper* UnwinderTestHelper::instance_;
TEST(Unwind_TwoNestedFunctions_CodePagesAPI) {
i::FLAG_allow_natives_syntax = true;
const char* test_script =
"function test_unwinder_api_inner() {"
" TryUnwind();"
" return 0;"
"}"
"function test_unwinder_api_outer() {"
" return test_unwinder_api_inner();"
"}"
"%NeverOptimizeFunction(test_unwinder_api_inner);"
"%NeverOptimizeFunction(test_unwinder_api_outer);"
"test_unwinder_api_outer();";
UnwinderTestHelper helper(test_script);
}
#endif
} // namespace test_unwinder_code_pages
} // namespace internal
} // namespace v8
......@@ -7,6 +7,7 @@
#include "src/api/api-inl.h"
#include "src/builtins/builtins.h"
#include "src/execution/isolate.h"
#include "src/execution/pointer-authentication.h"
#include "src/heap/spaces.h"
#include "src/objects/code-inl.h"
#include "test/cctest/cctest.h"
......@@ -38,6 +39,11 @@ TEST(Unwind_BadState_Fail) {
CHECK_NULL(register_state.pc);
}
void StorePc(uintptr_t stack[], int index, uintptr_t pc) {
Address sp = reinterpret_cast<Address>(&stack[index]) + kSystemPointerSize;
stack[index] = PointerAuthentication::SignPCWithSP(pc, sp);
}
TEST(Unwind_BuiltinPCInMiddle_Success) {
LocalContext env;
v8::Isolate* isolate = env->GetIsolate();
......@@ -49,7 +55,7 @@ TEST(Unwind_BuiltinPCInMiddle_Success) {
uintptr_t stack[3];
void* stack_base = stack + arraysize(stack);
stack[0] = reinterpret_cast<uintptr_t>(stack + 2); // saved FP (rbp).
stack[1] = 202; // Return address into C++ code.
StorePc(stack, 1, 202); // Return address into C++ code.
stack[2] = 303; // The SP points here in the caller's frame.
register_state.sp = stack;
......@@ -93,9 +99,9 @@ TEST(Unwind_BuiltinPCAtStart_Success) {
stack[0] = 101;
// Return address into JS code. It doesn't matter that this is not actually in
// JSEntry, because we only check that for the top frame.
stack[1] = reinterpret_cast<uintptr_t>(code + 10);
StorePc(stack, 1, reinterpret_cast<uintptr_t>(code + 10));
stack[2] = reinterpret_cast<uintptr_t>(stack + 5); // saved FP (rbp).
stack[3] = 303; // Return address into C++ code.
StorePc(stack, 3, 303); // Return address into C++ code.
stack[4] = 404;
stack[5] = 505;
......@@ -145,7 +151,7 @@ TEST(Unwind_CodeObjectPCInMiddle_Success) {
uintptr_t stack[3];
void* stack_base = stack + arraysize(stack);
stack[0] = reinterpret_cast<uintptr_t>(stack + 2); // saved FP (rbp).
stack[1] = 202; // Return address into C++ code.
StorePc(stack, 1, 202); // Return address into C++ code.
stack[2] = 303; // The SP points here in the caller's frame.
register_state.sp = stack;
......@@ -213,7 +219,7 @@ TEST(Unwind_JSEntryBeforeFrame_Fail) {
stack[3] = 131;
stack[4] = 141;
stack[5] = 151;
stack[6] = 100; // Return address into C++ code.
StorePc(stack, 6, 100); // Return address into C++ code.
stack[7] = 303; // The SP points here in the caller's frame.
stack[8] = 404;
stack[9] = 505;
......@@ -267,7 +273,7 @@ TEST(Unwind_OneJSFrame_Success) {
stack[3] = 131;
stack[4] = 141;
stack[5] = reinterpret_cast<uintptr_t>(stack + 9); // saved FP (rbp).
stack[6] = 100; // Return address into C++ code.
StorePc(stack, 6, 100); // Return address into C++ code.
stack[7] = 303; // The SP points here in the caller's frame.
stack[8] = 404;
stack[9] = 505;
......@@ -311,10 +317,10 @@ TEST(Unwind_TwoJSFrames_Success) {
stack[1] = 111;
stack[2] = reinterpret_cast<uintptr_t>(stack + 5); // saved FP (rbp).
// The fake return address is in the JS code range.
stack[3] = reinterpret_cast<uintptr_t>(code + 10);
StorePc(stack, 3, reinterpret_cast<uintptr_t>(code + 10));
stack[4] = 141;
stack[5] = reinterpret_cast<uintptr_t>(stack + 9); // saved FP (rbp).
stack[6] = 100; // Return address into C++ code.
StorePc(stack, 6, 100); // Return address into C++ code.
stack[7] = 303; // The SP points here in the caller's frame.
stack[8] = 404;
stack[9] = 505;
......@@ -371,7 +377,7 @@ TEST(Unwind_StackBounds_Basic) {
uintptr_t stack[3];
stack[0] = reinterpret_cast<uintptr_t>(stack + 2); // saved FP (rbp).
stack[1] = 202; // Return address into C++ code.
StorePc(stack, 1, 202); // Return address into C++ code.
stack[2] = 303; // The SP points here in the caller's frame.
register_state.sp = stack;
......@@ -414,12 +420,12 @@ TEST(Unwind_StackBounds_WithUnwinding) {
stack[3] = 131;
stack[4] = 141;
stack[5] = reinterpret_cast<uintptr_t>(stack + 9); // saved FP (rbp).
stack[6] = reinterpret_cast<uintptr_t>(code + 20); // JS code.
StorePc(stack, 6, reinterpret_cast<uintptr_t>(code + 20)); // JS code.
stack[7] = 303; // The SP points here in the caller's frame.
stack[8] = 404;
stack[9] = reinterpret_cast<uintptr_t>(stack) +
(12 * sizeof(uintptr_t)); // saved FP (OOB).
stack[10] = reinterpret_cast<uintptr_t>(code + 20); // JS code.
StorePc(stack, 10, reinterpret_cast<uintptr_t>(code + 20)); // JS code.
register_state.sp = stack;
register_state.fp = stack + 5;
......@@ -435,7 +441,7 @@ TEST(Unwind_StackBounds_WithUnwinding) {
// Change the return address so that it is not in range. We will not range
// check the stack[9] FP value because we have finished unwinding and the
// contents of rbp does not necessarily have to be the FP in this case.
stack[10] = 202;
StorePc(stack, 10, 202);
unwound = v8::Unwinder::TryUnwindV8Frames(unwind_state, &register_state,
stack_base);
CHECK(unwound);
......@@ -549,6 +555,76 @@ TEST(PCIsInV8_LargeCodeObject) {
CHECK(v8::Unwinder::PCIsInV8(unwind_state, pc));
}
#ifdef USE_SIMULATOR
// TODO(v8:10026): Make this also work without the simulator. The part that
// needs modifications is getting the RegisterState.
class UnwinderTestHelper {
public:
explicit UnwinderTestHelper(const std::string& test_function)
: isolate_(CcTest::isolate()) {
CHECK(!instance_);
instance_ = this;
v8::HandleScope scope(isolate_);
v8::Local<v8::ObjectTemplate> global = v8::ObjectTemplate::New(isolate_);
global->Set(v8_str("TryUnwind"),
v8::FunctionTemplate::New(isolate_, TryUnwind));
LocalContext env(isolate_, nullptr, global);
CompileRun(v8_str(test_function.c_str()));
}
~UnwinderTestHelper() { instance_ = nullptr; }
private:
static void TryUnwind(const v8::FunctionCallbackInfo<v8::Value>& args) {
instance_->DoTryUnwind();
}
void DoTryUnwind() {
// Set up RegisterState.
v8::RegisterState register_state;
SimulatorHelper simulator_helper;
if (!simulator_helper.Init(isolate_)) return;
simulator_helper.FillRegisters(&register_state);
// At this point, the PC will point to a Redirection object, which is not
// in V8 as far as the unwinder is concerned. To make this work, point to
// the return address, which is in V8, instead.
register_state.pc = register_state.lr;
UnwindState unwind_state = isolate_->GetUnwindState();
void* stack_base = reinterpret_cast<void*>(0xffffffffffffffffL);
bool unwound = v8::Unwinder::TryUnwindV8Frames(unwind_state,
&register_state, stack_base);
// Check that we have successfully unwound past js_entry_sp.
CHECK(unwound);
CHECK_GT(register_state.sp,
reinterpret_cast<void*>(CcTest::i_isolate()->js_entry_sp()));
}
v8::Isolate* isolate_;
static UnwinderTestHelper* instance_;
};
UnwinderTestHelper* UnwinderTestHelper::instance_;
TEST(Unwind_TwoNestedFunctions) {
i::FLAG_allow_natives_syntax = true;
const char* test_script =
"function test_unwinder_api_inner() {"
" TryUnwind();"
" return 0;"
"}"
"function test_unwinder_api_outer() {"
" return test_unwinder_api_inner();"
"}"
"%NeverOptimizeFunction(test_unwinder_api_inner);"
"%NeverOptimizeFunction(test_unwinder_api_outer);"
"test_unwinder_api_outer();";
UnwinderTestHelper helper(test_script);
}
#endif
#if __clang__
#pragma clang diagnostic pop
#endif
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment