mirror of
https://github.com/nodejs/node.git
synced 2025-05-15 10:01:49 +00:00

PR-URL: https://github.com/nodejs/node/pull/49639 Reviewed-By: Jiawen Geng <technicalcute@gmail.com> Reviewed-By: Rafael Gonzaga <rafael.nunu@hotmail.com> Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
233 lines
7.8 KiB
C++
233 lines
7.8 KiB
C++
// 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.
|
|
|
|
#include "src/codegen/arm64/decoder-arm64-inl.h"
|
|
#include "src/execution/arm64/simulator-arm64.h"
|
|
#include "src/execution/pointer-authentication.h"
|
|
#include "test/unittests/test-utils.h"
|
|
#include "testing/gtest/include/gtest/gtest.h"
|
|
|
|
#ifdef V8_OS_LINUX
|
|
|
|
#include <sys/prctl.h> // for prctl
|
|
|
|
#ifndef PR_PAC_APIBKEY
|
|
#define PR_PAC_APIBKEY (1UL << 1)
|
|
#endif
|
|
|
|
#ifndef PR_PAC_GET_ENABLED_KEYS
|
|
#define PR_PAC_GET_ENABLED_KEYS 61
|
|
#endif
|
|
|
|
#endif
|
|
|
|
namespace v8 {
|
|
namespace internal {
|
|
|
|
namespace {
|
|
Address SignPCForTesting(Isolate* isolate, Address pc, Address sp) {
|
|
if constexpr (!ENABLE_CONTROL_FLOW_INTEGRITY_BOOL) return pc;
|
|
|
|
#ifdef USE_SIMULATOR
|
|
pc = Simulator::AddPAC(pc, sp, Simulator::kPACKeyIB,
|
|
Simulator::kInstructionPointer);
|
|
#else
|
|
asm volatile(
|
|
" mov x17, %[pc]\n"
|
|
" mov x16, %[sp]\n"
|
|
" pacib1716\n"
|
|
" mov %[pc], x17\n"
|
|
: [pc] "+r"(pc)
|
|
: [sp] "r"(sp)
|
|
: "x16", "x17");
|
|
#endif
|
|
return pc;
|
|
}
|
|
|
|
void FunctionToUseForPointerAuthentication() { PrintF("hello, "); }
|
|
void AlternativeFunctionToUseForPointerAuthentication() { PrintF("world\n"); }
|
|
|
|
Address GetRawCodeAddress() {
|
|
return PointerAuthentication::StripPAC(
|
|
reinterpret_cast<Address>(&FunctionToUseForPointerAuthentication));
|
|
}
|
|
|
|
Address GetAlternativeRawCodeAddress() {
|
|
return PointerAuthentication::StripPAC(reinterpret_cast<Address>(
|
|
&AlternativeFunctionToUseForPointerAuthentication));
|
|
}
|
|
|
|
// If the platform supports it, corrupt the PAC of |address| so that
|
|
// authenticating it will cause a crash.
|
|
base::Optional<Address> CorruptPACIfSupported(Isolate* isolate,
|
|
Address address) {
|
|
if constexpr (!ENABLE_CONTROL_FLOW_INTEGRITY_BOOL) return base::nullopt;
|
|
|
|
// First, find where in an address the PAC is located.
|
|
|
|
// Produce a valid user address with all bits sets. This way, stripping the
|
|
// PAC from the address will reveal which bits are used for it. We need to
|
|
// clear the TTBR bit, as it differentiates between user and kernel addresses.
|
|
Address user_address_all_ones = 0xffff'ffff'ffff'ffff & ~kTTBRMask;
|
|
Address pac_mask = PointerAuthentication::StripPAC(user_address_all_ones) ^
|
|
user_address_all_ones;
|
|
|
|
// If the PAC bits are zero then StripPAC() was a no-op. This means pointer
|
|
// authentication isn't supported.
|
|
if (pac_mask == 0) {
|
|
return base::nullopt;
|
|
}
|
|
|
|
// At this point, pointer authentication is supported, but individual keys
|
|
// may still be disabled by the OS. We check that it's enabled, to ensure
|
|
// that corrupting the PAC will result in a crash.
|
|
|
|
#if defined(V8_OS_LINUX) && defined(V8_HOST_ARCH_ARM64)
|
|
// On Linux 5.13 and later, we can ask the OS what keys are enabled directly.
|
|
int enabled_keys = prctl(PR_PAC_GET_ENABLED_KEYS, 0, 0, 0, 0);
|
|
if ((enabled_keys != -1) &&
|
|
((enabled_keys & PR_PAC_APIBKEY) == PR_PAC_APIBKEY)) {
|
|
return address ^ pac_mask;
|
|
}
|
|
#endif
|
|
|
|
// Do a "best-effort" check to see if PAC is enabled, by signing values on the
|
|
// stack and check if bits are set in the PAC range. We do this check a few
|
|
// times because 0 is a valid PAC.
|
|
|
|
Address stack[] = {0xa, 0xb, 0xc, 0xd};
|
|
for (int slot = 0; slot < 4; slot++) {
|
|
stack[slot] = SignPCForTesting(isolate, stack[slot],
|
|
reinterpret_cast<Address>(&stack[slot]));
|
|
if ((stack[slot] & pac_mask) != 0) return address ^ pac_mask;
|
|
}
|
|
|
|
// None of the slots were signed with a non-zero PAC, assume this means PAC
|
|
// isn't enabled.
|
|
return base::nullopt;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
using PointerAuthArm64Test = TestWithIsolate;
|
|
|
|
TEST_F(PointerAuthArm64Test, AuthenticatePC) {
|
|
Address pc = GetRawCodeAddress();
|
|
Address stack = 0;
|
|
|
|
stack = SignPCForTesting(i_isolate(), pc, reinterpret_cast<Address>(&stack));
|
|
|
|
pc = PointerAuthentication::AuthenticatePC(&stack, 0);
|
|
EXPECT_EQ(pc, GetRawCodeAddress());
|
|
|
|
if (auto corrupted_stack = CorruptPACIfSupported(i_isolate(), stack)) {
|
|
stack = *corrupted_stack;
|
|
EXPECT_DEATH_IF_SUPPORTED(PointerAuthentication::AuthenticatePC(&stack, 0),
|
|
"");
|
|
}
|
|
}
|
|
|
|
TEST_F(PointerAuthArm64Test, ReplacePC) {
|
|
Address pc = GetRawCodeAddress();
|
|
Address stack = 0;
|
|
|
|
stack = SignPCForTesting(i_isolate(), pc, reinterpret_cast<Address>(&stack));
|
|
|
|
PointerAuthentication::ReplacePC(&stack, GetAlternativeRawCodeAddress(), 0);
|
|
|
|
if (auto corrupted_stack = CorruptPACIfSupported(i_isolate(), stack)) {
|
|
stack = *corrupted_stack;
|
|
EXPECT_DEATH_IF_SUPPORTED(PointerAuthentication::ReplacePC(
|
|
&stack, GetAlternativeRawCodeAddress(), 0),
|
|
"");
|
|
}
|
|
}
|
|
|
|
TEST_F(PointerAuthArm64Test, ReplacePCAfterGC) {
|
|
v8::PageAllocator* page_allocator = v8::internal::GetPlatformPageAllocator();
|
|
size_t page_size = v8::internal::AllocatePageSize();
|
|
|
|
// Allocate a page and mark it inaccessible, to simulate a code address to a
|
|
// page that was reclaimed after a GC.
|
|
Address pc = reinterpret_cast<Address>(v8::internal::AllocatePages(
|
|
page_allocator, page_allocator->GetRandomMmapAddr(), page_size, page_size,
|
|
PageAllocator::Permission::kReadWrite));
|
|
CHECK(SetPermissions(page_allocator, pc, page_size,
|
|
PageAllocator::Permission::kNoAccess));
|
|
|
|
// Replacing the signed PC on the stack should work even when the previous PC
|
|
// points to an inaccessible page.
|
|
|
|
Address stack = 0;
|
|
stack = SignPCForTesting(i_isolate(), pc, reinterpret_cast<Address>(&stack));
|
|
|
|
PointerAuthentication::ReplacePC(&stack, GetRawCodeAddress(), 0);
|
|
}
|
|
|
|
#ifdef USE_SIMULATOR
|
|
TEST_F(PointerAuthArm64Test, SimulatorComputePAC) {
|
|
Decoder<DispatchingDecoderVisitor>* decoder =
|
|
new Decoder<DispatchingDecoderVisitor>();
|
|
Simulator simulator(decoder);
|
|
|
|
uint64_t data1 = 0xfb623599da6e8127;
|
|
uint64_t data2 = 0x27979fadf7d53cb7;
|
|
uint64_t context = 0x477d469dec0b8762;
|
|
Simulator::PACKey key = {0x84be85ce9804e94b, 0xec2802d4e0a488e9, -1};
|
|
|
|
uint64_t pac1 = simulator.ComputePAC(data1, context, key);
|
|
uint64_t pac2 = simulator.ComputePAC(data2, context, key);
|
|
|
|
// NOTE: If the PAC implementation is changed, this may fail due to a hash
|
|
// collision.
|
|
CHECK_NE(pac1, pac2);
|
|
}
|
|
|
|
TEST_F(PointerAuthArm64Test, SimulatorAddAndAuthPAC) {
|
|
i::v8_flags.sim_abort_on_bad_auth = false;
|
|
Decoder<DispatchingDecoderVisitor>* decoder =
|
|
new Decoder<DispatchingDecoderVisitor>();
|
|
Simulator simulator(decoder);
|
|
|
|
uint64_t ptr = 0x0000000012345678;
|
|
uint64_t context = 0x477d469dec0b8762;
|
|
Simulator::PACKey key_a = {0x84be85ce9804e94b, 0xec2802d4e0a488e9, 0};
|
|
Simulator::PACKey key_b = {0xec1119e288704d13, 0xd7f6b76e1cea585e, 1};
|
|
|
|
uint64_t ptr_a =
|
|
simulator.AddPAC(ptr, context, key_a, Simulator::kInstructionPointer);
|
|
|
|
// Attempt to authenticate the pointer with PAC using different keys.
|
|
uint64_t success =
|
|
simulator.AuthPAC(ptr_a, context, key_a, Simulator::kInstructionPointer);
|
|
uint64_t fail =
|
|
simulator.AuthPAC(ptr_a, context, key_b, Simulator::kInstructionPointer);
|
|
|
|
uint64_t pac_mask =
|
|
simulator.CalculatePACMask(ptr, Simulator::kInstructionPointer, 0);
|
|
|
|
// NOTE: If the PAC implementation is changed, this may fail due to a hash
|
|
// collision.
|
|
CHECK_NE((ptr_a & pac_mask), 0);
|
|
CHECK_EQ(success, ptr);
|
|
CHECK_NE(fail, ptr);
|
|
}
|
|
|
|
TEST_F(PointerAuthArm64Test, SimulatorAddAndStripPAC) {
|
|
Decoder<DispatchingDecoderVisitor>* decoder =
|
|
new Decoder<DispatchingDecoderVisitor>();
|
|
Simulator simulator(decoder);
|
|
|
|
uint64_t ptr = 0xff00000012345678;
|
|
uint64_t pac_mask =
|
|
simulator.CalculatePACMask(ptr, Simulator::kInstructionPointer, 0);
|
|
uint64_t ptr_a = ptr | pac_mask;
|
|
|
|
CHECK_EQ(simulator.StripPAC(ptr_a, Simulator::kInstructionPointer), ptr);
|
|
}
|
|
#endif // USE_SIMULATOR
|
|
|
|
} // namespace internal
|
|
} // namespace v8
|