mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH v2 0/2] kernel memory protection for m68k, 030 users too!
@ 2025-11-20 10:38 Daniel Palmer
  2025-11-20 10:38 ` [PATCH v2 1/2] m68k: mm: motorola: Split the early term containing the end of the RO region Daniel Palmer
  2025-11-20 10:38 ` [PATCH v2 2/2] m68k: Implement kernel memory protection Daniel Palmer
  0 siblings, 2 replies; 3+ messages in thread
From: Daniel Palmer @ 2025-11-20 10:38 UTC (permalink / raw)
  To: geert, schmitzmic, gerg, linux-m68k; +Cc: linux-kernel, Daniel Palmer

This implements kernel memory protection for m68k.

Changes since v1:

- Add a patch to split the early termination that the tail of the ro
  data is on 020/030 so that we can get at the juicy pages on the inside.
- Since __ex_table is RO after init we can just mark from the start of
  the kernel to the end of the ro data in one pass so do that.
- While setting the WP bit if we find that we'd be marking too much
  memory, warn and abort.

Daniel Palmer (2):
  m68k: mm: motorola: Split the early term containing the end of the RO
    region
  m68k: Implement kernel memory protection

 arch/m68k/Kconfig       |  1 +
 arch/m68k/mm/init.c     | 73 +++++++++++++++++++++++++++++++++++++++++
 arch/m68k/mm/motorola.c | 47 ++++++++++++++++++++------
 3 files changed, 111 insertions(+), 10 deletions(-)

-- 
2.51.0


^ permalink raw reply	[flat|nested] 3+ messages in thread

* [PATCH v2 1/2] m68k: mm: motorola: Split the early term containing the end of the RO region
  2025-11-20 10:38 [PATCH v2 0/2] kernel memory protection for m68k, 030 users too! Daniel Palmer
@ 2025-11-20 10:38 ` Daniel Palmer
  2025-11-20 10:38 ` [PATCH v2 2/2] m68k: Implement kernel memory protection Daniel Palmer
  1 sibling, 0 replies; 3+ messages in thread
From: Daniel Palmer @ 2025-11-20 10:38 UTC (permalink / raw)
  To: geert, schmitzmic, gerg, linux-m68k; +Cc: linux-kernel, Daniel Palmer

On the 020/030 the end of the RO region (from the start of kernel text
to the end of the RO data) can end up on an "early termination" that
represents 64 pages and is modelled as a pmd in the kernel.

You cannot set flags on individual pages in the pmd as the pte level
doesn't exist in the tables.

This means it's not possible to set the write protect bit for a range of
pages in the pmd and you either need to push RW stuff onto the next pmd
which wastes memory or convert the early termination into a normal pmd
with ptes first.

There is already some logic for splitting the pmd at the start of
memory into ptes so the first page can be unmapped. Refactor that
logic out into a little function and use it for the existing use
case and splitting the pmd that the tail of the RO region is on
so its possible to set the wp bit on the RO pages within.

Signed-off-by: Daniel Palmer <daniel@thingy.jp>
---
 arch/m68k/mm/motorola.c | 47 ++++++++++++++++++++++++++++++++---------
 1 file changed, 37 insertions(+), 10 deletions(-)

diff --git a/arch/m68k/mm/motorola.c b/arch/m68k/mm/motorola.c
index 62283bc2ed79..9b5b9a52f819 100644
--- a/arch/m68k/mm/motorola.c
+++ b/arch/m68k/mm/motorola.c
@@ -298,6 +298,24 @@ static pmd_t * __init kernel_ptr_table(void)
 	return last_pmd_table;
 }
 
