242 lines · cpp
1//===-- Unit tests for pkey functions -------------------------------------===//2//3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.4// See https://llvm.org/LICENSE.txt for license information.5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception6//7//===----------------------------------------------------------------------===//8 9#include "hdr/errno_macros.h"10#include "hdr/signal_macros.h"11#include "hdr/types/size_t.h"12#include "src/sys/mman/mmap.h"13#include "src/sys/mman/munmap.h"14#include "src/sys/mman/pkey_alloc.h"15#include "src/sys/mman/pkey_free.h"16#include "src/sys/mman/pkey_get.h"17#include "src/sys/mman/pkey_mprotect.h"18#include "src/sys/mman/pkey_set.h"19#include "test/UnitTest/ErrnoCheckingTest.h"20#include "test/UnitTest/ErrnoSetterMatcher.h"21#include "test/UnitTest/LibcTest.h"22#include "test/UnitTest/TestLogger.h"23 24#include <linux/param.h> // For EXEC_PAGESIZE.25 26using LIBC_NAMESPACE::testing::tlog;27using LIBC_NAMESPACE::testing::ErrnoSetterMatcher::Fails;28using LIBC_NAMESPACE::testing::ErrnoSetterMatcher::Succeeds;29 30using LlvmLibcProtectionKeyTest = LIBC_NAMESPACE::testing::ErrnoCheckingTest;31 32constexpr size_t MMAP_SIZE = EXEC_PAGESIZE;33 34// Wrapper around a pkey to ensure it is freed.35class PKeyGuard {36public:37 int key;38 39 PKeyGuard() : key(-1) {}40 41 PKeyGuard(int key) : key(key) {}42 43 ~PKeyGuard() {44 if (key != -1) {45 LIBC_NAMESPACE::pkey_free(key);46 }47 }48};49 50// Wrapper around mmap to ensure munmap is called.51class MMapPageGuard {52public:53 void *addr = nullptr;54 size_t size = 0;55 56 static MMapPageGuard mmap(int prot) {57 void *addr = LIBC_NAMESPACE::mmap(nullptr, MMAP_SIZE, prot,58 MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);59 if (addr == MAP_FAILED) {60 return MMapPageGuard(nullptr, 0);61 }62 return MMapPageGuard(addr, MMAP_SIZE);63 }64 65 MMapPageGuard(void *addr, size_t size) : addr(addr), size(size) {}66 67 ~MMapPageGuard() {68 if (addr != nullptr) {69 LIBC_NAMESPACE::munmap(addr, size);70 }71 }72};73 74bool protection_keys_supported() {75 static bool supported = []() {76 PKeyGuard pkey(LIBC_NAMESPACE::pkey_alloc(0, 0));77 int err = libc_errno;78 libc_errno = 0;79 80 if (pkey.key < 0 || (err == ENOSPC || err == ENOSYS || err == EINVAL)) {81 tlog << "pkey_alloc failed with errno=" << err << "\n";82 return false;83 }84 85 int access_rights = LIBC_NAMESPACE::pkey_get(pkey.key);86 err = libc_errno;87 libc_errno = 0;88 if (access_rights < 0 || err == ENOSYS) {89 tlog << "pkey_get failed with errno=" << err << "\n";90 return false;91 }92 93 return true;94 }();95 return supported;96}97 98TEST_F(LlvmLibcProtectionKeyTest, MProtectWithPKeyDisablesWrite) {99 if (!protection_keys_supported()) {100 tlog << "Skipping test: pkey is not available\n";101 return;102 }103 104 PKeyGuard pkey(LIBC_NAMESPACE::pkey_alloc(0, PKEY_DISABLE_WRITE));105 ASSERT_NE(pkey.key, -1);106 107 MMapPageGuard page = MMapPageGuard::mmap(PROT_READ | PROT_WRITE);108 ASSERT_NE(page.addr, nullptr);109 110 volatile char *data = (char *)page.addr;111 data[0] = 'a';112 113 EXPECT_THAT(LIBC_NAMESPACE::pkey_mprotect(page.addr, page.size,114 PROT_READ | PROT_WRITE, pkey.key),115 Succeeds());116 117 // Read is still allowed.118 EXPECT_EQ(data[0], 'a');119 120 // Write is not allowed.121 EXPECT_DEATH([&data]() { data[0] = 'b'; }, WITH_SIGNAL(SIGSEGV));122}123 124TEST_F(LlvmLibcProtectionKeyTest, PKeySetChangesAccessRights) {125 if (!protection_keys_supported()) {126 tlog << "Skipping test: pkey is not available\n";127 return;128 }129 130 PKeyGuard pkey(LIBC_NAMESPACE::pkey_alloc(0, 0));131 ASSERT_NE(pkey.key, -1);132 133 MMapPageGuard page = MMapPageGuard::mmap(PROT_READ | PROT_WRITE);134 ASSERT_NE(page.addr, nullptr);135 136 EXPECT_THAT(LIBC_NAMESPACE::pkey_mprotect(page.addr, page.size,137 PROT_READ | PROT_WRITE, pkey.key),138 Succeeds());139 140 // Write is allowed by default.141 volatile char *data = (char *)page.addr;142 data[0] = 'a';143 144 EXPECT_THAT(LIBC_NAMESPACE::pkey_set(pkey.key, PKEY_DISABLE_WRITE),145 Succeeds());146 147 // Now read is allowed but write is not.148 EXPECT_EQ(data[0], 'a');149 EXPECT_DEATH([&data]() { data[0] = 'b'; }, WITH_SIGNAL(SIGSEGV));150 151 // Now neither read nor write is allowed.152 EXPECT_THAT(LIBC_NAMESPACE::pkey_set(pkey.key, PKEY_DISABLE_ACCESS |153 PKEY_DISABLE_WRITE),154 Succeeds());155 EXPECT_DEATH([&data]() { (void)data[0]; }, WITH_SIGNAL(SIGSEGV));156 EXPECT_DEATH([&data]() { data[0] = 'b'; }, WITH_SIGNAL(SIGSEGV));157}158 159TEST_F(LlvmLibcProtectionKeyTest, FallsBackToMProtectForInvalidPKey) {160 MMapPageGuard page = MMapPageGuard::mmap(PROT_READ | PROT_WRITE);161 ASSERT_NE(page.addr, nullptr);162 163 volatile char *data = (char *)page.addr;164 data[0] = 'a';165 166 EXPECT_THAT(167 LIBC_NAMESPACE::pkey_mprotect(page.addr, page.size, PROT_READ, -1),168 Succeeds());169 170 // Read is still allowed.171 EXPECT_EQ(data[0], 'a');172 173 // Write is not allowed.174 EXPECT_DEATH([&data]() { data[0] = 'b'; }, WITH_SIGNAL(SIGSEGV));175}176 177TEST_F(LlvmLibcProtectionKeyTest, ExhaustedKeysFailsWithENOSPC) {178 if (!protection_keys_supported()) {179 tlog << "Skipping test: pkey is not available\n";180 return;181 }182 183 // Use an unreasonably large limit to ensure test is cross-platform.184 // This limit is intended to be much larger than the actual hardware limit.185 constexpr int MAX_PKEYS = 64;186 PKeyGuard pkeys[MAX_PKEYS];187 for (int i = 0; i < MAX_PKEYS; ++i) {188 pkeys[i].key = LIBC_NAMESPACE::pkey_alloc(0, 0);189 }190 191 // pkey allocation should eventually fail with ENOSPC.192 PKeyGuard pkey(LIBC_NAMESPACE::pkey_alloc(0, 0));193 EXPECT_THAT(pkey.key, Fails(ENOSPC));194 libc_errno = 0;195}196 197TEST_F(LlvmLibcProtectionKeyTest, Accessors) {198 if (!protection_keys_supported()) {199 tlog << "Skipping test: pkey is not available\n";200 return;201 }202 203 PKeyGuard pkey(LIBC_NAMESPACE::pkey_alloc(0, PKEY_DISABLE_WRITE));204 ASSERT_NE(pkey.key, -1);205 206 // Check that pkey_alloc sets the access rights.207 EXPECT_EQ(LIBC_NAMESPACE::pkey_get(pkey.key), PKEY_DISABLE_WRITE);208 209 // Check that pkey_set changes the access rights.210 EXPECT_THAT(LIBC_NAMESPACE::pkey_set(pkey.key, PKEY_DISABLE_ACCESS),211 Succeeds());212 EXPECT_EQ(LIBC_NAMESPACE::pkey_get(pkey.key), PKEY_DISABLE_ACCESS);213}214 215TEST_F(LlvmLibcProtectionKeyTest, AccessorsErrorForInvalidValues) {216 if (!protection_keys_supported()) {217 tlog << "Skipping test: pkey is not available\n";218 return;219 }220 221 PKeyGuard pkey(LIBC_NAMESPACE::pkey_alloc(0, PKEY_DISABLE_WRITE));222 ASSERT_NE(pkey.key, -1);223 224 // Pkey is out of bounds in pkey_get.225 EXPECT_THAT(LIBC_NAMESPACE::pkey_get(100), Fails(EINVAL));226 EXPECT_THAT(LIBC_NAMESPACE::pkey_get(-1234), Fails(EINVAL));227 228 // Pkey is out of bounds in pkey_set.229 EXPECT_THAT(LIBC_NAMESPACE::pkey_set(100, PKEY_DISABLE_ACCESS),230 Fails(EINVAL));231 EXPECT_THAT(LIBC_NAMESPACE::pkey_set(-1234, PKEY_DISABLE_ACCESS),232 Fails(EINVAL));233 234 // Non-zero flags are not supported in pkey_alloc.235 EXPECT_THAT(LIBC_NAMESPACE::pkey_alloc(123, PKEY_DISABLE_WRITE),236 Fails(EINVAL));237 238 // Access rights are out of bounds.239 EXPECT_THAT(LIBC_NAMESPACE::pkey_alloc(0, 1000), Fails(EINVAL));240 EXPECT_THAT(LIBC_NAMESPACE::pkey_set(pkey.key, 1000), Fails(EINVAL));241}242