git: f803dd1e24ea - stable/13 - Add pmap_change_prot on arm64

From: Andrew Turner <andrew_at_FreeBSD.org>
Date: Mon, 25 Oct 2021 13:54:51 UTC
The branch stable/13 has been updated by andrew:

URL: https://cgit.FreeBSD.org/src/commit/?id=f803dd1e24eaa37bb0fd6395fb5312265f94f9f5

commit f803dd1e24eaa37bb0fd6395fb5312265f94f9f5
Author:     Andrew Turner <andrew@FreeBSD.org>
AuthorDate: 2021-09-20 16:49:18 +0000
Commit:     Andrew Turner <andrew@FreeBSD.org>
CommitDate: 2021-10-25 13:46:44 +0000

    Add pmap_change_prot on arm64
    
    Support changing the protection of preloaded kernel modules by
    implementing pmap_change_prot on arm64 and calling it from
    preload_protect.
    
    Reviewed by:    alc (previous version)
    Sponsored by:   The FreeBSD Foundation
    Differential Revision: https://reviews.freebsd.org/D32026
    
    (cherry picked from commit a85ce4ad7272ffa4b4649b0ed463341b743e815f)
---
 sys/arm64/arm64/pmap.c   | 94 +++++++++++++++++++++++++++++++++++++++++-------
 sys/arm64/include/pmap.h |  1 +
 sys/kern/link_elf.c      |  2 +-
 3 files changed, 83 insertions(+), 14 deletions(-)

diff --git a/sys/arm64/arm64/pmap.c b/sys/arm64/arm64/pmap.c
index cdd1ed49fc7d..3fa3a4b62711 100644
--- a/sys/arm64/arm64/pmap.c
+++ b/sys/arm64/arm64/pmap.c
@@ -382,7 +382,8 @@ static pv_entry_t pmap_pvh_remove(struct md_page *pvh, pmap_t pmap,
 static void pmap_abort_ptp(pmap_t pmap, vm_offset_t va, vm_page_t mpte);
 static bool pmap_activate_int(pmap_t pmap);
 static void pmap_alloc_asid(pmap_t pmap);
-static int pmap_change_attr_locked(vm_offset_t va, vm_size_t size, int mode);
+static int pmap_change_props_locked(vm_offset_t va, vm_size_t size,
+    vm_prot_t prot, int mode);
 static pt_entry_t *pmap_demote_l1(pmap_t pmap, pt_entry_t *l1, vm_offset_t va);
 static pt_entry_t *pmap_demote_l2_locked(pmap_t pmap, pt_entry_t *l2,
     vm_offset_t va, struct rwlock **lockp);
@@ -6127,17 +6128,41 @@ pmap_change_attr(vm_offset_t va, vm_size_t size, int mode)
 	int error;
 
 	PMAP_LOCK(kernel_pmap);
-	error = pmap_change_attr_locked(va, size, mode);
+	error = pmap_change_props_locked(va, size, PROT_NONE, mode);
+	PMAP_UNLOCK(kernel_pmap);
+	return (error);
+}
+
+/*
+ * Changes the specified virtual address range's protections to those
+ * specified by "prot".  Like pmap_change_attr(), protections for aliases
+ * in the direct map are updated as well.  Protections on aliasing mappings may
+ * be a subset of the requested protections; for example, mappings in the direct
+ * map are never executable.
+ */
+int
+pmap_change_prot(vm_offset_t va, vm_size_t size, vm_prot_t prot)
+{
+	int error;
+
+	/* Only supported within the kernel map. */
+	if (va < VM_MIN_KERNEL_ADDRESS)
+		return (EINVAL);
+
+	PMAP_LOCK(kernel_pmap);
+	error = pmap_change_props_locked(va, size, prot, -1);
 	PMAP_UNLOCK(kernel_pmap);
 	return (error);
 }
 
 static int
-pmap_change_attr_locked(vm_offset_t va, vm_size_t size, int mode)
+pmap_change_props_locked(vm_offset_t va, vm_size_t size, vm_prot_t prot,
+    int mode)
 {
 	vm_offset_t base, offset, tmpva;
 	pt_entry_t l3, *pte, *newpte;
-	int lvl;
+	pt_entry_t bits, mask;
+	int lvl, rv;
 
 	PMAP_LOCK_ASSERT(kernel_pmap, MA_OWNED);
 	base = trunc_page(va);
@@ -6148,12 +6173,44 @@ pmap_change_attr_locked(vm_offset_t va, vm_size_t size, int mode)
 	    !(base >= VM_MIN_KERNEL_ADDRESS && base < VM_MAX_KERNEL_ADDRESS))
 		return (EINVAL);
 
