cachepc-linux

Fork of AMDESE/linux with modifications for CachePC side-channel attack
git clone https://git.sinitax.com/sinitax/cachepc-linux
Log | Files | Refs | README | LICENSE | sfeed.txt

mlock-random-test.c (6682B)


      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
     25int 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
     46int 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("cannot 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 */
     85int 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 */
    138int 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 */
    215int 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
    262int 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}