Reland "[arm64] Protect return addresses stored on stack"
This is a reland of 137bfe47c9af56dcf8466e2736579616e51b86df
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/+/2051958
Reviewed-by: Georg Neis <neis@chromium.org>
Reviewed-by: Ross McIlroy <rmcilroy@chromium.org>
Commit-Queue: Georgia Kouveli <georgia.kouveli@arm.com>
Cr-Commit-Position: refs/heads/master@{#66254}
diff --git a/BUILD.gn b/BUILD.gn
index 9999cc0..a0ffefb 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -214,6 +214,10 @@
# 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_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 @@
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 @@
}
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 @@
"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 @@
"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 @@
"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 ddcf3af4..39d159f 100644
--- a/src/builtins/arm64/builtins-arm64.cc
+++ b/src/builtins/arm64/builtins-arm64.cc
@@ -1187,7 +1187,7 @@
// 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 @@
// Restore fp, lr.
__ Mov(sp, fp);
- __ Pop(fp, lr);
+ __ Pop<TurboAssembler::kAuthLR>(fp, lr);
__ LoadEntryFromBuiltinIndex(builtin);
__ Jump(builtin);
@@ -2053,7 +2053,7 @@
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 @@
// 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 @@
// 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();
}
diff --git a/src/codegen/arm64/macro-assembler-arm64-inl.h b/src/codegen/arm64/macro-assembler-arm64-inl.h
index 0128ec0..962533d 100644
--- a/src/codegen/arm64/macro-assembler-arm64-inl.h
+++ b/src/codegen/arm64/macro-assembler-arm64-inl.h
@@ -1075,6 +1075,166 @@
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();
diff --git a/src/codegen/arm64/macro-assembler-arm64.cc b/src/codegen/arm64/macro-assembler-arm64.cc
index 874aec2..6099f6b 100644
--- a/src/codegen/arm64/macro-assembler-arm64.cc
+++ b/src/codegen/arm64/macro-assembler-arm64.cc
@@ -60,7 +60,7 @@
list.Remove(exclusion);
list.Align();
- PushCPURegList(list);
+ PushCPURegList<kSignLR>(list);
int bytes = list.Count() * kXRegSizeInBits / 8;
@@ -84,7 +84,7 @@
list.Remove(exclusion);
list.Align();
- PopCPURegList(list);
+ PopCPURegList<kAuthLR>(list);
bytes += list.Count() * kXRegSizeInBits / 8;
return bytes;
@@ -1046,17 +1046,6 @@
// 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,
const CPURegister& src6, const CPURegister& src7) {
@@ -1071,21 +1060,6 @@
}
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,
const CPURegister& dst6, const CPURegister& dst7) {
@@ -1103,48 +1077,6 @@
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::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::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<int>(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<int>(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 @@
// 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<int64_t>(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<int64_t>(kXRegSize)));
+ Cmp(x16, x17);
Check(eq, AbortReason::kReturnAddressNotFoundInFrame);
}
@@ -2096,8 +2021,7 @@
// 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 @@
TryConvertDoubleToInt64(result, double_input, &done);
// If we fell through then inline version didn't succeed - call stub instead.
- Push(lr, double_input);
+ Push<TurboAssembler::kSignLR>(lr, double_input);
// DoubleToI preserves any registers it needs to clobber.
if (stub_mode == StubCallMode::kCallWasmRuntimeStub) {
@@ -2322,7 +2246,8 @@
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<TurboAssembler::kAuthLR>(xzr, lr);
Bind(&done);
// Keep our invariant that the upper 32 bits are zero.
@@ -2330,7 +2255,7 @@
}
void TurboAssembler::Prologue() {
- Push(lr, fp, cp, x1);
+ Push<TurboAssembler::kSignLR>(lr, fp, cp, x1);
Add(fp, sp, StandardFrameConstants::kFixedFrameSizeFromFp);
}
@@ -2341,7 +2266,7 @@
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<TurboAssembler::kSignLR>(lr, fp, type_reg, type_reg);
const int kFrameSize =
TypedFrameConstants::kFixedFrameSizeFromFp + kSystemPointerSize;
Add(fp, sp, kFrameSize);
@@ -2354,7 +2279,7 @@
type == StackFrame::WASM_EXIT) {
Register type_reg = temps.AcquireX();
Mov(type_reg, StackFrame::TypeToMarker(type));
- Push(lr, fp);
+ Push<TurboAssembler::kSignLR>(lr, fp);
Mov(fp, sp);
Push(type_reg, padreg);
// sp[3] : lr
@@ -2368,7 +2293,7 @@
// 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<TurboAssembler::kSignLR>(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 @@
// 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<TurboAssembler::kAuthLR>(fp, lr);
}
void MacroAssembler::ExitFramePreserveFPRegs() {
@@ -2415,7 +2340,7 @@
frame_type == StackFrame::BUILTIN_EXIT);
// Set up the new stack frame.
- Push(lr, fp);
+ Push<TurboAssembler::kSignLR>(lr, fp);
Mov(fp, sp);
Mov(scratch, StackFrame::TypeToMarker(frame_type));
Push(scratch, xzr);
@@ -2498,7 +2423,7 @@
// fp -> fp[0]: CallerFP (old fp)
// fp[...]: The rest of the frame.
Mov(sp, fp);
- Pop(fp, lr);
+ Pop<TurboAssembler::kAuthLR>(fp, lr);
}
void MacroAssembler::LoadGlobalProxy(Register dst) {
@@ -2751,25 +2676,19 @@
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 @@
// Record the actual write.
if (lr_status == kLRHasNotBeenSaved) {
- Push(padreg, lr);
+ Push<TurboAssembler::kSignLR>(padreg, lr);
}
CallRecordWriteStub(object, offset, remembered_set_action, fp_mode);
if (lr_status == kLRHasNotBeenSaved) {
- Pop(lr, padreg);
+ Pop<TurboAssembler::kAuthLR>(lr, padreg);
}
Bind(&done);
@@ -3183,7 +3102,7 @@
// 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<kDontSignLR>(saved_registers);
PushCPURegList(kCallerSavedV);
// We can use caller-saved registers as scratch values (except for argN).
@@ -3236,7 +3155,7 @@
}
PopCPURegList(kCallerSavedV);
- PopCPURegList(saved_registers);
+ PopCPURegList<kDontAuthLR>(saved_registers);
TmpList()->set_list(old_tmp_list);
FPTmpList()->set_list(old_fp_tmp_list);
@@ -3274,6 +3193,35 @@
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 735536c..965ec20 100644
--- a/src/codegen/arm64/macro-assembler-arm64.h
+++ b/src/codegen/arm64/macro-assembler-arm64.h
@@ -783,20 +783,33 @@
// 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 @@
// 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 @@
// 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 @@
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 @@
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 @@
// 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 @@
// 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 c95bf98..46a3612 100644
--- a/src/compiler/backend/arm64/code-generator-arm64.cc
+++ b/src/compiler/backend/arm64/code-generator-arm64.cc
@@ -291,7 +291,7 @@
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 @@
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 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 @@
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 @@
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 @@
// 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 @@
// 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,
diff --git a/src/compiler/backend/arm64/unwinding-info-writer-arm64.cc b/src/compiler/backend/arm64/unwinding-info-writer-arm64.cc
index 3747019..c8a570a 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 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 96cd8a7..251856e 100644
--- a/src/debug/arm64/debug-arm64.cc
+++ b/src/debug/arm64/debug-arm64.cc
@@ -38,7 +38,7 @@
__ 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));
diff --git a/src/deoptimizer/arm/deoptimizer-arm.cc b/src/deoptimizer/arm/deoptimizer-arm.cc
index becdc93..5ca6807 100644
--- a/src/deoptimizer/arm/deoptimizer-arm.cc
+++ b/src/deoptimizer/arm/deoptimizer-arm.cc
@@ -262,6 +262,8 @@
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 300e65a..1a13377 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 @@
__ 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 @@
}
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 @@
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 56431d4..2e29ac9 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 @@
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 @@
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);
diff --git a/src/deoptimizer/deoptimizer.h b/src/deoptimizer/deoptimizer.h
index beb2a9a..58c6556 100644
--- a/src/deoptimizer/deoptimizer.h
+++ b/src/deoptimizer/deoptimizer.h
@@ -712,7 +712,7 @@
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 2fd424a..fe14a3b 100644
--- a/src/deoptimizer/ia32/deoptimizer-ia32.cc
+++ b/src/deoptimizer/ia32/deoptimizer-ia32.cc
@@ -217,6 +217,8 @@
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 bf82e26..a7c3740 100644
--- a/src/deoptimizer/mips/deoptimizer-mips.cc
+++ b/src/deoptimizer/mips/deoptimizer-mips.cc
@@ -236,6 +236,8 @@
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 a1138d2..127c0f9 100644
--- a/src/deoptimizer/mips64/deoptimizer-mips64.cc
+++ b/src/deoptimizer/mips64/deoptimizer-mips64.cc
@@ -236,6 +236,8 @@
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 1d05968..8e872e0 100644
--- a/src/deoptimizer/ppc/deoptimizer-ppc.cc
+++ b/src/deoptimizer/ppc/deoptimizer-ppc.cc
@@ -258,6 +258,8 @@
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 63ea22f..9833457 100644
--- a/src/deoptimizer/s390/deoptimizer-s390.cc
+++ b/src/deoptimizer/s390/deoptimizer-s390.cc
@@ -254,6 +254,8 @@
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 724062c..e41bf88 100644
--- a/src/deoptimizer/x64/deoptimizer-x64.cc
+++ b/src/deoptimizer/x64/deoptimizer-x64.cc
@@ -221,18 +221,10 @@
}
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 @@
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 64adf17..c08fe20 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 @@
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 @@
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,
diff --git a/src/execution/arm64/pointer-authentication-arm64.h b/src/execution/arm64/pointer-authentication-arm64.h
new file mode 100644
index 0000000..c54a59f
--- /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<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_
diff --git a/src/execution/frames-inl.h b/src/execution/frames-inl.h
index f1b979b..0c095a30 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 @@
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 996f8c9..98bcdbb 100644
--- a/src/execution/frames.cc
+++ b/src/execution/frames.cc
@@ -491,16 +491,18 @@
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 @@
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 @@
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 3ffdee4..2eb8c8d 100644
--- a/src/execution/frames.h
+++ b/src/execution/frames.h
@@ -215,9 +215,7 @@
// 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 @@
// 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 @@
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 0000000..32a10dc
--- /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 0000000..f2d6377
--- /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 5665881..ba5d51d 100644
--- a/src/regexp/arm64/regexp-macro-assembler-arm64.cc
+++ b/src/regexp/arm64/regexp-macro-assembler-arm64.cc
@@ -740,7 +740,8 @@
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 @@
__ Mov(sp, fp);
// Restore registers.
- __ PopCPURegList(registers_to_retain);
+ __ PopCPURegList<TurboAssembler::kAuthLR>(registers_to_retain);
__ Ret();
@@ -1585,14 +1586,14 @@
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);
}
diff --git a/src/regexp/regexp-macro-assembler.cc b/src/regexp/regexp-macro-assembler.cc
index 30a9955..7fd5549 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 @@
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 @@
}
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 8a5a5a6..4db0dea 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 @@
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_
diff --git a/test/cctest/test-assembler-arm64.cc b/test/cctest/test-assembler-arm64.cc
index 0665f7a..52107c6 100644
--- a/test/cctest/test-assembler-arm64.cc
+++ b/test/cctest/test-assembler-arm64.cc
@@ -12227,7 +12227,8 @@
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 @@
}
break;
case PushPopRegList:
- __ PushSizeRegList(list, reg_size);
+ __ PushSizeRegList<TurboAssembler::kDontSignLR>(list, reg_size);
break;
}
@@ -12251,7 +12252,8 @@
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 @@
}
break;
case PushPopRegList:
- __ PopSizeRegList(list, reg_size);
+ __ PopSizeRegList<TurboAssembler::kDontAuthLR>(list, reg_size);
break;
}
}
@@ -12597,8 +12599,8 @@
__ 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 @@
__ 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();
diff --git a/test/cctest/test-sampler-api.cc b/test/cctest/test-sampler-api.cc
index 3c8f352..7197101 100644
--- a/test/cctest/test-sampler-api.cc
+++ b/test/cctest/test-sampler-api.cc
@@ -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 @@
};
-#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 {
diff --git a/test/cctest/test-unwinder-code-pages.cc b/test/cctest/test-unwinder-code-pages.cc
index 6177be6..fc023e4 100644
--- a/test/cctest/test-unwinder-code-pages.cc
+++ b/test/cctest/test-unwinder-code-pages.cc
@@ -591,6 +591,80 @@
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(®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<void*>(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<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
diff --git a/test/cctest/test-unwinder.cc b/test/cctest/test-unwinder.cc
index ffe46f4..59c8708 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
// 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 @@
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(®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<void*>(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<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