]>
Commit | Line | Data |
---|---|---|
b2441318 | 1 | // SPDX-License-Identifier: GPL-2.0 |
26b4224d SG |
2 | /* |
3 | * It tests the mlock/mlock2() when they are invoked | |
4 | * on randomly memory region. | |
5 | */ | |
6 | #include <unistd.h> | |
7 | #include <sys/resource.h> | |
8 | #include <sys/capability.h> | |
9 | #include <sys/mman.h> | |
10 | #include <fcntl.h> | |
11 | #include <string.h> | |
12 | #include <sys/ipc.h> | |
13 | #include <sys/shm.h> | |
14 | #include <time.h> | |
15 | #include "mlock2.h" | |
16 | ||
17 | #define CHUNK_UNIT (128 * 1024) | |
18 | #define MLOCK_RLIMIT_SIZE (CHUNK_UNIT * 2) | |
19 | #define MLOCK_WITHIN_LIMIT_SIZE CHUNK_UNIT | |
20 | #define MLOCK_OUTOF_LIMIT_SIZE (CHUNK_UNIT * 3) | |
21 | ||
22 | #define TEST_LOOP 100 | |
23 | #define PAGE_ALIGN(size, ps) (((size) + ((ps) - 1)) & ~((ps) - 1)) | |
24 | ||
25 | int set_cap_limits(rlim_t max) | |
26 | { | |
27 | struct rlimit new; | |
28 | cap_t cap = cap_init(); | |
29 | ||
30 | new.rlim_cur = max; | |
31 | new.rlim_max = max; | |
32 | if (setrlimit(RLIMIT_MEMLOCK, &new)) { | |
33 | perror("setrlimit() returns error\n"); | |
34 | return -1; | |
35 | } | |
36 | ||
37 | /* drop capabilities including CAP_IPC_LOCK */ | |
38 | if (cap_set_proc(cap)) { | |
39 | perror("cap_set_proc() returns error\n"); | |
40 | return -2; | |
41 | } | |
42 | ||
43 | return 0; | |
44 | } | |
45 | ||
46 | int get_proc_locked_vm_size(void) | |
47 | { | |
48 | FILE *f; | |
49 | int ret = -1; | |
50 | char line[1024] = {0}; | |
51 | unsigned long lock_size = 0; | |
52 | ||
53 | f = fopen("/proc/self/status", "r"); | |
54 | if (!f) { | |
55 | perror("fopen"); | |
56 | return -1; | |
57 | } | |
58 | ||
59 | while (fgets(line, 1024, f)) { | |
60 | if (strstr(line, "VmLck")) { | |
61 | ret = sscanf(line, "VmLck:\t%8lu kB", &lock_size); | |
62 | if (ret <= 0) { | |
63 | printf("sscanf() on VmLck error: %s: %d\n", | |
64 | line, ret); | |
65 | fclose(f); | |
66 | return -1; | |
67 | } | |
68 | fclose(f); | |
69 | return (int)(lock_size << 10); | |
70 | } | |
71 | } | |
72 | ||
73 | perror("cann't parse VmLck in /proc/self/status\n"); | |
74 | fclose(f); | |
75 | return -1; | |
76 | } | |
77 | ||
78 | /* | |
79 | * Get the MMUPageSize of the memory region including input | |
80 | * address from proc file. | |
81 | * | |
82 | * return value: on error case, 0 will be returned. | |
83 | * Otherwise the page size(in bytes) is returned. | |
84 | */ | |
85 | int get_proc_page_size(unsigned long addr) | |
86 | { | |
87 | FILE *smaps; | |
88 | char *line; | |
89 | unsigned long mmupage_size = 0; | |
90 | size_t size; | |
91 | ||
92 | smaps = seek_to_smaps_entry(addr); | |
93 | if (!smaps) { | |
94 | printf("Unable to parse /proc/self/smaps\n"); | |
95 | return 0; | |
96 | } | |
97 | ||
98 | while (getline(&line, &size, smaps) > 0) { | |
99 | if (!strstr(line, "MMUPageSize")) { | |
100 | free(line); | |
101 | line = NULL; | |
102 | size = 0; | |
103 | continue; | |
104 | } | |
105 | ||
106 | /* found the MMUPageSize of this section */ | |
107 | if (sscanf(line, "MMUPageSize: %8lu kB", | |
108 | &mmupage_size) < 1) { | |
109 | printf("Unable to parse smaps entry for Size:%s\n", | |
110 | line); | |
111 | break; | |
112 | } | |
113 | ||
114 | } | |
115 | free(line); | |
116 | if (smaps) | |
117 | fclose(smaps); | |
118 | return mmupage_size << 10; | |
119 | } | |
120 | ||
121 | /* | |
122 | * Test mlock/mlock2() on provided memory chunk. | |
123 | * It expects the mlock/mlock2() to be successful (within rlimit) | |
124 | * | |
125 | * With allocated memory chunk [p, p + alloc_size), this | |
126 | * test will choose start/len randomly to perform mlock/mlock2 | |
127 | * [start, start + len] memory range. The range is within range | |
128 | * of the allocated chunk. | |
129 | * | |
130 | * The memory region size alloc_size is within the rlimit. | |
131 | * So we always expect a success of mlock/mlock2. | |
132 | * | |
133 | * VmLck is assumed to be 0 before this test. | |
134 | * | |
135 | * return value: 0 - success | |
136 | * else: failure | |
137 | */ | |
138 | int test_mlock_within_limit(char *p, int alloc_size) | |
139 | { | |
140 | int i; | |
141 | int ret = 0; | |
142 | int locked_vm_size = 0; | |
143 | struct rlimit cur; | |
144 | int page_size = 0; | |
145 | ||
146 | getrlimit(RLIMIT_MEMLOCK, &cur); | |
147 | if (cur.rlim_cur < alloc_size) { | |
148 | printf("alloc_size[%d] < %u rlimit,lead to mlock failure\n", | |
149 | alloc_size, (unsigned int)cur.rlim_cur); | |
150 | return -1; | |
151 | } | |
152 | ||
153 | srand(time(NULL)); | |
154 | for (i = 0; i < TEST_LOOP; i++) { | |
155 | /* | |
156 | * - choose mlock/mlock2 randomly | |
157 | * - choose lock_size randomly but lock_size < alloc_size | |
158 | * - choose start_offset randomly but p+start_offset+lock_size | |
159 | * < p+alloc_size | |
160 | */ | |
161 | int is_mlock = !!(rand() % 2); | |
162 | int lock_size = rand() % alloc_size; | |
163 | int start_offset = rand() % (alloc_size - lock_size); | |
164 | ||
165 | if (is_mlock) | |
166 | ret = mlock(p + start_offset, lock_size); | |
167 | else | |
168 | ret = mlock2_(p + start_offset, lock_size, | |
169 | MLOCK_ONFAULT); | |
170 | ||
171 | if (ret) { | |
172 | printf("%s() failure at |%p(%d)| mlock:|%p(%d)|\n", | |
173 | is_mlock ? "mlock" : "mlock2", | |
174 | p, alloc_size, | |
175 | p + start_offset, lock_size); | |
176 | return ret; | |
177 | } | |
178 | } | |
179 | ||
180 | /* | |
181 | * Check VmLck left by the tests. | |
182 | */ | |
183 | locked_vm_size = get_proc_locked_vm_size(); | |
184 | page_size = get_proc_page_size((unsigned long)p); | |
185 | if (page_size == 0) { | |
186 | printf("cannot get proc MMUPageSize\n"); | |
187 | return -1; | |
188 | } | |
189 | ||
190 | if (locked_vm_size > PAGE_ALIGN(alloc_size, page_size) + page_size) { | |
191 | printf("test_mlock_within_limit() left VmLck:%d on %d chunk\n", | |
192 | locked_vm_size, alloc_size); | |
193 | return -1; | |
194 | } | |
195 | ||
196 | return 0; | |
197 | } | |
198 | ||
199 | ||
200 | /* | |
201 | * We expect the mlock/mlock2() to be fail (outof limitation) | |
202 | * | |
203 | * With allocated memory chunk [p, p + alloc_size), this | |
204 | * test will randomly choose start/len and perform mlock/mlock2 | |
205 | * on [start, start+len] range. | |
206 | * | |
207 | * The memory region size alloc_size is above the rlimit. | |
208 | * And the len to be locked is higher than rlimit. | |
209 | * So we always expect a failure of mlock/mlock2. | |
210 | * No locked page number should be increased as a side effect. | |
211 | * | |
212 | * return value: 0 - success | |
213 | * else: failure | |
214 | */ | |
215 | int test_mlock_outof_limit(char *p, int alloc_size) | |
216 | { | |
217 | int i; | |
218 | int ret = 0; | |
219 | int locked_vm_size = 0, old_locked_vm_size = 0; | |
220 | struct rlimit cur; | |
221 | ||
222 | getrlimit(RLIMIT_MEMLOCK, &cur); | |
223 | if (cur.rlim_cur >= alloc_size) { | |
224 | printf("alloc_size[%d] >%u rlimit, violates test condition\n", | |
225 | alloc_size, (unsigned int)cur.rlim_cur); | |
226 | return -1; | |
227 | } | |
228 | ||
229 | old_locked_vm_size = get_proc_locked_vm_size(); | |
230 | srand(time(NULL)); | |
231 | for (i = 0; i < TEST_LOOP; i++) { | |
232 | int is_mlock = !!(rand() % 2); | |
233 | int lock_size = (rand() % (alloc_size - cur.rlim_cur)) | |
234 | + cur.rlim_cur; | |
235 | int start_offset = rand() % (alloc_size - lock_size); | |
236 | ||
237 | if (is_mlock) | |
238 | ret = mlock(p + start_offset, lock_size); | |
239 | else | |
240 | ret = mlock2_(p + start_offset, lock_size, | |
241 | MLOCK_ONFAULT); | |
242 | if (ret == 0) { | |
243 | printf("%s() succeeds? on %p(%d) mlock%p(%d)\n", | |
244 | is_mlock ? "mlock" : "mlock2", | |
245 | p, alloc_size, | |
246 | p + start_offset, lock_size); | |
247 | return -1; | |
248 | } | |
249 | } | |
250 | ||
251 | locked_vm_size = get_proc_locked_vm_size(); | |
252 | if (locked_vm_size != old_locked_vm_size) { | |
253 | printf("tests leads to new mlocked page: old[%d], new[%d]\n", | |
254 | old_locked_vm_size, | |
255 | locked_vm_size); | |
256 | return -1; | |
257 | } | |
258 | ||
259 | return 0; | |
260 | } | |
261 | ||
262 | int main(int argc, char **argv) | |
263 | { | |
264 | char *p = NULL; | |
265 | int ret = 0; | |
266 | ||
267 | if (set_cap_limits(MLOCK_RLIMIT_SIZE)) | |
268 | return -1; | |
269 | ||
270 | p = malloc(MLOCK_WITHIN_LIMIT_SIZE); | |
271 | if (p == NULL) { | |
272 | perror("malloc() failure\n"); | |
273 | return -1; | |
274 | } | |
275 | ret = test_mlock_within_limit(p, MLOCK_WITHIN_LIMIT_SIZE); | |
276 | if (ret) | |
277 | return ret; | |
278 | munlock(p, MLOCK_WITHIN_LIMIT_SIZE); | |
279 | free(p); | |
280 | ||
281 | ||
282 | p = malloc(MLOCK_OUTOF_LIMIT_SIZE); | |
283 | if (p == NULL) { | |
284 | perror("malloc() failure\n"); | |
285 | return -1; | |
286 | } | |
287 | ret = test_mlock_outof_limit(p, MLOCK_OUTOF_LIMIT_SIZE); | |
288 | if (ret) | |
289 | return ret; | |
290 | munlock(p, MLOCK_OUTOF_LIMIT_SIZE); | |
291 | free(p); | |
292 | ||
293 | return 0; | |
294 | } |