Skip to contentSkip to conversation

mm/page_table_check: widen map counters

PATCH In review Ren Wei 5 participants 7 messages Jul 18 – Sep 1 1 message not in the archive

Subsystems (2)
lore
Share & tools
lore

Ren Wei

author
#1 lore raw

PATCH 1/1 mm/page_table_check: widen map counters

In reply to a message not in the archive

From: Zhiling Zou <[email protected]>

page_table_check_set() and page_table_check_clear() store per-page anonymous and file map counts in signed atomic_t counters. A page can have more than INT_MAX read-only mappings while still being mapped legally.

The global zero page is one example: repeated read faults on private anonymous mappings install read-only PTEs that are accounted as file mappings. Once file_map_count wraps, the next set or clear observes a negative count and trips BUG_ON(), allowing an unprivileged user to panic a kernel with CONFIG_PAGE_TABLE_CHECK enabled.

Use atomic64_t for both counters so page_table_check can keep the same type-conflict checks without overflowing at INT_MAX mappings.

Fixes df4e817b7108 ("mm: page table check")
Assisted-by Codex:gpt-5.4
---
 mm/page_table_check.c | 24 ++++++++++++------------
 1 file changed, 12 insertions(+), 12 deletions(-)
mm/page_table_check.c +12 -12
index 53a8997ec043..edd9d9edf6fc 100644
@@ -14,8 +14,8 @@
 #define pr_fmt(fmt)	"page_table_check: " fmt
 
 struct page_table_check {
-	atomic_t anon_map_count;
-	atomic_t file_map_count;
+	atomic64_t anon_map_count;
+	atomic64_t file_map_count;
 };
 
 static bool __page_table_check_enabled __initdata =
@@ -79,11 +79,11 @@ static void page_table_check_clear(unsigned long pfn, unsigned long pgcnt)
 		struct page_table_check *ptc = get_page_table_check(page_ext);
 
 		if (anon) {
-			BUG_ON(atomic_read(&ptc->file_map_count));
-			BUG_ON(atomic_dec_return(&ptc->anon_map_count) < 0);
+			BUG_ON(atomic64_read(&ptc->file_map_count));
+			BUG_ON(atomic64_dec_return(&ptc->anon_map_count) < 0);
 		} else {
-			BUG_ON(atomic_read(&ptc->anon_map_count));
-			BUG_ON(atomic_dec_return(&ptc->file_map_count) < 0);
+			BUG_ON(atomic64_read(&ptc->anon_map_count));
+			BUG_ON(atomic64_dec_return(&ptc->file_map_count) < 0);
 		}
 	}
 	rcu_read_unlock();
@@ -114,11 +114,11 @@ static void page_table_check_set(unsigned long pfn, unsigned long pgcnt,
 		struct page_table_check *ptc = get_page_table_check(page_ext);
 
 		if (anon) {
-			BUG_ON(atomic_read(&ptc->file_map_count));
-			BUG_ON(atomic_inc_return(&ptc->anon_map_count) > 1 && rw);
+			BUG_ON(atomic64_read(&ptc->file_map_count));
+			BUG_ON(atomic64_inc_return(&ptc->anon_map_count) > 1 && rw);
 		} else {
-			BUG_ON(atomic_read(&ptc->anon_map_count));
-			BUG_ON(atomic_inc_return(&ptc->file_map_count) < 0);
+			BUG_ON(atomic64_read(&ptc->anon_map_count));
+			BUG_ON(atomic64_inc_return(&ptc->file_map_count) < 0);
 		}
 	}
 	rcu_read_unlock();
@@ -139,8 +139,8 @@ void __page_table_check_zero(struct page *page, unsigned int order)
 	for_each_page_ext(page, 1 << order, page_ext, iter) {
 		struct page_table_check *ptc = get_page_table_check(page_ext);
 
-		BUG_ON(atomic_read(&ptc->anon_map_count));
-		BUG_ON(atomic_read(&ptc->file_map_count));
+		BUG_ON(atomic64_read(&ptc->anon_map_count));
+		BUG_ON(atomic64_read(&ptc->file_map_count));
 	}
 	rcu_read_unlock();
 }
