linux/tools/testing/selftests/powerpc/mm/pkey_siginfo.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0
   2
   3/*
   4 * Copyright 2020, Sandipan Das, IBM Corp.
   5 *
   6 * Test if the signal information reports the correct memory protection
   7 * key upon getting a key access violation fault for a page that was
   8 * attempted to be protected by two different keys from two competing
   9 * threads at the same time.
  10 */
  11
  12#define _GNU_SOURCE
  13#include <stdio.h>
  14#include <stdlib.h>
  15#include <string.h>
  16#include <signal.h>
  17
  18#include <unistd.h>
  19#include <pthread.h>
  20#include <sys/mman.h>
  21
  22#include "pkeys.h"
  23
  24#define PPC_INST_NOP    0x60000000
  25#define PPC_INST_BLR    0x4e800020
  26#define PROT_RWX        (PROT_READ | PROT_WRITE | PROT_EXEC)
  27
  28#define NUM_ITERATIONS  1000000
  29
  30static volatile sig_atomic_t perm_pkey, rest_pkey;
  31static volatile sig_atomic_t rights, fault_count;
  32static volatile unsigned int *volatile fault_addr;
  33static pthread_barrier_t iteration_barrier;
  34
  35static void segv_handler(int signum, siginfo_t *sinfo, void *ctx)
  36{
  37        void *pgstart;
  38        size_t pgsize;
  39        int pkey;
  40
  41        pkey = siginfo_pkey(sinfo);
  42
  43        /* Check if this fault originated from a pkey access violation */
  44        if (sinfo->si_code != SEGV_PKUERR) {
  45                sigsafe_err("got a fault for an unexpected reason\n");
  46                _exit(1);
  47        }
  48
  49        /* Check if this fault originated from the expected address */
  50        if (sinfo->si_addr != (void *) fault_addr) {
  51                sigsafe_err("got a fault for an unexpected address\n");
  52                _exit(1);
  53        }
  54
  55        /* Check if this fault originated from the restrictive pkey */
  56        if (pkey != rest_pkey) {
  57                sigsafe_err("got a fault for an unexpected pkey\n");
  58                _exit(1);
  59        }
  60
  61        /* Check if too many faults have occurred for the same iteration */
  62        if (fault_count > 0) {
  63                sigsafe_err("got too many faults for the same address\n");
  64                _exit(1);
  65        }
  66
  67        pgsize = getpagesize();
  68        pgstart = (void *) ((unsigned long) fault_addr & ~(pgsize - 1));
  69
  70        /*
  71         * If the current fault occurred due to lack of execute rights,
  72         * reassociate the page with the exec-only pkey since execute
  73         * rights cannot be changed directly for the faulting pkey as
  74         * IAMR is inaccessible from userspace.
  75         *
  76         * Otherwise, if the current fault occurred due to lack of
  77         * read-write rights, change the AMR permission bits for the
  78         * pkey.
  79         *
  80         * This will let the test continue.
  81         */
  82        if (rights == PKEY_DISABLE_EXECUTE &&
  83            mprotect(pgstart, pgsize, PROT_EXEC))
  84                _exit(1);
  85        else
  86                pkey_set_rights(pkey, 0);
  87
  88        fault_count++;
  89}
  90
  91struct region {
  92        unsigned long rights;
  93        unsigned int *base;
  94        size_t size;
  95};
  96
  97static void *protect(void *p)
  98{
  99        unsigned long rights;
 100        unsigned int *base;
 101        size_t size;
 102        int tid, i;
 103
 104        tid = gettid();
 105        base = ((struct region *) p)->base;
 106        size = ((struct region *) p)->size;
 107        FAIL_IF_EXIT(!base);
 108
 109        /* No read, write and execute restrictions */
 110        rights = 0;
 111
 112        printf("tid %d, pkey permissions are %s\n", tid, pkey_rights(rights));
 113
 114        /* Allocate the permissive pkey */
 115        perm_pkey = sys_pkey_alloc(0, rights);
 116        FAIL_IF_EXIT(perm_pkey < 0);
 117
 118        /*
 119         * Repeatedly try to protect the common region with a permissive
 120         * pkey
 121         */
 122        for (i = 0; i < NUM_ITERATIONS; i++) {
 123                /*
 124                 * Wait until the other thread has finished allocating the
 125                 * restrictive pkey or until the next iteration has begun
 126                 */
 127                pthread_barrier_wait(&iteration_barrier);
 128
 129                /* Try to associate the permissive pkey with the region */
 130                FAIL_IF_EXIT(sys_pkey_mprotect(base, size, PROT_RWX,
 131                                               perm_pkey));
 132        }
 133
 134        /* Free the permissive pkey */
 135        sys_pkey_free(perm_pkey);
 136
 137        return NULL;
 138}
 139
 140static void *protect_access(void *p)
 141{
 142        size_t size, numinsns;
 143        unsigned int *base;
 144        int tid, i;
 145
 146        tid = gettid();
 147        base = ((struct region *) p)->base;
 148        size = ((struct region *) p)->size;
 149        rights = ((struct region *) p)->rights;
 150        numinsns = size / sizeof(base[0]);
 151        FAIL_IF_EXIT(!base);
 152
 153        /* Allocate the restrictive pkey */
 154        rest_pkey = sys_pkey_alloc(0, rights);
 155        FAIL_IF_EXIT(rest_pkey < 0);
 156
 157        printf("tid %d, pkey permissions are %s\n", tid, pkey_rights(rights));
 158        printf("tid %d, %s randomly in range [%p, %p]\n", tid,
 159               (rights == PKEY_DISABLE_EXECUTE) ? "execute" :
 160               (rights == PKEY_DISABLE_WRITE)  ? "write" : "read",
 161               base, base + numinsns);
 162
 163        /*
 164         * Repeatedly try to protect the common region with a restrictive
 165         * pkey and read, write or execute from it
 166         */
 167        for (i = 0; i < NUM_ITERATIONS; i++) {
 168                /*
 169                 * Wait until the other thread has finished allocating the
 170                 * permissive pkey or until the next iteration has begun
 171                 */
 172                pthread_barrier_wait(&iteration_barrier);
 173
 174                /* Try to associate the restrictive pkey with the region */
 175                FAIL_IF_EXIT(sys_pkey_mprotect(base, size, PROT_RWX,
 176                                               rest_pkey));
 177
 178                /* Choose a random instruction word address from the region */
 179                fault_addr = base + (rand() % numinsns);
 180                fault_count = 0;
 181
 182                switch (rights) {
 183                /* Read protection test */
 184                case PKEY_DISABLE_ACCESS:
 185                        /*
 186                         * Read an instruction word from the region and
 187                         * verify if it has not been overwritten to
 188                         * something unexpected
 189                         */
 190                        FAIL_IF_EXIT(*fault_addr != PPC_INST_NOP &&
 191                                     *fault_addr != PPC_INST_BLR);
 192                        break;
 193
 194                /* Write protection test */
 195                case PKEY_DISABLE_WRITE:
 196                        /*
 197                         * Write an instruction word to the region and
 198                         * verify if the overwrite has succeeded
 199                         */
 200                        *fault_addr = PPC_INST_BLR;
 201                        FAIL_IF_EXIT(*fault_addr != PPC_INST_BLR);
 202                        break;
 203
 204                /* Execute protection test */
 205                case PKEY_DISABLE_EXECUTE:
 206                        /* Jump to the region and execute instructions */
 207                        asm volatile(
 208                                "mtctr  %0; bctrl"
 209                                : : "r"(fault_addr) : "ctr", "lr");
 210                        break;
 211                }
 212
 213                /*
 214                 * Restore the restrictions originally imposed by the
 215                 * restrictive pkey as the signal handler would have
 216                 * cleared out the corresponding AMR bits
 217                 */
 218                pkey_set_rights(rest_pkey, rights);
 219        }
 220
 221        /* Free restrictive pkey */
 222        sys_pkey_free(rest_pkey);
 223
 224        return NULL;
 225}
 226
 227static void reset_pkeys(unsigned long rights)
 228{
 229        int pkeys[NR_PKEYS], i;
 230
 231        /* Exhaustively allocate all available pkeys */
 232        for (i = 0; i < NR_PKEYS; i++)
 233                pkeys[i] = sys_pkey_alloc(0, rights);
 234
 235        /* Free all allocated pkeys */
 236        for (i = 0; i < NR_PKEYS; i++)
 237                sys_pkey_free(pkeys[i]);
 238}
 239
 240static int test(void)
 241{
 242        pthread_t prot_thread, pacc_thread;
 243        struct sigaction act;
 244        pthread_attr_t attr;
 245        size_t numinsns;
 246        struct region r;
 247        int ret, i;
 248
 249        srand(time(NULL));
 250        ret = pkeys_unsupported();
 251        if (ret)
 252                return ret;
 253
 254        /* Allocate the region */
 255        r.size = getpagesize();
 256        r.base = mmap(NULL, r.size, PROT_RWX,
 257                      MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
 258        FAIL_IF(r.base == MAP_FAILED);
 259
 260        /*
 261         * Fill the region with no-ops with a branch at the end
 262         * for returning to the caller
 263         */
 264        numinsns = r.size / sizeof(r.base[0]);
 265        for (i = 0; i < numinsns - 1; i++)
 266                r.base[i] = PPC_INST_NOP;
 267        r.base[i] = PPC_INST_BLR;
 268
 269        /* Setup SIGSEGV handler */
 270        act.sa_handler = 0;
 271        act.sa_sigaction = segv_handler;
 272        FAIL_IF(sigprocmask(SIG_SETMASK, 0, &act.sa_mask) != 0);
 273        act.sa_flags = SA_SIGINFO;
 274        act.sa_restorer = 0;
 275        FAIL_IF(sigaction(SIGSEGV, &act, NULL) != 0);
 276
 277        /*
 278         * For these tests, the parent process should clear all bits of
 279         * AMR and IAMR, i.e. impose no restrictions, for all available
 280         * pkeys. This will be the base for the initial AMR and IAMR
 281         * values for all the test thread pairs.
 282         *
 283         * If the AMR and IAMR bits of all available pkeys are cleared
 284         * before running the tests and a fault is generated when
 285         * attempting to read, write or execute instructions from a
 286         * pkey protected region, the pkey responsible for this must be
 287         * the one from the protect-and-access thread since the other
 288         * one is fully permissive. Despite that, if the pkey reported
 289         * by siginfo is not the restrictive pkey, then there must be a
 290         * kernel bug.
 291         */
 292        reset_pkeys(0);
 293
 294        /* Setup barrier for protect and protect-and-access threads */
 295        FAIL_IF(pthread_attr_init(&attr) != 0);
 296        FAIL_IF(pthread_barrier_init(&iteration_barrier, NULL, 2) != 0);
 297
 298        /* Setup and start protect and protect-and-read threads */
 299        puts("starting thread pair (protect, protect-and-read)");
 300        r.rights = PKEY_DISABLE_ACCESS;
 301        FAIL_IF(pthread_create(&prot_thread, &attr, &protect, &r) != 0);
 302        FAIL_IF(pthread_create(&pacc_thread, &attr, &protect_access, &r) != 0);
 303        FAIL_IF(pthread_join(prot_thread, NULL) != 0);
 304        FAIL_IF(pthread_join(pacc_thread, NULL) != 0);
 305
 306        /* Setup and start protect and protect-and-write threads */
 307        puts("starting thread pair (protect, protect-and-write)");
 308        r.rights = PKEY_DISABLE_WRITE;
 309        FAIL_IF(pthread_create(&prot_thread, &attr, &protect, &r) != 0);
 310        FAIL_IF(pthread_create(&pacc_thread, &attr, &protect_access, &r) != 0);
 311        FAIL_IF(pthread_join(prot_thread, NULL) != 0);
 312        FAIL_IF(pthread_join(pacc_thread, NULL) != 0);
 313
 314        /* Setup and start protect and protect-and-execute threads */
 315        puts("starting thread pair (protect, protect-and-execute)");
 316        r.rights = PKEY_DISABLE_EXECUTE;
 317        FAIL_IF(pthread_create(&prot_thread, &attr, &protect, &r) != 0);
 318        FAIL_IF(pthread_create(&pacc_thread, &attr, &protect_access, &r) != 0);
 319        FAIL_IF(pthread_join(prot_thread, NULL) != 0);
 320        FAIL_IF(pthread_join(pacc_thread, NULL) != 0);
 321
 322        /* Cleanup */
 323        FAIL_IF(pthread_attr_destroy(&attr) != 0);
 324        FAIL_IF(pthread_barrier_destroy(&iteration_barrier) != 0);
 325        munmap(r.base, r.size);
 326
 327        return 0;
 328}
 329
 330int main(void)
 331{
 332        return test_harness(test, "pkey_siginfo");
 333}
 334