The Linux Kernel Mailing List
 help / color / mirror / Atom feed
* [PATCH 0/2] Add support to dump the kernel page tables
@ 2026-07-07  6:45 haoran.jiang
  2026-07-07  6:45 ` [PATCH 1/2] LoongArch: Enforce W^X for page-mapped virtual memory region haoran.jiang
  2026-07-07  6:45 ` [PATCH 2/2] LoongArch: Add support to dump the kernel page tables haoran.jiang
  0 siblings, 2 replies; 3+ messages in thread
From: haoran.jiang @ 2026-07-07  6:45 UTC (permalink / raw)
  To: loongarch; +Cc: linux-kernel, chenhuacai, kernel, yangtiezhu, Haoran Jiang

From: Haoran Jiang <jianghaoran@kylinos.cn>

Enforce the W^X principle on page tables, 
and also support the kernel page dump feature.

The following tests were performed:

kdb:
echo kgdbts=V1 > /sys/module/kgdbts/parameters/kgdbts

ebpf:
./test_progs -t struct_ops -d struct_ops_multi_pages
./test_progs -t fexit_stress
./test_progs -t module_fentry_shadow
./test_progs -t fentry_test/fentry
./test_progs -t fexit_test/fexit
./test_progs -t modify_return
./test_progs -t fexit_sleep
./test_progs -t test_overhead
./test_progs -t trampoline_count
./test_progs -t kprobe
./test_progs -t uprobe

livepatch:
insmod samples/livepatch/livepatch_sample.ko

kprobe:
insmod samples/kprobes/kprobe_example.ko
insmod samples/kprobes/kretprobe_example.ko

unxibench:
before this patch:
running 1 parallel copy of tests: 1492
running 128 parallel copies of tests:  8765

after this patch:
running 1 parallel copy of tests:  1440
running 128 parallel copies of tests:  8749


Haoran Jiang (2):
  LoongArch: Enforce W^X for page-mapped virtual memory region
  LoongArch: Add support to dump the kernel page tables

 arch/loongarch/Kconfig                    |   2 +
 arch/loongarch/include/asm/pgtable-bits.h |  12 +-
 arch/loongarch/kernel/ftrace_dyn.c        |  16 +
 arch/loongarch/kernel/inst.c              |  25 +-
 arch/loongarch/kernel/jump_label.c        |   3 +
 arch/loongarch/kernel/kgdb.c              |  50 +++
 arch/loongarch/kernel/kprobes.c           |   4 +-
 arch/loongarch/mm/Makefile                |   1 +
 arch/loongarch/mm/ptdump.c                | 474 ++++++++++++++++++++++
 9 files changed, 575 insertions(+), 12 deletions(-)
 create mode 100644 arch/loongarch/mm/ptdump.c

-- 
2.43.0


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

* [PATCH 1/2] LoongArch: Enforce W^X for page-mapped virtual memory region
  2026-07-07  6:45 [PATCH 0/2] Add support to dump the kernel page tables haoran.jiang
@ 2026-07-07  6:45 ` haoran.jiang
  2026-07-07  6:45 ` [PATCH 2/2] LoongArch: Add support to dump the kernel page tables haoran.jiang
  1 sibling, 0 replies; 3+ messages in thread
From: haoran.jiang @ 2026-07-07  6:45 UTC (permalink / raw)
  To: loongarch; +Cc: linux-kernel, chenhuacai, kernel, yangtiezhu, Haoran Jiang

From: Haoran Jiang <jianghaoran@kylinos.cn>

Add the non-executable permission flag `_PAGE_NO_EXEC` to
the default attributes of kernel page tables, so that page
table permissions comply with the W^X (Write XOR Execute)
principle by default. Also enable `STRICT_MODULE_RWX` to
enforce strict memory permissions on the module region,
making the code region non‑writable, the data region non‑executable,
and the read‑only data region both non‑writable and non‑executable.
When code section modifications are needed, the `set_memory_xx()`
API is used to temporarily alter the read/write permissions
of the code section.

Signed-off-by: Haoran Jiang <jianghaoran@kylinos.cn>
---
 arch/loongarch/Kconfig                    |  1 +
 arch/loongarch/include/asm/pgtable-bits.h | 12 +++---
 arch/loongarch/kernel/ftrace_dyn.c        | 16 ++++++++
 arch/loongarch/kernel/inst.c              | 25 ++++++++++--
 arch/loongarch/kernel/jump_label.c        |  3 ++
 arch/loongarch/kernel/kgdb.c              | 50 +++++++++++++++++++++++
 arch/loongarch/kernel/kprobes.c           |  4 +-
 7 files changed, 99 insertions(+), 12 deletions(-)

