blob: e23e2e199eb4e527561d8ad17e12ad0570970a42 [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0+
/*
* Ptrace test for Memory Protection Key registers
*
* Copyright (C) 2015 Anshuman Khandual, IBM Corporation.
* Copyright (C) 2018 IBM Corporation.
*/
#include <limits.h>
#include <linux/kernel.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <fcntl.h>
#include <unistd.h>
#include "ptrace.h"
#include "child.h"
#ifndef __NR_pkey_alloc
#define __NR_pkey_alloc 384
#endif
#ifndef __NR_pkey_free
#define __NR_pkey_free 385
#endif
#ifndef NT_PPC_PKEY
#define NT_PPC_PKEY 0x110
#endif
#ifndef PKEY_DISABLE_EXECUTE
#define PKEY_DISABLE_EXECUTE 0x4
#endif
#define AMR_BITS_PER_PKEY 2
#define PKEY_REG_BITS (sizeof(u64) * 8)
#define pkeyshift(pkey) (PKEY_REG_BITS - ((pkey + 1) * AMR_BITS_PER_PKEY))
#define CORE_FILE_LIMIT (5 * 1024 * 1024) /* 5 MB should be enough */
static const char core_pattern_file[] = "/proc/sys/kernel/core_pattern";
static const char user_write[] = "[User Write (Running)]";
static const char core_read_running[] = "[Core Read (Running)]";
/* Information shared between the parent and the child. */
struct shared_info {
struct child_sync child_sync;
/* AMR value the parent expects to read in the core file. */
unsigned long amr;
/* IAMR value the parent expects to read in the core file. */
unsigned long iamr;
/* UAMOR value the parent expects to read in the core file. */
unsigned long uamor;
/* When the child crashed. */
time_t core_time;
};
static int sys_pkey_alloc(unsigned long flags, unsigned long init_access_rights)
{
return syscall(__NR_pkey_alloc, flags, init_access_rights);
}
static int sys_pkey_free(int pkey)
{
return syscall(__NR_pkey_free, pkey);
}
static int increase_core_file_limit(void)
{
struct rlimit rlim;
int ret;
ret = getrlimit(RLIMIT_CORE, &rlim);
FAIL_IF(ret);
if (rlim.rlim_cur != RLIM_INFINITY && rlim.rlim_cur < CORE_FILE_LIMIT) {
rlim.rlim_cur = CORE_FILE_LIMIT;
if (rlim.rlim_max != RLIM_INFINITY &&
rlim.rlim_max < CORE_FILE_LIMIT)
rlim.rlim_max = CORE_FILE_LIMIT;
ret = setrlimit(RLIMIT_CORE, &rlim);
FAIL_IF(ret);
}
ret = getrlimit(RLIMIT_FSIZE, &rlim);
FAIL_IF(ret);
if (rlim.rlim_cur != RLIM_INFINITY && rlim.rlim_cur < CORE_FILE_LIMIT) {
rlim.rlim_cur = CORE_FILE_LIMIT;
if (rlim.rlim_max != RLIM_INFINITY &&
rlim.rlim_max < CORE_FILE_LIMIT)
rlim.rlim_max = CORE_FILE_LIMIT;
ret = setrlimit(RLIMIT_FSIZE, &rlim);
FAIL_IF(ret);
}
return TEST_PASS;
}
static int child(struct shared_info *info)
{
bool disable_execute = true;
int pkey1, pkey2, pkey3;
int *ptr, ret;
/* Wait until parent fills out the initial register values. */
ret = wait_parent(&info->child_sync);
if (ret)
return ret;
ret = increase_core_file_limit();
FAIL_IF(ret);
/* Get some pkeys so that we can change their bits in the AMR. */
pkey1 = sys_pkey_alloc(0, PKEY_DISABLE_EXECUTE);
if (pkey1 < 0) {
pkey1 = sys_pkey_alloc(0, 0);
FAIL_IF(pkey1 < 0);
disable_execute = false;
}
pkey2 = sys_pkey_alloc(0, 0);
FAIL_IF(pkey2 < 0);
pkey3 = sys_pkey_alloc(0, 0);
FAIL_IF(pkey3 < 0);
info->amr |= 3ul << pkeyshift(pkey1) | 2ul << pkeyshift(pkey2);
if (disable_execute)
info->iamr |= 1ul << pkeyshift(pkey1);
else
info->iamr &= ~(1ul << pkeyshift(pkey1));
info->iamr &= ~(1ul << pkeyshift(pkey2) | 1ul << pkeyshift(pkey3));
info->uamor |= 3ul << pkeyshift(pkey1) | 3ul << pkeyshift(pkey2);
printf("%-30s AMR: %016lx pkey1: %d pkey2: %d pkey3: %d\n",
user_write, info->amr, pkey1, pkey2, pkey3);
mtspr(SPRN_AMR, info->amr);
/*
* We won't use pkey3. This tests whether the kernel restores the UAMOR
* permissions after a key is freed.
*/
sys_pkey_free(pkey3);
info->core_time = time(NULL);
/* Crash. */
ptr = 0;
*ptr = 1;
/* Shouldn't get here. */
FAIL_IF(true);
return TEST_FAIL;
}
/* Return file size if filename exists and pass sanity check, or zero if not. */
static off_t try_core_file(const char *filename, struct shared_info *info,
pid_t pid)
{
struct stat buf;
int ret;
ret = stat(filename, &buf);
if (ret == -1)
return TEST_FAIL;
/* Make sure we're not using a stale core file. */
return buf.st_mtime >= info->core_time ? buf.st_size : TEST_FAIL;
}
static Elf64_Nhdr *next_note(Elf64_Nhdr *nhdr)
{
return (void *) nhdr + sizeof(*nhdr) +
__ALIGN_KERNEL(nhdr->n_namesz, 4) +
__ALIGN_KERNEL(nhdr->n_descsz, 4);
}
static int check_core_file(struct shared_info *info, Elf64_Ehdr *ehdr,
off_t core_size)
{
unsigned long *regs;
Elf64_Phdr *phdr;
Elf64_Nhdr *nhdr;
size_t phdr_size;
void *p = ehdr, *note;
int ret;
ret = memcmp(ehdr->e_ident, ELFMAG, SELFMAG);
FAIL_IF(ret);
FAIL_IF(ehdr->e_type != ET_CORE);
FAIL_IF(ehdr->e_machine != EM_PPC64);
FAIL_IF(ehdr->e_phoff == 0 || ehdr->e_phnum == 0);
/*
* e_phnum is at most 65535 so calculating the size of the
* program header cannot overflow.
*/
phdr_size = sizeof(*phdr) * ehdr->e_phnum;
/* Sanity check the program header table location. */
FAIL_IF(ehdr->e_phoff + phdr_size < ehdr->e_phoff);
FAIL_IF(ehdr->e_phoff + phdr_size > core_size);
/* Find the PT_NOTE segment. */
for (phdr = p + ehdr->e_phoff;
(void *) phdr < p + ehdr->e_phoff + phdr_size;
phdr += ehdr->e_phentsize)
if (phdr->p_type == PT_NOTE)
break;
FAIL_IF((void *) phdr >= p + ehdr->e_phoff + phdr_size);
/* Find the NT_PPC_PKEY note. */
for (nhdr = p + phdr->p_offset;
(void *) nhdr < p + phdr->p_offset + phdr->p_filesz;
nhdr = next_note(nhdr))
if (nhdr->n_type == NT_PPC_PKEY)
break;
FAIL_IF((void *) nhdr >= p + phdr->p_offset + phdr->p_filesz);
FAIL_IF(nhdr->n_descsz == 0);
p = nhdr;
note = p + sizeof(*nhdr) + __ALIGN_KERNEL(nhdr->n_namesz, 4);
regs = (unsigned long *) note;
printf("%-30s AMR: %016lx IAMR: %016lx UAMOR: %016lx\n",
core_read_running, regs[0], regs[1], regs[2]);
FAIL_IF(regs[0] != info->amr);
FAIL_IF(regs[1] != info->iamr);
FAIL_IF(regs[2] != info->uamor);
return TEST_PASS;
}
static int parent(struct shared_info *info, pid_t pid)
{
char *filenames, *filename[3];
int fd, i, ret, status;
unsigned long regs[3];
off_t core_size;
void *core;
/*
* Get the initial values for AMR, IAMR and UAMOR and communicate them
* to the child.
*/
ret = ptrace_read_regs(pid, NT_PPC_PKEY, regs, 3);
PARENT_SKIP_IF_UNSUPPORTED(ret, &info->child_sync);
PARENT_FAIL_IF(ret, &info->child_sync);
info->amr = regs[0];
info->iamr = regs[1];
info->uamor = regs[2];
/* Wake up child so that it can set itself up. */
ret = prod_child(&info->child_sync);
PARENT_FAIL_IF(ret, &info->child_sync);
ret = wait(&status);
if (ret != pid) {
printf("Child's exit status not captured\n");
return TEST_FAIL;
} else if (!WIFSIGNALED(status) || !WCOREDUMP(status)) {
printf("Child didn't dump core\n");
return TEST_FAIL;
}
/* Construct array of core file names to try. */
filename[0] = filenames = malloc(PATH_MAX);
if (!filenames) {
perror("Error allocating memory");
return TEST_FAIL;
}
ret = snprintf(filename[0], PATH_MAX, "core-pkey.%d", pid);
if (ret < 0 || ret >= PATH_MAX) {
ret = TEST_FAIL;
goto out;
}
filename[1] = filename[0] + ret + 1;
ret = snprintf(filename[1], PATH_MAX - ret - 1, "core.%d", pid);
if (ret < 0 || ret >= PATH_MAX - ret - 1) {
ret = TEST_FAIL;
goto out;
}
filename[2] = "core";
for (i = 0; i < 3; i++) {
core_size = try_core_file(filename[i], info, pid);
if (core_size != TEST_FAIL)
break;
}
if (i == 3) {
printf("Couldn't find core file\n");
ret = TEST_FAIL;
goto out;
}
fd = open(filename[i], O_RDONLY);
if (fd == -1) {
perror("Error opening core file");
ret = TEST_FAIL;
goto out;
}
core = mmap(NULL, core_size, PROT_READ, MAP_PRIVATE, fd, 0);
if (core == (void *) -1) {
perror("Error mmaping core file");
ret = TEST_FAIL;
goto out;
}
ret = check_core_file(info, core, core_size);
munmap(core, core_size);
close(fd);
unlink(filename[i]);
out:
free(filenames);
return ret;
}
static int write_core_pattern(const char *core_pattern)
{
size_t len = strlen(core_pattern), ret;
FILE *f;
f = fopen(core_pattern_file, "w");
if (!f) {
perror("Error writing to core_pattern file");
return TEST_FAIL;
}
ret = fwrite(core_pattern, 1, len, f);
fclose(f);
if (ret != len) {
perror("Error writing to core_pattern file");
return TEST_FAIL;
}
return TEST_PASS;
}
static int setup_core_pattern(char **core_pattern_, bool *changed_)
{
FILE *f;
char *core_pattern;
int ret;
core_pattern = malloc(PATH_MAX);
if (!core_pattern) {
perror("Error allocating memory");
return TEST_FAIL;
}
f = fopen(core_pattern_file, "r");
if (!f) {
perror("Error opening core_pattern file");
ret = TEST_FAIL;
goto out;
}
ret = fread(core_pattern, 1, PATH_MAX, f);
fclose(f);
if (!ret) {
perror("Error reading core_pattern file");
ret = TEST_FAIL;
goto out;
}
/* Check whether we can predict the name of the core file. */
if (!strcmp(core_pattern, "core") || !strcmp(core_pattern, "core.%p"))
*changed_ = false;
else {
ret = write_core_pattern("core-pkey.%p");
if (ret)
goto out;
*changed_ = true;
}
*core_pattern_ = core_pattern;
ret = TEST_PASS;
out:
if (ret)
free(core_pattern);
return ret;
}
static int core_pkey(void)
{
char *core_pattern;
bool changed_core_pattern;
struct shared_info *info;
int shm_id;
int ret;
pid_t pid;
ret = setup_core_pattern(&core_pattern, &changed_core_pattern);
if (ret)
return ret;
shm_id = shmget(IPC_PRIVATE, sizeof(*info), 0777 | IPC_CREAT);
info = shmat(shm_id, NULL, 0);
ret = init_child_sync(&info->child_sync);
if (ret)
return ret;
pid = fork();
if (pid < 0) {
perror("fork() failed");
ret = TEST_FAIL;
} else if (pid == 0)
ret = child(info);
else
ret = parent(info, pid);
shmdt(info);
if (pid) {
destroy_child_sync(&info->child_sync);
shmctl(shm_id, IPC_RMID, NULL);
if (changed_core_pattern)
write_core_pattern(core_pattern);
}
free(core_pattern);
return ret;
}
int main(int argc, char *argv[])
{
return test_harness(core_pkey, "core_pkey");
}