diff --git a/BUILD.gn b/BUILD.gn index 9999cc008d..a0ffefb4da 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -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", diff --git a/src/builtins/arm64/builtins-arm64.cc b/src/builtins/arm64/builtins-arm64.cc index ddcf3af4b6..39d159f968 100644 --- a/src/builtins/arm64/builtins-arm64.cc +++ b/src/builtins/arm64/builtins-arm64.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(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(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(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(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(lr, 0); // Store the return address. __ Blr(x10); // Call the C++ function. - __ Peek(lr, 0); // Return to calling code. + __ Peek(lr, 0); // Return to calling code. __ AssertFPCRState(); __ Ret(); } diff --git a/src/codegen/arm64/macro-assembler-arm64-inl.h b/src/codegen/arm64/macro-assembler-arm64-inl.h index 0128ec069c..962533d78e 100644 --- a/src/codegen/arm64/macro-assembler-arm64-inl.h +++ b/src/codegen/arm64/macro-assembler-arm64-inl.h @@ -1075,6 +1075,166 @@ void MacroAssembler::JumpIfNotSmi(Register value, Label* not_smi_label) { void TurboAssembler::jmp(Label* L) { B(L); } +template +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 +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 +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 +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 +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 +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 +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 handle) { UseScratchRegisterScope temps(this); Register tmp = temps.AcquireX(); diff --git a/src/codegen/arm64/macro-assembler-arm64.cc b/src/codegen/arm64/macro-assembler-arm64.cc index 874aec285d..6099f6b34e 100644 --- a/src/codegen/arm64/macro-assembler-arm64.cc +++ b/src/codegen/arm64/macro-assembler-arm64.cc @@ -60,7 +60,7 @@ int TurboAssembler::PushCallerSaved(SaveFPRegsMode fp_mode, list.Remove(exclusion); list.Align(); - PushCPURegList(list); + PushCPURegList(list); int bytes = list.Count() * kXRegSizeInBits / 8; @@ -84,7 +84,7 @@ int TurboAssembler::PopCallerSaved(SaveFPRegsMode fp_mode, Register exclusion) { list.Remove(exclusion); list.Align(); - PopCPURegList(list); + PopCPURegList(list); bytes += list.Count() * kXRegSizeInBits / 8; return bytes; @@ -1045,17 +1045,6 @@ void TurboAssembler::Abs(const Register& rd, const Register& rm, // Abstracted stack operations. -void TurboAssembler::Push(const CPURegister& src0, const CPURegister& src1, - const CPURegister& src2, const CPURegister& src3) { - DCHECK(AreSameSizeAndType(src0, src1, src2, src3)); - - 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); -} - void TurboAssembler::Push(const CPURegister& src0, const CPURegister& src1, const CPURegister& src2, const CPURegister& src3, const CPURegister& src4, const CPURegister& src5, @@ -1070,21 +1059,6 @@ void TurboAssembler::Push(const CPURegister& src0, const CPURegister& src1, PushHelper(count - 4, size, src4, src5, src6, src7); } -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); -} - void TurboAssembler::Pop(const CPURegister& dst0, const CPURegister& dst1, const CPURegister& dst2, const CPURegister& dst3, const CPURegister& dst4, const CPURegister& dst5, @@ -1103,48 +1077,6 @@ void TurboAssembler::Pop(const CPURegister& dst0, const CPURegister& dst1, PopHelper(count - 4, size, dst4, dst5, dst6, dst7); } -void TurboAssembler::Push(const Register& src0, const VRegister& src1) { - 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())); -} - -void TurboAssembler::PushCPURegList(CPURegList registers) { - 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); - } -} - -void TurboAssembler::PopCPURegList(CPURegList registers) { - int size = registers.RegisterSizeInBytes(); - DCHECK_EQ(0, (size * registers.Count()) % 16); - - // 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); - } -} - void MacroAssembler::PushMultipleTimes(CPURegister src, Register count) { UseScratchRegisterScope temps(this); Register temp = temps.AcquireSameSizeAs(count); @@ -1249,28 +1181,6 @@ void TurboAssembler::PopHelper(int count, int size, const CPURegister& dst0, } } -void TurboAssembler::Poke(const CPURegister& src, const Operand& offset) { - 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)); -} - -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)); -} - void TurboAssembler::PokePair(const CPURegister& src1, const CPURegister& src2, int offset) { DCHECK(AreSameSizeAndType(src1, src2)); @@ -1286,50 +1196,61 @@ void MacroAssembler::PeekPair(const CPURegister& dst1, const CPURegister& dst2, } void MacroAssembler::PushCalleeSavedRegisters() { - // Ensure that the macro-assembler doesn't use any scratch registers. - InstructionAccurateScope scope(this); +#ifdef V8_ENABLE_CONTROL_FLOW_INTEGRITY + Paciasp(); +#endif - MemOperand tos(sp, -2 * static_cast(kXRegSize), PreIndex); + { + // Ensure that the macro-assembler doesn't use any scratch registers. + InstructionAccurateScope scope(this); - stp(d14, d15, tos); - stp(d12, d13, tos); - stp(d10, d11, tos); - stp(d8, d9, tos); + MemOperand tos(sp, -2 * static_cast(kXRegSize), PreIndex); - STATIC_ASSERT( - EntryFrameConstants::kCalleeSavedRegisterBytesPushedBeforeFpLrPair == - 8 * kSystemPointerSize); + stp(d14, d15, tos); + stp(d12, d13, tos); + stp(d10, d11, tos); + stp(d8, d9, tos); - stp(x29, x30, tos); // fp, lr + STATIC_ASSERT( + EntryFrameConstants::kCalleeSavedRegisterBytesPushedBeforeFpLrPair == + 8 * kSystemPointerSize); + stp(x29, x30, tos); // fp, lr - STATIC_ASSERT( - EntryFrameConstants::kCalleeSavedRegisterBytesPushedAfterFpLrPair == - 10 * kSystemPointerSize); + STATIC_ASSERT( + EntryFrameConstants::kCalleeSavedRegisterBytesPushedAfterFpLrPair == + 10 * kSystemPointerSize); - stp(x27, x28, tos); - stp(x25, x26, tos); - stp(x23, x24, tos); - stp(x21, x22, tos); - stp(x19, x20, tos); + stp(x27, x28, tos); + stp(x25, x26, tos); + stp(x23, x24, tos); + stp(x21, x22, tos); + stp(x19, x20, tos); + } } void MacroAssembler::PopCalleeSavedRegisters() { - // Ensure that the macro-assembler doesn't use any scratch registers. - InstructionAccurateScope scope(this); + { + // Ensure that the macro-assembler doesn't use any scratch registers. + InstructionAccurateScope scope(this); - MemOperand tos(sp, 2 * kXRegSize, PostIndex); + MemOperand tos(sp, 2 * kXRegSize, PostIndex); - ldp(x19, x20, tos); - ldp(x21, x22, tos); - ldp(x23, x24, tos); - ldp(x25, x26, tos); - ldp(x27, x28, tos); - ldp(x29, x30, tos); + ldp(x19, x20, tos); + ldp(x21, x22, tos); + ldp(x23, x24, tos); + ldp(x25, x26, tos); + ldp(x27, x28, tos); + ldp(x29, x30, tos); - ldp(d8, d9, tos); - ldp(d10, d11, tos); - ldp(d12, d13, tos); - ldp(d14, d15, tos); + ldp(d8, d9, tos); + ldp(d10, d11, tos); + ldp(d12, d13, tos); + ldp(d14, d15, tos); + } + +#ifdef V8_ENABLE_CONTROL_FLOW_INTEGRITY + Autiasp(); +#endif } void TurboAssembler::AssertSpAligned() { @@ -2017,18 +1938,22 @@ void TurboAssembler::StoreReturnAddressAndCall(Register target) { // GC, since the callee function will return to it. UseScratchRegisterScope temps(this); - Register scratch1 = temps.AcquireX(); + temps.Exclude(x16, x17); Label return_location; - Adr(scratch1, &return_location); - Poke(scratch1, 0); + Adr(x17, &return_location); +#ifdef V8_ENABLE_CONTROL_FLOW_INTEGRITY + Add(x16, sp, kSystemPointerSize); + Pacia1716(); +#endif + Poke(x17, 0); if (emit_debug_code()) { - // Verify that the slot below fp[kSPOffset]-8 points to the return location. - Register scratch2 = temps.AcquireX(); - Ldr(scratch2, MemOperand(fp, ExitFrameConstants::kSPOffset)); - Ldr(scratch2, MemOperand(scratch2, -static_cast(kXRegSize))); - Cmp(scratch2, scratch1); + // Verify that the slot below fp[kSPOffset]-8 points to the signed return + // location. + Ldr(x16, MemOperand(fp, ExitFrameConstants::kSPOffset)); + Ldr(x16, MemOperand(x16, -static_cast(kXRegSize))); + Cmp(x16, x17); Check(eq, AbortReason::kReturnAddressNotFoundInFrame); } @@ -2096,8 +2021,7 @@ void TurboAssembler::PrepareForTailCall(Register callee_args_count, // Restore caller's frame pointer and return address now as they will be // overwritten by the copying loop. - Ldr(lr, MemOperand(fp, StandardFrameConstants::kCallerPCOffset)); - Ldr(fp, MemOperand(fp, StandardFrameConstants::kCallerFPOffset)); + RestoreFPAndLR(); // Now copy callee arguments to the caller frame going backwards to avoid // callee arguments corruption (source and destination areas could overlap). @@ -2309,7 +2233,7 @@ void TurboAssembler::TruncateDoubleToI(Isolate* isolate, Zone* zone, TryConvertDoubleToInt64(result, double_input, &done); // If we fell through then inline version didn't succeed - call stub instead. - Push(lr, double_input); + Push(lr, double_input); // DoubleToI preserves any registers it needs to clobber. if (stub_mode == StubCallMode::kCallWasmRuntimeStub) { @@ -2322,7 +2246,8 @@ void TurboAssembler::TruncateDoubleToI(Isolate* isolate, Zone* zone, Ldr(result, MemOperand(sp, 0)); DCHECK_EQ(xzr.SizeInBytes(), double_input.SizeInBytes()); - Pop(xzr, lr); // xzr to drop the double input on the stack. + // Pop into xzr here to drop the double input on the stack: + Pop(xzr, lr); Bind(&done); // Keep our invariant that the upper 32 bits are zero. @@ -2330,7 +2255,7 @@ void TurboAssembler::TruncateDoubleToI(Isolate* isolate, Zone* zone, } void TurboAssembler::Prologue() { - Push(lr, fp, cp, x1); + Push(lr, fp, cp, x1); Add(fp, sp, StandardFrameConstants::kFixedFrameSizeFromFp); } @@ -2341,7 +2266,7 @@ void TurboAssembler::EnterFrame(StackFrame::Type type) { Register type_reg = temps.AcquireX(); Mov(type_reg, StackFrame::TypeToMarker(type)); // type_reg pushed twice for alignment. - Push(lr, fp, type_reg, type_reg); + Push(lr, fp, type_reg, type_reg); const int kFrameSize = TypedFrameConstants::kFixedFrameSizeFromFp + kSystemPointerSize; Add(fp, sp, kFrameSize); @@ -2354,7 +2279,7 @@ void TurboAssembler::EnterFrame(StackFrame::Type type) { type == StackFrame::WASM_EXIT) { Register type_reg = temps.AcquireX(); Mov(type_reg, StackFrame::TypeToMarker(type)); - Push(lr, fp); + Push(lr, fp); Mov(fp, sp); Push(type_reg, padreg); // sp[3] : lr @@ -2368,7 +2293,7 @@ void TurboAssembler::EnterFrame(StackFrame::Type type) { // Users of this frame type push a context pointer after the type field, // so do it here to keep the stack pointer aligned. - Push(lr, fp, type_reg, cp); + Push(lr, fp, type_reg, cp); // The context pointer isn't part of the fixed frame, so add an extra slot // to account for it. @@ -2385,7 +2310,7 @@ void TurboAssembler::LeaveFrame(StackFrame::Type type) { // Drop the execution stack down to the frame pointer and restore // the caller frame pointer and return address. Mov(sp, fp); - Pop(fp, lr); + Pop(fp, lr); } void MacroAssembler::ExitFramePreserveFPRegs() { @@ -2415,7 +2340,7 @@ void MacroAssembler::EnterExitFrame(bool save_doubles, const Register& scratch, frame_type == StackFrame::BUILTIN_EXIT); // Set up the new stack frame. - Push(lr, fp); + Push(lr, fp); Mov(fp, sp); Mov(scratch, StackFrame::TypeToMarker(frame_type)); Push(scratch, xzr); @@ -2498,7 +2423,7 @@ void MacroAssembler::LeaveExitFrame(bool restore_doubles, // fp -> fp[0]: CallerFP (old fp) // fp[...]: The rest of the frame. Mov(sp, fp); - Pop(fp, lr); + Pop(fp, lr); } void MacroAssembler::LoadGlobalProxy(Register dst) { @@ -2751,25 +2676,19 @@ void MacroAssembler::RecordWriteField(Register object, int offset, void TurboAssembler::SaveRegisters(RegList registers) { DCHECK_GT(NumRegs(registers), 0); - CPURegList regs(lr); - for (int i = 0; i < Register::kNumRegisters; ++i) { - if ((registers >> i) & 1u) { - regs.Combine(Register::XRegFromCode(i)); - } - } - + CPURegList regs(CPURegister::kRegister, kXRegSizeInBits, registers); + // If we were saving LR, we might need to sign it. + DCHECK(!regs.IncludesAliasOf(lr)); + regs.Align(); PushCPURegList(regs); } void TurboAssembler::RestoreRegisters(RegList registers) { DCHECK_GT(NumRegs(registers), 0); - CPURegList regs(lr); - for (int i = 0; i < Register::kNumRegisters; ++i) { - if ((registers >> i) & 1u) { - regs.Combine(Register::XRegFromCode(i)); - } - } - + CPURegList regs(CPURegister::kRegister, kXRegSizeInBits, registers); + // If we were saving LR, we might need to sign it. + DCHECK(!regs.IncludesAliasOf(lr)); + regs.Align(); PopCPURegList(regs); } @@ -2924,11 +2843,11 @@ void MacroAssembler::RecordWrite(Register object, Operand offset, // Record the actual write. if (lr_status == kLRHasNotBeenSaved) { - Push(padreg, lr); + Push(padreg, lr); } CallRecordWriteStub(object, offset, remembered_set_action, fp_mode); if (lr_status == kLRHasNotBeenSaved) { - Pop(lr, padreg); + Pop(lr, padreg); } Bind(&done); @@ -3183,7 +3102,7 @@ void TurboAssembler::Printf(const char* format, CPURegister arg0, // Preserve all caller-saved registers as well as NZCV. // PushCPURegList asserts that the size of each list is a multiple of 16 // bytes. - PushCPURegList(saved_registers); + PushCPURegList(saved_registers); PushCPURegList(kCallerSavedV); // We can use caller-saved registers as scratch values (except for argN). @@ -3236,7 +3155,7 @@ void TurboAssembler::Printf(const char* format, CPURegister arg0, } PopCPURegList(kCallerSavedV); - PopCPURegList(saved_registers); + PopCPURegList(saved_registers); TmpList()->set_list(old_tmp_list); FPTmpList()->set_list(old_fp_tmp_list); @@ -3274,6 +3193,35 @@ void TurboAssembler::ResetSpeculationPoisonRegister() { Mov(kSpeculationPoisonRegister, -1); } +void TurboAssembler::RestoreFPAndLR() { + static_assert(StandardFrameConstants::kCallerFPOffset + kSystemPointerSize == + StandardFrameConstants::kCallerPCOffset, + "Offsets must be consecutive for ldp!"); +#ifdef V8_ENABLE_CONTROL_FLOW_INTEGRITY + // Make sure we can use x16 and x17. + UseScratchRegisterScope temps(this); + temps.Exclude(x16, x17); + // We can load the return address directly into x17. + Add(x16, fp, StandardFrameConstants::kCallerSPOffset); + Ldp(fp, x17, MemOperand(fp, StandardFrameConstants::kCallerFPOffset)); + Autia1716(); + Mov(lr, x17); +#else + Ldp(fp, lr, MemOperand(fp, StandardFrameConstants::kCallerFPOffset)); +#endif +} + +void TurboAssembler::StoreReturnAddressInWasmExitFrame(Label* return_location) { + UseScratchRegisterScope temps(this); + temps.Exclude(x16, x17); + Adr(x17, return_location); +#ifdef V8_ENABLE_CONTROL_FLOW_INTEGRITY + Add(x16, fp, WasmExitFrameConstants::kCallingPCOffset + kSystemPointerSize); + Pacia1716(); +#endif + Str(x17, MemOperand(fp, WasmExitFrameConstants::kCallingPCOffset)); +} + } // namespace internal } // namespace v8 diff --git a/src/codegen/arm64/macro-assembler-arm64.h b/src/codegen/arm64/macro-assembler-arm64.h index 735536c6cd..965ec20ccd 100644 --- a/src/codegen/arm64/macro-assembler-arm64.h +++ b/src/codegen/arm64/macro-assembler-arm64.h @@ -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 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 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 void Push(const Register& src0, const VRegister& src1); // This is a convenience method for pushing a single Handle. @@ -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 void PushCPURegList(CPURegList registers); + template 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 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 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 inline void PushSizeRegList( RegList registers, unsigned reg_size, CPURegister::RegisterType type = CPURegister::kRegister) { - PushCPURegList(CPURegList(type, reg_size, registers)); + PushCPURegList(CPURegList(type, reg_size, registers)); } + template inline void PopSizeRegList( RegList registers, unsigned reg_size, CPURegister::RegisterType type = CPURegister::kRegister) { - PopCPURegList(CPURegList(type, reg_size, registers)); + PopCPURegList(CPURegList(type, reg_size, registers)); } + template inline void PushXRegList(RegList regs) { - PushSizeRegList(regs, kXRegSizeInBits); + PushSizeRegList(regs, kXRegSizeInBits); } + template inline void PopXRegList(RegList regs) { - PopSizeRegList(regs, kXRegSizeInBits); + PopSizeRegList(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 ------------------------------------------------------------------ diff --git a/src/compiler/backend/arm64/code-generator-arm64.cc b/src/compiler/backend/arm64/code-generator-arm64.cc index c95bf988ec..46a36128d1 100644 --- a/src/compiler/backend/arm64/code-generator-arm64.cc +++ b/src/compiler/backend/arm64/code-generator-arm64.cc @@ -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(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(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(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(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(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(saves); // Restore fp registers. CPURegList saves_fp = CPURegList(CPURegister::kVRegister, kDRegSizeInBits, diff --git a/src/compiler/backend/arm64/unwinding-info-writer-arm64.cc b/src/compiler/backend/arm64/unwinding-info-writer-arm64.cc index 3747019c7d..c8a570af53 100644 --- a/src/compiler/backend/arm64/unwinding-info-writer-arm64.cc +++ b/src/compiler/backend/arm64/unwinding-info-writer-arm64.cc @@ -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; diff --git a/src/debug/arm64/debug-arm64.cc b/src/debug/arm64/debug-arm64.cc index 96cd8a7b74..251856e284 100644 --- a/src/debug/arm64/debug-arm64.cc +++ b/src/debug/arm64/debug-arm64.cc @@ -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(fp, lr); __ LoadTaggedPointerField( x0, FieldMemOperand(x1, JSFunction::kSharedFunctionInfoOffset)); diff --git a/src/deoptimizer/arm/deoptimizer-arm.cc b/src/deoptimizer/arm/deoptimizer-arm.cc index becdc93a4c..5ca6807b1e 100644 --- a/src/deoptimizer/arm/deoptimizer-arm.cc +++ b/src/deoptimizer/arm/deoptimizer-arm.cc @@ -262,6 +262,8 @@ void FrameDescription::SetCallerConstantPool(unsigned offset, intptr_t value) { UNREACHABLE(); } +void FrameDescription::SetPc(intptr_t pc) { pc_ = pc; } + #undef __ } // namespace internal diff --git a/src/deoptimizer/arm64/deoptimizer-arm64.cc b/src/deoptimizer/arm64/deoptimizer-arm64.cc index 300e65ab00..1a13377077 100644 --- a/src/deoptimizer/arm64/deoptimizer-arm64.cc +++ b/src/deoptimizer/arm64/deoptimizer-arm64.cc @@ -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
(GetTop()) + offset + kPCOnStackSize; + uint64_t old_context = GetTop() + GetFrameSize(); + PointerAuthentication::ReplaceContext(reinterpret_cast(&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 diff --git a/src/deoptimizer/deoptimizer.cc b/src/deoptimizer/deoptimizer.cc index 56431d4eea..2e29ac9dfd 100644 --- a/src/deoptimizer/deoptimizer.cc +++ b/src/deoptimizer/deoptimizer.cc @@ -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(fp_address); caller_pc_ = Memory(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(&caller_pc_), sp, caller_frame_top_); + input_frame_context_ = Memory( fp_address + CommonFrameConstants::kContextOrFrameTypeOffset); diff --git a/src/deoptimizer/deoptimizer.h b/src/deoptimizer/deoptimizer.h index beb2a9aa50..58c65562d9 100644 --- a/src/deoptimizer/deoptimizer.h +++ b/src/deoptimizer/deoptimizer.h @@ -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; } diff --git a/src/deoptimizer/ia32/deoptimizer-ia32.cc b/src/deoptimizer/ia32/deoptimizer-ia32.cc index 2fd424a667..fe14a3b646 100644 --- a/src/deoptimizer/ia32/deoptimizer-ia32.cc +++ b/src/deoptimizer/ia32/deoptimizer-ia32.cc @@ -217,6 +217,8 @@ void FrameDescription::SetCallerConstantPool(unsigned offset, intptr_t value) { UNREACHABLE(); } +void FrameDescription::SetPc(intptr_t pc) { pc_ = pc; } + #undef __ } // namespace internal diff --git a/src/deoptimizer/mips/deoptimizer-mips.cc b/src/deoptimizer/mips/deoptimizer-mips.cc index bf82e2631b..a7c374089a 100644 --- a/src/deoptimizer/mips/deoptimizer-mips.cc +++ b/src/deoptimizer/mips/deoptimizer-mips.cc @@ -236,6 +236,8 @@ void FrameDescription::SetCallerConstantPool(unsigned offset, intptr_t value) { UNREACHABLE(); } +void FrameDescription::SetPc(intptr_t pc) { pc_ = pc; } + #undef __ } // namespace internal diff --git a/src/deoptimizer/mips64/deoptimizer-mips64.cc b/src/deoptimizer/mips64/deoptimizer-mips64.cc index a1138d202f..127c0f92e5 100644 --- a/src/deoptimizer/mips64/deoptimizer-mips64.cc +++ b/src/deoptimizer/mips64/deoptimizer-mips64.cc @@ -236,6 +236,8 @@ void FrameDescription::SetCallerConstantPool(unsigned offset, intptr_t value) { UNREACHABLE(); } +void FrameDescription::SetPc(intptr_t pc) { pc_ = pc; } + #undef __ } // namespace internal diff --git a/src/deoptimizer/ppc/deoptimizer-ppc.cc b/src/deoptimizer/ppc/deoptimizer-ppc.cc index 1d05968806..8e872e0f9c 100644 --- a/src/deoptimizer/ppc/deoptimizer-ppc.cc +++ b/src/deoptimizer/ppc/deoptimizer-ppc.cc @@ -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 diff --git a/src/deoptimizer/s390/deoptimizer-s390.cc b/src/deoptimizer/s390/deoptimizer-s390.cc index 63ea22f71a..9833457145 100644 --- a/src/deoptimizer/s390/deoptimizer-s390.cc +++ b/src/deoptimizer/s390/deoptimizer-s390.cc @@ -254,6 +254,8 @@ void FrameDescription::SetCallerConstantPool(unsigned offset, intptr_t value) { UNREACHABLE(); } +void FrameDescription::SetPc(intptr_t pc) { pc_ = pc; } + #undef __ } // namespace internal diff --git a/src/deoptimizer/x64/deoptimizer-x64.cc b/src/deoptimizer/x64/deoptimizer-x64.cc index 724062c506..e41bf88959 100644 --- a/src/deoptimizer/x64/deoptimizer-x64.cc +++ b/src/deoptimizer/x64/deoptimizer-x64.cc @@ -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 diff --git a/src/diagnostics/unwinder.cc b/src/diagnostics/unwinder.cc index 64adf17b82..c08fe20330 100644 --- a/src/diagnostics/unwinder.cc +++ b/src/diagnostics/unwinder.cc @@ -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( - Load(reinterpret_cast(fp) + caller_pc_offset)); + i::Address ret_addr = + Load(reinterpret_cast(fp) + caller_pc_offset); + return reinterpret_cast(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( - Load(reinterpret_cast(fp) + caller_pc_offset)); + i::Address ret_addr = + Load(reinterpret_cast(fp) + caller_pc_offset); + return reinterpret_cast(i::PointerAuthentication::StripPAC(ret_addr)); } void* GetCallerFPFromFP(void* fp, void* pc, diff --git a/src/execution/arm64/pointer-authentication-arm64.h b/src/execution/arm64/pointer-authentication-arm64.h new file mode 100644 index 0000000000..c54a59f29c --- /dev/null +++ b/src/execution/arm64/pointer-authentication-arm64.h @@ -0,0 +1,164 @@ +// 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(pc_address) + offset_from_sp; + uint64_t pc = reinterpret_cast(*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(pc_address) + offset_from_sp; + uint64_t old_pc = reinterpret_cast(*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(*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_ diff --git a/src/execution/frames-inl.h b/src/execution/frames-inl.h index f1b979b7be..0c095a307c 100644 --- a/src/execution/frames-inl.h +++ b/src/execution/frames-inl.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) { diff --git a/src/execution/frames.cc b/src/execution/frames.cc index 996f8c9595..98bcdbbc2f 100644 --- a/src/execution/frames.cc +++ b/src/execution/frames.cc @@ -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(pc - holder.InstructionStart()); + holder.GetHeap()->GcSafeCodeContains(holder, old_pc)); + unsigned pc_offset = + static_cast(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( 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; diff --git a/src/execution/frames.h b/src/execution/frames.h index 3ffdee4b05..2eb8c8dfaa 100644 --- a/src/execution/frames.h +++ b/src/execution/frames.h @@ -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); diff --git a/src/execution/pointer-authentication-dummy.h b/src/execution/pointer-authentication-dummy.h new file mode 100644 index 0000000000..32a10dc0dd --- /dev/null +++ b/src/execution/pointer-authentication-dummy.h @@ -0,0 +1,56 @@ +// 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_ diff --git a/src/execution/pointer-authentication.h b/src/execution/pointer-authentication.h new file mode 100644 index 0000000000..f2d63773f4 --- /dev/null +++ b/src/execution/pointer-authentication.h @@ -0,0 +1,65 @@ +// 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_ diff --git a/src/regexp/arm64/regexp-macro-assembler-arm64.cc b/src/regexp/arm64/regexp-macro-assembler-arm64.cc index 56658819b1..ba5d51d660 100644 --- a/src/regexp/arm64/regexp-macro-assembler-arm64.cc +++ b/src/regexp/arm64/regexp-macro-assembler-arm64.cc @@ -740,7 +740,8 @@ Handle RegExpMacroAssemblerARM64::GetCode(Handle source) { DCHECK_EQ(11, kCalleeSaved.Count()); registers_to_retain.Combine(lr); - __ PushCPURegList(registers_to_retain); + DCHECK(registers_to_retain.IncludesAliasOf(lr)); + __ PushCPURegList(registers_to_retain); __ PushCPURegList(argument_registers); // Set frame pointer in place. @@ -1035,7 +1036,7 @@ Handle RegExpMacroAssemblerARM64::GetCode(Handle source) { __ Mov(sp, fp); // Restore registers. - __ PopCPURegList(registers_to_retain); + __ PopCPURegList(registers_to_retain); __ Ret(); @@ -1585,14 +1586,14 @@ void RegExpMacroAssemblerARM64::CallIf(Label* to, Condition condition) { void RegExpMacroAssemblerARM64::RestoreLinkRegister() { - __ Pop(lr, xzr); + __ Pop(padreg, lr); __ Add(lr, lr, Operand(masm_->CodeObject())); } void RegExpMacroAssemblerARM64::SaveLinkRegister() { __ Sub(lr, lr, Operand(masm_->CodeObject())); - __ Push(xzr, lr); + __ Push(lr, padreg); } diff --git a/src/regexp/regexp-macro-assembler.cc b/src/regexp/regexp-macro-assembler.cc index 30a9955dc3..7fd55495ec 100644 --- a/src/regexp/regexp-macro-assembler.cc +++ b/src/regexp/regexp-macro-assembler.cc @@ -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. diff --git a/test/cctest/cctest.h b/test/cctest/cctest.h index 8a5a5a6d31..4db0dea049 100644 --- a/test/cctest/cctest.h +++ b/test/cctest/cctest.h @@ -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(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(simulator_->get_pc()); + state->sp = reinterpret_cast( + simulator_->get_register(v8::internal::Simulator::sp)); + state->fp = reinterpret_cast( + simulator_->get_register(v8::internal::Simulator::r11)); + state->lr = reinterpret_cast( + 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(simulator_->pc()); + state->sp = reinterpret_cast(simulator_->sp()); + state->fp = reinterpret_cast(simulator_->fp()); + state->lr = reinterpret_cast(simulator_->lr()); +#elif V8_TARGET_ARCH_MIPS || V8_TARGET_ARCH_MIPS64 + state->pc = reinterpret_cast(simulator_->get_pc()); + state->sp = reinterpret_cast( + simulator_->get_register(v8::internal::Simulator::sp)); + state->fp = reinterpret_cast( + simulator_->get_register(v8::internal::Simulator::fp)); +#elif V8_TARGET_ARCH_PPC || V8_TARGET_ARCH_PPC64 + state->pc = reinterpret_cast(simulator_->get_pc()); + state->sp = reinterpret_cast( + simulator_->get_register(v8::internal::Simulator::sp)); + state->fp = reinterpret_cast( + simulator_->get_register(v8::internal::Simulator::fp)); + state->lr = reinterpret_cast(simulator_->get_lr()); +#elif V8_TARGET_ARCH_S390 || V8_TARGET_ARCH_S390X + state->pc = reinterpret_cast(simulator_->get_pc()); + state->sp = reinterpret_cast( + simulator_->get_register(v8::internal::Simulator::sp)); + state->fp = reinterpret_cast( + simulator_->get_register(v8::internal::Simulator::fp)); + state->lr = reinterpret_cast( + simulator_->get_register(v8::internal::Simulator::ra)); +#endif + } + + private: + v8::internal::Simulator* simulator_; +}; +#endif // USE_SIMULATOR + #endif // ifndef CCTEST_H_ diff --git a/test/cctest/test-assembler-arm64.cc b/test/cctest/test-assembler-arm64.cc index 0665f7a1f7..52107c6c5a 100644 --- a/test/cctest/test-assembler-arm64.cc +++ b/test/cctest/test-assembler-arm64.cc @@ -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(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(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(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(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(all_regs); + __ PopXRegList(all_regs); __ Drop(12); END(); @@ -14597,13 +14599,13 @@ TEST(near_call_no_relocation) { __ Bind(&test); __ Mov(x0, 0x0); - __ Push(lr, xzr); + __ Push(lr, xzr); { Assembler::BlockConstPoolScope scope(&masm); int offset = (function.pos() - __ pc_offset()) / kInstrSize; __ near_call(offset, RelocInfo::NONE); } - __ Pop(xzr, lr); + __ Pop(xzr, lr); END(); RUN(); diff --git a/test/cctest/test-sampler-api.cc b/test/cctest/test-sampler-api.cc index 3c8f352551..7197101d41 100644 --- a/test/cctest/test-sampler-api.cc +++ b/test/cctest/test-sampler-api.cc @@ -7,7 +7,6 @@ #include #include #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(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(simulator_->get_pc()); - state->sp = reinterpret_cast( - simulator_->get_register(v8::internal::Simulator::sp)); - state->fp = reinterpret_cast( - simulator_->get_register(v8::internal::Simulator::r11)); - state->lr = reinterpret_cast( - 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(simulator_->pc()); - state->sp = reinterpret_cast(simulator_->sp()); - state->fp = reinterpret_cast(simulator_->fp()); - state->lr = reinterpret_cast(simulator_->lr()); -#elif V8_TARGET_ARCH_MIPS || V8_TARGET_ARCH_MIPS64 - state->pc = reinterpret_cast(simulator_->get_pc()); - state->sp = reinterpret_cast( - simulator_->get_register(v8::internal::Simulator::sp)); - state->fp = reinterpret_cast( - simulator_->get_register(v8::internal::Simulator::fp)); -#elif V8_TARGET_ARCH_PPC || V8_TARGET_ARCH_PPC64 - state->pc = reinterpret_cast(simulator_->get_pc()); - state->sp = reinterpret_cast( - simulator_->get_register(v8::internal::Simulator::sp)); - state->fp = reinterpret_cast( - simulator_->get_register(v8::internal::Simulator::fp)); - state->lr = reinterpret_cast(simulator_->get_lr()); -#elif V8_TARGET_ARCH_S390 || V8_TARGET_ARCH_S390X - state->pc = reinterpret_cast(simulator_->get_pc()); - state->sp = reinterpret_cast( - simulator_->get_register(v8::internal::Simulator::sp)); - state->fp = reinterpret_cast( - simulator_->get_register(v8::internal::Simulator::fp)); - state->lr = reinterpret_cast( - simulator_->get_register(v8::internal::Simulator::ra)); -#endif - } - - private: - v8::internal::Simulator* simulator_; -}; -#endif // USE_SIMULATOR - - class SamplingTestHelper { public: struct CodeEventEntry { diff --git a/test/cctest/test-unwinder-code-pages.cc b/test/cctest/test-unwinder-code-pages.cc index 6177be6de8..fc023e4145 100644 --- a/test/cctest/test-unwinder-code-pages.cc +++ b/test/cctest/test-unwinder-code-pages.cc @@ -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 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& args) { + instance_->DoTryUnwind(); + } + + void DoTryUnwind() { + // Set up RegisterState. + v8::RegisterState register_state; + SimulatorHelper simulator_helper; + if (!simulator_helper.Init(isolate_)) return; + simulator_helper.FillRegisters(®ister_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(0xffffffffffffffffL); + bool unwound = v8::Unwinder::TryUnwindV8Frames( + entry_stubs, pages_length, code_pages, ®ister_state, stack_base); + // Check that we have successfully unwound past js_entry_sp. + CHECK(unwound); + CHECK_GT(register_state.sp, + reinterpret_cast(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 diff --git a/test/cctest/test-unwinder.cc b/test/cctest/test-unwinder.cc index ffe46f4ca2..59c8708767 100644 --- a/test/cctest/test-unwinder.cc +++ b/test/cctest/test-unwinder.cc @@ -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
(&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(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(code + 10); + StorePc(stack, 1, reinterpret_cast(code + 10)); stack[2] = reinterpret_cast(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(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(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(stack + 5); // saved FP (rbp). // The fake return address is in the JS code range. - stack[3] = reinterpret_cast(code + 10); + StorePc(stack, 3, reinterpret_cast(code + 10)); stack[4] = 141; stack[5] = reinterpret_cast(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(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(stack + 9); // saved FP (rbp). - stack[6] = reinterpret_cast(code + 20); // JS code. + StorePc(stack, 6, reinterpret_cast(code + 20)); // JS code. stack[7] = 303; // The SP points here in the caller's frame. stack[8] = 404; stack[9] = reinterpret_cast(stack) + (12 * sizeof(uintptr_t)); // saved FP (OOB). - stack[10] = reinterpret_cast(code + 20); // JS code. + StorePc(stack, 10, reinterpret_cast(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, ®ister_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 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& args) { + instance_->DoTryUnwind(); + } + + void DoTryUnwind() { + // Set up RegisterState. + v8::RegisterState register_state; + SimulatorHelper simulator_helper; + if (!simulator_helper.Init(isolate_)) return; + simulator_helper.FillRegisters(®ister_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(0xffffffffffffffffL); + bool unwound = v8::Unwinder::TryUnwindV8Frames(unwind_state, + ®ister_state, stack_base); + // Check that we have successfully unwound past js_entry_sp. + CHECK(unwound); + CHECK_GT(register_state.sp, + reinterpret_cast(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