cachepc-linux

Fork of AMDESE/linux with modifications for CachePC side-channel attack
git clone https://git.sinitax.com/sinitax/cachepc-linux
Log | Files | Refs | README | LICENSE | sfeed.txt

pac.c (8961B)


      1// SPDX-License-Identifier: GPL-2.0
      2// Copyright (C) 2020 ARM Limited
      3
      4#define _GNU_SOURCE
      5
      6#include <sys/auxv.h>
      7#include <sys/types.h>
      8#include <sys/wait.h>
      9#include <signal.h>
     10#include <setjmp.h>
     11#include <sched.h>
     12
     13#include "../../kselftest_harness.h"
     14#include "helper.h"
     15
     16#define PAC_COLLISION_ATTEMPTS 10
     17/*
     18 * The kernel sets TBID by default. So bits 55 and above should remain
     19 * untouched no matter what.
     20 * The VA space size is 48 bits. Bigger is opt-in.
     21 */
     22#define PAC_MASK (~0xff80ffffffffffff)
     23#define ARBITRARY_VALUE (0x1234)
     24#define ASSERT_PAUTH_ENABLED() \
     25do { \
     26	unsigned long hwcaps = getauxval(AT_HWCAP); \
     27	/* data key instructions are not in NOP space. This prevents a SIGILL */ \
     28	if (!(hwcaps & HWCAP_PACA))					\
     29		SKIP(return, "PAUTH not enabled"); \
     30} while (0)
     31#define ASSERT_GENERIC_PAUTH_ENABLED() \
     32do { \
     33	unsigned long hwcaps = getauxval(AT_HWCAP); \
     34	/* generic key instructions are not in NOP space. This prevents a SIGILL */ \
     35	if (!(hwcaps & HWCAP_PACG)) \
     36		SKIP(return, "Generic PAUTH not enabled");	\
     37} while (0)
     38
     39void sign_specific(struct signatures *sign, size_t val)
     40{
     41	sign->keyia = keyia_sign(val);
     42	sign->keyib = keyib_sign(val);
     43	sign->keyda = keyda_sign(val);
     44	sign->keydb = keydb_sign(val);
     45}
     46
     47void sign_all(struct signatures *sign, size_t val)
     48{
     49	sign->keyia = keyia_sign(val);
     50	sign->keyib = keyib_sign(val);
     51	sign->keyda = keyda_sign(val);
     52	sign->keydb = keydb_sign(val);
     53	sign->keyg  = keyg_sign(val);
     54}
     55
     56int n_same(struct signatures *old, struct signatures *new, int nkeys)
     57{
     58	int res = 0;
     59
     60	res += old->keyia == new->keyia;
     61	res += old->keyib == new->keyib;
     62	res += old->keyda == new->keyda;
     63	res += old->keydb == new->keydb;
     64	if (nkeys == NKEYS)
     65		res += old->keyg == new->keyg;
     66
     67	return res;
     68}
     69
     70int n_same_single_set(struct signatures *sign, int nkeys)
     71{
     72	size_t vals[nkeys];
     73	int same = 0;
     74
     75	vals[0] = sign->keyia & PAC_MASK;
     76	vals[1] = sign->keyib & PAC_MASK;
     77	vals[2] = sign->keyda & PAC_MASK;
     78	vals[3] = sign->keydb & PAC_MASK;
     79
     80	if (nkeys >= 4)
     81		vals[4] = sign->keyg & PAC_MASK;
     82
     83	for (int i = 0; i < nkeys - 1; i++) {
     84		for (int j = i + 1; j < nkeys; j++) {
     85			if (vals[i] == vals[j])
     86				same += 1;
     87		}
     88	}
     89	return same;
     90}
     91
     92int exec_sign_all(struct signatures *signed_vals, size_t val)
     93{
     94	int new_stdin[2];
     95	int new_stdout[2];
     96	int status;
     97	int i;
     98	ssize_t ret;
     99	pid_t pid;
    100	cpu_set_t mask;
    101
    102	ret = pipe(new_stdin);
    103	if (ret == -1) {
    104		perror("pipe returned error");
    105		return -1;
    106	}
    107
    108	ret = pipe(new_stdout);
    109	if (ret == -1) {
    110		perror("pipe returned error");
    111		return -1;
    112	}
    113
    114	/*
    115	 * pin this process and all its children to a single CPU, so it can also
    116	 * guarantee a context switch with its child
    117	 */
    118	sched_getaffinity(0, sizeof(mask), &mask);
    119
    120	for (i = 0; i < sizeof(cpu_set_t); i++)
    121		if (CPU_ISSET(i, &mask))
    122			break;
    123
    124	CPU_ZERO(&mask);
    125	CPU_SET(i, &mask);
    126	sched_setaffinity(0, sizeof(mask), &mask);
    127
    128	pid = fork();
    129	// child
    130	if (pid == 0) {
    131		dup2(new_stdin[0], STDIN_FILENO);
    132		if (ret == -1) {
    133			perror("dup2 returned error");
    134			exit(1);
    135		}
    136
    137		dup2(new_stdout[1], STDOUT_FILENO);
    138		if (ret == -1) {
    139			perror("dup2 returned error");
    140			exit(1);
    141		}
    142
    143		close(new_stdin[0]);
    144		close(new_stdin[1]);
    145		close(new_stdout[0]);
    146		close(new_stdout[1]);
    147
    148		ret = execl("exec_target", "exec_target", (char *)NULL);
    149		if (ret == -1) {
    150			perror("exec returned error");
    151			exit(1);
    152		}
    153	}
    154
    155	close(new_stdin[0]);
    156	close(new_stdout[1]);
    157
    158	ret = write(new_stdin[1], &val, sizeof(size_t));
    159	if (ret == -1) {
    160		perror("write returned error");
    161		return -1;
    162	}
    163
    164	/*
    165	 * wait for the worker to finish, so that read() reads all data
    166	 * will also context switch with worker so that this function can be used
    167	 * for context switch tests
    168	 */
    169	waitpid(pid, &status, 0);
    170	if (WIFEXITED(status) == 0) {
    171		fprintf(stderr, "worker exited unexpectedly\n");
    172		return -1;
    173	}
    174	if (WEXITSTATUS(status) != 0) {
    175		fprintf(stderr, "worker exited with error\n");
    176		return -1;
    177	}
    178
    179	ret = read(new_stdout[0], signed_vals, sizeof(struct signatures));
    180	if (ret == -1) {
    181		perror("read returned error");
    182		return -1;
    183	}
    184
    185	return 0;
    186}
    187
    188sigjmp_buf jmpbuf;
    189void pac_signal_handler(int signum, siginfo_t *si, void *uc)
    190{
    191	if (signum == SIGSEGV || signum == SIGILL)
    192		siglongjmp(jmpbuf, 1);
    193}
    194
    195/* check that a corrupted PAC results in SIGSEGV or SIGILL */
    196TEST(corrupt_pac)
    197{
    198	struct sigaction sa;
    199
    200	ASSERT_PAUTH_ENABLED();
    201	if (sigsetjmp(jmpbuf, 1) == 0) {
    202		sa.sa_sigaction = pac_signal_handler;
    203		sa.sa_flags = SA_SIGINFO | SA_RESETHAND;
    204		sigemptyset(&sa.sa_mask);
    205
    206		sigaction(SIGSEGV, &sa, NULL);
    207		sigaction(SIGILL, &sa, NULL);
    208
    209		pac_corruptor();
    210		ASSERT_TRUE(0) TH_LOG("SIGSEGV/SIGILL signal did not occur");
    211	}
    212}
    213
    214/*
    215 * There are no separate pac* and aut* controls so checking only the pac*
    216 * instructions is sufficient
    217 */
    218TEST(pac_instructions_not_nop)
    219{
    220	size_t keyia = 0;
    221	size_t keyib = 0;
    222	size_t keyda = 0;
    223	size_t keydb = 0;
    224
    225	ASSERT_PAUTH_ENABLED();
    226
    227	for (int i = 0; i < PAC_COLLISION_ATTEMPTS; i++) {
    228		keyia |= keyia_sign(i) & PAC_MASK;
    229		keyib |= keyib_sign(i) & PAC_MASK;
    230		keyda |= keyda_sign(i) & PAC_MASK;
    231		keydb |= keydb_sign(i) & PAC_MASK;
    232	}
    233
    234	ASSERT_NE(0, keyia) TH_LOG("keyia instructions did nothing");
    235	ASSERT_NE(0, keyib) TH_LOG("keyib instructions did nothing");
    236	ASSERT_NE(0, keyda) TH_LOG("keyda instructions did nothing");
    237	ASSERT_NE(0, keydb) TH_LOG("keydb instructions did nothing");
    238}
    239
    240TEST(pac_instructions_not_nop_generic)
    241{
    242	size_t keyg = 0;
    243
    244	ASSERT_GENERIC_PAUTH_ENABLED();
    245
    246	for (int i = 0; i < PAC_COLLISION_ATTEMPTS; i++)
    247		keyg |= keyg_sign(i) & PAC_MASK;
    248
    249	ASSERT_NE(0, keyg)  TH_LOG("keyg instructions did nothing");
    250}
    251
    252TEST(single_thread_different_keys)
    253{
    254	int same = 10;
    255	int nkeys = NKEYS;
    256	int tmp;
    257	struct signatures signed_vals;
    258	unsigned long hwcaps = getauxval(AT_HWCAP);
    259
    260	/* generic and data key instructions are not in NOP space. This prevents a SIGILL */
    261	ASSERT_PAUTH_ENABLED();
    262	if (!(hwcaps & HWCAP_PACG)) {
    263		TH_LOG("WARNING: Generic PAUTH not enabled. Skipping generic key checks");
    264		nkeys = NKEYS - 1;
    265	}
    266
    267	/*
    268	 * In Linux the PAC field can be up to 7 bits wide. Even if keys are
    269	 * different, there is about 5% chance for PACs to collide with
    270	 * different addresses. This chance rapidly increases with fewer bits
    271	 * allocated for the PAC (e.g. wider address). A comparison of the keys
    272	 * directly will be more reliable.
    273	 * All signed values need to be different at least once out of n
    274	 * attempts to be certain that the keys are different
    275	 */
    276	for (int i = 0; i < PAC_COLLISION_ATTEMPTS; i++) {
    277		if (nkeys == NKEYS)
    278			sign_all(&signed_vals, i);
    279		else
    280			sign_specific(&signed_vals, i);
    281
    282		tmp = n_same_single_set(&signed_vals, nkeys);
    283		if (tmp < same)
    284			same = tmp;
    285	}
    286
    287	ASSERT_EQ(0, same) TH_LOG("%d keys clashed every time", same);
    288}
    289
    290/*
    291 * fork() does not change keys. Only exec() does so call a worker program.
    292 * Its only job is to sign a value and report back the resutls
    293 */
    294TEST(exec_changed_keys)
    295{
    296	struct signatures new_keys;
    297	struct signatures old_keys;
    298	int ret;
    299	int same = 10;
    300	int nkeys = NKEYS;
    301	unsigned long hwcaps = getauxval(AT_HWCAP);
    302
    303	/* generic and data key instructions are not in NOP space. This prevents a SIGILL */
    304	ASSERT_PAUTH_ENABLED();
    305	if (!(hwcaps & HWCAP_PACG)) {
    306		TH_LOG("WARNING: Generic PAUTH not enabled. Skipping generic key checks");
    307		nkeys = NKEYS - 1;
    308	}
    309
    310	for (int i = 0; i < PAC_COLLISION_ATTEMPTS; i++) {
    311		ret = exec_sign_all(&new_keys, i);
    312		ASSERT_EQ(0, ret) TH_LOG("failed to run worker");
    313
    314		if (nkeys == NKEYS)
    315			sign_all(&old_keys, i);
    316		else
    317			sign_specific(&old_keys, i);
    318
    319		ret = n_same(&old_keys, &new_keys, nkeys);
    320		if (ret < same)
    321			same = ret;
    322	}
    323
    324	ASSERT_EQ(0, same) TH_LOG("exec() did not change %d keys", same);
    325}
    326
    327TEST(context_switch_keep_keys)
    328{
    329	int ret;
    330	struct signatures trash;
    331	struct signatures before;
    332	struct signatures after;
    333
    334	ASSERT_PAUTH_ENABLED();
    335
    336	sign_specific(&before, ARBITRARY_VALUE);
    337
    338	/* will context switch with a process with different keys at least once */
    339	ret = exec_sign_all(&trash, ARBITRARY_VALUE);
    340	ASSERT_EQ(0, ret) TH_LOG("failed to run worker");
    341
    342	sign_specific(&after, ARBITRARY_VALUE);
    343
    344	ASSERT_EQ(before.keyia, after.keyia) TH_LOG("keyia changed after context switching");
    345	ASSERT_EQ(before.keyib, after.keyib) TH_LOG("keyib changed after context switching");
    346	ASSERT_EQ(before.keyda, after.keyda) TH_LOG("keyda changed after context switching");
    347	ASSERT_EQ(before.keydb, after.keydb) TH_LOG("keydb changed after context switching");
    348}
    349
    350TEST(context_switch_keep_keys_generic)
    351{
    352	int ret;
    353	struct signatures trash;
    354	size_t before;
    355	size_t after;
    356
    357	ASSERT_GENERIC_PAUTH_ENABLED();
    358
    359	before = keyg_sign(ARBITRARY_VALUE);
    360
    361	/* will context switch with a process with different keys at least once */
    362	ret = exec_sign_all(&trash, ARBITRARY_VALUE);
    363	ASSERT_EQ(0, ret) TH_LOG("failed to run worker");
    364
    365	after = keyg_sign(ARBITRARY_VALUE);
    366
    367	ASSERT_EQ(before, after) TH_LOG("keyg changed after context switching");
    368}
    369
    370TEST_HARNESS_MAIN