+	bits = 0;
+	mask = 0;
+	if (mode != -1) {
+		bits = ATTR_S1_IDX(mode);
+		mask = ATTR_S1_IDX_MASK;
+		if (mode == VM_MEMATTR_DEVICE) {
+			mask |= ATTR_S1_XN;
+			bits |= ATTR_S1_XN;
+		}
+	}
+	if (prot != VM_PROT_NONE) {
+		/* Don't mark the DMAP as executable. It never is on arm64. */
+		if (VIRT_IN_DMAP(base)) {
+			prot &= ~VM_PROT_EXECUTE;
+			/*
+			 * XXX Mark the DMAP as writable for now. We rely
+			 * on this in ddb & dtrace to insert breakpoint
+			 * instructions.
+			 */
+			prot |= VM_PROT_WRITE;
+		}
+
+		if ((prot & VM_PROT_WRITE) == 0) {
+			bits |= ATTR_S1_AP(ATTR_S1_AP_RO);
+		}
+		if ((prot & VM_PROT_EXECUTE) == 0) {
+			bits |= ATTR_S1_PXN;
+		}
+		bits |= ATTR_S1_UXN;
+		mask |= ATTR_S1_AP_MASK | ATTR_S1_XN;
+	}
+
 	for (tmpva = base; tmpva < base + size; ) {
 		pte = pmap_pte(kernel_pmap, tmpva, &lvl);
 		if (pte == NULL)
 			return (EINVAL);
 
-		if ((pmap_load(pte) & ATTR_S1_IDX_MASK) == ATTR_S1_IDX(mode)) {
+		if ((pmap_load(pte) & mask) == bits) {
 			/*
 			 * We already have the correct attribute,
 			 * ignore this entry.
@@ -6194,14 +6251,23 @@ pmap_change_attr_locked(vm_offset_t va, vm_size_t size, int mode)
 			case 3:
 				/* Update the entry */
 				l3 = pmap_load(pte);
-				l3 &= ~ATTR_S1_IDX_MASK;
-				l3 |= ATTR_S1_IDX(mode);
-				if (mode == VM_MEMATTR_DEVICE)
-					l3 |= ATTR_S1_XN;
+				l3 &= ~mask;
+				l3 |= bits;
 
 				pmap_update_entry(kernel_pmap, pte, l3, tmpva,
 				    PAGE_SIZE);
 
+				if (!VIRT_IN_DMAP(tmpva)) {
+					/*
+					 * Keep the DMAP memory in sync.
+					 */
+					rv = pmap_change_props_locked(
+					    PHYS_TO_DMAP(l3 & ~ATTR_MASK),
+					    L3_SIZE, prot, mode);
+					if (rv != 0)
+						return (rv);
+				}
+
 				/*
 				 * If moving to a non-cacheable entry flush
 				 * the cache.
@@ -6363,12 +6429,14 @@ pmap_demote_l2_locked(pmap_t pmap, pt_entry_t *l2, vm_offset_t va,
 		/*
 		 * If the page table page is missing and the mapping
 		 * is for a kernel address, the mapping must belong to
-		 * the direct map.  Page table pages are preallocated
-		 * for every other part of the kernel address space,
-		 * so the direct map region is the only part of the
+		 * either the direct map or the early kernel memory.
+		 * Page table pages are preallocated for every other
+		 * part of the kernel address space, so the direct map
+		 * region and early kernel memory are the only parts of the
 		 * kernel address space that must be handled here.
 		 */
-		KASSERT(!ADDR_IS_KERNEL(va) || VIRT_IN_DMAP(va),
+		KASSERT(!ADDR_IS_KERNEL(va) || VIRT_IN_DMAP(va) ||
+		    (va >= VM_MIN_KERNEL_ADDRESS && va < kernel_vm_end),
 		    ("pmap_demote_l2: No saved mpte for va %#lx", va));
 
 		/*
diff --git a/sys/arm64/include/pmap.h b/sys/arm64/include/pmap.h
index da739bb765e8..11a0388ab350 100644
--- a/sys/arm64/include/pmap.h
+++ b/sys/arm64/include/pmap.h
@@ -167,6 +167,7 @@ extern vm_offset_t virtual_end;
 void	pmap_activate_vm(pmap_t);
 void	pmap_bootstrap(vm_offset_t, vm_offset_t, vm_paddr_t, vm_size_t);
 int	pmap_change_attr(vm_offset_t va, vm_size_t size, int mode);
+int	pmap_change_prot(vm_offset_t va, vm_size_t size, vm_prot_t prot);
 void	pmap_kenter(vm_offset_t sva, vm_size_t size, vm_paddr_t pa, int mode);
 void	pmap_kenter_device(vm_offset_t, vm_size_t, vm_paddr_t);
 bool	pmap_klookup(vm_offset_t va, vm_paddr_t *pa);
diff --git a/sys/kern/link_elf.c b/sys/kern/link_elf.c
index ea21bf447a55..2faaa003380a 100644
--- a/sys/kern/link_elf.c
+++ b/sys/kern/link_elf.c
@@ -769,7 +769,7 @@ parse_vnet(elf_file_t ef)
 static int
 preload_protect(elf_file_t ef, vm_prot_t prot)
 {
-#ifdef __amd64__
+#if defined(__aarch64__) || defined(__amd64__)
 	Elf_Ehdr *hdr;
 	Elf_Phdr *phdr, *phlimit;
 	vm_prot_t nprot;