blob: 9c6feae2d457bd63821a31c3cc0ddd5fe807af11 [file] [log] [blame] [edit]
//===-- Unit tests for pkey functions -------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "hdr/errno_macros.h"
#include "hdr/signal_macros.h"
#include "hdr/types/size_t.h"
#include "src/sys/mman/mmap.h"
#include "src/sys/mman/munmap.h"
#include "src/sys/mman/pkey_alloc.h"
#include "src/sys/mman/pkey_free.h"
#include "src/sys/mman/pkey_get.h"
#include "src/sys/mman/pkey_mprotect.h"
#include "src/sys/mman/pkey_set.h"
#include "test/UnitTest/ErrnoCheckingTest.h"
#include "test/UnitTest/ErrnoSetterMatcher.h"
#include "test/UnitTest/LibcTest.h"
#include "test/UnitTest/TestLogger.h"
#include <linux/param.h> // For EXEC_PAGESIZE.
using LIBC_NAMESPACE::testing::tlog;
using LIBC_NAMESPACE::testing::ErrnoSetterMatcher::Fails;
using LIBC_NAMESPACE::testing::ErrnoSetterMatcher::Succeeds;
using LlvmLibcProtectionKeyTest = LIBC_NAMESPACE::testing::ErrnoCheckingTest;
constexpr size_t MMAP_SIZE = EXEC_PAGESIZE;
// Wrapper around a pkey to ensure it is freed.
class PKeyGuard {
public:
int key;
PKeyGuard() : key(-1) {}
PKeyGuard(int key) : key(key) {}
~PKeyGuard() {
if (key != -1) {
LIBC_NAMESPACE::pkey_free(key);
}
}
};
// Wrapper around mmap to ensure munmap is called.
class MMapPageGuard {
public:
void *addr = nullptr;
size_t size = 0;
static MMapPageGuard mmap(int prot) {
void *addr = LIBC_NAMESPACE::mmap(nullptr, MMAP_SIZE, prot,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (addr == MAP_FAILED) {
return MMapPageGuard(nullptr, 0);
}
return MMapPageGuard(addr, MMAP_SIZE);
}
MMapPageGuard(void *addr, size_t size) : addr(addr), size(size) {}
~MMapPageGuard() {
if (addr != nullptr) {
LIBC_NAMESPACE::munmap(addr, size);
}
}
};
bool protection_keys_supported() {
static bool supported = []() {
PKeyGuard pkey(LIBC_NAMESPACE::pkey_alloc(0, 0));
int err = libc_errno;
libc_errno = 0;
if (pkey.key < 0 || (err == ENOSPC || err == ENOSYS || err == EINVAL)) {
tlog << "pkey_alloc failed with errno=" << err << "\n";
return false;
}
int access_rights = LIBC_NAMESPACE::pkey_get(pkey.key);
err = libc_errno;
libc_errno = 0;
if (access_rights < 0 || err == ENOSYS) {
tlog << "pkey_get failed with errno=" << err << "\n";
return false;
}
return true;
}();
return supported;
}
TEST_F(LlvmLibcProtectionKeyTest, MProtectWithPKeyDisablesWrite) {
if (!protection_keys_supported()) {
tlog << "Skipping test: pkey is not available\n";
return;
}
PKeyGuard pkey(LIBC_NAMESPACE::pkey_alloc(0, PKEY_DISABLE_WRITE));
ASSERT_NE(pkey.key, -1);
MMapPageGuard page = MMapPageGuard::mmap(PROT_READ | PROT_WRITE);
ASSERT_NE(page.addr, nullptr);
volatile char *data = (char *)page.addr;
data[0] = 'a';
EXPECT_THAT(LIBC_NAMESPACE::pkey_mprotect(page.addr, page.size,
PROT_READ | PROT_WRITE, pkey.key),
Succeeds());
// Read is still allowed.
EXPECT_EQ(data[0], 'a');
// Write is not allowed.
EXPECT_DEATH([&data]() { data[0] = 'b'; }, WITH_SIGNAL(SIGSEGV));
}
TEST_F(LlvmLibcProtectionKeyTest, PKeySetChangesAccessRights) {
if (!protection_keys_supported()) {
tlog << "Skipping test: pkey is not available\n";
return;
}
PKeyGuard pkey(LIBC_NAMESPACE::pkey_alloc(0, 0));
ASSERT_NE(pkey.key, -1);
MMapPageGuard page = MMapPageGuard::mmap(PROT_READ | PROT_WRITE);
ASSERT_NE(page.addr, nullptr);
EXPECT_THAT(LIBC_NAMESPACE::pkey_mprotect(page.addr, page.size,
PROT_READ | PROT_WRITE, pkey.key),
Succeeds());
// Write is allowed by default.
volatile char *data = (char *)page.addr;
data[0] = 'a';
EXPECT_THAT(LIBC_NAMESPACE::pkey_set(pkey.key, PKEY_DISABLE_WRITE),
Succeeds());
// Now read is allowed but write is not.
EXPECT_EQ(data[0], 'a');
EXPECT_DEATH([&data]() { data[0] = 'b'; }, WITH_SIGNAL(SIGSEGV));
// Now neither read nor write is allowed.
EXPECT_THAT(LIBC_NAMESPACE::pkey_set(pkey.key, PKEY_DISABLE_ACCESS |
PKEY_DISABLE_WRITE),
Succeeds());
EXPECT_DEATH([&data]() { (void)data[0]; }, WITH_SIGNAL(SIGSEGV));
EXPECT_DEATH([&data]() { data[0] = 'b'; }, WITH_SIGNAL(SIGSEGV));
}
TEST_F(LlvmLibcProtectionKeyTest, FallsBackToMProtectForInvalidPKey) {
MMapPageGuard page = MMapPageGuard::mmap(PROT_READ | PROT_WRITE);
ASSERT_NE(page.addr, nullptr);
volatile char *data = (char *)page.addr;
data[0] = 'a';
EXPECT_THAT(
LIBC_NAMESPACE::pkey_mprotect(page.addr, page.size, PROT_READ, -1),
Succeeds());
// Read is still allowed.
EXPECT_EQ(data[0], 'a');
// Write is not allowed.
EXPECT_DEATH([&data]() { data[0] = 'b'; }, WITH_SIGNAL(SIGSEGV));
}
TEST_F(LlvmLibcProtectionKeyTest, ExhaustedKeysFailsWithENOSPC) {
if (!protection_keys_supported()) {
tlog << "Skipping test: pkey is not available\n";
return;
}
// Use an unreasonably large limit to ensure test is cross-platform.
// This limit is intended to be much larger than the actual hardware limit.
constexpr int MAX_PKEYS = 64;
PKeyGuard pkeys[MAX_PKEYS];
for (int i = 0; i < MAX_PKEYS; ++i) {
pkeys[i].key = LIBC_NAMESPACE::pkey_alloc(0, 0);
}
// pkey allocation should eventually fail with ENOSPC.
PKeyGuard pkey(LIBC_NAMESPACE::pkey_alloc(0, 0));
EXPECT_THAT(pkey.key, Fails(ENOSPC));
libc_errno = 0;
}
TEST_F(LlvmLibcProtectionKeyTest, Accessors) {
if (!protection_keys_supported()) {
tlog << "Skipping test: pkey is not available\n";
return;
}
PKeyGuard pkey(LIBC_NAMESPACE::pkey_alloc(0, PKEY_DISABLE_WRITE));
ASSERT_NE(pkey.key, -1);
// Check that pkey_alloc sets the access rights.
EXPECT_EQ(LIBC_NAMESPACE::pkey_get(pkey.key), PKEY_DISABLE_WRITE);
// Check that pkey_set changes the access rights.
EXPECT_THAT(LIBC_NAMESPACE::pkey_set(pkey.key, PKEY_DISABLE_ACCESS),
Succeeds());
EXPECT_EQ(LIBC_NAMESPACE::pkey_get(pkey.key), PKEY_DISABLE_ACCESS);
}
TEST_F(LlvmLibcProtectionKeyTest, AccessorsErrorForInvalidValues) {
if (!protection_keys_supported()) {
tlog << "Skipping test: pkey is not available\n";
return;
}
PKeyGuard pkey(LIBC_NAMESPACE::pkey_alloc(0, PKEY_DISABLE_WRITE));
ASSERT_NE(pkey.key, -1);
// Pkey is out of bounds in pkey_get.
EXPECT_THAT(LIBC_NAMESPACE::pkey_get(100), Fails(EINVAL));
EXPECT_THAT(LIBC_NAMESPACE::pkey_get(-1234), Fails(EINVAL));
// Pkey is out of bounds in pkey_set.
EXPECT_THAT(LIBC_NAMESPACE::pkey_set(100, PKEY_DISABLE_ACCESS),
Fails(EINVAL));
EXPECT_THAT(LIBC_NAMESPACE::pkey_set(-1234, PKEY_DISABLE_ACCESS),
Fails(EINVAL));
// Non-zero flags are not supported in pkey_alloc.
EXPECT_THAT(LIBC_NAMESPACE::pkey_alloc(123, PKEY_DISABLE_WRITE),
Fails(EINVAL));
// Access rights are out of bounds.
EXPECT_THAT(LIBC_NAMESPACE::pkey_alloc(0, 1000), Fails(EINVAL));
EXPECT_THAT(LIBC_NAMESPACE::pkey_set(pkey.key, 1000), Fails(EINVAL));
}