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