|  | // Copyright 2021 The Chromium Authors | 
|  | // Use of this source code is governed by a BSD-style license that can be | 
|  | // found in the LICENSE file. | 
|  |  | 
|  | #include "gin/v8_platform_page_allocator.h" | 
|  |  | 
|  | #include "base/check_op.h" | 
|  | #include "base/cpu.h" | 
|  | #include "build/build_config.h" | 
|  | #include "testing/gtest/include/gtest/gtest.h" | 
|  |  | 
|  | // includes for Branch Target Instruction tests | 
|  | #if defined(ARCH_CPU_ARM64) && (OS_LINUX || OS_ANDROID) | 
|  | // BTI is only available for AArch64, relevant platform are Android and Linux | 
|  |  | 
|  | #include "base/allocator/partition_allocator/arm_bti_test_functions.h" | 
|  | #include "base/allocator/partition_allocator/page_allocator_constants.h" | 
|  | #if BUILDFLAG(IS_POSIX) | 
|  | #include <signal.h> | 
|  | #include "testing/gtest/include/gtest/gtest-death-test.h" | 
|  | #endif | 
|  | #endif  // defined(ARCH_CPU_ARM64) && (OS_LINUX || OS_ANDROID) | 
|  |  | 
|  | namespace gin { | 
|  |  | 
|  | TEST(V8PlatformPageAllocatorTest, VerifyGetPageConfig) { | 
|  | auto sut = gin::PageAllocator(); | 
|  |  | 
|  | CHECK_EQ(sut.GetPageConfigPermissionsForTesting(v8::PageAllocator::kNoAccess), | 
|  | partition_alloc::PageAccessibilityConfiguration::kInaccessible); | 
|  | CHECK_EQ(sut.GetPageConfigPermissionsForTesting(v8::PageAllocator::kRead), | 
|  | partition_alloc::PageAccessibilityConfiguration::kRead); | 
|  | CHECK_EQ( | 
|  | sut.GetPageConfigPermissionsForTesting(v8::PageAllocator::kReadWrite), | 
|  | partition_alloc::PageAccessibilityConfiguration::kReadWrite); | 
|  | CHECK_EQ(sut.GetPageConfigPermissionsForTesting( | 
|  | v8::PageAllocator::kReadWriteExecute), | 
|  | partition_alloc::PageAccessibilityConfiguration::kReadWriteExecute); | 
|  |  | 
|  | #if defined(__ARM_FEATURE_BTI_DEFAULT) | 
|  | CHECK_EQ( | 
|  | sut.GetPageConfigPermissionsForTesting(v8::PageAllocator::kReadExecute), | 
|  | base::CPU::GetInstanceNoAllocation().has_bti() | 
|  | ? partition_alloc::PageAccessibilityConfiguration:: | 
|  | kReadExecuteProtected | 
|  | : partition_alloc::PageAccessibilityConfiguration::kReadExecute); | 
|  | #else | 
|  | CHECK_EQ( | 
|  | sut.GetPageConfigPermissionsForTesting(v8::PageAllocator::kReadExecute), | 
|  | partition_alloc::PageAccessibilityConfiguration::kReadExecute); | 
|  | #endif | 
|  |  | 
|  | CHECK_EQ(sut.GetPageConfigPermissionsForTesting( | 
|  | v8::PageAllocator::kNoAccessWillJitLater), | 
|  | partition_alloc::PageAccessibilityConfiguration::kInaccessible); | 
|  | } | 
|  |  | 
|  | #if defined(ARCH_CPU_ARM64) && (OS_LINUX || OS_ANDROID) | 
|  |  | 
|  | using BTITestFunction = int64_t (*)(int64_t); | 
|  |  | 
|  | TEST(V8PlatformPageAllocatorBTITest, VerifyReadExecutePagesAreProtected) { | 
|  | auto page_allocator = gin::PageAllocator(); | 
|  |  | 
|  | auto const memory_size = | 
|  | partition_alloc::internal::PageAllocationGranularity(); | 
|  | auto const memory_alignment = | 
|  | partition_alloc::internal::PageAllocationGranularity(); | 
|  |  | 
|  | // Next, map some read-write memory and copy some test helper functions there. | 
|  | char* const buffer = reinterpret_cast<char*>(page_allocator.AllocatePages( | 
|  | nullptr, memory_size, memory_alignment, | 
|  | v8::PageAllocator::Permission::kReadWriteExecute)); | 
|  |  | 
|  | ptrdiff_t const function_range = | 
|  | reinterpret_cast<char*>(arm_bti_test_function_end) - | 
|  | reinterpret_cast<char*>(arm_bti_test_function); | 
|  | ptrdiff_t const invalid_offset = | 
|  | reinterpret_cast<char*>(arm_bti_test_function_invalid_offset) - | 
|  | reinterpret_cast<char*>(arm_bti_test_function); | 
|  |  | 
|  | // ensure alignment to 4 bytes required by function call convention | 
|  | EXPECT_EQ(0u, ((uint64_t)buffer) % 4); | 
|  | EXPECT_EQ(0u, ((uint64_t)function_range) % 4); | 
|  | EXPECT_EQ(0u, ((uint64_t)invalid_offset) % 4); | 
|  |  | 
|  | memcpy(buffer, reinterpret_cast<void*>(arm_bti_test_function), | 
|  | function_range); | 
|  |  | 
|  | // Next re-protect the page to the permission level to test | 
|  | page_allocator.SetPermissions(buffer, memory_size, | 
|  | v8::PageAllocator::Permission::kReadExecute); | 
|  |  | 
|  | // Attempt to call a function with BTI landing pad. | 
|  | BTITestFunction const bti_enabled_fn = | 
|  | reinterpret_cast<BTITestFunction>(buffer); | 
|  |  | 
|  | // bti_enabled_fn must return 18, no matter if BTI is actually enabled or not. | 
|  | EXPECT_EQ(bti_enabled_fn(15), 18); | 
|  |  | 
|  | // Next, attempt to call a function without BTI landing pad. | 
|  | BTITestFunction const bti_invalid_fn = | 
|  | reinterpret_cast<BTITestFunction>(buffer + invalid_offset); | 
|  |  | 
|  | // Expectation for behaviour of bti_invalid_fn depends on the capabilities of | 
|  | // the actual CPU we are running on. The code that were are trying to execute | 
|  | // is assembly code and always has BTI enabled. | 
|  | if (base::CPU::GetInstanceNoAllocation().has_bti()) { | 
|  | #if BUILDFLAG(IS_POSIX)  // signal handling is available on POSIX compliant | 
|  | // systems only | 
|  | EXPECT_EXIT({ bti_invalid_fn(15); }, testing::KilledBySignal(SIGILL), | 
|  | "");  // Should crash with SIGILL. | 
|  | #endif                // BUILDFLAG(IS_POSIX) | 
|  | } else { | 
|  | EXPECT_EQ(bti_invalid_fn(15), 17); | 
|  | } | 
|  |  | 
|  | page_allocator.FreePages(buffer, memory_size); | 
|  | } | 
|  |  | 
|  | TEST(V8PlatformAllocatorBTITest, VerifyReadWriteExecutePagesAreNotProtected) { | 
|  | auto page_allocator = gin::PageAllocator(); | 
|  |  | 
|  | auto const memory_size = | 
|  | partition_alloc::internal::PageAllocationGranularity(); | 
|  | auto const memory_alignment = | 
|  | partition_alloc::internal::PageAllocationGranularity(); | 
|  |  | 
|  | // Next, map some read-write memory and copy some test helper functions there. | 
|  | char* const buffer = reinterpret_cast<char*>(page_allocator.AllocatePages( | 
|  | nullptr, memory_size, memory_alignment, | 
|  | v8::PageAllocator::Permission::kReadWriteExecute)); | 
|  |  | 
|  | ptrdiff_t const function_range = | 
|  | reinterpret_cast<char*>(arm_bti_test_function_end) - | 
|  | reinterpret_cast<char*>(arm_bti_test_function); | 
|  | ptrdiff_t const invalid_offset = | 
|  | reinterpret_cast<char*>(arm_bti_test_function_invalid_offset) - | 
|  | reinterpret_cast<char*>(arm_bti_test_function); | 
|  |  | 
|  | // ensure alignment to 4 bytes required by function call convention | 
|  | EXPECT_EQ(0u, ((uint64_t)buffer) % 4); | 
|  | EXPECT_EQ(0u, ((uint64_t)function_range) % 4); | 
|  | EXPECT_EQ(0u, ((uint64_t)invalid_offset) % 4); | 
|  |  | 
|  | memcpy(buffer, reinterpret_cast<void*>(arm_bti_test_function), | 
|  | function_range); | 
|  |  | 
|  | // Attempt to call a function with BTI landing pad. | 
|  | BTITestFunction const bti_enabled_fn = | 
|  | reinterpret_cast<BTITestFunction>(buffer); | 
|  |  | 
|  | // bti_enabled_fn must return 18, no matter if BTI is actually enabled or not. | 
|  | EXPECT_EQ(bti_enabled_fn(15), 18); | 
|  |  | 
|  | // Next, attempt to call a function without BTI landing pad. | 
|  | BTITestFunction const bti_invalid_fn = | 
|  | reinterpret_cast<BTITestFunction>(buffer + invalid_offset); | 
|  |  | 
|  | // Since permission kReadWriteExecute wont actually cause BTI to be enabled | 
|  | // for the allocated page, calling this function must return without error. | 
|  | EXPECT_EQ(bti_invalid_fn(15), 17); | 
|  |  | 
|  | page_allocator.FreePages(buffer, memory_size); | 
|  | } | 
|  | #endif  // if defined(ARCH_CPU_ARM64) && (OS_LINUX || OS_ANDROID) | 
|  |  | 
|  | }  // namespace gin |