linux/tools/testing/selftests/arm64/pauth/pac.c
<<
>>
Prefs
   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
 371