Signature · 1 line
2.43.0
maintainer
#2 lore raw
Quoted 7 lines · On Sat, 18 Jul 2026 12:27:20 +0800 Ren Wei <[email protected]> wrote:

From: Zhiling Zou <[email protected]>

page_table_check_set() and page_table_check_clear() store per-page anonymous and file map counts in signed atomic_t counters. A page can have more than INT_MAX read-only mappings while still being mapped legally.

Quoted 8 lines

The global zero page is one example: repeated read faults on private anonymous mappings install read-only PTEs that are accounted as file mappings. Once file_map_count wraps, the next set or clear observes a negative count and trips BUG_ON(), allowing an unprivileged user to panic a kernel with CONFIG_PAGE_TABLE_CHECK enabled.

Use atomic64_t for both counters so page_table_check can keep the same type-conflict checks without overflowing at INT_MAX mappings.

Thanks.

Quoted 9 lines
Fixes df4e817b7108 ("mm: page table check")

...

index 53a8997ec043..edd9d9edf6fc 100644

mm/page_table_check.c line 19

Quoted 8 lines
mm/page_table_check.c +2 -2
@@ -14,8 +14,8 @@
 #define pr_fmt(fmt)	"page_table_check: " fmt
 
 struct page_table_check {
-	atomic_t anon_map_count;
-	atomic_t file_map_count;
+	atomic64_t anon_map_count;
+	atomic64_t file_map_count;
 };

AI review suggest this might cause problems in, of all places, page_ext.c:

	https://sashiko.dev/#/patchset/b4414b588418c52a99d959d35a40e064ae8b6e72.1784337674.git.roxy520tt@gmail.com

My googling indicates that a 64-bit atomic op on a 32-bit-aligned address is probably usually OK, but it doesn't sound smart.

Probably adding __aligned(sizeof(atomic64_t)) will address?

#6 lore raw
In reply to Ren Wei
Quoted 4 lines · Ren Wei

page_table_check_set() and page_table_check_clear() store per-page anonymous and file map counts in signed atomic_t counters. A page can have more than INT_MAX read-only mappings while still being mapped legally.

No it can't. mapcount is 32-bit.

Quoted 1 line

The global zero page is one example: repeated read faults on private

You mean "is a special case". page_table_check should handle this case specially.

#4 lore raw
Quoted 6 lines · Andrew Morton
Quoted 1 line · On Fri, 17 Jul 2026 22:11:08 -0700 Andrew Morton <[email protected]>…

Probably adding __aligned(sizeof(atomic64_t)) will address?

err, no. I think page_ext code will need alteration for this.

Thanks Andrew and Matthew.

Agreed. Widening the counters is not the right direction: it changes the page_ext storage/alignment requirements, and normal page mapcount is still 32-bit as Matthew pointed out.

I'll rework this around a zero-page-specific fix and send v2 after checking the exact page_table_check paths.

Thanks for the review.

maintainer
#5 lore raw
Quoted 6 lines · tt roxy
Quoted 5 lines · Andrew Morton
Quoted 1 line · On Fri, 17 Jul 2026 22:11:08 -0700 Andrew Morton <[email protected]>…

Probably adding __aligned(sizeof(atomic64_t)) will address?

Quoted 7 lines
Quoted 1 line

err, no. I think page_ext code will need alteration for this.

Thanks Andrew and Matthew.

Agreed. Widening the counters is not the right direction: it changes the page_ext storage/alignment requirements, and normal page mapcount is still 32-bit as Matthew pointed out.

Also, double page table check overhead. So, I agree let's address the special case instead of widening page_ext.

Thanks,
Pasha

Quoted 5 lines

I'll rework this around a zero-page-specific fix and send v2 after checking the exact page_table_check paths.

Thanks for the review.

maintainer
#7 lore raw
In reply to Ren Wei
Quoted 16 lines · On Sat, 18 Jul 2026 12:27:20 +0800 Ren Wei <[email protected]> wrote:

From: Zhiling Zou <[email protected]>

