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