1// SPDX-License-Identifier: GPL-2.0-or-later 2/* 3 * Copyright (c) 2019 Red Hat, Inc. 4 * 5 * Memory Protection Keys for Userspace (PKU aka PKEYs) is a Skylake-SP 6 * server feature that provides a mechanism for enforcing page-based 7 * protections, but without requiring modification of the page tables 8 * when an application changes protection domains. It works by dedicating 9 * 4 previously ignored bits in each page table entry to a "protection key", 10 * giving 16 possible keys. 11 * 12 * Basic method for PKEYs testing: 13 * 1. test allocates a pkey(e.g. PKEY_DISABLE_ACCESS) via pkey_alloc() 14 * 2. pkey_mprotect() apply this pkey to a piece of memory(buffer) 15 * 3. check if access right of the buffer has been changed and take effect 16 * 4. remove the access right(pkey) from this buffer via pkey_mprotect() 17 * 5. check if buffer area can be read or write after removing pkey 18 * 6. pkey_free() releases the pkey after using it 19 * 20 * Looping around this basic test on diffenrent types of memory. 21 */ 22 23#define _GNU_SOURCE 24#include <stdio.h> 25#include <unistd.h> 26#include <errno.h> 27#include <stdlib.h> 28#include <sys/syscall.h> 29#include <sys/mman.h> 30#include <sys/wait.h> 31 32#include "pkey.h" 33 34#define TEST_FILE "pkey_testfile" 35#define STR "abcdefghijklmnopqrstuvwxyz12345\n" 36#define PATH_VM_NRHPS "/proc/sys/vm/nr_hugepages" 37 38static int size; 39 40static struct tcase { 41 unsigned long flags; 42 unsigned long access_rights; 43 char *name; 44} tcases[] = { 45 {0, PKEY_DISABLE_ACCESS, "PKEY_DISABLE_ACCESS"}, 46 {0, PKEY_DISABLE_WRITE, "PKEY_DISABLE_WRITE"}, 47}; 48 49static void setup(void) 50{ 51 int i, fd; 52 53 check_pkey_support(); 54 55 if (tst_hugepages == test.hugepages.number) 56 size = SAFE_READ_MEMINFO("Hugepagesize:") * 1024; 57 else 58 size = getpagesize(); 59 60 fd = SAFE_OPEN(TEST_FILE, O_RDWR | O_CREAT, 0664); 61 for (i = 0; i < 128; i++) 62 SAFE_WRITE(SAFE_WRITE_ALL, fd, STR, strlen(STR)); 63 64 SAFE_CLOSE(fd); 65} 66 67static struct mmap_param { 68 int prot; 69 int flags; 70 int fd; 71} mmap_params[] = { 72 {PROT_READ, MAP_ANONYMOUS | MAP_PRIVATE, -1}, 73 {PROT_READ, MAP_ANONYMOUS | MAP_SHARED, -1}, 74 {PROT_READ, MAP_ANONYMOUS | MAP_PRIVATE | MAP_HUGETLB, -1}, 75 {PROT_READ, MAP_ANONYMOUS | MAP_SHARED | MAP_HUGETLB, -1}, 76 {PROT_READ, MAP_PRIVATE, 0}, 77 {PROT_READ, MAP_SHARED, 0}, 78 79 {PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1}, 80 {PROT_WRITE, MAP_ANONYMOUS | MAP_SHARED, -1}, 81 {PROT_WRITE, MAP_PRIVATE, 0}, 82 {PROT_WRITE, MAP_SHARED, 0}, 83 {PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE | MAP_HUGETLB, -1}, 84 {PROT_WRITE, MAP_ANONYMOUS | MAP_SHARED | MAP_HUGETLB, -1}, 85 86 {PROT_EXEC, MAP_ANONYMOUS | MAP_PRIVATE, -1}, 87 {PROT_EXEC, MAP_ANONYMOUS | MAP_SHARED, -1}, 88 {PROT_EXEC, MAP_ANONYMOUS | MAP_PRIVATE | MAP_HUGETLB, -1}, 89 {PROT_EXEC, MAP_ANONYMOUS | MAP_SHARED | MAP_HUGETLB, -1}, 90 {PROT_EXEC, MAP_PRIVATE, 0}, 91 {PROT_EXEC, MAP_SHARED, 0}, 92 93 {PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1}, 94 {PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_SHARED, -1}, 95 {PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE | MAP_HUGETLB, -1}, 96 {PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_SHARED | MAP_HUGETLB, -1}, 97 {PROT_READ | PROT_WRITE, MAP_PRIVATE, 0}, 98 {PROT_READ | PROT_WRITE, MAP_SHARED, 0}, 99 100 {PROT_READ | PROT_WRITE | PROT_EXEC, MAP_ANONYMOUS | MAP_PRIVATE, -1}, 101 {PROT_READ | PROT_WRITE | PROT_EXEC, MAP_ANONYMOUS | MAP_SHARED, -1}, 102 {PROT_READ | PROT_WRITE | PROT_EXEC, MAP_ANONYMOUS | MAP_PRIVATE | MAP_HUGETLB, -1}, 103 {PROT_READ | PROT_WRITE | PROT_EXEC, MAP_ANONYMOUS | MAP_SHARED | MAP_HUGETLB, -1}, 104 {PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE, 0}, 105 {PROT_READ | PROT_WRITE | PROT_EXEC, MAP_SHARED, 0}, 106}; 107 108static char *flag_to_str(int flags) 109{ 110 switch (flags) { 111 case MAP_PRIVATE: 112 return "MAP_PRIVATE"; 113 case MAP_SHARED: 114 return "MAP_SHARED"; 115 case MAP_ANONYMOUS | MAP_PRIVATE: 116 return "MAP_ANONYMOUS|MAP_PRIVATE"; 117 case MAP_ANONYMOUS | MAP_SHARED: 118 return "MAP_ANONYMOUS|MAP_SHARED"; 119 case MAP_ANONYMOUS | MAP_PRIVATE | MAP_HUGETLB: 120 return "MAP_ANONYMOUS|MAP_PRIVATE|MAP_HUGETLB"; 121 case MAP_ANONYMOUS | MAP_SHARED | MAP_HUGETLB: 122 return "MAP_ANONYMOUS|MAP_SHARED|MAP_HUGETLB"; 123 default: 124 return "UNKNOWN FLAGS"; 125 } 126} 127 128static void pkey_test(struct tcase *tc, struct mmap_param *mpa) 129{ 130 pid_t pid; 131 char *buffer; 132 int pkey, status; 133 int fd = mpa->fd; 134 135 if (!tst_hugepages && (mpa->flags & MAP_HUGETLB)) { 136 tst_res(TINFO, "Skip test on (%s) buffer", flag_to_str(mpa->flags)); 137 return; 138 } 139 140 if (fd == 0) 141 fd = SAFE_OPEN(TEST_FILE, O_RDWR | O_CREAT, 0664); 142 143 buffer = SAFE_MMAP(NULL, size, mpa->prot, mpa->flags, fd, 0); 144 145 pkey = ltp_pkey_alloc(tc->flags, tc->access_rights); 146 if (pkey == -1) 147 tst_brk(TBROK | TERRNO, "pkey_alloc failed"); 148 149 tst_res(TINFO, "Set %s on (%s) buffer", tc->name, flag_to_str(mpa->flags)); 150 if (ltp_pkey_mprotect(buffer, size, mpa->prot, pkey) == -1) 151 tst_brk(TBROK | TERRNO, "pkey_mprotect failed"); 152 153 pid = SAFE_FORK(); 154 if (pid == 0) { 155 tst_no_corefile(0); 156 157 switch (tc->access_rights) { 158 case PKEY_DISABLE_ACCESS: 159 tst_res(TFAIL | TERRNO, 160 "Read buffer success, buffer[0] = %d", *buffer); 161 break; 162 case PKEY_DISABLE_WRITE: 163 *buffer = 'a'; 164 tst_res(TFAIL | TERRNO, 165 "Write buffer success, buffer[0] = %d", *buffer); 166 break; 167 } 168 exit(0); 169 } 170 171 SAFE_WAITPID(pid, &status, 0); 172 173 if (WIFSIGNALED(status) && WTERMSIG(status) == SIGSEGV) 174 tst_res(TPASS, "Child ended by %s as expected", tst_strsig(SIGSEGV)); 175 else 176 tst_res(TFAIL, "Child: %s", tst_strstatus(status)); 177 178 tst_res(TINFO, "Remove %s from the buffer", tc->name); 179 if (ltp_pkey_mprotect(buffer, size, mpa->prot, 0x0) == -1) 180 tst_brk(TBROK | TERRNO, "pkey_mprotect failed"); 181 182 switch (mpa->prot) { 183 case PROT_READ: 184 tst_res(TPASS, "Read buffer success, buffer[0] = %d", *buffer); 185 break; 186 case PROT_WRITE: 187 *buffer = 'a'; 188 tst_res(TPASS, "Write buffer success, buffer[0] = %d", *buffer); 189 break; 190 case PROT_READ | PROT_WRITE: 191 case PROT_READ | PROT_WRITE | PROT_EXEC: 192 *buffer = 'a'; 193 tst_res(TPASS, "Read & Write buffer success, buffer[0] = %d", *buffer); 194 break; 195 } 196 197 if (fd >= 0) 198 SAFE_CLOSE(fd); 199 200 SAFE_MUNMAP(buffer, size); 201 202 if (ltp_pkey_free(pkey) == -1) 203 tst_brk(TBROK | TERRNO, "pkey_free failed"); 204} 205 206static void verify_pkey(unsigned int i) 207{ 208 long unsigned int j; 209 struct mmap_param *mpa; 210 211 struct tcase *tc = &tcases[i]; 212 213 for (j = 0; j < ARRAY_SIZE(mmap_params); j++) { 214 mpa = &mmap_params[j]; 215 216 pkey_test(tc, mpa); 217 } 218} 219 220static struct tst_test test = { 221 .tcnt = ARRAY_SIZE(tcases), 222 .needs_root = 1, 223 .needs_tmpdir = 1, 224 .forks_child = 1, 225 .test = verify_pkey, 226 .setup = setup, 227 .hugepages = {1, TST_REQUEST}, 228}; 229