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