1 | /* |
2 | * Copyright IBM Corp. |
3 | * |
4 | * This program is free software; you can redistribute it and/or modify it |
5 | * under the terms of version 2.1 of the GNU Lesser General Public License |
6 | * as published by the Free Software Foundation. |
7 | * |
8 | * This program is distributed in the hope that it would be useful, but |
9 | * WITHOUT ANY WARRANTY; without even the implied warranty of |
10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
11 | * |
12 | */ |
13 | |
14 | #include <assert.h> |
15 | #include <errno.h> |
16 | #include <fcntl.h> |
17 | #include <signal.h> |
18 | #include <stdarg.h> |
19 | #include <stdio.h> |
20 | #include <stdlib.h> |
21 | #include <string.h> |
22 | #include <sys/mman.h> |
23 | #include <sys/ptrace.h> |
24 | #include <sys/syscall.h> |
25 | #include <ucontext.h> |
26 | #include <unistd.h> |
27 | |
28 | #include "utils.h" |
29 | |
30 | char *file_name; |
31 | |
32 | int in_test; |
33 | volatile int faulted; |
34 | volatile void *dar; |
35 | int errors; |
36 | |
37 | static void segv(int signum, siginfo_t *info, void *ctxt_v) |
38 | { |
39 | ucontext_t *ctxt = (ucontext_t *)ctxt_v; |
40 | struct pt_regs *regs = ctxt->uc_mcontext.regs; |
41 | |
42 | if (!in_test) { |
43 | fprintf(stderr, "Segfault outside of test !\n" ); |
44 | exit(1); |
45 | } |
46 | |
47 | faulted = 1; |
48 | dar = (void *)regs->dar; |
49 | regs->nip += 4; |
50 | } |
51 | |
52 | static inline void do_read(const volatile void *addr) |
53 | { |
54 | int ret; |
55 | |
56 | asm volatile("lwz %0,0(%1); twi 0,%0,0; isync;\n" |
57 | : "=r" (ret) : "r" (addr) : "memory" ); |
58 | } |
59 | |
60 | static inline void do_write(const volatile void *addr) |
61 | { |
62 | int val = 0x1234567; |
63 | |
64 | asm volatile("stw %0,0(%1); sync; \n" |
65 | : : "r" (val), "r" (addr) : "memory" ); |
66 | } |
67 | |
68 | static inline void check_faulted(void *addr, long page, long subpage, int write) |
69 | { |
70 | int want_fault = (subpage == ((page + 3) % 16)); |
71 | |
72 | if (write) |
73 | want_fault |= (subpage == ((page + 1) % 16)); |
74 | |
75 | if (faulted != want_fault) { |
76 | printf("Failed at %p (p=%ld,sp=%ld,w=%d), want=%s, got=%s !\n" , |
77 | addr, page, subpage, write, |
78 | want_fault ? "fault" : "pass" , |
79 | faulted ? "fault" : "pass" ); |
80 | ++errors; |
81 | } |
82 | |
83 | if (faulted) { |
84 | if (dar != addr) { |
85 | printf("Fault expected at %p and happened at %p !\n" , |
86 | addr, dar); |
87 | } |
88 | faulted = 0; |
89 | asm volatile("sync" : : : "memory" ); |
90 | } |
91 | } |
92 | |
93 | static int run_test(void *addr, unsigned long size) |
94 | { |
95 | unsigned int *map; |
96 | long i, j, pages, err; |
97 | |
98 | pages = size / 0x10000; |
99 | map = malloc(pages * 4); |
100 | assert(map); |
101 | |
102 | /* |
103 | * for each page, mark subpage i % 16 read only and subpage |
104 | * (i + 3) % 16 inaccessible |
105 | */ |
106 | for (i = 0; i < pages; i++) { |
107 | map[i] = (0x40000000 >> (((i + 1) * 2) % 32)) | |
108 | (0xc0000000 >> (((i + 3) * 2) % 32)); |
109 | } |
110 | |
111 | err = syscall(__NR_subpage_prot, addr, size, map); |
112 | if (err) { |
113 | perror("subpage_perm" ); |
114 | return 1; |
115 | } |
116 | free(map); |
117 | |
118 | in_test = 1; |
119 | errors = 0; |
120 | for (i = 0; i < pages; i++) { |
121 | for (j = 0; j < 16; j++, addr += 0x1000) { |
122 | do_read(addr); |
123 | check_faulted(addr, page: i, subpage: j, write: 0); |
124 | do_write(addr); |
125 | check_faulted(addr, page: i, subpage: j, write: 1); |
126 | } |
127 | } |
128 | |
129 | in_test = 0; |
130 | if (errors) { |
131 | printf("%d errors detected\n" , errors); |
132 | return 1; |
133 | } |
134 | |
135 | return 0; |
136 | } |
137 | |
138 | static int syscall_available(void) |
139 | { |
140 | int rc; |
141 | |
142 | errno = 0; |
143 | rc = syscall(__NR_subpage_prot, 0, 0, 0); |
144 | |
145 | return rc == 0 || (errno != ENOENT && errno != ENOSYS); |
146 | } |
147 | |
148 | int test_anon(void) |
149 | { |
150 | unsigned long align; |
151 | struct sigaction act = { |
152 | .sa_sigaction = segv, |
153 | .sa_flags = SA_SIGINFO |
154 | }; |
155 | void *mallocblock; |
156 | unsigned long mallocsize; |
157 | |
158 | SKIP_IF(!syscall_available()); |
159 | |
160 | if (getpagesize() != 0x10000) { |
161 | fprintf(stderr, "Kernel page size must be 64K!\n" ); |
162 | return 1; |
163 | } |
164 | |
165 | sigaction(SIGSEGV, &act, NULL); |
166 | |
167 | mallocsize = 4 * 16 * 1024 * 1024; |
168 | |
169 | FAIL_IF(posix_memalign(&mallocblock, 64 * 1024, mallocsize)); |
170 | |
171 | align = (unsigned long)mallocblock; |
172 | if (align & 0xffff) |
173 | align = (align | 0xffff) + 1; |
174 | |
175 | mallocblock = (void *)align; |
176 | |
177 | printf("allocated malloc block of 0x%lx bytes at %p\n" , |
178 | mallocsize, mallocblock); |
179 | |
180 | printf("testing malloc block...\n" ); |
181 | |
182 | return run_test(addr: mallocblock, size: mallocsize); |
183 | } |
184 | |
185 | int test_file(void) |
186 | { |
187 | struct sigaction act = { |
188 | .sa_sigaction = segv, |
189 | .sa_flags = SA_SIGINFO |
190 | }; |
191 | void *fileblock; |
192 | off_t filesize; |
193 | int fd; |
194 | |
195 | SKIP_IF(!syscall_available()); |
196 | |
197 | fd = open(file_name, O_RDWR); |
198 | if (fd == -1) { |
199 | perror("failed to open file" ); |
200 | return 1; |
201 | } |
202 | sigaction(SIGSEGV, &act, NULL); |
203 | |
204 | filesize = lseek(fd, 0, SEEK_END); |
205 | if (filesize & 0xffff) |
206 | filesize &= ~0xfffful; |
207 | |
208 | fileblock = mmap(NULL, filesize, PROT_READ | PROT_WRITE, |
209 | MAP_SHARED, fd, 0); |
210 | if (fileblock == MAP_FAILED) { |
211 | perror("failed to map file" ); |
212 | return 1; |
213 | } |
214 | printf("allocated %s for 0x%lx bytes at %p\n" , |
215 | file_name, filesize, fileblock); |
216 | |
217 | printf("testing file map...\n" ); |
218 | |
219 | return run_test(addr: fileblock, size: filesize); |
220 | } |
221 | |
222 | int main(int argc, char *argv[]) |
223 | { |
224 | int rc; |
225 | |
226 | rc = test_harness(test_anon, "subpage_prot_anon" ); |
227 | if (rc) |
228 | return rc; |
229 | |
230 | if (argc > 1) |
231 | file_name = argv[1]; |
232 | else |
233 | file_name = "tempfile" ; |
234 | |
235 | return test_harness(test_file, "subpage_prot_file" ); |
236 | } |
237 | |