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