162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci
362306a36Sopenharmony_ci/*
462306a36Sopenharmony_ci * Copyright 2020, Sandipan Das, IBM Corp.
562306a36Sopenharmony_ci *
662306a36Sopenharmony_ci * Test if the signal information reports the correct memory protection
762306a36Sopenharmony_ci * key upon getting a key access violation fault for a page that was
862306a36Sopenharmony_ci * attempted to be protected by two different keys from two competing
962306a36Sopenharmony_ci * threads at the same time.
1062306a36Sopenharmony_ci */
1162306a36Sopenharmony_ci
1262306a36Sopenharmony_ci#define _GNU_SOURCE
1362306a36Sopenharmony_ci#include <stdio.h>
1462306a36Sopenharmony_ci#include <stdlib.h>
1562306a36Sopenharmony_ci#include <string.h>
1662306a36Sopenharmony_ci#include <signal.h>
1762306a36Sopenharmony_ci
1862306a36Sopenharmony_ci#include <unistd.h>
1962306a36Sopenharmony_ci#include <pthread.h>
2062306a36Sopenharmony_ci#include <sys/mman.h>
2162306a36Sopenharmony_ci
2262306a36Sopenharmony_ci#include "pkeys.h"
2362306a36Sopenharmony_ci
2462306a36Sopenharmony_ci#define PPC_INST_NOP	0x60000000
2562306a36Sopenharmony_ci#define PPC_INST_BLR	0x4e800020
2662306a36Sopenharmony_ci#define PROT_RWX	(PROT_READ | PROT_WRITE | PROT_EXEC)
2762306a36Sopenharmony_ci
2862306a36Sopenharmony_ci#define NUM_ITERATIONS	1000000
2962306a36Sopenharmony_ci
3062306a36Sopenharmony_cistatic volatile sig_atomic_t perm_pkey, rest_pkey;
3162306a36Sopenharmony_cistatic volatile sig_atomic_t rights, fault_count;
3262306a36Sopenharmony_cistatic volatile unsigned int *volatile fault_addr;
3362306a36Sopenharmony_cistatic pthread_barrier_t iteration_barrier;
3462306a36Sopenharmony_ci
3562306a36Sopenharmony_cistatic void segv_handler(int signum, siginfo_t *sinfo, void *ctx)
3662306a36Sopenharmony_ci{
3762306a36Sopenharmony_ci	void *pgstart;
3862306a36Sopenharmony_ci	size_t pgsize;
3962306a36Sopenharmony_ci	int pkey;
4062306a36Sopenharmony_ci
4162306a36Sopenharmony_ci	pkey = siginfo_pkey(sinfo);
4262306a36Sopenharmony_ci
4362306a36Sopenharmony_ci	/* Check if this fault originated from a pkey access violation */
4462306a36Sopenharmony_ci	if (sinfo->si_code != SEGV_PKUERR) {
4562306a36Sopenharmony_ci		sigsafe_err("got a fault for an unexpected reason\n");
4662306a36Sopenharmony_ci		_exit(1);
4762306a36Sopenharmony_ci	}
4862306a36Sopenharmony_ci
4962306a36Sopenharmony_ci	/* Check if this fault originated from the expected address */
5062306a36Sopenharmony_ci	if (sinfo->si_addr != (void *) fault_addr) {
5162306a36Sopenharmony_ci		sigsafe_err("got a fault for an unexpected address\n");
5262306a36Sopenharmony_ci		_exit(1);
5362306a36Sopenharmony_ci	}
5462306a36Sopenharmony_ci
5562306a36Sopenharmony_ci	/* Check if this fault originated from the restrictive pkey */
5662306a36Sopenharmony_ci	if (pkey != rest_pkey) {
5762306a36Sopenharmony_ci		sigsafe_err("got a fault for an unexpected pkey\n");
5862306a36Sopenharmony_ci		_exit(1);
5962306a36Sopenharmony_ci	}
6062306a36Sopenharmony_ci
6162306a36Sopenharmony_ci	/* Check if too many faults have occurred for the same iteration */
6262306a36Sopenharmony_ci	if (fault_count > 0) {
6362306a36Sopenharmony_ci		sigsafe_err("got too many faults for the same address\n");
6462306a36Sopenharmony_ci		_exit(1);
6562306a36Sopenharmony_ci	}
6662306a36Sopenharmony_ci
6762306a36Sopenharmony_ci	pgsize = getpagesize();
6862306a36Sopenharmony_ci	pgstart = (void *) ((unsigned long) fault_addr & ~(pgsize - 1));
6962306a36Sopenharmony_ci
7062306a36Sopenharmony_ci	/*
7162306a36Sopenharmony_ci	 * If the current fault occurred due to lack of execute rights,
7262306a36Sopenharmony_ci	 * reassociate the page with the exec-only pkey since execute
7362306a36Sopenharmony_ci	 * rights cannot be changed directly for the faulting pkey as
7462306a36Sopenharmony_ci	 * IAMR is inaccessible from userspace.
7562306a36Sopenharmony_ci	 *
7662306a36Sopenharmony_ci	 * Otherwise, if the current fault occurred due to lack of
7762306a36Sopenharmony_ci	 * read-write rights, change the AMR permission bits for the
7862306a36Sopenharmony_ci	 * pkey.
7962306a36Sopenharmony_ci	 *
8062306a36Sopenharmony_ci	 * This will let the test continue.
8162306a36Sopenharmony_ci	 */
8262306a36Sopenharmony_ci	if (rights == PKEY_DISABLE_EXECUTE &&
8362306a36Sopenharmony_ci	    mprotect(pgstart, pgsize, PROT_EXEC))
8462306a36Sopenharmony_ci		_exit(1);
8562306a36Sopenharmony_ci	else
8662306a36Sopenharmony_ci		pkey_set_rights(pkey, 0);
8762306a36Sopenharmony_ci
8862306a36Sopenharmony_ci	fault_count++;
8962306a36Sopenharmony_ci}
9062306a36Sopenharmony_ci
9162306a36Sopenharmony_cistruct region {
9262306a36Sopenharmony_ci	unsigned long rights;
9362306a36Sopenharmony_ci	unsigned int *base;
9462306a36Sopenharmony_ci	size_t size;
9562306a36Sopenharmony_ci};
9662306a36Sopenharmony_ci
9762306a36Sopenharmony_cistatic void *protect(void *p)
9862306a36Sopenharmony_ci{
9962306a36Sopenharmony_ci	unsigned long rights;
10062306a36Sopenharmony_ci	unsigned int *base;
10162306a36Sopenharmony_ci	size_t size;
10262306a36Sopenharmony_ci	int tid, i;
10362306a36Sopenharmony_ci
10462306a36Sopenharmony_ci	tid = gettid();
10562306a36Sopenharmony_ci	base = ((struct region *) p)->base;
10662306a36Sopenharmony_ci	size = ((struct region *) p)->size;
10762306a36Sopenharmony_ci	FAIL_IF_EXIT(!base);
10862306a36Sopenharmony_ci
10962306a36Sopenharmony_ci	/* No read, write and execute restrictions */
11062306a36Sopenharmony_ci	rights = 0;
11162306a36Sopenharmony_ci
11262306a36Sopenharmony_ci	printf("tid %d, pkey permissions are %s\n", tid, pkey_rights(rights));
11362306a36Sopenharmony_ci
11462306a36Sopenharmony_ci	/* Allocate the permissive pkey */
11562306a36Sopenharmony_ci	perm_pkey = sys_pkey_alloc(0, rights);
11662306a36Sopenharmony_ci	FAIL_IF_EXIT(perm_pkey < 0);
11762306a36Sopenharmony_ci
11862306a36Sopenharmony_ci	/*
11962306a36Sopenharmony_ci	 * Repeatedly try to protect the common region with a permissive
12062306a36Sopenharmony_ci	 * pkey
12162306a36Sopenharmony_ci	 */
12262306a36Sopenharmony_ci	for (i = 0; i < NUM_ITERATIONS; i++) {
12362306a36Sopenharmony_ci		/*
12462306a36Sopenharmony_ci		 * Wait until the other thread has finished allocating the
12562306a36Sopenharmony_ci		 * restrictive pkey or until the next iteration has begun
12662306a36Sopenharmony_ci		 */
12762306a36Sopenharmony_ci		pthread_barrier_wait(&iteration_barrier);
12862306a36Sopenharmony_ci
12962306a36Sopenharmony_ci		/* Try to associate the permissive pkey with the region */
13062306a36Sopenharmony_ci		FAIL_IF_EXIT(sys_pkey_mprotect(base, size, PROT_RWX,
13162306a36Sopenharmony_ci					       perm_pkey));
13262306a36Sopenharmony_ci	}
13362306a36Sopenharmony_ci
13462306a36Sopenharmony_ci	/* Free the permissive pkey */
13562306a36Sopenharmony_ci	sys_pkey_free(perm_pkey);
13662306a36Sopenharmony_ci
13762306a36Sopenharmony_ci	return NULL;
13862306a36Sopenharmony_ci}
13962306a36Sopenharmony_ci
14062306a36Sopenharmony_cistatic void *protect_access(void *p)
14162306a36Sopenharmony_ci{
14262306a36Sopenharmony_ci	size_t size, numinsns;
14362306a36Sopenharmony_ci	unsigned int *base;
14462306a36Sopenharmony_ci	int tid, i;
14562306a36Sopenharmony_ci
14662306a36Sopenharmony_ci	tid = gettid();
14762306a36Sopenharmony_ci	base = ((struct region *) p)->base;
14862306a36Sopenharmony_ci	size = ((struct region *) p)->size;
14962306a36Sopenharmony_ci	rights = ((struct region *) p)->rights;
15062306a36Sopenharmony_ci	numinsns = size / sizeof(base[0]);
15162306a36Sopenharmony_ci	FAIL_IF_EXIT(!base);
15262306a36Sopenharmony_ci
15362306a36Sopenharmony_ci	/* Allocate the restrictive pkey */
15462306a36Sopenharmony_ci	rest_pkey = sys_pkey_alloc(0, rights);
15562306a36Sopenharmony_ci	FAIL_IF_EXIT(rest_pkey < 0);
15662306a36Sopenharmony_ci
15762306a36Sopenharmony_ci	printf("tid %d, pkey permissions are %s\n", tid, pkey_rights(rights));
15862306a36Sopenharmony_ci	printf("tid %d, %s randomly in range [%p, %p]\n", tid,
15962306a36Sopenharmony_ci	       (rights == PKEY_DISABLE_EXECUTE) ? "execute" :
16062306a36Sopenharmony_ci	       (rights == PKEY_DISABLE_WRITE)  ? "write" : "read",
16162306a36Sopenharmony_ci	       base, base + numinsns);
16262306a36Sopenharmony_ci
16362306a36Sopenharmony_ci	/*
16462306a36Sopenharmony_ci	 * Repeatedly try to protect the common region with a restrictive
16562306a36Sopenharmony_ci	 * pkey and read, write or execute from it
16662306a36Sopenharmony_ci	 */
16762306a36Sopenharmony_ci	for (i = 0; i < NUM_ITERATIONS; i++) {
16862306a36Sopenharmony_ci		/*
16962306a36Sopenharmony_ci		 * Wait until the other thread has finished allocating the
17062306a36Sopenharmony_ci		 * permissive pkey or until the next iteration has begun
17162306a36Sopenharmony_ci		 */
17262306a36Sopenharmony_ci		pthread_barrier_wait(&iteration_barrier);
17362306a36Sopenharmony_ci
17462306a36Sopenharmony_ci		/* Try to associate the restrictive pkey with the region */
17562306a36Sopenharmony_ci		FAIL_IF_EXIT(sys_pkey_mprotect(base, size, PROT_RWX,
17662306a36Sopenharmony_ci					       rest_pkey));
17762306a36Sopenharmony_ci
17862306a36Sopenharmony_ci		/* Choose a random instruction word address from the region */
17962306a36Sopenharmony_ci		fault_addr = base + (rand() % numinsns);
18062306a36Sopenharmony_ci		fault_count = 0;
18162306a36Sopenharmony_ci
18262306a36Sopenharmony_ci		switch (rights) {
18362306a36Sopenharmony_ci		/* Read protection test */
18462306a36Sopenharmony_ci		case PKEY_DISABLE_ACCESS:
18562306a36Sopenharmony_ci			/*
18662306a36Sopenharmony_ci			 * Read an instruction word from the region and
18762306a36Sopenharmony_ci			 * verify if it has not been overwritten to
18862306a36Sopenharmony_ci			 * something unexpected
18962306a36Sopenharmony_ci			 */
19062306a36Sopenharmony_ci			FAIL_IF_EXIT(*fault_addr != PPC_INST_NOP &&
19162306a36Sopenharmony_ci				     *fault_addr != PPC_INST_BLR);
19262306a36Sopenharmony_ci			break;
19362306a36Sopenharmony_ci
19462306a36Sopenharmony_ci		/* Write protection test */
19562306a36Sopenharmony_ci		case PKEY_DISABLE_WRITE:
19662306a36Sopenharmony_ci			/*
19762306a36Sopenharmony_ci			 * Write an instruction word to the region and
19862306a36Sopenharmony_ci			 * verify if the overwrite has succeeded
19962306a36Sopenharmony_ci			 */
20062306a36Sopenharmony_ci			*fault_addr = PPC_INST_BLR;
20162306a36Sopenharmony_ci			FAIL_IF_EXIT(*fault_addr != PPC_INST_BLR);
20262306a36Sopenharmony_ci			break;
20362306a36Sopenharmony_ci
20462306a36Sopenharmony_ci		/* Execute protection test */
20562306a36Sopenharmony_ci		case PKEY_DISABLE_EXECUTE:
20662306a36Sopenharmony_ci			/* Jump to the region and execute instructions */
20762306a36Sopenharmony_ci			asm volatile(
20862306a36Sopenharmony_ci				"mtctr	%0; bctrl"
20962306a36Sopenharmony_ci				: : "r"(fault_addr) : "ctr", "lr");
21062306a36Sopenharmony_ci			break;
21162306a36Sopenharmony_ci		}
21262306a36Sopenharmony_ci
21362306a36Sopenharmony_ci		/*
21462306a36Sopenharmony_ci		 * Restore the restrictions originally imposed by the
21562306a36Sopenharmony_ci		 * restrictive pkey as the signal handler would have
21662306a36Sopenharmony_ci		 * cleared out the corresponding AMR bits
21762306a36Sopenharmony_ci		 */
21862306a36Sopenharmony_ci		pkey_set_rights(rest_pkey, rights);
21962306a36Sopenharmony_ci	}
22062306a36Sopenharmony_ci
22162306a36Sopenharmony_ci	/* Free restrictive pkey */
22262306a36Sopenharmony_ci	sys_pkey_free(rest_pkey);
22362306a36Sopenharmony_ci
22462306a36Sopenharmony_ci	return NULL;
22562306a36Sopenharmony_ci}
22662306a36Sopenharmony_ci
22762306a36Sopenharmony_cistatic void reset_pkeys(unsigned long rights)
22862306a36Sopenharmony_ci{
22962306a36Sopenharmony_ci	int pkeys[NR_PKEYS], i;
23062306a36Sopenharmony_ci
23162306a36Sopenharmony_ci	/* Exhaustively allocate all available pkeys */
23262306a36Sopenharmony_ci	for (i = 0; i < NR_PKEYS; i++)
23362306a36Sopenharmony_ci		pkeys[i] = sys_pkey_alloc(0, rights);
23462306a36Sopenharmony_ci
23562306a36Sopenharmony_ci	/* Free all allocated pkeys */
23662306a36Sopenharmony_ci	for (i = 0; i < NR_PKEYS; i++)
23762306a36Sopenharmony_ci		sys_pkey_free(pkeys[i]);
23862306a36Sopenharmony_ci}
23962306a36Sopenharmony_ci
24062306a36Sopenharmony_cistatic int test(void)
24162306a36Sopenharmony_ci{
24262306a36Sopenharmony_ci	pthread_t prot_thread, pacc_thread;
24362306a36Sopenharmony_ci	struct sigaction act;
24462306a36Sopenharmony_ci	pthread_attr_t attr;
24562306a36Sopenharmony_ci	size_t numinsns;
24662306a36Sopenharmony_ci	struct region r;
24762306a36Sopenharmony_ci	int ret, i;
24862306a36Sopenharmony_ci
24962306a36Sopenharmony_ci	srand(time(NULL));
25062306a36Sopenharmony_ci	ret = pkeys_unsupported();
25162306a36Sopenharmony_ci	if (ret)
25262306a36Sopenharmony_ci		return ret;
25362306a36Sopenharmony_ci
25462306a36Sopenharmony_ci	/* Allocate the region */
25562306a36Sopenharmony_ci	r.size = getpagesize();
25662306a36Sopenharmony_ci	r.base = mmap(NULL, r.size, PROT_RWX,
25762306a36Sopenharmony_ci		      MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
25862306a36Sopenharmony_ci	FAIL_IF(r.base == MAP_FAILED);
25962306a36Sopenharmony_ci
26062306a36Sopenharmony_ci	/*
26162306a36Sopenharmony_ci	 * Fill the region with no-ops with a branch at the end
26262306a36Sopenharmony_ci	 * for returning to the caller
26362306a36Sopenharmony_ci	 */
26462306a36Sopenharmony_ci	numinsns = r.size / sizeof(r.base[0]);
26562306a36Sopenharmony_ci	for (i = 0; i < numinsns - 1; i++)
26662306a36Sopenharmony_ci		r.base[i] = PPC_INST_NOP;
26762306a36Sopenharmony_ci	r.base[i] = PPC_INST_BLR;
26862306a36Sopenharmony_ci
26962306a36Sopenharmony_ci	/* Setup SIGSEGV handler */
27062306a36Sopenharmony_ci	act.sa_handler = 0;
27162306a36Sopenharmony_ci	act.sa_sigaction = segv_handler;
27262306a36Sopenharmony_ci	FAIL_IF(sigprocmask(SIG_SETMASK, 0, &act.sa_mask) != 0);
27362306a36Sopenharmony_ci	act.sa_flags = SA_SIGINFO;
27462306a36Sopenharmony_ci	act.sa_restorer = 0;
27562306a36Sopenharmony_ci	FAIL_IF(sigaction(SIGSEGV, &act, NULL) != 0);
27662306a36Sopenharmony_ci
27762306a36Sopenharmony_ci	/*
27862306a36Sopenharmony_ci	 * For these tests, the parent process should clear all bits of
27962306a36Sopenharmony_ci	 * AMR and IAMR, i.e. impose no restrictions, for all available
28062306a36Sopenharmony_ci	 * pkeys. This will be the base for the initial AMR and IAMR
28162306a36Sopenharmony_ci	 * values for all the test thread pairs.
28262306a36Sopenharmony_ci	 *
28362306a36Sopenharmony_ci	 * If the AMR and IAMR bits of all available pkeys are cleared
28462306a36Sopenharmony_ci	 * before running the tests and a fault is generated when
28562306a36Sopenharmony_ci	 * attempting to read, write or execute instructions from a
28662306a36Sopenharmony_ci	 * pkey protected region, the pkey responsible for this must be
28762306a36Sopenharmony_ci	 * the one from the protect-and-access thread since the other
28862306a36Sopenharmony_ci	 * one is fully permissive. Despite that, if the pkey reported
28962306a36Sopenharmony_ci	 * by siginfo is not the restrictive pkey, then there must be a
29062306a36Sopenharmony_ci	 * kernel bug.
29162306a36Sopenharmony_ci	 */
29262306a36Sopenharmony_ci	reset_pkeys(0);
29362306a36Sopenharmony_ci
29462306a36Sopenharmony_ci	/* Setup barrier for protect and protect-and-access threads */
29562306a36Sopenharmony_ci	FAIL_IF(pthread_attr_init(&attr) != 0);
29662306a36Sopenharmony_ci	FAIL_IF(pthread_barrier_init(&iteration_barrier, NULL, 2) != 0);
29762306a36Sopenharmony_ci
29862306a36Sopenharmony_ci	/* Setup and start protect and protect-and-read threads */
29962306a36Sopenharmony_ci	puts("starting thread pair (protect, protect-and-read)");
30062306a36Sopenharmony_ci	r.rights = PKEY_DISABLE_ACCESS;
30162306a36Sopenharmony_ci	FAIL_IF(pthread_create(&prot_thread, &attr, &protect, &r) != 0);
30262306a36Sopenharmony_ci	FAIL_IF(pthread_create(&pacc_thread, &attr, &protect_access, &r) != 0);
30362306a36Sopenharmony_ci	FAIL_IF(pthread_join(prot_thread, NULL) != 0);
30462306a36Sopenharmony_ci	FAIL_IF(pthread_join(pacc_thread, NULL) != 0);
30562306a36Sopenharmony_ci
30662306a36Sopenharmony_ci	/* Setup and start protect and protect-and-write threads */
30762306a36Sopenharmony_ci	puts("starting thread pair (protect, protect-and-write)");
30862306a36Sopenharmony_ci	r.rights = PKEY_DISABLE_WRITE;
30962306a36Sopenharmony_ci	FAIL_IF(pthread_create(&prot_thread, &attr, &protect, &r) != 0);
31062306a36Sopenharmony_ci	FAIL_IF(pthread_create(&pacc_thread, &attr, &protect_access, &r) != 0);
31162306a36Sopenharmony_ci	FAIL_IF(pthread_join(prot_thread, NULL) != 0);
31262306a36Sopenharmony_ci	FAIL_IF(pthread_join(pacc_thread, NULL) != 0);
31362306a36Sopenharmony_ci
31462306a36Sopenharmony_ci	/* Setup and start protect and protect-and-execute threads */
31562306a36Sopenharmony_ci	puts("starting thread pair (protect, protect-and-execute)");
31662306a36Sopenharmony_ci	r.rights = PKEY_DISABLE_EXECUTE;
31762306a36Sopenharmony_ci	FAIL_IF(pthread_create(&prot_thread, &attr, &protect, &r) != 0);
31862306a36Sopenharmony_ci	FAIL_IF(pthread_create(&pacc_thread, &attr, &protect_access, &r) != 0);
31962306a36Sopenharmony_ci	FAIL_IF(pthread_join(prot_thread, NULL) != 0);
32062306a36Sopenharmony_ci	FAIL_IF(pthread_join(pacc_thread, NULL) != 0);
32162306a36Sopenharmony_ci
32262306a36Sopenharmony_ci	/* Cleanup */
32362306a36Sopenharmony_ci	FAIL_IF(pthread_attr_destroy(&attr) != 0);
32462306a36Sopenharmony_ci	FAIL_IF(pthread_barrier_destroy(&iteration_barrier) != 0);
32562306a36Sopenharmony_ci	munmap(r.base, r.size);
32662306a36Sopenharmony_ci
32762306a36Sopenharmony_ci	return 0;
32862306a36Sopenharmony_ci}
32962306a36Sopenharmony_ci
33062306a36Sopenharmony_ciint main(void)
33162306a36Sopenharmony_ci{
33262306a36Sopenharmony_ci	return test_harness(test, "pkey_siginfo");
33362306a36Sopenharmony_ci}
334