code: plan9front

Download patch

ref: 7bb1a9a18566ea9c8ae7f6c2fa99e448026521d2
parent: e988d56a2f6b87531a12559a336b5de4471605b4
author: cinap_lenrek <cinap_lenrek@felloff.net>
date: Thu Aug 29 03:35:22 EDT 2019

pc64: map kernel text readonly and everything else no-execute

the idea is to catch bugs and make kernel exploitation
harder by mapping the kernel text section readonly
and everything else no-execute.

l.s maps the KZERO address space using 2MB pages so
to get the 4K granularity for the text section we use
the new ptesplit() function to split that mapping up.

we need to set EFER no-execute enable bit early
in apbootstrap so secondary application processors
will understand the NX bit in our shared kernel page
tables. also APBOOTSTRAP needs to be kept executable.

rebootjump() needs to mark REBOOTADDR page executable.

--- a/sys/src/9/pc64/apbootstrap.s
+++ b/sys/src/9/pc64/apbootstrap.s
@@ -42,6 +42,9 @@
 	QUAD $0
 TEXT _apmach(SB), 1, $-4 			/* address APBOOTSTRAP+0x20 */
 	QUAD $0
+TEXT _apefer(SB), 1, $-4
+	QUAD $0x100				/* Long Mode Enable */
+
 TEXT _apbootstrap(SB), 1, $-4 
 	MOVW	CS, AX
 	MOVW	AX, DS				/* initialise DS */
@@ -85,7 +88,7 @@
 
 	MOVL	$0xc0000080, CX			/* Extended Feature Enable */
 	RDMSR
-	ORL	$0x00000100, AX			/* Long Mode Enable */
+	ORL	_apefer-KZERO(SB), AX
 	WRMSR
 
 	MOVL	CR0, DX
--- a/sys/src/9/pc64/main.c
+++ b/sys/src/9/pc64/main.c
@@ -301,6 +301,7 @@
 rebootjump(uintptr entry, uintptr code, ulong size)
 {
 	void (*f)(uintptr, uintptr, ulong);
+	uintptr *pte;
 
 	splhi();
 	arch->introff();
@@ -310,6 +311,12 @@
 	 */
 	*mmuwalk(m->pml4, 0, 3, 0) = *mmuwalk(m->pml4, KZERO, 3, 0);
 	*mmuwalk(m->pml4, 0, 2, 0) = *mmuwalk(m->pml4, KZERO, 2, 0);
+
+	if((pte = mmuwalk(m->pml4, REBOOTADDR, 1, 0)) != nil)
+		*pte &= ~PTENOEXEC;
+	if((pte = mmuwalk(m->pml4, REBOOTADDR, 0, 0)) != nil)
+		*pte &= ~PTENOEXEC;
+
 	mmuflushtlb();
 
 	/* setup reboot trampoline function */
--- a/sys/src/9/pc64/memory.c
+++ b/sys/src/9/pc64/memory.c
@@ -221,6 +221,8 @@
 {
 	uintptr m;
 	
+	if(conf.mem[0].npage != 0)
+		return xspanalloc(BY2PG, BY2PG, 0);
 	m = mapalloc(&rmapram, 0, BY2PG, BY2PG);
 	if(m == 0)
 		return nil;
@@ -543,11 +545,11 @@
 	switch(type){
 	case MemRAM:
 		mapfree(&rmapram, base, len);
-		flags = PTEWRITE|PTEVALID;
+		flags = PTEWRITE|PTENOEXEC|PTEVALID;
 		break;
 	case MemUMB:
 		mapfree(&rmapumb, base, len);
-		flags = PTEWRITE|PTEUNCACHED|PTEVALID;
+		flags = PTEWRITE|PTEUNCACHED|PTENOEXEC|PTEVALID;
 		break;
 	case MemUPA:
 		mapfree(&rmapupa, base, len);
--- a/sys/src/9/pc64/mmu.c
+++ b/sys/src/9/pc64/mmu.c
@@ -73,6 +73,8 @@
 	mmuflushtlb();
 }
 
+static void kernelro(void);
+
 void
 mmuinit(void)
 {
@@ -84,6 +86,9 @@
 	m->pml4[512] = 0;
 	m->pml4[0] = 0;
 
+	if(m->machno == 0)
+		kernelro();
+
 	m->tss = mallocz(sizeof(Tss), 1);
 	if(m->tss == nil)
 		panic("mmuinit: no memory for Tss");
@@ -244,8 +249,6 @@
 			up->kmapcount++;
 		}
 		page = p->page;
-	} else if(conf.mem[0].npage != 0) {
-		page = mallocalign(PTSZ, BY2PG, 0, 0);
 	} else {
 		page = rampage();
 	}
@@ -285,6 +288,59 @@
 ptecount(uintptr va, int level)
 {
 	return (1<<PTSHIFT) - (va & PGLSZ(level+1)-1) / PGLSZ(level);
+}
+
+static void
+ptesplit(uintptr* table, uintptr va)
+{
+	uintptr *pte, pa, off;
+
+	pte = mmuwalk(table, va, 1, 0);
+	if(pte == nil || (*pte & PTESIZE) == 0 || (va & PGLSZ(1)-1) == 0)
+		return;
+	table = rampage();
+	if(table == nil)
+		panic("ptesplit: out of memory\n");
+	va &= -PGLSZ(1);
+	pa = *pte & ~PTESIZE;
+	for(off = 0; off < PGLSZ(1); off += PGLSZ(0))
+		table[PTLX(va + off, 0)] = pa + off;
+	*pte = PADDR(table) | PTEVALID|PTEWRITE;
+	invlpg(va);
+}
+
+/*
+ * map kernel text segment readonly
+ * and everything else no-execute.
+ */
+static void
+kernelro(void)
+{
+	uintptr *pte, psz, va;
+
+	ptesplit(m->pml4, APBOOTSTRAP);
+	ptesplit(m->pml4, KTZERO);
+	ptesplit(m->pml4, (uintptr)etext-1);
+
+	for(va = KZERO; va != 0; va += psz){
+		psz = PGLSZ(0);
+		pte = mmuwalk(m->pml4, va, 0, 0);
+		if(pte == nil){
+			if(va & PGLSZ(1)-1)
+				continue;
+			pte = mmuwalk(m->pml4, va, 1, 0);
+			if(pte == nil)
+				continue;
+			psz = PGLSZ(1);
+		}
+		if((*pte & PTEVALID) == 0)
+			continue;
+		if(va >= KTZERO && va < (uintptr)etext)
+			*pte &= ~PTEWRITE;
+		else if(va != (APBOOTSTRAP & -BY2PG))
+			*pte |= PTENOEXEC;
+		invlpg(va);
+	}
 }
 
 void
--- a/sys/src/9/pc64/squidboy.c
+++ b/sys/src/9/pc64/squidboy.c
@@ -79,6 +79,7 @@
 	apbootp[1] = (uintptr)PADDR(pml4);
 	apbootp[2] = (uintptr)apic;
 	apbootp[3] = (uintptr)mach;
+	apbootp[4] |= (uintptr)m->havenx<<11;	/* EFER */
 
 	/*
 	 * Universal Startup Algorithm.