Home | History | Annotate | Download | only in vm
      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 <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 }
    295