brintos

brintos / llvm-project-archived public Read only

0
0
Text · 7.2 KiB · 9c6feae Raw
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