+/*
+ * This splits an early term created by head.S into ptes
+ * so things like removing pages in the range, marking
+ * part of the range as write protected can happen.
+ */
+static inline pte_t * __init __split_early_term(unsigned long physaddr)
+{
+	pte_t *pte_dir, *pte_dir_tmp;
+
+	pte_dir = kernel_page_table();
+	pte_dir_tmp = pte_dir;
+
+	for (int i = 0; i < PTRS_PER_PTE; physaddr += PAGE_SIZE, i++)
+		pte_val(*pte_dir_tmp++) = physaddr;
+
+	return pte_dir;
+}
+
 static void __init map_node(int node)
 {
 	unsigned long physaddr, virtaddr, size;
@@ -348,25 +366,34 @@ static void __init map_node(int node)
 
 		if (CPU_IS_020_OR_030) {
 			if (virtaddr) {
+				const unsigned long ro_tail_pmd =
+					((unsigned long) __end_rodata) & PMD_MASK;
+
+				if (virtaddr == ro_tail_pmd) {
+#ifdef DEBUG
+					printk("[wp split]\n");
+#endif
+					pte_dir = __split_early_term(physaddr);
+					pmd_set(pmd_dir, pte_dir);
+}
+				else {
 #ifdef DEBUG
-				printk ("[early term]");
+					printk("[early term]");
 #endif
-				pmd_val(*pmd_dir) = physaddr;
-				physaddr += PMD_SIZE;
+					pmd_val(*pmd_dir) = physaddr;
+				}
 			} else {
-				int i;
 #ifdef DEBUG
 				printk ("[zero map]");
 #endif
-				pte_dir = kernel_page_table();
+				pte_dir = __split_early_term(physaddr);
+				/* Remove the zero page */
+				pte_val(*pte_dir) = 0;
 				pmd_set(pmd_dir, pte_dir);
-
-				pte_val(*pte_dir++) = 0;
-				physaddr += PAGE_SIZE;
-				for (i = 1; i < PTRS_PER_PTE; physaddr += PAGE_SIZE, i++)
-					pte_val(*pte_dir++) = physaddr;
 			}
+
 			size -= PMD_SIZE;
+			physaddr += PMD_SIZE;
 			virtaddr += PMD_SIZE;
 		} else {
 			if (!pmd_present(*pmd_dir)) {
-- 
2.51.0


^ permalink raw reply	[flat|nested] 3+ messages in thread

* [PATCH v2 2/2] m68k: Implement kernel memory protection
  2025-11-20 10:38 [PATCH v2 0/2] kernel memory protection for m68k, 030 users too! Daniel Palmer
  2025-11-20 10:38 ` [PATCH v2 1/2] m68k: mm: motorola: Split the early term containing the end of the RO region Daniel Palmer
@ 2025-11-20 10:38 ` Daniel Palmer
  1 sibling, 0 replies; 3+ messages in thread
From: Daniel Palmer @ 2025-11-20 10:38 UTC (permalink / raw)
  To: geert, schmitzmic, gerg, linux-m68k; +Cc: linux-kernel, Daniel Palmer

Every time I boot linux on my various m68k machines I see
"This architecture does not have kernel memory protection."

I wondered why this was as some of my machines even have one of
those fancy MMU doodads. I worked out it was because we don't have
CONFIG_ARCH_HAS_STRICT_KERNEL_RWX, found kernel_set_cachemode()
seemed like it had the code for setting some extra flags for
kernel pages and turned that into something that sets write
protect for kernel pages.

So now we can make CONFIG_ARCH_HAS_STRICT_KERNEL_RWX=y and
provide mark_rodata_ro() to mark the kernel text and rodata
as write protected.

The test enabled by CONFIG_DEBUG_RODATA_TEST=y says this is
working.

Tested on Amiga 4000 (060), virt (040), MVME147 (030)

Signed-off-by: Daniel Palmer <daniel@thingy.jp>
---
 arch/m68k/Kconfig   |  1 +
 arch/m68k/mm/init.c | 73 +++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 74 insertions(+)

diff --git a/arch/m68k/Kconfig b/arch/m68k/Kconfig
index 11835eb59d94..2137fd19ffbd 100644
--- a/arch/m68k/Kconfig
+++ b/arch/m68k/Kconfig
@@ -8,6 +8,7 @@ config M68K
 	select ARCH_HAS_CPU_FINALIZE_INIT if MMU
 	select ARCH_HAS_CURRENT_STACK_POINTER
 	select ARCH_HAS_DMA_PREP_COHERENT if M68K_NONCOHERENT_DMA && !COLDFIRE
+	select ARCH_HAS_STRICT_KERNEL_RWX if MMU
 	select ARCH_HAS_SYNC_DMA_FOR_DEVICE if M68K_NONCOHERENT_DMA
 	select ARCH_HAVE_NMI_SAFE_CMPXCHG if RMW_INSNS
 	select ARCH_MIGHT_HAVE_PC_PARPORT if ISA
diff --git a/arch/m68k/mm/init.c b/arch/m68k/mm/init.c
index 488411af1b3f..f11597751741 100644
--- a/arch/m68k/mm/init.c
+++ b/arch/m68k/mm/init.c
@@ -123,3 +123,76 @@ void __init mem_init(void)
 {
 	init_pointer_tables();
 }
+
+#ifdef CONFIG_MMU
+/*
+ * Based on (basically copy/pasted) kernel_set_cachemode() because
+ * presumably that is correct and covers the required differences.
+ */
+static void __mark_ro_data(unsigned long virtaddr, ssize_t size)
+{
+	pgd_t *pgd_dir;
+	p4d_t *p4d_dir;
+	pud_t *pud_dir;
+	pmd_t *pmd_dir;
+	pte_t *pte_dir;
+
+	while (size > 0) {
+		pgd_dir = pgd_offset_k(virtaddr);
+		p4d_dir = p4d_offset(pgd_dir, virtaddr);
+		pud_dir = pud_offset(p4d_dir, virtaddr);
+		if (pud_bad(*pud_dir)) {
+			pud_clear(pud_dir);
+			return;
+		}
+		pmd_dir = pmd_offset(pud_dir, virtaddr);
+
+#if CONFIG_PGTABLE_LEVELS == 3
+		if (CPU_IS_020_OR_030) {
+			unsigned long pmd = pmd_val(*pmd_dir);
+
+			if ((pmd & _DESCTYPE_MASK) == _PAGE_PRESENT) {
+				/* We cannot write protect part of an early term */
+				if (WARN_ON(size < PMD_SIZE))
+					break;
+
+				*pmd_dir = __pmd(pmd | _PAGE_RONLY);
+				virtaddr += PMD_SIZE;
+				size -= PMD_SIZE;
+				continue;
+			}
+		}
+#endif
+
+		if (pmd_bad(*pmd_dir)) {
+			pmd_clear(pmd_dir);
+			return;
+		}
+
+		/* We cannot write protect part of a page */
+		if (WARN_ON(size < PAGE_SIZE))
+			break;
+
+		pte_dir = pte_offset_kernel(pmd_dir, virtaddr);
+
+		set_pte(pte_dir, pte_wrprotect(*pte_dir));
+		virtaddr += PAGE_SIZE;
+		size -= PAGE_SIZE;
+	}
+}
+
+void mark_rodata_ro(void)
+{
+	unsigned long start;
+	unsigned long end;
+
+	/* kernel text + 1 page , kernel_pg_dir lives in the first page, so skip that */
+	start = (unsigned long) _stext + PAGE_SIZE;
+	end = (unsigned long) __end_rodata;
+
+	pr_info("Write protecting kernel text and read-only data: 0x%lx - 0x%lx\n", start, end);
+	__mark_ro_data(start, end - start);
+
+	flush_tlb_all();
+}
+#endif
-- 
2.51.0


^ permalink raw reply	[flat|nested] 3+ messages in thread

end of thread, other threads:[~2025-11-20 10:39 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2025-11-20 10:38 [PATCH v2 0/2] kernel memory protection for m68k, 030 users too! Daniel Palmer
2025-11-20 10:38 ` [PATCH v2 1/2] m68k: mm: motorola: Split the early term containing the end of the RO region Daniel Palmer
2025-11-20 10:38 ` [PATCH v2 2/2] m68k: Implement kernel memory protection Daniel Palmer

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®