mm/page_table_check: widen map counters
Subsystems (2)
PATCH 1/1 mm/page_table_check: widen map counters
In reply to a message not in the archiveFrom: 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.
--- mm/page_table_check.c | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-)
mm/page_table_check.c +12 -12
@@ -14,8 +14,8 @@#define pr_fmt (fmt) "page_table_check: " fmtstruct 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
Andrew Morton
maintainerQuoted 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()andpage_table_check_clear()store per-page anonymous and file map counts in signedatomic_tcounters. A page can have more thanINT_MAXread-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_countwraps, the next set or clear observes a negative count and tripsBUG_ON(), allowing an unprivileged user to panic a kernel withCONFIG_PAGE_TABLE_CHECKenabled.Use
atomic64_tfor both counters sopage_table_checkcan keep the same type-conflict checks without overflowing atINT_MAXmappings.
Thanks.
Quoted 9 lines
Fixes df4e817b7108 ("mm: page table check")...
index
53a8997ec043..edd9d9edf6fc100644
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: " fmtstruct 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?
Quoted 4 lines · Ren Wei
page_table_check_set()andpage_table_check_clear()store per-page anonymous and file map counts in signedatomic_tcounters. A page can have more thanINT_MAXread-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.
Andrew Morton
maintainerQuoted 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.
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_extcode 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.
Pasha Tatashin
maintainerQuoted 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_extcode will need alteration for this.Thanks Andrew and Matthew.
Agreed. Widening the counters is not the right direction: it changes the
page_extstorage/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_checkpaths.Thanks for the review.
Andrew Morton
maintainerQuoted 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()andpage_table_check_clear()store per-page anonymous and file map counts in signedatomic_tcounters. A page can have more thanINT_MAXread-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_countwraps, the next set or clear observes a negative count and tripsBUG_ON(), allowing an unprivileged user to panic a kernel withCONFIG_PAGE_TABLE_CHECKenabled.Use
atomic64_tfor both counters sopage_table_checkcan keep the same type-conflict checks without overflowing atINT_MAXmappings.
Quoted 6 lines
Fixes df4e817b7108 ("mm: page table check")Reported-by Vega <[email protected]>Assisted-by Codex:gpt-5.4Signed-off-by Zhiling Zou <[email protected]>Reviewed-by Ren Wei <[email protected]>
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: " fmtstruct 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