| // SPDX-License-Identifier: GPL-2.0 | |
| /* | |
| * It tests the mlock/mlock2() when they are invoked | |
| * on randomly memory region. | |
| */ | |
| #include <unistd.h> | |
| #include <sys/resource.h> | |
| #include <sys/capability.h> | |
| #include <sys/mman.h> | |
| #include <fcntl.h> | |
| #include <string.h> | |
| #include <sys/ipc.h> | |
| #include <sys/shm.h> | |
| #include <time.h> | |
| #include "mlock2.h" | |
| #define CHUNK_UNIT (128 * 1024) | |
| #define MLOCK_RLIMIT_SIZE (CHUNK_UNIT * 2) | |
| #define MLOCK_WITHIN_LIMIT_SIZE CHUNK_UNIT | |
| #define MLOCK_OUTOF_LIMIT_SIZE (CHUNK_UNIT * 3) | |
| #define TEST_LOOP 100 | |
| #define PAGE_ALIGN(size, ps) (((size) + ((ps) - 1)) & ~((ps) - 1)) | |
| int set_cap_limits(rlim_t max) | |
| { | |
| struct rlimit new; | |
| cap_t cap = cap_init(); | |
| new.rlim_cur = max; | |
| new.rlim_max = max; | |
| if (setrlimit(RLIMIT_MEMLOCK, &new)) { | |
| perror("setrlimit() returns error\n"); | |
| return -1; | |
| } | |
| /* drop capabilities including CAP_IPC_LOCK */ | |
| if (cap_set_proc(cap)) { | |
| perror("cap_set_proc() returns error\n"); | |
| return -2; | |
| } | |
| return 0; | |
| } | |
| int get_proc_locked_vm_size(void) | |
| { | |
| FILE *f; | |
| int ret = -1; | |
| char line[1024] = {0}; | |
| unsigned long lock_size = 0; | |
| f = fopen("/proc/self/status", "r"); | |
| if (!f) { | |
| perror("fopen"); | |
| return -1; | |
| } | |
| while (fgets(line, 1024, f)) { | |
| if (strstr(line, "VmLck")) { | |
| ret = sscanf(line, "VmLck:\t%8lu kB", &lock_size); | |
| if (ret <= 0) { | |
| printf("sscanf() on VmLck error: %s: %d\n", | |
| line, ret); | |
| fclose(f); | |
| return -1; | |
| } | |
| fclose(f); | |
| return (int)(lock_size << 10); | |
| } | |
| } | |
| perror("cann't parse VmLck in /proc/self/status\n"); | |
| fclose(f); | |
| return -1; | |
| } | |
| /* | |
| * Get the MMUPageSize of the memory region including input | |
| * address from proc file. | |
| * | |
| * return value: on error case, 0 will be returned. | |
| * Otherwise the page size(in bytes) is returned. | |
| */ | |
| int get_proc_page_size(unsigned long addr) | |
| { | |
| FILE *smaps; | |
| char *line; | |
| unsigned long mmupage_size = 0; | |
| size_t size; | |
| smaps = seek_to_smaps_entry(addr); | |
| if (!smaps) { | |
| printf("Unable to parse /proc/self/smaps\n"); | |
| return 0; | |
| } | |
| while (getline(&line, &size, smaps) > 0) { | |
| if (!strstr(line, "MMUPageSize")) { | |
| free(line); | |
| line = NULL; | |
| size = 0; | |
| continue; | |
| } | |
| /* found the MMUPageSize of this section */ | |
| if (sscanf(line, "MMUPageSize: %8lu kB", | |
| &mmupage_size) < 1) { | |
| printf("Unable to parse smaps entry for Size:%s\n", | |
| line); | |
| break; | |
| } | |
| } | |
| free(line); | |
| if (smaps) | |
| fclose(smaps); | |
| return mmupage_size << 10; | |
| } | |
| /* | |
| * Test mlock/mlock2() on provided memory chunk. | |
| * It expects the mlock/mlock2() to be successful (within rlimit) | |
| * | |
| * With allocated memory chunk [p, p + alloc_size), this | |
| * test will choose start/len randomly to perform mlock/mlock2 | |
| * [start, start + len] memory range. The range is within range | |
| * of the allocated chunk. | |
| * | |
| * The memory region size alloc_size is within the rlimit. | |
| * So we always expect a success of mlock/mlock2. | |
| * | |
| * VmLck is assumed to be 0 before this test. | |
| * | |
| * return value: 0 - success | |
| * else: failure | |
| */ | |
| int test_mlock_within_limit(char *p, int alloc_size) | |
| { | |
| int i; | |
| int ret = 0; | |
| int locked_vm_size = 0; | |
| struct rlimit cur; | |
| int page_size = 0; | |
| getrlimit(RLIMIT_MEMLOCK, &cur); | |
| if (cur.rlim_cur < alloc_size) { | |
| printf("alloc_size[%d] < %u rlimit,lead to mlock failure\n", | |
| alloc_size, (unsigned int)cur.rlim_cur); | |
| return -1; | |
| } | |
| srand(time(NULL)); | |
| for (i = 0; i < TEST_LOOP; i++) { | |
| /* | |
| * - choose mlock/mlock2 randomly | |
| * - choose lock_size randomly but lock_size < alloc_size | |
| * - choose start_offset randomly but p+start_offset+lock_size | |
| * < p+alloc_size | |
| */ | |
| int is_mlock = !!(rand() % 2); | |
| int lock_size = rand() % alloc_size; | |
| int start_offset = rand() % (alloc_size - lock_size); | |
| if (is_mlock) | |
| ret = mlock(p + start_offset, lock_size); | |
| else | |
| ret = mlock2_(p + start_offset, lock_size, | |
| MLOCK_ONFAULT); | |
| if (ret) { | |
| printf("%s() failure at |%p(%d)| mlock:|%p(%d)|\n", | |
| is_mlock ? "mlock" : "mlock2", | |
| p, alloc_size, | |
| p + start_offset, lock_size); | |
| return ret; | |
| } | |
| } | |
| /* | |
| * Check VmLck left by the tests. | |
| */ | |
| locked_vm_size = get_proc_locked_vm_size(); | |
| page_size = get_proc_page_size((unsigned long)p); | |
| if (page_size == 0) { | |
| printf("cannot get proc MMUPageSize\n"); | |
| return -1; | |
| } | |
| if (locked_vm_size > PAGE_ALIGN(alloc_size, page_size) + page_size) { | |
| printf("test_mlock_within_limit() left VmLck:%d on %d chunk\n", | |
| locked_vm_size, alloc_size); | |
| return -1; | |
| } | |
| return 0; | |
| } | |
| /* | |
| * We expect the mlock/mlock2() to be fail (outof limitation) | |
| * | |
| * With allocated memory chunk [p, p + alloc_size), this | |
| * test will randomly choose start/len and perform mlock/mlock2 | |
| * on [start, start+len] range. | |
| * | |
| * The memory region size alloc_size is above the rlimit. | |
| * And the len to be locked is higher than rlimit. | |
| * So we always expect a failure of mlock/mlock2. | |
| * No locked page number should be increased as a side effect. | |
| * | |
| * return value: 0 - success | |
| * else: failure | |
| */ | |
| int test_mlock_outof_limit(char *p, int alloc_size) | |
| { | |
| int i; | |
| int ret = 0; | |
| int locked_vm_size = 0, old_locked_vm_size = 0; | |
| struct rlimit cur; | |
| getrlimit(RLIMIT_MEMLOCK, &cur); | |
| if (cur.rlim_cur >= alloc_size) { | |
| printf("alloc_size[%d] >%u rlimit, violates test condition\n", | |
| alloc_size, (unsigned int)cur.rlim_cur); | |
| return -1; | |
| } | |
| old_locked_vm_size = get_proc_locked_vm_size(); | |
| srand(time(NULL)); | |
| for (i = 0; i < TEST_LOOP; i++) { | |
| int is_mlock = !!(rand() % 2); | |
| int lock_size = (rand() % (alloc_size - cur.rlim_cur)) | |
| + cur.rlim_cur; | |
| int start_offset = rand() % (alloc_size - lock_size); | |
| if (is_mlock) | |
| ret = mlock(p + start_offset, lock_size); | |
| else | |
| ret = mlock2_(p + start_offset, lock_size, | |
| MLOCK_ONFAULT); | |
| if (ret == 0) { | |
| printf("%s() succeeds? on %p(%d) mlock%p(%d)\n", | |
| is_mlock ? "mlock" : "mlock2", | |
| p, alloc_size, | |
| p + start_offset, lock_size); | |
| return -1; | |
| } | |
| } | |
| locked_vm_size = get_proc_locked_vm_size(); | |
| if (locked_vm_size != old_locked_vm_size) { | |
| printf("tests leads to new mlocked page: old[%d], new[%d]\n", | |
| old_locked_vm_size, | |
| locked_vm_size); | |
| return -1; | |
| } | |
| return 0; | |
| } | |
| int main(int argc, char **argv) | |
| { | |
| char *p = NULL; | |
| int ret = 0; | |
| if (set_cap_limits(MLOCK_RLIMIT_SIZE)) | |
| return -1; | |
| p = malloc(MLOCK_WITHIN_LIMIT_SIZE); | |
| if (p == NULL) { | |
| perror("malloc() failure\n"); | |
| return -1; | |
| } | |
| ret = test_mlock_within_limit(p, MLOCK_WITHIN_LIMIT_SIZE); | |
| if (ret) | |
| return ret; | |
| munlock(p, MLOCK_WITHIN_LIMIT_SIZE); | |
| free(p); | |
| p = malloc(MLOCK_OUTOF_LIMIT_SIZE); | |
| if (p == NULL) { | |
| perror("malloc() failure\n"); | |
| return -1; | |
| } | |
| ret = test_mlock_outof_limit(p, MLOCK_OUTOF_LIMIT_SIZE); | |
| if (ret) | |
| return ret; | |
| munlock(p, MLOCK_OUTOF_LIMIT_SIZE); | |
| free(p); | |
| return 0; | |
| } |