On 8/28/20 2:16 PM, Boyan Karatotev wrote:
PAuth adds 5 different keys that can be used to sign addresses.
Add a test that verifies that the kernel initializes them uniquely and preserves them across context switches.
Reviewed-by: Vincenzo Frascino Vincenzo.Frascino@arm.com
Cc: Shuah Khan shuah@kernel.org Cc: Catalin Marinas catalin.marinas@arm.com Cc: Will Deacon will@kernel.org Signed-off-by: Boyan Karatotev boyan.karatotev@arm.com
tools/testing/selftests/arm64/pauth/pac.c | 116 ++++++++++++++++++++++ 1 file changed, 116 insertions(+)
diff --git a/tools/testing/selftests/arm64/pauth/pac.c b/tools/testing/selftests/arm64/pauth/pac.c index 16dea47b11c7..718f49adc275 100644 --- a/tools/testing/selftests/arm64/pauth/pac.c +++ b/tools/testing/selftests/arm64/pauth/pac.c @@ -1,10 +1,13 @@ // SPDX-License-Identifier: GPL-2.0 // Copyright (C) 2020 ARM Limited +#define _GNU_SOURCE
#include <sys/auxv.h> #include <sys/types.h> #include <sys/wait.h> #include <signal.h> +#include <sched.h> #include "../../kselftest_harness.h" #include "helper.h" @@ -21,6 +24,7 @@
- The VA space size is 48 bits. Bigger is opt-in.
*/ #define PAC_MASK (~0xff80ffffffffffff) +#define ARBITRARY_VALUE (0x1234) #define ASSERT_PAUTH_ENABLED() \ do { \ unsigned long hwcaps = getauxval(AT_HWCAP); \ @@ -66,13 +70,36 @@ int are_same(struct signatures *old, struct signatures *new, int nkeys) return res; } +int are_unique(struct signatures *sign, int nkeys) +{
- size_t vals[nkeys];
- vals[0] = sign->keyia & PAC_MASK;
- vals[1] = sign->keyib & PAC_MASK;
- vals[2] = sign->keyda & PAC_MASK;
- vals[3] = sign->keydb & PAC_MASK;
- if (nkeys >= 4)
vals[4] = sign->keyg & PAC_MASK;
- for (int i = 0; i < nkeys - 1; i++) {
for (int j = i + 1; j < nkeys; j++) {
if (vals[i] == vals[j])
return 0;
}
- }
- return 1;
+}
int exec_sign_all(struct signatures *signed_vals, size_t val) { int new_stdin[2]; int new_stdout[2]; int status;
- int i; ssize_t ret; pid_t pid;
- cpu_set_t mask;
ret = pipe(new_stdin); if (ret == -1) { @@ -86,6 +113,20 @@ int exec_sign_all(struct signatures *signed_vals, size_t val) return -1; }
- /*
* pin this process and all its children to a single CPU, so it can also
* guarantee a context switch with its child
*/
- sched_getaffinity(0, sizeof(mask), &mask);
- for (i = 0; i < sizeof(cpu_set_t); i++)
if (CPU_ISSET(i, &mask))
break;
- CPU_ZERO(&mask);
- CPU_SET(i, &mask);
- sched_setaffinity(0, sizeof(mask), &mask);
- pid = fork(); // child if (pid == 0) {
@@ -192,6 +233,38 @@ TEST(pac_instructions_not_nop_generic) ASSERT_NE(0, keyg) TH_LOG("keyg instructions did nothing"); } +TEST(single_thread_unique_keys) +{
- int unique = 0;
- int nkeys = NKEYS;
- struct signatures signed_vals;
- unsigned long hwcaps = getauxval(AT_HWCAP);
- /* generic and data key instructions are not in NOP space. This prevents a SIGILL */
- ASSERT_NE(0, hwcaps & HWCAP_PACA) TH_LOG("PAUTH not enabled");
- if (!(hwcaps & HWCAP_PACG)) {
TH_LOG("WARNING: Generic PAUTH not enabled. Skipping generic key checks");
nkeys = NKEYS - 1;
- }
- /*
* The PAC field is up to 7 bits. Even with unique keys there is about
* 5% chance for a collision. This chance rapidly increases the fewer
* bits there are, a comparison of the keys directly will be more
* reliable All signed values need to be unique at least once out of n
* attempts to be certain that the keys are unique
*/
- for (int i = 0; i < PAC_COLLISION_ATTEMPTS; i++) {
if (nkeys == NKEYS)
sign_all(&signed_vals, i);
else
sign_specific(&signed_vals, i);
unique |= are_unique(&signed_vals, nkeys);
- }
- ASSERT_EQ(1, unique) TH_LOG("keys clashed every time");
+}
/*
- fork() does not change keys. Only exec() does so call a worker program.
- Its only job is to sign a value and report back the resutls
@@ -227,5 +300,48 @@ TEST(exec_unique_keys) ASSERT_EQ(1, different) TH_LOG("exec() did not change keys"); } +TEST(context_switch_keep_keys) +{
- int ret;
- struct signatures trash;
- struct signatures before;
- struct signatures after;
- ASSERT_PAUTH_ENABLED();
- sign_specific(&before, ARBITRARY_VALUE);
- /* will context switch with a process with different keys at least once */
- ret = exec_sign_all(&trash, ARBITRARY_VALUE);
- ASSERT_EQ(0, ret) TH_LOG("failed to run worker");
- sign_specific(&after, ARBITRARY_VALUE);
- ASSERT_EQ(before.keyia, after.keyia) TH_LOG("keyia changed after context switching");
- ASSERT_EQ(before.keyib, after.keyib) TH_LOG("keyib changed after context switching");
- ASSERT_EQ(before.keyda, after.keyda) TH_LOG("keyda changed after context switching");
- ASSERT_EQ(before.keydb, after.keydb) TH_LOG("keydb changed after context switching");
+}
+TEST(context_switch_keep_keys_generic) +{
- int ret;
- struct signatures trash;
- size_t before;
- size_t after;
- ASSERT_GENERIC_PAUTH_ENABLED();
- before = keyg_sign(ARBITRARY_VALUE);
- /* will context switch with a process with different keys at least once */
- ret = exec_sign_all(&trash, ARBITRARY_VALUE);
- ASSERT_EQ(0, ret) TH_LOG("failed to run worker");
- after = keyg_sign(ARBITRARY_VALUE);
- ASSERT_EQ(before, after) TH_LOG("keyg changed after context switching");
+}
TEST_HARNESS_MAIN