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

mremap_dontunmap.c (11755B)


      1// SPDX-License-Identifier: GPL-2.0
      2
      3/*
      4 * Tests for mremap w/ MREMAP_DONTUNMAP.
      5 *
      6 * Copyright 2020, Brian Geffon <bgeffon@google.com>
      7 */
      8#define _GNU_SOURCE
      9#include <sys/mman.h>
     10#include <errno.h>
     11#include <stdio.h>
     12#include <stdlib.h>
     13#include <string.h>
     14#include <unistd.h>
     15
     16#include "../kselftest.h"
     17
     18#ifndef MREMAP_DONTUNMAP
     19#define MREMAP_DONTUNMAP 4
     20#endif
     21
     22unsigned long page_size;
     23char *page_buffer;
     24
     25static void dump_maps(void)
     26{
     27	char cmd[32];
     28
     29	snprintf(cmd, sizeof(cmd), "cat /proc/%d/maps", getpid());
     30	system(cmd);
     31}
     32
     33#define BUG_ON(condition, description)					      \
     34	do {								      \
     35		if (condition) {					      \
     36			fprintf(stderr, "[FAIL]\t%s():%d\t%s:%s\n", __func__, \
     37				__LINE__, (description), strerror(errno));    \
     38			dump_maps();					  \
     39			exit(1);					      \
     40		} 							      \
     41	} while (0)
     42
     43// Try a simple operation for to "test" for kernel support this prevents
     44// reporting tests as failed when it's run on an older kernel.
     45static int kernel_support_for_mremap_dontunmap()
     46{
     47	int ret = 0;
     48	unsigned long num_pages = 1;
     49	void *source_mapping = mmap(NULL, num_pages * page_size, PROT_NONE,
     50				    MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
     51	BUG_ON(source_mapping == MAP_FAILED, "mmap");
     52
     53	// This simple remap should only fail if MREMAP_DONTUNMAP isn't
     54	// supported.
     55	void *dest_mapping =
     56	    mremap(source_mapping, num_pages * page_size, num_pages * page_size,
     57		   MREMAP_DONTUNMAP | MREMAP_MAYMOVE, 0);
     58	if (dest_mapping == MAP_FAILED) {
     59		ret = errno;
     60	} else {
     61		BUG_ON(munmap(dest_mapping, num_pages * page_size) == -1,
     62		       "unable to unmap destination mapping");
     63	}
     64
     65	BUG_ON(munmap(source_mapping, num_pages * page_size) == -1,
     66	       "unable to unmap source mapping");
     67	return ret;
     68}
     69
     70// This helper will just validate that an entire mapping contains the expected
     71// byte.
     72static int check_region_contains_byte(void *addr, unsigned long size, char byte)
     73{
     74	BUG_ON(size & (page_size - 1),
     75	       "check_region_contains_byte expects page multiples");
     76	BUG_ON((unsigned long)addr & (page_size - 1),
     77	       "check_region_contains_byte expects page alignment");
     78
     79	memset(page_buffer, byte, page_size);
     80
     81	unsigned long num_pages = size / page_size;
     82	unsigned long i;
     83
     84	// Compare each page checking that it contains our expected byte.
     85	for (i = 0; i < num_pages; ++i) {
     86		int ret =
     87		    memcmp(addr + (i * page_size), page_buffer, page_size);
     88		if (ret) {
     89			return ret;
     90		}
     91	}
     92
     93	return 0;
     94}
     95
     96// this test validates that MREMAP_DONTUNMAP moves the pagetables while leaving
     97// the source mapping mapped.
     98static void mremap_dontunmap_simple()
     99{
    100	unsigned long num_pages = 5;
    101
    102	void *source_mapping =
    103	    mmap(NULL, num_pages * page_size, PROT_READ | PROT_WRITE,
    104		 MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
    105	BUG_ON(source_mapping == MAP_FAILED, "mmap");
    106
    107	memset(source_mapping, 'a', num_pages * page_size);
    108
    109	// Try to just move the whole mapping anywhere (not fixed).
    110	void *dest_mapping =
    111	    mremap(source_mapping, num_pages * page_size, num_pages * page_size,
    112		   MREMAP_DONTUNMAP | MREMAP_MAYMOVE, NULL);
    113	BUG_ON(dest_mapping == MAP_FAILED, "mremap");
    114
    115	// Validate that the pages have been moved, we know they were moved if
    116	// the dest_mapping contains a's.
    117	BUG_ON(check_region_contains_byte
    118	       (dest_mapping, num_pages * page_size, 'a') != 0,
    119	       "pages did not migrate");
    120	BUG_ON(check_region_contains_byte
    121	       (source_mapping, num_pages * page_size, 0) != 0,
    122	       "source should have no ptes");
    123
    124	BUG_ON(munmap(dest_mapping, num_pages * page_size) == -1,
    125	       "unable to unmap destination mapping");
    126	BUG_ON(munmap(source_mapping, num_pages * page_size) == -1,
    127	       "unable to unmap source mapping");
    128}
    129
    130// This test validates that MREMAP_DONTUNMAP on a shared mapping works as expected.
    131static void mremap_dontunmap_simple_shmem()
    132{
    133	unsigned long num_pages = 5;
    134
    135	int mem_fd = memfd_create("memfd", MFD_CLOEXEC);
    136	BUG_ON(mem_fd < 0, "memfd_create");
    137
    138	BUG_ON(ftruncate(mem_fd, num_pages * page_size) < 0,
    139			"ftruncate");
    140
    141	void *source_mapping =
    142	    mmap(NULL, num_pages * page_size, PROT_READ | PROT_WRITE,
    143		 MAP_FILE | MAP_SHARED, mem_fd, 0);
    144	BUG_ON(source_mapping == MAP_FAILED, "mmap");
    145
    146	BUG_ON(close(mem_fd) < 0, "close");
    147
    148	memset(source_mapping, 'a', num_pages * page_size);
    149
    150	// Try to just move the whole mapping anywhere (not fixed).
    151	void *dest_mapping =
    152	    mremap(source_mapping, num_pages * page_size, num_pages * page_size,
    153		   MREMAP_DONTUNMAP | MREMAP_MAYMOVE, NULL);
    154	if (dest_mapping == MAP_FAILED && errno == EINVAL) {
    155		// Old kernel which doesn't support MREMAP_DONTUNMAP on shmem.
    156		BUG_ON(munmap(source_mapping, num_pages * page_size) == -1,
    157			"unable to unmap source mapping");
    158		return;
    159	}
    160
    161	BUG_ON(dest_mapping == MAP_FAILED, "mremap");
    162
    163	// Validate that the pages have been moved, we know they were moved if
    164	// the dest_mapping contains a's.
    165	BUG_ON(check_region_contains_byte
    166	       (dest_mapping, num_pages * page_size, 'a') != 0,
    167	       "pages did not migrate");
    168
    169	// Because the region is backed by shmem, we will actually see the same
    170	// memory at the source location still.
    171	BUG_ON(check_region_contains_byte
    172	       (source_mapping, num_pages * page_size, 'a') != 0,
    173	       "source should have no ptes");
    174
    175	BUG_ON(munmap(dest_mapping, num_pages * page_size) == -1,
    176	       "unable to unmap destination mapping");
    177	BUG_ON(munmap(source_mapping, num_pages * page_size) == -1,
    178	       "unable to unmap source mapping");
    179}
    180
    181// This test validates MREMAP_DONTUNMAP will move page tables to a specific
    182// destination using MREMAP_FIXED, also while validating that the source
    183// remains intact.
    184static void mremap_dontunmap_simple_fixed()
    185{
    186	unsigned long num_pages = 5;
    187
    188	// Since we want to guarantee that we can remap to a point, we will
    189	// create a mapping up front.
    190	void *dest_mapping =
    191	    mmap(NULL, num_pages * page_size, PROT_READ | PROT_WRITE,
    192		 MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
    193	BUG_ON(dest_mapping == MAP_FAILED, "mmap");
    194	memset(dest_mapping, 'X', num_pages * page_size);
    195
    196	void *source_mapping =
    197	    mmap(NULL, num_pages * page_size, PROT_READ | PROT_WRITE,
    198		 MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
    199	BUG_ON(source_mapping == MAP_FAILED, "mmap");
    200	memset(source_mapping, 'a', num_pages * page_size);
    201
    202	void *remapped_mapping =
    203	    mremap(source_mapping, num_pages * page_size, num_pages * page_size,
    204		   MREMAP_FIXED | MREMAP_DONTUNMAP | MREMAP_MAYMOVE,
    205		   dest_mapping);
    206	BUG_ON(remapped_mapping == MAP_FAILED, "mremap");
    207	BUG_ON(remapped_mapping != dest_mapping,
    208	       "mremap should have placed the remapped mapping at dest_mapping");
    209
    210	// The dest mapping will have been unmap by mremap so we expect the Xs
    211	// to be gone and replaced with a's.
    212	BUG_ON(check_region_contains_byte
    213	       (dest_mapping, num_pages * page_size, 'a') != 0,
    214	       "pages did not migrate");
    215
    216	// And the source mapping will have had its ptes dropped.
    217	BUG_ON(check_region_contains_byte
    218	       (source_mapping, num_pages * page_size, 0) != 0,
    219	       "source should have no ptes");
    220
    221	BUG_ON(munmap(dest_mapping, num_pages * page_size) == -1,
    222	       "unable to unmap destination mapping");
    223	BUG_ON(munmap(source_mapping, num_pages * page_size) == -1,
    224	       "unable to unmap source mapping");
    225}
    226
    227// This test validates that we can MREMAP_DONTUNMAP for a portion of an
    228// existing mapping.
    229static void mremap_dontunmap_partial_mapping()
    230{
    231	/*
    232	 *  source mapping:
    233	 *  --------------
    234	 *  | aaaaaaaaaa |
    235	 *  --------------
    236	 *  to become:
    237	 *  --------------
    238	 *  | aaaaa00000 |
    239	 *  --------------
    240	 *  With the destination mapping containing 5 pages of As.
    241	 *  ---------
    242	 *  | aaaaa |
    243	 *  ---------
    244	 */
    245	unsigned long num_pages = 10;
    246	void *source_mapping =
    247	    mmap(NULL, num_pages * page_size, PROT_READ | PROT_WRITE,
    248		 MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
    249	BUG_ON(source_mapping == MAP_FAILED, "mmap");
    250	memset(source_mapping, 'a', num_pages * page_size);
    251
    252	// We will grab the last 5 pages of the source and move them.
    253	void *dest_mapping =
    254	    mremap(source_mapping + (5 * page_size), 5 * page_size,
    255		   5 * page_size,
    256		   MREMAP_DONTUNMAP | MREMAP_MAYMOVE, NULL);
    257	BUG_ON(dest_mapping == MAP_FAILED, "mremap");
    258
    259	// We expect the first 5 pages of the source to contain a's and the
    260	// final 5 pages to contain zeros.
    261	BUG_ON(check_region_contains_byte(source_mapping, 5 * page_size, 'a') !=
    262	       0, "first 5 pages of source should have original pages");
    263	BUG_ON(check_region_contains_byte
    264	       (source_mapping + (5 * page_size), 5 * page_size, 0) != 0,
    265	       "final 5 pages of source should have no ptes");
    266
    267	// Finally we expect the destination to have 5 pages worth of a's.
    268	BUG_ON(check_region_contains_byte(dest_mapping, 5 * page_size, 'a') !=
    269	       0, "dest mapping should contain ptes from the source");
    270
    271	BUG_ON(munmap(dest_mapping, 5 * page_size) == -1,
    272	       "unable to unmap destination mapping");
    273	BUG_ON(munmap(source_mapping, num_pages * page_size) == -1,
    274	       "unable to unmap source mapping");
    275}
    276
    277// This test validates that we can remap over only a portion of a mapping.
    278static void mremap_dontunmap_partial_mapping_overwrite(void)
    279{
    280	/*
    281	 *  source mapping:
    282	 *  ---------
    283	 *  |aaaaa|
    284	 *  ---------
    285	 *  dest mapping initially:
    286	 *  -----------
    287	 *  |XXXXXXXXXX|
    288	 *  ------------
    289	 *  Source to become:
    290	 *  ---------
    291	 *  |00000|
    292	 *  ---------
    293	 *  With the destination mapping containing 5 pages of As.
    294	 *  ------------
    295	 *  |aaaaaXXXXX|
    296	 *  ------------
    297	 */
    298	void *source_mapping =
    299	    mmap(NULL, 5 * page_size, PROT_READ | PROT_WRITE,
    300		 MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
    301	BUG_ON(source_mapping == MAP_FAILED, "mmap");
    302	memset(source_mapping, 'a', 5 * page_size);
    303
    304	void *dest_mapping =
    305	    mmap(NULL, 10 * page_size, PROT_READ | PROT_WRITE,
    306		 MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
    307	BUG_ON(dest_mapping == MAP_FAILED, "mmap");
    308	memset(dest_mapping, 'X', 10 * page_size);
    309
    310	// We will grab the last 5 pages of the source and move them.
    311	void *remapped_mapping =
    312	    mremap(source_mapping, 5 * page_size,
    313		   5 * page_size,
    314		   MREMAP_DONTUNMAP | MREMAP_MAYMOVE | MREMAP_FIXED, dest_mapping);
    315	BUG_ON(dest_mapping == MAP_FAILED, "mremap");
    316	BUG_ON(dest_mapping != remapped_mapping, "expected to remap to dest_mapping");
    317
    318	BUG_ON(check_region_contains_byte(source_mapping, 5 * page_size, 0) !=
    319	       0, "first 5 pages of source should have no ptes");
    320
    321	// Finally we expect the destination to have 5 pages worth of a's.
    322	BUG_ON(check_region_contains_byte(dest_mapping, 5 * page_size, 'a') != 0,
    323			"dest mapping should contain ptes from the source");
    324
    325	// Finally the last 5 pages shouldn't have been touched.
    326	BUG_ON(check_region_contains_byte(dest_mapping + (5 * page_size),
    327				5 * page_size, 'X') != 0,
    328			"dest mapping should have retained the last 5 pages");
    329
    330	BUG_ON(munmap(dest_mapping, 10 * page_size) == -1,
    331	       "unable to unmap destination mapping");
    332	BUG_ON(munmap(source_mapping, 5 * page_size) == -1,
    333	       "unable to unmap source mapping");
    334}
    335
    336int main(void)
    337{
    338	page_size = sysconf(_SC_PAGE_SIZE);
    339
    340	// test for kernel support for MREMAP_DONTUNMAP skipping the test if
    341	// not.
    342	if (kernel_support_for_mremap_dontunmap() != 0) {
    343		printf("No kernel support for MREMAP_DONTUNMAP\n");
    344		return KSFT_SKIP;
    345	}
    346
    347	// Keep a page sized buffer around for when we need it.
    348	page_buffer =
    349	    mmap(NULL, page_size, PROT_READ | PROT_WRITE,
    350		 MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
    351	BUG_ON(page_buffer == MAP_FAILED, "unable to mmap a page.");
    352
    353	mremap_dontunmap_simple();
    354	mremap_dontunmap_simple_shmem();
    355	mremap_dontunmap_simple_fixed();
    356	mremap_dontunmap_partial_mapping();
    357	mremap_dontunmap_partial_mapping_overwrite();
    358
    359	BUG_ON(munmap(page_buffer, page_size) == -1,
    360	       "unable to unmap page buffer");
    361
    362	printf("OK\n");
    363	return 0;
    364}