Home | History | Annotate | Line # | Download | only in x86_pte_tester
x86_pte_tester.c revision 1.1
      1 /*	$NetBSD: x86_pte_tester.c,v 1.1 2020/04/26 09:08:41 maxv Exp $	*/
      2 
      3 /*
      4  * Copyright (c) 2016 The NetBSD Foundation, Inc.
      5  * All rights reserved.
      6  *
      7  * Redistribution and use in source and binary forms, with or without
      8  * modification, are permitted provided that the following conditions
      9  * are met:
     10  * 1. Redistributions of source code must retain the above copyright
     11  *    notice, this list of conditions and the following disclaimer.
     12  * 2. Redistributions in binary form must reproduce the above copyright
     13  *    notice, this list of conditions and the following disclaimer in the
     14  *    documentation and/or other materials provided with the distribution.
     15  *
     16  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
     17  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
     18  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     19  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
     20  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
     21  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
     22  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
     23  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
     24  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
     25  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
     26  * POSSIBILITY OF SUCH DAMAGE.
     27  */
     28 
     29 #define __HAVE_DIRECT_MAP
     30 #define __HAVE_PCPU_AREA
     31 #define SVS
     32 
     33 #include <sys/cdefs.h>
     34 #include <sys/param.h>
     35 #include <sys/module.h>
     36 #include <sys/proc.h>
     37 #include <sys/sysctl.h>
     38 #include <uvm/uvm.h>
     39 #include <x86/pmap.h>
     40 
     41 #if defined(__x86_64__)
     42 # include <amd64/pmap.h>
     43 # define NLEVEL 4
     44 #else
     45 # error "Unsupported configuration"
     46 #endif
     47 
     48 static struct {
     49 	struct sysctllog *ctx_sysctllog;
     50 	vaddr_t levels[NLEVEL];
     51 	struct {
     52 		size_t l4;
     53 		size_t l3;
     54 		size_t l2;
     55 		size_t l1;
     56 	} coord;
     57 	struct {
     58 		size_t n_rwx;
     59 		bool kernel_map_with_low_ptes;
     60 		bool pte_is_user_accessible;
     61 		size_t n_user_space_is_kernel;
     62 		size_t n_kernel_space_is_user;
     63 		size_t n_svs_g_bit_set;
     64 	} results;
     65 } tester_ctx;
     66 
     67 typedef enum {
     68 	WALK_NEXT, /* go to the next level */
     69 	WALK_SKIP, /* skip the next level, but keep iterating on the current one */
     70 	WALK_STOP  /* stop the iteration on the current level */
     71 } walk_type;
     72 
     73 /* -------------------------------------------------------------------------- */
     74 
     75 #define is_flag(__ent, __flag)	(((__ent) & __flag) != 0)
     76 #define is_valid(__ent)		is_flag(__ent, PTE_P)
     77 #define get_pa(__pde)		(__pde & PTE_FRAME)
     78 
     79 #define L4_MAX_NENTRIES (PAGE_SIZE / sizeof(pd_entry_t))
     80 #define L3_MAX_NENTRIES (PAGE_SIZE / sizeof(pd_entry_t))
     81 #define L2_MAX_NENTRIES (PAGE_SIZE / sizeof(pd_entry_t))
     82 #define L1_MAX_NENTRIES (PAGE_SIZE / sizeof(pd_entry_t))
     83 
     84 static void
     85 scan_l1(paddr_t pa, walk_type (fn)(pd_entry_t pde, size_t slot, int lvl))
     86 {
     87 	pd_entry_t *pd = (pd_entry_t *)tester_ctx.levels[0];
     88 	size_t i;
     89 
     90 	pmap_kenter_pa(tester_ctx.levels[0], pa, VM_PROT_READ, 0);
     91 	pmap_update(pmap_kernel());
     92 
     93 	for (i = 0; i < L1_MAX_NENTRIES; i++) {
     94 		tester_ctx.coord.l1 = i;
     95 		if (is_valid(pd[i])) {
     96 			fn(pd[i], i, 1);
     97 		}
     98 	}
     99 
    100 	pmap_kremove(tester_ctx.levels[0], PAGE_SIZE);
    101 	pmap_update(pmap_kernel());
    102 }
    103 
    104 static void
    105 scan_l2(paddr_t pa, walk_type (fn)(pd_entry_t pde, size_t slot, int lvl))
    106 {
    107 	pd_entry_t *pd = (pd_entry_t *)tester_ctx.levels[1];
    108 	walk_type ret;
    109 	size_t i;
    110 
    111 	pmap_kenter_pa(tester_ctx.levels[1], pa, VM_PROT_READ, 0);
    112 	pmap_update(pmap_kernel());
    113 
    114 	for (i = 0; i < L2_MAX_NENTRIES; i++) {
    115 		tester_ctx.coord.l2 = i;
    116 		if (!is_valid(pd[i]))
    117 			continue;
    118 		ret = fn(pd[i], i, 2);
    119 		if (ret == WALK_STOP)
    120 			break;
    121 		if (is_flag(pd[i], PTE_PS))
    122 			continue;
    123 		if (ret == WALK_NEXT)
    124 			scan_l1(get_pa(pd[i]), fn);
    125 	}
    126 
    127 	pmap_kremove(tester_ctx.levels[1], PAGE_SIZE);
    128 	pmap_update(pmap_kernel());
    129 }
    130 
    131 static void
    132 scan_l3(paddr_t pa, walk_type (fn)(pd_entry_t pde, size_t slot, int lvl))
    133 {
    134 	pd_entry_t *pd = (pd_entry_t *)tester_ctx.levels[2];
    135 	walk_type ret;
    136 	size_t i;
    137 
    138 	pmap_kenter_pa(tester_ctx.levels[2], pa, VM_PROT_READ, 0);
    139 	pmap_update(pmap_kernel());
    140 
    141 	for (i = 0; i < L3_MAX_NENTRIES; i++) {
    142 		tester_ctx.coord.l3 = i;
    143 		if (!is_valid(pd[i]))
    144 			continue;
    145 		ret = fn(pd[i], i, 3);
    146 		if (ret == WALK_STOP)
    147 			break;
    148 		if (is_flag(pd[i], PTE_PS))
    149 			continue;
    150 		if (ret == WALK_NEXT)
    151 			scan_l2(get_pa(pd[i]), fn);
    152 	}
    153 
    154 	pmap_kremove(tester_ctx.levels[2], PAGE_SIZE);
    155 	pmap_update(pmap_kernel());
    156 }
    157 
    158 static void
    159 scan_l4(paddr_t pa, walk_type (fn)(pd_entry_t pde, size_t slot, int lvl))
    160 {
    161 	pd_entry_t *pd = (pd_entry_t *)tester_ctx.levels[3];
    162 	walk_type ret;
    163 	size_t i;
    164 
    165 	pmap_kenter_pa(tester_ctx.levels[3], pa, VM_PROT_READ, 0);
    166 	pmap_update(pmap_kernel());
    167 
    168 	for (i = 0; i < L4_MAX_NENTRIES; i++) {
    169 		tester_ctx.coord.l4 = i;
    170 		if (!is_valid(pd[i]))
    171 			continue;
    172 		ret = fn(pd[i], i, 4);
    173 		if (ret == WALK_STOP)
    174 			break;
    175 		if (is_flag(pd[i], PTE_PS))
    176 			continue;
    177 		if (ret == WALK_NEXT)
    178 			scan_l3(get_pa(pd[i]), fn);
    179 	}
    180 
    181 	pmap_kremove(tester_ctx.levels[3], PAGE_SIZE);
    182 	pmap_update(pmap_kernel());
    183 }
    184 
    185 static void
    186 scan_tree(paddr_t pa, walk_type (fn)(pd_entry_t pde, size_t slot, int lvl))
    187 {
    188 	scan_l4(pa, fn);
    189 }
    190 
    191 /* -------------------------------------------------------------------------- */
    192 
    193 /*
    194  * Rule: the number of kernel RWX pages should be zero.
    195  */
    196 static walk_type
    197 count_krwx(pd_entry_t pde, size_t slot, int lvl)
    198 {
    199 	if (lvl == NLEVEL && slot < 256) {
    200 		return WALK_SKIP;
    201 	}
    202 	if (is_flag(pde, PTE_NX) || !is_flag(pde, PTE_W)) {
    203 		return WALK_SKIP;
    204 	}
    205 	if (lvl != 1 && !is_flag(pde, PTE_PS)) {
    206 		return WALK_NEXT;
    207 	}
    208 
    209 	if (lvl == 4) {
    210 		tester_ctx.results.n_rwx += (NBPD_L4 / PAGE_SIZE);
    211 	} else if (lvl == 3) {
    212 		tester_ctx.results.n_rwx += (NBPD_L3 / PAGE_SIZE);
    213 	} else if (lvl == 2) {
    214 		tester_ctx.results.n_rwx += (NBPD_L2 / PAGE_SIZE);
    215 	} else if (lvl == 1) {
    216 		tester_ctx.results.n_rwx += (NBPD_L1 / PAGE_SIZE);
    217 	}
    218 
    219 	return WALK_NEXT;
    220 }
    221 
    222 /*
    223  * Rule: the lower half of the kernel map must be zero.
    224  */
    225 static walk_type
    226 check_kernel_map(pd_entry_t pde, size_t slot, int lvl)
    227 {
    228 	if (lvl != NLEVEL) {
    229 		return WALK_STOP;
    230 	}
    231 	if (slot >= 256) {
    232 		return WALK_SKIP;
    233 	}
    234 	if (pde != 0) {
    235 		tester_ctx.results.kernel_map_with_low_ptes |= true;
    236 	}
    237 	return WALK_SKIP;
    238 }
    239 
    240 /*
    241  * Rule: the PTE space must not have user permissions.
    242  */
    243 static walk_type
    244 check_pte_space(pd_entry_t pde, size_t slot, int lvl)
    245 {
    246 	if (lvl != NLEVEL) {
    247 		return WALK_STOP;
    248 	}
    249 	if (slot != PDIR_SLOT_PTE) {
    250 		return WALK_SKIP;
    251 	}
    252 	if (is_flag(pde, PTE_U)) {
    253 		tester_ctx.results.pte_is_user_accessible |= true;
    254 	}
    255 	return WALK_SKIP;
    256 }
    257 
    258 /*
    259  * Rule: each page in the lower half must have user permissions.
    260  */
    261 static walk_type
    262 check_user_space(pd_entry_t pde, size_t slot, int lvl)
    263 {
    264 	if (lvl == NLEVEL && slot >= 256) {
    265 		return WALK_SKIP;
    266 	}
    267 	if (!is_flag(pde, PTE_U)) {
    268 		tester_ctx.results.n_user_space_is_kernel += 1;
    269 		return WALK_SKIP;
    270 	}
    271 	return WALK_NEXT;
    272 }
    273 
    274 /*
    275  * Rule: each page in the higher half must have kernel permissions.
    276  */
    277 static walk_type
    278 check_kernel_space(pd_entry_t pde, size_t slot, int lvl)
    279 {
    280 	if (lvl == NLEVEL && slot < 256) {
    281 		return WALK_SKIP;
    282 	}
    283 	if (lvl == NLEVEL && slot == PDIR_SLOT_PTE) {
    284 		return WALK_SKIP;
    285 	}
    286 	if (is_flag(pde, PTE_U)) {
    287 		tester_ctx.results.n_kernel_space_is_user += 1;
    288 		return WALK_SKIP;
    289 	}
    290 	return WALK_NEXT;
    291 }
    292 
    293 /*
    294  * Rule: the SVS map is allowed to use the G bit only on the PCPU area.
    295  */
    296 static walk_type
    297 check_svs_g_bit(pd_entry_t pde, size_t slot, int lvl)
    298 {
    299 	if (lvl == NLEVEL && slot == PDIR_SLOT_PCPU) {
    300 		return WALK_SKIP;
    301 	}
    302 	if (is_flag(pde, PTE_G)) {
    303 		tester_ctx.results.n_svs_g_bit_set += 1;
    304 		return WALK_SKIP;
    305 	}
    306 	return WALK_NEXT;
    307 }
    308 
    309 /* -------------------------------------------------------------------------- */
    310 
    311 static void
    312 scan_svs(void)
    313 {
    314 	extern bool svs_enabled;
    315 	paddr_t pa0;
    316 
    317 	if (!svs_enabled) {
    318 		tester_ctx.results.n_svs_g_bit_set = -1;
    319 		return;
    320 	}
    321 
    322 	kpreempt_disable();
    323 	pa0 = curcpu()->ci_svs_updirpa;
    324 	scan_tree(pa0, &check_user_space);
    325 	scan_tree(pa0, &check_kernel_space);
    326 	scan_tree(pa0, &check_svs_g_bit);
    327 	kpreempt_enable();
    328 }
    329 
    330 static void
    331 scan_proc(struct proc *p)
    332 {
    333 	struct pmap *pmap = p->p_vmspace->vm_map.pmap;
    334 	paddr_t pa0;
    335 
    336 	mutex_enter(&pmap->pm_lock);
    337 
    338 	kpreempt_disable();
    339 	pa0 = (paddr_t)pmap->pm_pdirpa[0];
    340 	scan_tree(pa0, &check_user_space);
    341 	scan_tree(pa0, &check_kernel_space);
    342 	scan_tree(pa0, &check_pte_space);
    343 	kpreempt_enable();
    344 
    345 	mutex_exit(&pmap->pm_lock);
    346 }
    347 
    348 static void
    349 x86_pte_run_scans(void)
    350 {
    351 	struct pmap *kpm = pmap_kernel();
    352 	paddr_t pa0;
    353 
    354 	memset(&tester_ctx.results, 0, sizeof(tester_ctx.results));
    355 
    356 	/* Scan the current user process. */
    357 	scan_proc(curproc);
    358 
    359 	/* Scan the SVS mapping. */
    360 	scan_svs();
    361 
    362 	/* Scan the kernel map. */
    363 	pa0 = (paddr_t)kpm->pm_pdirpa[0];
    364 	scan_tree(pa0, &count_krwx);
    365 	scan_tree(pa0, &check_kernel_map);
    366 }
    367 
    368 static void
    369 x86_pte_levels_init(void)
    370 {
    371 	size_t i;
    372 	for (i = 0; i < NLEVEL; i++) {
    373 		tester_ctx.levels[i] = uvm_km_alloc(kernel_map, PAGE_SIZE, 0,
    374 		    UVM_KMF_VAONLY);
    375 	}
    376 }
    377 
    378 static void
    379 x86_pte_levels_destroy(void)
    380 {
    381 	size_t i;
    382 	for (i = 0; i < NLEVEL; i++) {
    383 		uvm_km_free(kernel_map, tester_ctx.levels[i], PAGE_SIZE,
    384 		    UVM_KMF_VAONLY);
    385 	}
    386 }
    387 
    388 /* -------------------------------------------------------------------------- */
    389 
    390 static int
    391 x86_pte_sysctl_run(SYSCTLFN_ARGS)
    392 {
    393 	if (oldlenp == NULL)
    394 		return EINVAL;
    395 
    396 	x86_pte_run_scans();
    397 
    398 	if (oldp == NULL) {
    399 		*oldlenp = sizeof(tester_ctx.results);
    400 		return 0;
    401 	}
    402 
    403 	if (*oldlenp < sizeof(tester_ctx.results))
    404 		return ENOMEM;
    405 
    406 	return copyout(&tester_ctx.results, oldp, sizeof(tester_ctx.results));
    407 }
    408 
    409 static int
    410 x86_pte_sysctl_init(void)
    411 {
    412 	struct sysctllog **log = &tester_ctx.ctx_sysctllog;
    413 	const struct sysctlnode *rnode, *cnode;
    414 	int error;
    415 
    416 	error = sysctl_createv(log, 0, NULL, &rnode, CTLFLAG_PERMANENT,
    417 	    CTLTYPE_NODE, "x86_pte_test",
    418 	    SYSCTL_DESCR("x86_pte testing interface"),
    419 	    NULL, 0, NULL, 0, CTL_KERN, CTL_CREATE, CTL_EOL);
    420 	if (error)
    421 		goto out;
    422 
    423 	error = sysctl_createv(log, 0, &rnode, &cnode, CTLFLAG_PERMANENT,
    424 	    CTLTYPE_STRUCT, "test",
    425 	    SYSCTL_DESCR("execute a x86_pte test"),
    426 	    x86_pte_sysctl_run, 0, NULL, 0, CTL_CREATE, CTL_EOL);
    427 
    428 out:
    429  	if (error)
    430 		sysctl_teardown(log);
    431 	return error;
    432 }
    433 
    434 static void
    435 x86_pte_sysctl_destroy(void)
    436 {
    437 	sysctl_teardown(&tester_ctx.ctx_sysctllog);
    438 }
    439 
    440 /* -------------------------------------------------------------------------- */
    441 
    442 MODULE(MODULE_CLASS_MISC, x86_pte_tester, NULL);
    443 
    444 static int
    445 x86_pte_tester_modcmd(modcmd_t cmd, void *arg __unused)
    446 {
    447 	int error = 0;
    448 
    449 	switch (cmd) {
    450 	case MODULE_CMD_INIT:
    451 		x86_pte_levels_init();
    452 		error = x86_pte_sysctl_init();
    453 		break;
    454 	case MODULE_CMD_FINI:
    455 		x86_pte_sysctl_destroy();
    456 		x86_pte_levels_destroy();
    457 		break;
    458 	default:
    459 		error = ENOTTY;
    460 		break;
    461 	}
    462 
    463 	return error;
    464 }
    465