162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * It tests the mlock/mlock2() when they are invoked
462306a36Sopenharmony_ci * on randomly memory region.
562306a36Sopenharmony_ci */
662306a36Sopenharmony_ci#include <unistd.h>
762306a36Sopenharmony_ci#include <sys/resource.h>
862306a36Sopenharmony_ci#include <sys/capability.h>
962306a36Sopenharmony_ci#include <sys/mman.h>
1062306a36Sopenharmony_ci#include <linux/mman.h>
1162306a36Sopenharmony_ci#include <fcntl.h>
1262306a36Sopenharmony_ci#include <string.h>
1362306a36Sopenharmony_ci#include <sys/ipc.h>
1462306a36Sopenharmony_ci#include <sys/shm.h>
1562306a36Sopenharmony_ci#include <time.h>
1662306a36Sopenharmony_ci#include "mlock2.h"
1762306a36Sopenharmony_ci
1862306a36Sopenharmony_ci#define CHUNK_UNIT (128 * 1024)
1962306a36Sopenharmony_ci#define MLOCK_RLIMIT_SIZE (CHUNK_UNIT * 2)
2062306a36Sopenharmony_ci#define MLOCK_WITHIN_LIMIT_SIZE CHUNK_UNIT
2162306a36Sopenharmony_ci#define MLOCK_OUTOF_LIMIT_SIZE (CHUNK_UNIT * 3)
2262306a36Sopenharmony_ci
2362306a36Sopenharmony_ci#define TEST_LOOP 100
2462306a36Sopenharmony_ci#define PAGE_ALIGN(size, ps) (((size) + ((ps) - 1)) & ~((ps) - 1))
2562306a36Sopenharmony_ci
2662306a36Sopenharmony_ciint set_cap_limits(rlim_t max)
2762306a36Sopenharmony_ci{
2862306a36Sopenharmony_ci	struct rlimit new;
2962306a36Sopenharmony_ci	cap_t cap = cap_init();
3062306a36Sopenharmony_ci
3162306a36Sopenharmony_ci	new.rlim_cur = max;
3262306a36Sopenharmony_ci	new.rlim_max = max;
3362306a36Sopenharmony_ci	if (setrlimit(RLIMIT_MEMLOCK, &new)) {
3462306a36Sopenharmony_ci		perror("setrlimit() returns error\n");
3562306a36Sopenharmony_ci		return -1;
3662306a36Sopenharmony_ci	}
3762306a36Sopenharmony_ci
3862306a36Sopenharmony_ci	/* drop capabilities including CAP_IPC_LOCK */
3962306a36Sopenharmony_ci	if (cap_set_proc(cap)) {
4062306a36Sopenharmony_ci		perror("cap_set_proc() returns error\n");
4162306a36Sopenharmony_ci		return -2;
4262306a36Sopenharmony_ci	}
4362306a36Sopenharmony_ci
4462306a36Sopenharmony_ci	return 0;
4562306a36Sopenharmony_ci}
4662306a36Sopenharmony_ci
4762306a36Sopenharmony_ciint get_proc_locked_vm_size(void)
4862306a36Sopenharmony_ci{
4962306a36Sopenharmony_ci	FILE *f;
5062306a36Sopenharmony_ci	int ret = -1;
5162306a36Sopenharmony_ci	char line[1024] = {0};
5262306a36Sopenharmony_ci	unsigned long lock_size = 0;
5362306a36Sopenharmony_ci
5462306a36Sopenharmony_ci	f = fopen("/proc/self/status", "r");
5562306a36Sopenharmony_ci	if (!f) {
5662306a36Sopenharmony_ci		perror("fopen");
5762306a36Sopenharmony_ci		return -1;
5862306a36Sopenharmony_ci	}
5962306a36Sopenharmony_ci
6062306a36Sopenharmony_ci	while (fgets(line, 1024, f)) {
6162306a36Sopenharmony_ci		if (strstr(line, "VmLck")) {
6262306a36Sopenharmony_ci			ret = sscanf(line, "VmLck:\t%8lu kB", &lock_size);
6362306a36Sopenharmony_ci			if (ret <= 0) {
6462306a36Sopenharmony_ci				printf("sscanf() on VmLck error: %s: %d\n",
6562306a36Sopenharmony_ci						line, ret);
6662306a36Sopenharmony_ci				fclose(f);
6762306a36Sopenharmony_ci				return -1;
6862306a36Sopenharmony_ci			}
6962306a36Sopenharmony_ci			fclose(f);
7062306a36Sopenharmony_ci			return (int)(lock_size << 10);
7162306a36Sopenharmony_ci		}
7262306a36Sopenharmony_ci	}
7362306a36Sopenharmony_ci
7462306a36Sopenharmony_ci	perror("cannot parse VmLck in /proc/self/status\n");
7562306a36Sopenharmony_ci	fclose(f);
7662306a36Sopenharmony_ci	return -1;
7762306a36Sopenharmony_ci}
7862306a36Sopenharmony_ci
7962306a36Sopenharmony_ci/*
8062306a36Sopenharmony_ci * Get the MMUPageSize of the memory region including input
8162306a36Sopenharmony_ci * address from proc file.
8262306a36Sopenharmony_ci *
8362306a36Sopenharmony_ci * return value: on error case, 0 will be returned.
8462306a36Sopenharmony_ci * Otherwise the page size(in bytes) is returned.
8562306a36Sopenharmony_ci */
8662306a36Sopenharmony_ciint get_proc_page_size(unsigned long addr)
8762306a36Sopenharmony_ci{
8862306a36Sopenharmony_ci	FILE *smaps;
8962306a36Sopenharmony_ci	char *line;
9062306a36Sopenharmony_ci	unsigned long mmupage_size = 0;
9162306a36Sopenharmony_ci	size_t size;
9262306a36Sopenharmony_ci
9362306a36Sopenharmony_ci	smaps = seek_to_smaps_entry(addr);
9462306a36Sopenharmony_ci	if (!smaps) {
9562306a36Sopenharmony_ci		printf("Unable to parse /proc/self/smaps\n");
9662306a36Sopenharmony_ci		return 0;
9762306a36Sopenharmony_ci	}
9862306a36Sopenharmony_ci
9962306a36Sopenharmony_ci	while (getline(&line, &size, smaps) > 0) {
10062306a36Sopenharmony_ci		if (!strstr(line, "MMUPageSize")) {
10162306a36Sopenharmony_ci			free(line);
10262306a36Sopenharmony_ci			line = NULL;
10362306a36Sopenharmony_ci			size = 0;
10462306a36Sopenharmony_ci			continue;
10562306a36Sopenharmony_ci		}
10662306a36Sopenharmony_ci
10762306a36Sopenharmony_ci		/* found the MMUPageSize of this section */
10862306a36Sopenharmony_ci		if (sscanf(line, "MMUPageSize:    %8lu kB",
10962306a36Sopenharmony_ci					&mmupage_size) < 1) {
11062306a36Sopenharmony_ci			printf("Unable to parse smaps entry for Size:%s\n",
11162306a36Sopenharmony_ci					line);
11262306a36Sopenharmony_ci			break;
11362306a36Sopenharmony_ci		}
11462306a36Sopenharmony_ci
11562306a36Sopenharmony_ci	}
11662306a36Sopenharmony_ci	free(line);
11762306a36Sopenharmony_ci	if (smaps)
11862306a36Sopenharmony_ci		fclose(smaps);
11962306a36Sopenharmony_ci	return mmupage_size << 10;
12062306a36Sopenharmony_ci}
12162306a36Sopenharmony_ci
12262306a36Sopenharmony_ci/*
12362306a36Sopenharmony_ci * Test mlock/mlock2() on provided memory chunk.
12462306a36Sopenharmony_ci * It expects the mlock/mlock2() to be successful (within rlimit)
12562306a36Sopenharmony_ci *
12662306a36Sopenharmony_ci * With allocated memory chunk [p, p + alloc_size), this
12762306a36Sopenharmony_ci * test will choose start/len randomly to perform mlock/mlock2
12862306a36Sopenharmony_ci * [start, start +  len] memory range. The range is within range
12962306a36Sopenharmony_ci * of the allocated chunk.
13062306a36Sopenharmony_ci *
13162306a36Sopenharmony_ci * The memory region size alloc_size is within the rlimit.
13262306a36Sopenharmony_ci * So we always expect a success of mlock/mlock2.
13362306a36Sopenharmony_ci *
13462306a36Sopenharmony_ci * VmLck is assumed to be 0 before this test.
13562306a36Sopenharmony_ci *
13662306a36Sopenharmony_ci *    return value: 0 - success
13762306a36Sopenharmony_ci *    else: failure
13862306a36Sopenharmony_ci */
13962306a36Sopenharmony_ciint test_mlock_within_limit(char *p, int alloc_size)
14062306a36Sopenharmony_ci{
14162306a36Sopenharmony_ci	int i;
14262306a36Sopenharmony_ci	int ret = 0;
14362306a36Sopenharmony_ci	int locked_vm_size = 0;
14462306a36Sopenharmony_ci	struct rlimit cur;
14562306a36Sopenharmony_ci	int page_size = 0;
14662306a36Sopenharmony_ci
14762306a36Sopenharmony_ci	getrlimit(RLIMIT_MEMLOCK, &cur);
14862306a36Sopenharmony_ci	if (cur.rlim_cur < alloc_size) {
14962306a36Sopenharmony_ci		printf("alloc_size[%d] < %u rlimit,lead to mlock failure\n",
15062306a36Sopenharmony_ci				alloc_size, (unsigned int)cur.rlim_cur);
15162306a36Sopenharmony_ci		return -1;
15262306a36Sopenharmony_ci	}
15362306a36Sopenharmony_ci
15462306a36Sopenharmony_ci	srand(time(NULL));
15562306a36Sopenharmony_ci	for (i = 0; i < TEST_LOOP; i++) {
15662306a36Sopenharmony_ci		/*
15762306a36Sopenharmony_ci		 * - choose mlock/mlock2 randomly
15862306a36Sopenharmony_ci		 * - choose lock_size randomly but lock_size < alloc_size
15962306a36Sopenharmony_ci		 * - choose start_offset randomly but p+start_offset+lock_size
16062306a36Sopenharmony_ci		 *   < p+alloc_size
16162306a36Sopenharmony_ci		 */
16262306a36Sopenharmony_ci		int is_mlock = !!(rand() % 2);
16362306a36Sopenharmony_ci		int lock_size = rand() % alloc_size;
16462306a36Sopenharmony_ci		int start_offset = rand() % (alloc_size - lock_size);
16562306a36Sopenharmony_ci
16662306a36Sopenharmony_ci		if (is_mlock)
16762306a36Sopenharmony_ci			ret = mlock(p + start_offset, lock_size);
16862306a36Sopenharmony_ci		else
16962306a36Sopenharmony_ci			ret = mlock2_(p + start_offset, lock_size,
17062306a36Sopenharmony_ci				       MLOCK_ONFAULT);
17162306a36Sopenharmony_ci
17262306a36Sopenharmony_ci		if (ret) {
17362306a36Sopenharmony_ci			printf("%s() failure at |%p(%d)| mlock:|%p(%d)|\n",
17462306a36Sopenharmony_ci					is_mlock ? "mlock" : "mlock2",
17562306a36Sopenharmony_ci					p, alloc_size,
17662306a36Sopenharmony_ci					p + start_offset, lock_size);
17762306a36Sopenharmony_ci			return ret;
17862306a36Sopenharmony_ci		}
17962306a36Sopenharmony_ci	}
18062306a36Sopenharmony_ci
18162306a36Sopenharmony_ci	/*
18262306a36Sopenharmony_ci	 * Check VmLck left by the tests.
18362306a36Sopenharmony_ci	 */
18462306a36Sopenharmony_ci	locked_vm_size = get_proc_locked_vm_size();
18562306a36Sopenharmony_ci	page_size = get_proc_page_size((unsigned long)p);
18662306a36Sopenharmony_ci	if (page_size == 0) {
18762306a36Sopenharmony_ci		printf("cannot get proc MMUPageSize\n");
18862306a36Sopenharmony_ci		return -1;
18962306a36Sopenharmony_ci	}
19062306a36Sopenharmony_ci
19162306a36Sopenharmony_ci	if (locked_vm_size > PAGE_ALIGN(alloc_size, page_size) + page_size) {
19262306a36Sopenharmony_ci		printf("test_mlock_within_limit() left VmLck:%d on %d chunk\n",
19362306a36Sopenharmony_ci				locked_vm_size, alloc_size);
19462306a36Sopenharmony_ci		return -1;
19562306a36Sopenharmony_ci	}
19662306a36Sopenharmony_ci
19762306a36Sopenharmony_ci	return 0;
19862306a36Sopenharmony_ci}
19962306a36Sopenharmony_ci
20062306a36Sopenharmony_ci
20162306a36Sopenharmony_ci/*
20262306a36Sopenharmony_ci * We expect the mlock/mlock2() to be fail (outof limitation)
20362306a36Sopenharmony_ci *
20462306a36Sopenharmony_ci * With allocated memory chunk [p, p + alloc_size), this
20562306a36Sopenharmony_ci * test will randomly choose start/len and perform mlock/mlock2
20662306a36Sopenharmony_ci * on [start, start+len] range.
20762306a36Sopenharmony_ci *
20862306a36Sopenharmony_ci * The memory region size alloc_size is above the rlimit.
20962306a36Sopenharmony_ci * And the len to be locked is higher than rlimit.
21062306a36Sopenharmony_ci * So we always expect a failure of mlock/mlock2.
21162306a36Sopenharmony_ci * No locked page number should be increased as a side effect.
21262306a36Sopenharmony_ci *
21362306a36Sopenharmony_ci *    return value: 0 - success
21462306a36Sopenharmony_ci *    else: failure
21562306a36Sopenharmony_ci */
21662306a36Sopenharmony_ciint test_mlock_outof_limit(char *p, int alloc_size)
21762306a36Sopenharmony_ci{
21862306a36Sopenharmony_ci	int i;
21962306a36Sopenharmony_ci	int ret = 0;
22062306a36Sopenharmony_ci	int locked_vm_size = 0, old_locked_vm_size = 0;
22162306a36Sopenharmony_ci	struct rlimit cur;
22262306a36Sopenharmony_ci
22362306a36Sopenharmony_ci	getrlimit(RLIMIT_MEMLOCK, &cur);
22462306a36Sopenharmony_ci	if (cur.rlim_cur >= alloc_size) {
22562306a36Sopenharmony_ci		printf("alloc_size[%d] >%u rlimit, violates test condition\n",
22662306a36Sopenharmony_ci				alloc_size, (unsigned int)cur.rlim_cur);
22762306a36Sopenharmony_ci		return -1;
22862306a36Sopenharmony_ci	}
22962306a36Sopenharmony_ci
23062306a36Sopenharmony_ci	old_locked_vm_size = get_proc_locked_vm_size();
23162306a36Sopenharmony_ci	srand(time(NULL));
23262306a36Sopenharmony_ci	for (i = 0; i < TEST_LOOP; i++) {
23362306a36Sopenharmony_ci		int is_mlock = !!(rand() % 2);
23462306a36Sopenharmony_ci		int lock_size = (rand() % (alloc_size - cur.rlim_cur))
23562306a36Sopenharmony_ci			+ cur.rlim_cur;
23662306a36Sopenharmony_ci		int start_offset = rand() % (alloc_size - lock_size);
23762306a36Sopenharmony_ci
23862306a36Sopenharmony_ci		if (is_mlock)
23962306a36Sopenharmony_ci			ret = mlock(p + start_offset, lock_size);
24062306a36Sopenharmony_ci		else
24162306a36Sopenharmony_ci			ret = mlock2_(p + start_offset, lock_size,
24262306a36Sopenharmony_ci					MLOCK_ONFAULT);
24362306a36Sopenharmony_ci		if (ret == 0) {
24462306a36Sopenharmony_ci			printf("%s() succeeds? on %p(%d) mlock%p(%d)\n",
24562306a36Sopenharmony_ci					is_mlock ? "mlock" : "mlock2",
24662306a36Sopenharmony_ci					p, alloc_size,
24762306a36Sopenharmony_ci					p + start_offset, lock_size);
24862306a36Sopenharmony_ci			return -1;
24962306a36Sopenharmony_ci		}
25062306a36Sopenharmony_ci	}
25162306a36Sopenharmony_ci
25262306a36Sopenharmony_ci	locked_vm_size = get_proc_locked_vm_size();
25362306a36Sopenharmony_ci	if (locked_vm_size != old_locked_vm_size) {
25462306a36Sopenharmony_ci		printf("tests leads to new mlocked page: old[%d], new[%d]\n",
25562306a36Sopenharmony_ci				old_locked_vm_size,
25662306a36Sopenharmony_ci				locked_vm_size);
25762306a36Sopenharmony_ci		return -1;
25862306a36Sopenharmony_ci	}
25962306a36Sopenharmony_ci
26062306a36Sopenharmony_ci	return 0;
26162306a36Sopenharmony_ci}
26262306a36Sopenharmony_ci
26362306a36Sopenharmony_ciint main(int argc, char **argv)
26462306a36Sopenharmony_ci{
26562306a36Sopenharmony_ci	char *p = NULL;
26662306a36Sopenharmony_ci	int ret = 0;
26762306a36Sopenharmony_ci
26862306a36Sopenharmony_ci	if (set_cap_limits(MLOCK_RLIMIT_SIZE))
26962306a36Sopenharmony_ci		return -1;
27062306a36Sopenharmony_ci
27162306a36Sopenharmony_ci	p = malloc(MLOCK_WITHIN_LIMIT_SIZE);
27262306a36Sopenharmony_ci	if (p == NULL) {
27362306a36Sopenharmony_ci		perror("malloc() failure\n");
27462306a36Sopenharmony_ci		return -1;
27562306a36Sopenharmony_ci	}
27662306a36Sopenharmony_ci	ret = test_mlock_within_limit(p, MLOCK_WITHIN_LIMIT_SIZE);
27762306a36Sopenharmony_ci	if (ret)
27862306a36Sopenharmony_ci		return ret;
27962306a36Sopenharmony_ci	munlock(p, MLOCK_WITHIN_LIMIT_SIZE);
28062306a36Sopenharmony_ci	free(p);
28162306a36Sopenharmony_ci
28262306a36Sopenharmony_ci
28362306a36Sopenharmony_ci	p = malloc(MLOCK_OUTOF_LIMIT_SIZE);
28462306a36Sopenharmony_ci	if (p == NULL) {
28562306a36Sopenharmony_ci		perror("malloc() failure\n");
28662306a36Sopenharmony_ci		return -1;
28762306a36Sopenharmony_ci	}
28862306a36Sopenharmony_ci	ret = test_mlock_outof_limit(p, MLOCK_OUTOF_LIMIT_SIZE);
28962306a36Sopenharmony_ci	if (ret)
29062306a36Sopenharmony_ci		return ret;
29162306a36Sopenharmony_ci	munlock(p, MLOCK_OUTOF_LIMIT_SIZE);
29262306a36Sopenharmony_ci	free(p);
29362306a36Sopenharmony_ci
29462306a36Sopenharmony_ci	return 0;
29562306a36Sopenharmony_ci}
296