From 8beef54a334bb244574506491472e1c955388198 Mon Sep 17 00:00:00 2001
From: Sasha Levin <sashal@kernel.org>
Date: Fri, 1 Aug 2025 08:47:24 -0400
Subject: [PATCH 1/2] coccinelle: add semantic patch to detect kmap_local LIFO
 violations

Add a Coccinelle semantic patch to detect violations of kmap_local's
Last-In-First-Out (LIFO) ordering requirement. When using kmap_local_page(),
kmap_atomic(), pte_offset_map(), or pte_offset_map_rw_nolock() variants,
the mappings must be unmapped in reverse order to maintain correct highmem
slot ordering.

This semantic patch identifies patterns where:
- Multiple kmap operations are unmapped in the wrong order
- The same pattern applies to pte_offset_map() and kmap_atomic()
- Conditional unmapping patterns that violate LIFO ordering

These violations can cause warnings on CONFIG_HIGHPTE systems:
  WARNING: CPU: 0 PID: 604 at mm/highmem.c:622 kunmap_local_indexed+0x178/0x17c
  addr \!= __fix_to_virt(FIX_KMAP_BEGIN + idx)

Co-developed-by: Claude claude-opus-4-20250514
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
 scripts/coccinelle/api/kmap_local_lifo.cocci | 114 +++++++++++++++++++
 1 file changed, 114 insertions(+)
 create mode 100644 scripts/coccinelle/api/kmap_local_lifo.cocci

diff --git a/scripts/coccinelle/api/kmap_local_lifo.cocci b/scripts/coccinelle/api/kmap_local_lifo.cocci
new file mode 100644
index 000000000000..e6ba780753de
--- /dev/null
+++ b/scripts/coccinelle/api/kmap_local_lifo.cocci
@@ -0,0 +1,114 @@
+// SPDX-License-Identifier: GPL-2.0
+/// Detect violations of kmap_local LIFO ordering
+///
+/// kmap_local_page() and pte_offset_map() operations must follow
+/// Last-In-First-Out (LIFO) ordering. This means if you map A then B,
+/// you must unmap B then A.
+///
+// Confidence: High
+// Copyright: (C) 2025 Sasha Levin
+
+virtual report
+
+// Pattern 1: kmap_local_page() followed by kunmap_local() in wrong order
+@kmap_lifo@
+expression E1, E2;
+identifier ptr1 != ptr2;
+identifier ptr2;
+position p1, p2, p3, p4;
+@@
+
+ptr1 = kmap_local_page(E1)@p1;
+... when != kunmap_local(ptr1)
+ptr2 = kmap_local_page(E2)@p2;
+... when != kunmap_local(ptr1)
+    when != kunmap_local(ptr2)
+kunmap_local(ptr1)@p3;
+... when != kunmap_local(ptr2)
+kunmap_local(ptr2)@p4;
+
+@script:python depends on report@
+p1 << kmap_lifo.p1;
+p2 << kmap_lifo.p2;
+p3 << kmap_lifo.p3;
+ptr1 << kmap_lifo.ptr1;
+ptr2 << kmap_lifo.ptr2;
+@@
+
+coccilib.report.print_report(p3[0], "WARNING: kmap_local LIFO violation - %s mapped before %s but unmapped first" % (ptr1, ptr2))
+
+// Pattern 2: pte_offset_map() followed by pte_unmap() in wrong order
+@pte_lifo@
+expression E1, E2, E3, E4;
+identifier ptr1 != ptr2;
+identifier ptr2;
+position p1, p2, p3, p4;
+@@
+
+ptr1 = pte_offset_map(E1, E2)@p1;
+... when != pte_unmap(ptr1)
+ptr2 = pte_offset_map(E3, E4)@p2;
+... when != pte_unmap(ptr1)
+    when != pte_unmap(ptr2)
+pte_unmap(ptr1)@p3;
+... when != pte_unmap(ptr2)
+pte_unmap(ptr2)@p4;
+
+@script:python depends on report@
+p1 << pte_lifo.p1;
+p2 << pte_lifo.p2;
+p3 << pte_lifo.p3;
+ptr1 << pte_lifo.ptr1;
+ptr2 << pte_lifo.ptr2;
+@@
+
+coccilib.report.print_report(p3[0], "WARNING: pte_offset_map LIFO violation - %s mapped before %s but unmapped first" % (ptr1, ptr2))
+
+// Pattern 3: kmap_atomic() followed by kunmap_atomic() in wrong order
+@atomic_lifo@
+expression E1, E2;
+identifier ptr1 != ptr2;
+identifier ptr2;
+position p1, p2, p3, p4;
+@@
+
+ptr1 = kmap_atomic(E1)@p1;
+... when != kunmap_atomic(ptr1)
+ptr2 = kmap_atomic(E2)@p2;
+... when != kunmap_atomic(ptr1)
+    when != kunmap_atomic(ptr2)
+kunmap_atomic(ptr1)@p3;
+... when != kunmap_atomic(ptr2)
+kunmap_atomic(ptr2)@p4;
+
+@script:python depends on report@
+p1 << atomic_lifo.p1;
+p2 << atomic_lifo.p2;
+p3 << atomic_lifo.p3;
+ptr1 << atomic_lifo.ptr1;
+ptr2 << atomic_lifo.ptr2;
+@@
+
+coccilib.report.print_report(p3[0], "WARNING: kmap_atomic LIFO violation - %s mapped before %s but unmapped first" % (ptr1, ptr2))
+
+// Pattern 4: Specific pattern for userfaultfd conditional unmapping
+@userfault_pattern@
+identifier dst_pte, src_pte;
+position p1, p2, p3, p4;
+@@
+
+dst_pte@p1 = pte_offset_map_rw_nolock(...);
+... when exists
+src_pte@p2 = pte_offset_map_rw_nolock(...);
+... when exists
+when any
+if (dst_pte)
+	pte_unmap(dst_pte)@p3;
+if (src_pte)
+	pte_unmap(src_pte)@p4;
+
+@script:python depends on report@
+p3 << userfault_pattern.p3;
+@@
+
+coccilib.report.print_report(p3[0], "WARNING: pte_offset_map_rw_nolock LIFO violation - dst_pte mapped before src_pte but unmapped first")
-- 
2.39.5