diff --git a/arch/loongarch/Kconfig b/arch/loongarch/Kconfig
index bec7cc1d72ee..2c5de66bf047 100644
--- a/arch/loongarch/Kconfig
+++ b/arch/loongarch/Kconfig
@@ -27,6 +27,7 @@ config LOONGARCH
 	select ARCH_HAS_PTE_SPECIAL if 64BIT
 	select ARCH_HAS_SET_MEMORY
 	select ARCH_HAS_SET_DIRECT_MAP
+	select ARCH_HAS_STRICT_MODULE_RWX
 	select ARCH_HAS_TICK_BROADCAST if GENERIC_CLOCKEVENTS_BROADCAST
 	select ARCH_HAS_UBSAN
 	select ARCH_HAS_VDSO_ARCH_DATA
diff --git a/arch/loongarch/include/asm/pgtable-bits.h b/arch/loongarch/include/asm/pgtable-bits.h
index b565573cd82e..8ad5dccb312e 100644
--- a/arch/loongarch/include/asm/pgtable-bits.h
+++ b/arch/loongarch/include/asm/pgtable-bits.h
@@ -110,17 +110,17 @@
 #define _HPAGE_CHG_MASK	(_PAGE_MODIFIED | _PAGE_SPECIAL | _PFN_MASK | _CACHE_MASK | _PAGE_PLV | _PAGE_HUGE)
 
 #define PAGE_NONE	__pgprot(_PAGE_PROTNONE | _PAGE_NO_READ | \
-				 _PAGE_USER | _CACHE_CC)
+				 _PAGE_USER | _CACHE_CC | _PAGE_NO_EXEC)
 #define PAGE_SHARED	__pgprot(_PAGE_PRESENT | _PAGE_WRITE | \
-				 _PAGE_USER | _CACHE_CC)
-#define PAGE_READONLY	__pgprot(_PAGE_PRESENT | _PAGE_USER | _CACHE_CC)
+				 _PAGE_USER | _CACHE_CC | _PAGE_NO_EXEC)
+#define PAGE_READONLY	__pgprot(_PAGE_PRESENT | _PAGE_USER | _CACHE_CC | _PAGE_NO_EXEC)
 
 #define PAGE_KERNEL	__pgprot(_PAGE_PRESENT | __READABLE | __WRITEABLE | \
-				 _PAGE_GLOBAL | _PAGE_KERN | _CACHE_CC)
+				 _PAGE_GLOBAL | _PAGE_KERN | _CACHE_CC | _PAGE_NO_EXEC)
 #define PAGE_KERNEL_SUC __pgprot(_PAGE_PRESENT | __READABLE | __WRITEABLE | \
-				 _PAGE_GLOBAL | _PAGE_KERN |  _CACHE_SUC)
+				 _PAGE_GLOBAL | _PAGE_KERN |  _CACHE_SUC | _PAGE_NO_EXEC)
 #define PAGE_KERNEL_WUC __pgprot(_PAGE_PRESENT | __READABLE | __WRITEABLE | \
-				 _PAGE_GLOBAL | _PAGE_KERN |  _CACHE_WUC)
+				 _PAGE_GLOBAL | _PAGE_KERN |  _CACHE_WUC | _PAGE_NO_EXEC)
 
 #ifndef __ASSEMBLER__
 
diff --git a/arch/loongarch/kernel/ftrace_dyn.c b/arch/loongarch/kernel/ftrace_dyn.c
index d5d81d74034c..d1d8c4c2da75 100644
--- a/arch/loongarch/kernel/ftrace_dyn.c
+++ b/arch/loongarch/kernel/ftrace_dyn.c
@@ -8,10 +8,24 @@
 #include <linux/ftrace.h>
 #include <linux/kprobes.h>
 #include <linux/uaccess.h>
+#include <linux/memory.h>
 
 #include <asm/inst.h>
 #include <asm/module.h>
 