page_table_check_set() and page_table_check_clear() store per-page anonymous and file map counts in signed atomic_t counters. A page can have more than INT_MAX read-only mappings while still being mapped legally.

The global zero page is one example: repeated read faults on private anonymous mappings install read-only PTEs that are accounted as file mappings. Once file_map_count wraps, the next set or clear observes a negative count and trips BUG_ON(), allowing an unprivileged user to panic a kernel with CONFIG_PAGE_TABLE_CHECK enabled.

Use atomic64_t for both counters so page_table_check can keep the same type-conflict checks without overflowing at INT_MAX mappings.

Quoted 6 lines
Fixes df4e817b7108 ("mm: page table check")
Assisted-by Codex:gpt-5.4

Thanks.

I've merged Zhiling's "mm/page_table_check: prevent map count overflow", which appears to address the same issue.
https://lore.kernel.org/1f8848512d2e3ded944f8d595c29faee8fdaeab0.1784645969.git.roxy520tt@gmail.com

But the patch I merged is very different from the below. I assume the patch I merged is the one which you want because it was sent three days later. Correct?

However my question regarding the possibly missing barrier remains unaddressed:

	https://lore.kernel.org/all/[email protected]/
Quoted 58 lines
mm/page_table_check.c +12 -12
@@ -14,8 +14,8 @@
 #define pr_fmt(fmt)	"page_table_check: " fmt
 
 struct page_table_check {
-	atomic_t anon_map_count;
-	atomic_t file_map_count;
+	atomic64_t anon_map_count;
+	atomic64_t file_map_count;
 };
 
 static bool __page_table_check_enabled __initdata =
@@ -79,11 +79,11 @@ static void page_table_check_clear(unsigned long pfn, unsigned long pgcnt)
 		struct page_table_check *ptc = get_page_table_check(page_ext);
 
 		if (anon) {
-			BUG_ON(atomic_read(&ptc->file_map_count));
-			BUG_ON(atomic_dec_return(&ptc->anon_map_count) < 0);
+			BUG_ON(atomic64_read(&ptc->file_map_count));
+			BUG_ON(atomic64_dec_return(&ptc->anon_map_count) < 0);
 		} else {
-			BUG_ON(atomic_read(&ptc->anon_map_count));
-			BUG_ON(atomic_dec_return(&ptc->file_map_count) < 0);
+			BUG_ON(atomic64_read(&ptc->anon_map_count));
+			BUG_ON(atomic64_dec_return(&ptc->file_map_count) < 0);
 		}
 	}
 	rcu_read_unlock();
@@ -114,11 +114,11 @@ static void page_table_check_set(unsigned long pfn, unsigned long pgcnt,
 		struct page_table_check *ptc = get_page_table_check(page_ext);
 
 		if (anon) {
-			BUG_ON(atomic_read(&ptc->file_map_count));
-			BUG_ON(atomic_inc_return(&ptc->anon_map_count) > 1 && rw);
+			BUG_ON(atomic64_read(&ptc->file_map_count));
+			BUG_ON(atomic64_inc_return(&ptc->anon_map_count) > 1 && rw);
 		} else {
-			BUG_ON(atomic_read(&ptc->anon_map_count));
-			BUG_ON(atomic_inc_return(&ptc->file_map_count) < 0);
+			BUG_ON(atomic64_read(&ptc->anon_map_count));
+			BUG_ON(atomic64_inc_return(&ptc->file_map_count) < 0);
 		}
 	}
 	rcu_read_unlock();
@@ -139,8 +139,8 @@ void __page_table_check_zero(struct page *page, unsigned int order)
 	for_each_page_ext(page, 1 << order, page_ext, iter) {
 		struct page_table_check *ptc = get_page_table_check(page_ext);
 
-		BUG_ON(atomic_read(&ptc->anon_map_count));
-		BUG_ON(atomic_read(&ptc->file_map_count));
+		BUG_ON(atomic64_read(&ptc->anon_map_count));
+		BUG_ON(atomic64_read(&ptc->file_map_count));
 	}
 	rcu_read_unlock();
 }
Signature · 1 line
2.43.0