#include "base/allocator/partition_allocator/partition_alloc_buildflags.h"
#if BUILDFLAG(ENABLE_PKEYS)
#include <link.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include "base/allocator/partition_allocator/page_allocator.h"
#include "base/allocator/partition_allocator/page_allocator_constants.h"
#include "base/allocator/partition_allocator/partition_alloc.h"
#include "base/allocator/partition_allocator/partition_alloc_base/no_destructor.h"
#include "base/allocator/partition_allocator/partition_alloc_forward.h"
#include "base/allocator/partition_allocator/pkey.h"
#include "testing/gtest/include/gtest/gtest.h"
#define ISOLATED_FUNCTION extern "C" __attribute__((used))
constexpr size_t kIsolatedThreadStackSize = 64 * 1024;
constexpr int kNumPkey = 16;
constexpr size_t kTestReturnValue = 0x8765432187654321llu;
namespace partition_alloc::internal {
struct IsolatedGlobals {
int pkey = kInvalidPkey;
void* isolatedStack;
partition_alloc::internal::base::NoDestructor<
partition_alloc::PartitionAllocator>
allocator{};
partition_alloc::ThreadSafePartitionRoot* allocatorRoot;
} isolatedGlobals PA_PKEY_ALIGN;
int ProtFromSegmentFlags(ElfW(Word) flags) {
int prot = 0;
if (flags & PF_R) {
prot |= PROT_READ;
}
if (flags & PF_W) {
prot |= PROT_WRITE;
}
if (flags & PF_X) {
prot |= PROT_EXEC;
}
return prot;
}
int ProtectROSegments(struct dl_phdr_info* info, size_t info_size, void* data) {
if (!strcmp(info->dlpi_name, "linux-vdso.so.1")) {
return 0;
}
for (int i = 0; i < info->dlpi_phnum; i++) {
const ElfW(Phdr)* phdr = &info->dlpi_phdr[i];
if (phdr->p_type != PT_LOAD && phdr->p_type != PT_GNU_RELRO) {
continue;
}
if (phdr->p_flags & PF_W) {
continue;
}
uintptr_t start = info->dlpi_addr + phdr->p_vaddr;
uintptr_t end = start + phdr->p_memsz;
uintptr_t startPage = RoundDownToSystemPage(start);
uintptr_t endPage = RoundUpToSystemPage(end);
uintptr_t size = endPage - startPage;
PA_PCHECK(PkeyMprotect(reinterpret_cast<void*>(startPage), size,
ProtFromSegmentFlags(phdr->p_flags),
isolatedGlobals.pkey) == 0);
}
return 0;
}
class PkeyTest : public testing::Test {
protected:
void PkeyProtectMemory() {
PA_PCHECK(dl_iterate_phdr(ProtectROSegments, nullptr) == 0);
PA_PCHECK(PkeyMprotect(&isolatedGlobals, sizeof(isolatedGlobals),
PROT_READ | PROT_WRITE, isolatedGlobals.pkey) == 0);
PA_PCHECK(PkeyMprotect(isolatedGlobals.isolatedStack,
kIsolatedThreadStackSize, PROT_READ | PROT_WRITE,
isolatedGlobals.pkey) == 0);
}
void InitializeIsolatedThread() {
isolatedGlobals.isolatedStack =
mmap(nullptr, kIsolatedThreadStackSize, PROT_READ | PROT_WRITE,
MAP_ANONYMOUS | MAP_PRIVATE | MAP_STACK, -1, 0);
PA_PCHECK(isolatedGlobals.isolatedStack != MAP_FAILED);
PkeyProtectMemory();
}
void SetUp() override {
int pkey = PkeyAlloc(0);
if (pkey == -1) {
return;
}
isolatedGlobals.pkey = pkey;
isolatedGlobals.allocator->init({
partition_alloc::PartitionOptions::AlignedAlloc::kAllowed,
partition_alloc::PartitionOptions::ThreadCache::kDisabled,
partition_alloc::PartitionOptions::Quarantine::kDisallowed,
partition_alloc::PartitionOptions::Cookie::kAllowed,
partition_alloc::PartitionOptions::BackupRefPtr::kDisabled,
partition_alloc::PartitionOptions::BackupRefPtrZapping::kDisabled,
partition_alloc::PartitionOptions::UseConfigurablePool::kNo,
partition_alloc::PartitionOptions::AddDummyRefCount::kDisabled,
isolatedGlobals.pkey,
});
isolatedGlobals.allocatorRoot = isolatedGlobals.allocator->root();
InitializeIsolatedThread();
}
void TearDown() override {
if (isolatedGlobals.pkey == kInvalidPkey) {
return;
}
PA_PCHECK(PkeyMprotect(&isolatedGlobals, sizeof(isolatedGlobals),
PROT_READ | PROT_WRITE, kDefaultPkey) == 0);
isolatedGlobals.pkey = kDefaultPkey;
InitializeIsolatedThread();
PkeyFree(isolatedGlobals.pkey);
}
};
ISOLATED_FUNCTION uint64_t IsolatedAllocFree(void* arg) {
char* buf = (char*)isolatedGlobals.allocatorRoot->AllocWithFlagsNoHooks(
0, 1024, partition_alloc::PartitionPageSize());
if (!buf) {
return 0xffffffffffffffffllu;
}
isolatedGlobals.allocatorRoot->FreeNoHooks(buf);
return kTestReturnValue;
}
TEST_F(PkeyTest, AllocWithoutDefaultPkey) {
if (isolatedGlobals.pkey == kInvalidPkey) {
return;
}
uint64_t ret;
uint32_t pkru_value = 0;
for (int pkey = 0; pkey < kNumPkey; pkey++) {
if (pkey != isolatedGlobals.pkey) {
pkru_value |= (PKEY_DISABLE_ACCESS | PKEY_DISABLE_WRITE) << (2 * pkey);
}
}
asm volatile(
".byte 0x0f,0x01,0xef\n"
"xchg %4, %%rsp\n"
"push %4\n"
"call IsolatedAllocFree\n"
"push %%rax\n"
"mov $0b10101010101010101010101010101000, %%rax\n"
"xor %%rcx, %%rcx\n"
"xor %%rdx, %%rdx\n"
".byte 0x0f,0x01,0xef\n"
"pop %0\n"
"pop %%rsp\n"
: "=r"(ret)
: "a"(pkru_value), "c"(0), "d"(0),
"r"(reinterpret_cast<uintptr_t>(isolatedGlobals.isolatedStack) +
kIsolatedThreadStackSize - 8)
: "memory", "cc", "r8", "r9", "r10", "r11", "xmm0", "xmm1", "xmm2",
"xmm3", "xmm4", "xmm5", "xmm6", "xmm7", "xmm8", "xmm9", "xmm10",
"xmm11", "xmm12", "xmm13", "xmm14", "xmm15", "flags", "fpsr", "st",
"st(1)", "st(2)", "st(3)", "st(4)", "st(5)", "st(6)", "st(7)");
ASSERT_EQ(ret, kTestReturnValue);
}
}
#endif