+
+void ftrace_arch_code_modify_prepare(void)
+	__acquires(&text_mutex)
+{
+	mutex_lock(&text_mutex);
+}
+
+void ftrace_arch_code_modify_post_process(void)
+	__releases(&text_mutex)
+{
+	mutex_unlock(&text_mutex);
+}
+
 static int ftrace_modify_code(unsigned long pc, u32 old, u32 new, bool validate)
 {
 	u32 replaced;
@@ -171,6 +185,8 @@ int ftrace_init_nop(struct module *mod, struct dyn_ftrace *rec)
 	u32 old, new;
 	unsigned long pc;
 
+	guard(mutex)(&text_mutex);
+
 	pc = rec->ip;
 	old = larch_insn_gen_nop();
 	new = larch_insn_gen_move(LOONGARCH_GPR_T0, LOONGARCH_GPR_RA);
diff --git a/arch/loongarch/kernel/inst.c b/arch/loongarch/kernel/inst.c
index 0b9228b7c13a..3de94d465c3c 100644
--- a/arch/loongarch/kernel/inst.c
+++ b/arch/loongarch/kernel/inst.c
@@ -6,12 +6,11 @@
 #include <linux/uaccess.h>
 #include <linux/set_memory.h>
 #include <linux/stop_machine.h>
+#include <linux/memory.h>
 
 #include <asm/cacheflush.h>
 #include <asm/inst.h>
 
-static DEFINE_RAW_SPINLOCK(patch_lock);
-
 void simu_pc(struct pt_regs *regs, union loongarch_instruction insn)
 {
 	unsigned long pc = regs->csr_era;
@@ -207,14 +206,32 @@ int larch_insn_read(void *addr, u32 *insnp)
 int larch_insn_write(void *addr, u32 insn)
 {
 	int ret;
+	int err = 0;
+	size_t start;
 	unsigned long flags = 0;
 
 	if ((unsigned long)addr & 3)
 		return -EINVAL;
 
-	raw_spin_lock_irqsave(&patch_lock, flags);
+	start = round_down((size_t)addr, PAGE_SIZE);
+
+	lockdep_assert_held(&text_mutex);
+
+	err = set_memory_rw(start, 1);
+	if (err) {
+		pr_info("%s: set_memory_rw() failed\n", __func__);
+		return err;
+	}
+
+	local_irq_save(flags);
 	ret = copy_to_kernel_nofault(addr, &insn, LOONGARCH_INSN_SIZE);
-	raw_spin_unlock_irqrestore(&patch_lock, flags);
+	local_irq_restore(flags);
+
+	err = set_memory_rox(start, 1);
+	if (err) {
+		pr_info("%s: set_memory_rox() failed\n", __func__);
+		return err;
+	}
 
 	return ret;
 }
diff --git a/arch/loongarch/kernel/jump_label.c b/arch/loongarch/kernel/jump_label.c
index 24a3f4d8540c..e6bb040fe4c5 100644
--- a/arch/loongarch/kernel/jump_label.c
+++ b/arch/loongarch/kernel/jump_label.c
@@ -6,6 +6,7 @@
  */
 #include <linux/kernel.h>
 #include <linux/jump_label.h>
+#include <linux/memory.h>
 #include <asm/cacheflush.h>
 #include <asm/inst.h>
 
@@ -19,7 +20,9 @@ bool arch_jump_label_transform_queue(struct jump_entry *entry, enum jump_label_t
 	else
 		insn = larch_insn_gen_nop();
 
+	mutex_lock(&text_mutex);
 	larch_insn_write(addr, insn);
+	mutex_unlock(&text_mutex);
 
 	return true;
 }
diff --git a/arch/loongarch/kernel/kgdb.c b/arch/loongarch/kernel/kgdb.c
index 17664a6043b1..cacaff338f90 100644
--- a/arch/loongarch/kernel/kgdb.c
+++ b/arch/loongarch/kernel/kgdb.c
@@ -21,6 +21,7 @@
 #include <asm/irq_regs.h>
 #include <asm/ptrace.h>
 #include <asm/sigcontext.h>
+#include <asm/tlbflush.h>
 
 int kgdb_watch_activated;
 static unsigned int stepped_opcode;
@@ -233,6 +234,51 @@ noinline void arch_kgdb_breakpoint(void)
 }
 STACK_FRAME_NON_STANDARD(arch_kgdb_breakpoint);
 
+static inline bool kgdb_set_page_rwx(unsigned long addr, bool protect)
+{
+	pte_t *pte = virt_to_kpte(addr);
+
+	if (WARN_ON(!pte) || pte_none(ptep_get(pte)))
+		return false;
+
+	if (protect)
+		set_pte(pte, __pte(pte_val(ptep_get(pte)) & ~(_PAGE_WRITE | _PAGE_DIRTY)));
+	else
+		set_pte(pte, __pte(pte_val(ptep_get(pte)) | (_PAGE_WRITE | _PAGE_DIRTY)));
+
+	local_flush_tlb_one(addr);
+	return true;
+}
+
+int kgdb_arch_set_breakpoint(struct kgdb_bkpt *bpt)
+{
+	int err;
+
+	err = copy_from_kernel_nofault(bpt->saved_instr, (char *)bpt->bpt_addr,
+					BREAK_INSTR_SIZE);
+	if (err)
+		return err;
+
+	kgdb_set_page_rwx(bpt->bpt_addr, 0);
+	err = copy_to_kernel_nofault((char *)bpt->bpt_addr,
+					arch_kgdb_ops.gdb_bpt_instr, BREAK_INSTR_SIZE);
+	kgdb_set_page_rwx(bpt->bpt_addr, 1);
+	return err;
+}
+
+int kgdb_arch_remove_breakpoint(struct kgdb_bkpt *bpt)
+{
+	int err;
+
+	kgdb_set_page_rwx(bpt->bpt_addr, 0);
+	err = copy_to_kernel_nofault((char *)bpt->bpt_addr,
+					(char *)bpt->saved_instr, BREAK_INSTR_SIZE);
+	kgdb_set_page_rwx(bpt->bpt_addr, 1);
+
+	return err;
+}
+
+
 /*
  * Calls linux_debug_hook before the kernel dies. If KGDB is enabled,
  * then try to fall into the debugger
@@ -393,9 +439,11 @@ static int do_single_step(struct pt_regs *regs)
 
 	stepped_address = addr;
 
+	kgdb_set_page_rwx(stepped_address, 0);
 	/* Replace the opcode with the break instruction */
 	error = copy_to_kernel_nofault((void *)stepped_address,
 				       arch_kgdb_ops.gdb_bpt_instr, BREAK_INSTR_SIZE);
+	kgdb_set_page_rwx(stepped_address, 1);
 	flush_icache_range(addr, addr + BREAK_INSTR_SIZE);
 
 	if (error) {
@@ -413,8 +461,10 @@ static int do_single_step(struct pt_regs *regs)
 static void undo_single_step(struct pt_regs *regs)
 {
 	if (stepped_opcode) {
+		kgdb_set_page_rwx(stepped_address, 0);
 		copy_to_kernel_nofault((void *)stepped_address,
 				       (void *)&stepped_opcode, BREAK_INSTR_SIZE);
+		kgdb_set_page_rwx(stepped_address, 1);
 		flush_icache_range(stepped_address, stepped_address + BREAK_INSTR_SIZE);
 	}
 
diff --git a/arch/loongarch/kernel/kprobes.c b/arch/loongarch/kernel/kprobes.c
index 1985ed30dd16..cb3565178bb4 100644
--- a/arch/loongarch/kernel/kprobes.c
+++ b/arch/loongarch/kernel/kprobes.c
@@ -12,8 +12,8 @@ DEFINE_PER_CPU(struct kprobe_ctlblk, kprobe_ctlblk);
 
 static void arch_prepare_ss_slot(struct kprobe *p)
 {
-	p->ainsn.insn[0] = *p->addr;
-	p->ainsn.insn[1] = KPROBE_SSTEPBP_INSN;
+	larch_insn_patch_text(p->ainsn.insn, *p->addr);
+	larch_insn_patch_text(p->ainsn.insn + 1, KPROBE_SSTEPBP_INSN);
 	p->ainsn.restore = (unsigned long)p->addr + LOONGARCH_INSN_SIZE;
 }
 NOKPROBE_SYMBOL(arch_prepare_ss_slot);
-- 
2.43.0


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

* [PATCH 2/2] LoongArch: Add support to dump the kernel page tables
  2026-07-07  6:45 [PATCH 0/2] Add support to dump the kernel page tables haoran.jiang
  2026-07-07  6:45 ` [PATCH 1/2] LoongArch: Enforce W^X for page-mapped virtual memory region haoran.jiang
@ 2026-07-07  6:45 ` haoran.jiang
  1 sibling, 0 replies; 3+ messages in thread
From: haoran.jiang @ 2026-07-07  6:45 UTC (permalink / raw)
  To: loongarch
  Cc: linux-kernel, chenhuacai, kernel, yangtiezhu, Haoran Jiang,
	Xiujie Jiang

From: Haoran Jiang <jianghaoran@kylinos.cn>

In a similar manner to riscv,arm64,x86 etc.,this patch allows
dumping the page tables of the LoongArch page-mapped virtual memory
region via a debugfs file, which is useful for kernel developers to
inspect page table layouts and verify permissions and type settings.

Co-developed-by: Xiujie Jiang <jiangxiujie@kylinos.cn>
Signed-off-by: Xiujie Jiang <jiangxiujie@kylinos.cn>
Signed-off-by: Haoran Jiang <jianghaoran@kylinos.cn>
---
 arch/loongarch/Kconfig     |   1 +
 arch/loongarch/mm/Makefile |   1 +
 arch/loongarch/mm/ptdump.c | 474 +++++++++++++++++++++++++++++++++++++
 3 files changed, 476 insertions(+)
 create mode 100644 arch/loongarch/mm/ptdump.c

diff --git a/arch/loongarch/Kconfig b/arch/loongarch/Kconfig
index 2c5de66bf047..632998c64a74 100644
--- a/arch/loongarch/Kconfig
+++ b/arch/loongarch/Kconfig
@@ -25,6 +25,7 @@ config LOONGARCH
 	select ARCH_HAS_NON_OVERLAPPING_ADDRESS_SPACE
 	select ARCH_HAS_PREEMPT_LAZY
 	select ARCH_HAS_PTE_SPECIAL if 64BIT
+	select ARCH_HAS_PTDUMP
 	select ARCH_HAS_SET_MEMORY
 	select ARCH_HAS_SET_DIRECT_MAP
 	select ARCH_HAS_STRICT_MODULE_RWX
diff --git a/arch/loongarch/mm/Makefile b/arch/loongarch/mm/Makefile
index 2aae3773de77..0be8ee87fc5b 100644
--- a/arch/loongarch/mm/Makefile
+++ b/arch/loongarch/mm/Makefile
@@ -10,5 +10,6 @@ obj-y				+= init.o cache.o tlb.o tlbex.o extable.o \
 obj-$(CONFIG_HIGHMEM)		+= highmem.o
 obj-$(CONFIG_HUGETLB_PAGE)	+= hugetlbpage.o
 obj-$(CONFIG_KASAN)		+= kasan_init.o
+obj-$(CONFIG_PTDUMP_DEBUGFS)            += ptdump.o
 
 KASAN_SANITIZE_kasan_init.o     := n
diff --git a/arch/loongarch/mm/ptdump.c b/arch/loongarch/mm/ptdump.c
new file mode 100644
index 000000000000..003ed1d55615
--- /dev/null
+++ b/arch/loongarch/mm/ptdump.c
@@ -0,0 +1,474 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Derived from riscv implementation
+ */
+
+#include <linux/init.h>
+#include <linux/debugfs.h>
+#include <linux/seq_file.h>
+#include <linux/ptdump.h>
+#include <linux/pgtable.h>
+#include <asm/kasan.h>
+
+#define _PAGE_PLV_VAL 0x0
+
+#define pt_dump_seq_printf(m, fmt, args...)	\
+({						\
+	if (m)					\
+		seq_printf(m, fmt, ##args);	\
+})
+
+#define pt_dump_seq_puts(m, fmt)	\
+({					\
+	if (m)				\
+		seq_puts(m, fmt);	\
+})
+
+/*
+ * The page dumper groups page table entries of the same type into a single
+ * description. It uses pg_state to track the range information while
+ * iterating over the pte entries. When the continuity is broken it then
+ * dumps out a description of the range.
+ */
+struct pg_state {
+	struct ptdump_state ptdump;
+	struct seq_file *seq;
+	const struct addr_marker *marker;
+	unsigned long start_address;
+	unsigned long start_pa;
+	unsigned long last_pa;
+	int level;
+	u64 current_prot;
+	bool check_wx;
+	unsigned long wx_pages;
+};
+
+/* Address marker */
+struct addr_marker {
+	unsigned long start_address;
+	const char *name;
+};
+
+/* Private information for debugfs */
+struct ptd_mm_info {
+	struct mm_struct                *mm;
+	const struct addr_marker        *markers;
+	unsigned long base_addr;
+	unsigned long end;
+};
+
+/* Page Table Entry */
+struct prot_bits {
+	u64 mask;
+	u64 val;
+	const char *set;
+	const char *clear;
+};
+
+/* Page Level */
+struct pg_level {
+	const char *name;
+	u64 mask;
+};
+
+enum address_markers_idx {
+	PCI_IO_START_NR,
+	PCI_IO_END_NR,
+	MODULES_START_NR,
+	MODULES_END_NR,
+	VMALLOC_START_NR,
+	VMALLOC_END_NR,
+#ifdef CONFIG_SPARSEMEM_VMEMMAP
+	VMEMMAP_START_NR,
+	VMEMMAP_END_NR,
+#endif
+#ifdef CONFIG_KFENCE
+	KFENCE_AREA_START_NR,
+	KFENCE_AREA_END_NR,
+#endif
+#ifdef CONFIG_KASAN
+	KASAN_SHADOW_START_NR,
+	KASAN_SHADOW_END_NR,
+#endif
+	FIXMAP_START_NR,
+	FIXMAP_END_NR,
+	END_OF_SPACE_NR
+};
+
+
+
+static struct addr_marker address_markers[] = {
+	{0, "PCI I/O start"},
+	{0, "PCI I/O end"},
+	{0, "modules start"},
+	{0, "modules end"},
+	{0, "vmalloc() area"},
+	{0, "vmalloc() end"},
+#ifdef CONFIG_SPARSEMEM_VMEMMAP
+	{0, "vmemmap start"},
+	{0, "vmemmap end"},
+#endif
+#ifdef CONFIG_KFENCE
+	{0, "kfence area start"},
+	{0, "kfence area end"},
+#endif
+#ifdef CONFIG_KASAN
+	{0, "Kasan shadow start"},
+	{0, "Kasan shadow end"},
+#endif
+	{0, "Fixmap start"},
+	{0, "Fixmap end"},
+	{-1, NULL},
+};
+
+
+static struct ptd_mm_info kernel_ptd_info = {
+	.mm		= &init_mm,
+	.markers	= address_markers,
+	.base_addr	= 0,
+	.end		= ULONG_MAX,
+};
+
+static const struct prot_bits pte_bits[] = {
+	{
+		.mask = _PAGE_VALID,
+		.val = _PAGE_VALID,
+		.set = "V",
+		.clear = " ",
+	}, {
+		.mask = _PAGE_DIRTY,
+		.val = _PAGE_DIRTY,
+		.set = "D",
+		.clear = " ",
+	}, {
+		.mask = _PAGE_PLV,
+		.val = _PAGE_PLV_VAL,
+		.set = "KERN",
+		.clear = "USR ",
+	}, {
+		.mask = _CACHE_MASK,
+		.val = _CACHE_MASK,
+		.set = "  ",
+		.clear = "SUC",
+	}, {
+		.mask = _PAGE_GLOBAL,
+		.val = _PAGE_GLOBAL,
+		.set = "G",
+		.clear = " ",
+	}, {
+		.mask = _PAGE_PRESENT,
+		.val = _PAGE_PRESENT,
+		.set = "P",
+		.clear = " ",
+	}, {
+		.mask = _PAGE_WRITE,
+		.val = _PAGE_WRITE,
+		.set = "W",
+		.clear = " ",
+	}, {
+		.mask = _PAGE_HGLOBAL,
+		.val = _PAGE_HGLOBAL,
+		.set = "G",
+		.clear = "  ",
+	}, {
+		.mask = _PAGE_NO_READ,
+		.val = _PAGE_NO_READ,
+		.set = "NR",
+		.clear = "  ",
+	}, {
+		.mask = _PAGE_NO_EXEC,
+		.val = _PAGE_NO_EXEC,
+		.set = "NX",
+		.clear = "  ",
+	}, {
+		.mask = _PAGE_RPLV,
+		.val = _PAGE_RPLV,
+		.set = "RPLV",
+		.clear = "    ",
+	}
+};
+
+static struct pg_level pg_level[] = {
+	{ /* pgd */
+		.name = "PGD",
+	}, { /* p4d */
+		.name = (CONFIG_PGTABLE_LEVELS > 4) ? "P4D" : "PGD",
+	}, { /* pud */
+		.name = (CONFIG_PGTABLE_LEVELS > 3) ? "PUD" : "PGD",
+	}, { /* pmd */
+		.name = (CONFIG_PGTABLE_LEVELS > 2) ? "PMD" : "PGD",
+	}, { /* pte */
+		.name = "PTE",
+	},
+};
+
+static void dump_prot(struct pg_state *st)
+{
+
+	unsigned int i;
+
+	for (i = 0; i < ARRAY_SIZE(pte_bits); i++) {
+		const char *s;
+
+		if (pte_bits[i].mask == _CACHE_MASK) {
+			if ((st->current_prot & pte_bits[i].mask) == _CACHE_CC)
+				s = "CC ";
+			else if ((st->current_prot & pte_bits[i].mask) == _CACHE_WUC)
+				s = "WUC";
+			else
+				s = pte_bits[i].clear;
+		} else if (pte_bits[i].mask == _PAGE_GLOBAL) {
+			if ((st->current_prot & pte_bits[i].mask) == pte_bits[i].val) {
+				if (st->level != 4)
+					s = "PSE";
+				else
+					s = pte_bits[i].set;
+			} else {
+				s = pte_bits[i].clear;
+			}
+
+		} else {
+			if ((st->current_prot & pte_bits[i].mask) == pte_bits[i].val)
+				s = pte_bits[i].set;
+			else
+				s = pte_bits[i].clear;
+		}
+
+
+		if (s)
+			pt_dump_seq_printf(st->seq, " %s", s);
+	}
+
+}
+
+
+#ifdef CONFIG_64BIT
+#define ADDR_FORMAT	"0x%016lx"
+#else
+#define ADDR_FORMAT	"0x%08lx"
+#endif
+static void dump_addr(struct pg_state *st, unsigned long addr)
+{
+	static const char units[] = "KMGTPE";
+	const char *unit = units;
+	unsigned long delta;
+
+	pt_dump_seq_printf(st->seq, ADDR_FORMAT "-" ADDR_FORMAT "   ",
+			   st->start_address, addr);
+
+	pt_dump_seq_printf(st->seq, " " ADDR_FORMAT " ", st->start_pa);
+	delta = (addr - st->start_address) >> 10;
+
+	while (!(delta & 1023) && unit[1]) {
+		delta >>= 10;
+		unit++;
+	}
+
+	pt_dump_seq_printf(st->seq, "%9lu%c %s", delta, *unit,
+			   pg_level[st->level].name);
+}
+
+static void note_prot_wx(struct pg_state *st, unsigned long addr)
+{
+	if (!st->check_wx)
+		return;
+	if ((st->current_prot & _PAGE_WRITE) == 0)
+		return;
+	if ((st->current_prot & _PAGE_NO_EXEC) == _PAGE_NO_EXEC)
+		return;
+
+	WARN_ONCE(1, "loongarch/mm: Found insecure W+X mapping at address %p/%pS\n",
+			(void *)st->start_address, (void *)st->start_address);
+
+	st->wx_pages += (addr - st->start_address) / PAGE_SIZE;
+
+}
+
+static void note_page(struct ptdump_state *pt_st, unsigned long addr,
+		      int level, u64 val)
+{
+	struct pg_state *st = container_of(pt_st, struct pg_state, ptdump);
+	u64 pa = PFN_PHYS(pte_pfn(__pte(val)));
+	u64 prot = 0;
+
+	if (level >= 0)
+		prot = val & pg_level[level].mask;
+
+	if (st->level == -1) {
+		st->level = level;
+		st->current_prot = prot;
+		st->start_address = addr;
+		st->start_pa = pa;
+		st->last_pa = pa;
+		pt_dump_seq_printf(st->seq, "---[ %s ]---\n", st->marker->name);
+	} else if (prot != st->current_prot ||
+		   level != st->level || addr >= st->marker[1].start_address) {
+		if (st->current_prot) {
+			note_prot_wx(st, addr);
+			dump_addr(st, addr);
+			dump_prot(st);
+			pt_dump_seq_puts(st->seq, "\n");
+		}
+
+		while (addr >= st->marker[1].start_address) {
+			st->marker++;
+			pt_dump_seq_printf(st->seq, "---[ %s ]---\n",
+					   st->marker->name);
+		}
+
+		st->start_address = addr;
+		st->start_pa = pa;
+		st->last_pa = pa;
+		st->current_prot = prot;
+		st->level = level;
+	} else {
+		st->last_pa = pa;
+	}
+}
+
+static void note_page_pte(struct ptdump_state *pt_st, unsigned long addr, pte_t pte)
+{
+	note_page(pt_st, addr, 4, pte_val(pte));
+}
+
+static void note_page_pmd(struct ptdump_state *pt_st, unsigned long addr, pmd_t pmd)
+{
+	note_page(pt_st, addr, 3, pmd_val(pmd));
+}
+
+static void note_page_pud(struct ptdump_state *pt_st, unsigned long addr, pud_t pud)
+{
+	note_page(pt_st, addr, 2, pud_val(pud));
+}
+
+static void note_page_p4d(struct ptdump_state *pt_st, unsigned long addr, p4d_t p4d)
+{
+	note_page(pt_st, addr, 1, p4d_val(p4d));
+}
+
+static void note_page_pgd(struct ptdump_state *pt_st, unsigned long addr, pgd_t pgd)
+{
+	note_page(pt_st, addr, 0, pgd_val(pgd));
+}
+
+static void note_page_flush(struct ptdump_state *pt_st)
+{
+	pte_t pte_zero = {0};
+
+	note_page(pt_st, 0, -1, pte_val(pte_zero));
+}
+
+static void ptdump_walk(struct seq_file *s, struct ptd_mm_info *pinfo)
+{
+	struct pg_state st = {
+		.seq = s,
+		.marker = pinfo->markers,
+		.level = -1,
+		.ptdump = {
+			.note_page_pte = note_page_pte,
+			.note_page_pmd = note_page_pmd,
+			.note_page_pud = note_page_pud,
+			.note_page_p4d = note_page_p4d,
+			.note_page_pgd = note_page_pgd,
+			.note_page_flush = note_page_flush,
+			.range = (struct ptdump_range[]) {
+				{pinfo->base_addr, pinfo->end},
+				{0, 0}
+			}
+		}
+	};
+
+	ptdump_walk_pgd(&st.ptdump, pinfo->mm, NULL);
+}
+
+bool ptdump_check_wx(void)
+{
+	struct pg_state st = {
+		.seq = NULL,
+		.marker = (struct addr_marker[]) {
+			{0, NULL},
+			{-1, NULL},
+		},
+		.level = -1,
+		.check_wx = true,
+		.ptdump = {
+			.note_page_pte = note_page_pte,
+			.note_page_pmd = note_page_pmd,
+			.note_page_pud = note_page_pud,
+			.note_page_p4d = note_page_p4d,
+			.note_page_pgd = note_page_pgd,
+			.note_page_flush = note_page_flush,
+			.range = (struct ptdump_range[]) {
+				{vm_map_base, ULONG_MAX},
+				{0, 0}
+			}
+		}
+	};
+
+	ptdump_walk_pgd(&st.ptdump, &init_mm, NULL);
+
+	if (st.wx_pages) {
+		pr_warn("Checked W+X mappings: failed, %lu W+X pages found\n",
+			st.wx_pages);
+
+		return false;
+	}
+
+	pr_info("Checked W+X mappings: passed, no W+X pages found\n");
+
+	return true;
+}
+
+static int ptdump_show(struct seq_file *m, void *v)
+{
+	struct ptd_mm_info *info = m->private;
+
+	ptdump_walk(m, info);
+	return 0;
+}
+DEFINE_SHOW_ATTRIBUTE(ptdump);
+
+static int __init ptdump_init(void)
+{
+	unsigned int i, j;
+
+	address_markers[PCI_IO_START_NR].start_address = (unsigned long)PCI_IOBASE;
+	address_markers[PCI_IO_END_NR].start_address = (unsigned long)PCI_IOBASE + IO_SPACE_LIMIT;
+
+	address_markers[MODULES_START_NR].start_address = MODULES_VADDR;
+	address_markers[MODULES_END_NR].start_address = MODULES_END;
+
+	address_markers[VMALLOC_START_NR].start_address = VMALLOC_START;
+	address_markers[VMALLOC_END_NR].start_address = VMALLOC_END;
+
+#ifdef CONFIG_SPARSEMEM_VMEMMAP
+	address_markers[VMEMMAP_START_NR].start_address = (unsigned long)vmemmap;
+	address_markers[VMEMMAP_END_NR].start_address = VMEMMAP_END;
+#endif
+
+#ifdef CONFIG_KFENCE
+	address_markers[KFENCE_AREA_START_NR].start_address = KFENCE_AREA_START;
+	address_markers[KFENCE_AREA_END_NR].start_address = KFENCE_AREA_END;
+#endif
+
+#ifdef CONFIG_KASAN
+	address_markers[KASAN_SHADOW_START_NR].start_address = KASAN_SHADOW_START;
+	address_markers[KASAN_SHADOW_END_NR].start_address = KASAN_SHADOW_END;
+#endif
+
+	address_markers[FIXMAP_START_NR].start_address = FIXADDR_START;
+	address_markers[FIXMAP_END_NR].start_address = FIXADDR_TOP;
+
+	kernel_ptd_info.base_addr = vm_map_base;
+
+	for (i = 0; i < ARRAY_SIZE(pg_level); i++) {
+		for (j = 0; j < ARRAY_SIZE(pte_bits); j++)
+			pg_level[i].mask |= pte_bits[j].mask;
+	}
+
+	debugfs_create_file("kernel_page_tables", 0400, NULL, &kernel_ptd_info, &ptdump_fops);
+
+	return 0;
+}
+
+device_initcall(ptdump_init);
-- 
2.43.0


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

end of thread, other threads:[~2026-07-07  6:47 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-07  6:45 [PATCH 0/2] Add support to dump the kernel page tables haoran.jiang
2026-07-07  6:45 ` [PATCH 1/2] LoongArch: Enforce W^X for page-mapped virtual memory region haoran.jiang
2026-07-07  6:45 ` [PATCH 2/2] LoongArch: Add support to dump the kernel page tables haoran.jiang

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