diff --git a/fs/proc/page.c b/fs/proc/page.c
index 9f1077d94cde..61187568e5f7 100644
--- a/fs/proc/page.c
+++ b/fs/proc/page.c
@@ -182,8 +182,7 @@ u64 stable_page_flags(struct page *page)
 
 	u |= kpf_copy_bit(k, KPF_LOCKED,	PG_locked);
 
-	u |= kpf_copy_bit(k, KPF_SLAB,		PG_slab);
-	if (PageTail(page) && PageSlab(compound_head(page)))
+	if (PageSlab(page) || PageSlab(compound_head(page)))
 		u |= 1 << KPF_SLAB;
 
 	u |= kpf_copy_bit(k, KPF_ERROR,		PG_error);
diff --git a/include/linux/page-flags.h b/include/linux/page-flags.h
index 1c3b6e5c8bfd..b3c2cf71467e 100644
--- a/include/linux/page-flags.h
+++ b/include/linux/page-flags.h
@@ -107,7 +107,7 @@ enum pageflags {
 	PG_workingset,
 	PG_waiters,		/* Page has waiters, check its waitqueue. Must be bit #7 and in the same byte as "PG_locked" */
 	PG_error,
-	PG_slab,
+	PG_slab,		/* Slab page if !PageAnon() */
 	PG_owner_priv_1,	/* Owner use. If pagecache, fs may use*/
 	PG_arch_1,
 	PG_reserved,
@@ -142,6 +142,63 @@ enum pageflags {
 
 	PG_readahead = PG_reclaim,
 
+	/*
+	 * Depending on the way an anonymous folio can be mapped into a page
+	 * table (e.g., single PMD/PUD/CONT of the head page vs. PTE-mapped
+	 * THP), PG_anon_exclusive may be set only for the head page or for
+	 * subpages of an anonymous folio.
+	 *
+	 * PG_anon_exclusive is *usually* only expressive in combination with a
+	 * page table entry. Depending on the page table entry type it might
+	 * store the following information:
+	 *
+	 *	Is what's mapped via this page table entry exclusive to the
+	 *	single process and can be mapped writable without further
+	 *	checks? If not, it might be shared and we might have to COW.
+	 *
+	 * For now, we only expect PTE-mapped THPs to make use of
+	 * PG_anon_exclusive in subpages. For other anonymous compound
+	 * folios (i.e., hugetlb), only the head page is logically mapped and
+	 * holds this information.
+	 *
+	 * For example, an exclusive, PMD-mapped THP only has PG_anon_exclusive
+	 * set on the head page. When replacing the PMD by a page table full
+	 * of PTEs, PG_anon_exclusive, if set on the head page, will be set on
+	 * all tail pages accordingly. Note that converting from a PTE-mapping
+	 * to a PMD mapping using the same compound page is currently not
+	 * possible and consequently doesn't require care.
+	 *
+	 * If GUP wants to take a reliable pin (FOLL_PIN) on an anonymous page,
+	 * it should only pin if the relevant PG_anon_bit is set. In that case,
+	 * the pin will be fully reliable and stay consistent with the pages
+	 * mapped into the page table, as the bit cannot get cleared (e.g., by
+	 * fork(), KSM) while the page is pinned. For anonymous pages that
+	 * are mapped R/W, PG_anon_exclusive can be assumed to always be set
+	 * because such pages cannot possibly be shared.
+	 *
+	 * The page table lock protecting the page table entry is the primary
+	 * synchronization mechanism for PG_anon_exclusive; GUP-fast that does
+	 * not take the PT lock needs special care when trying to clear the
+	 * flag.
+	 *
+	 * Page table entry types and PG_anon_exclusive:
+	 * * Present: PG_anon_exclusive applies.
+	 * * Swap: the information is lost. PG_anon_exclusive was cleared.
+	 * * Migration: the entry holds this information instead.
+	 *		PG_anon_exclusive was cleared.
+	 * * Device private: PG_anon_exclusive applies.
+	 * * Device exclusive: PG_anon_exclusive applies.
+	 * * HW Poison: PG_anon_exclusive is stale and not changed.
+	 *
+	 * If the page may be pinned (FOLL_PIN), clearing PG_anon_exclusive is
+	 * not allowed and the flag will stick around until the page is freed
+	 * and folio->mapping is cleared.
+	 *
+	 * Before clearing folio->mapping, PG_anon_exclusive has to be cleared
+	 * to not result in PageSlab() false positives.
+	 */
+	PG_anon_exclusive = PG_slab,
+
 	/* Filesystems */
 	PG_checked = PG_owner_priv_1,
 
@@ -425,7 +482,6 @@ PAGEFLAG(Active, active, PF_HEAD) __CLEARPAGEFLAG(Active, active, PF_HEAD)
 	TESTCLEARFLAG(Active, active, PF_HEAD)
 PAGEFLAG(Workingset, workingset, PF_HEAD)
 	TESTCLEARFLAG(Workingset, workingset, PF_HEAD)
-__PAGEFLAG(Slab, slab, PF_NO_TAIL)
 __PAGEFLAG(SlobFree, slob_free, PF_NO_TAIL)
 PAGEFLAG(Checked, checked, PF_NO_COMPOUND)	   /* Used by some filesystems */
 
@@ -920,6 +976,70 @@ extern bool is_free_buddy_page(struct page *page);
 
 __PAGEFLAG(Isolated, isolated, PF_ANY);
 
+static __always_inline bool folio_test_slab(struct folio *folio)
+{
+	return !folio_test_anon(folio) &&
+	       test_bit(PG_slab, folio_flags(folio, FOLIO_PF_NO_TAIL));
+}
+
+static __always_inline int PageSlab(struct page *page)
+{
+	return !PageAnon(page) &&
+		test_bit(PG_slab, &PF_NO_TAIL(page, 0)->flags);
+}
+
+static __always_inline void __folio_set_slab(struct folio *folio)
+{
+	VM_BUG_ON(folio_test_anon(folio));
+	__set_bit(PG_slab, folio_flags(folio, FOLIO_PF_NO_TAIL));
+}
+
+static __always_inline void __SetPageSlab(struct page *page)
+{
+	VM_BUG_ON_PGFLAGS(PageAnon(page), page);
+	__set_bit(PG_slab, &PF_NO_TAIL(page, 1)->flags);
+}
+
+static __always_inline void __folio_clear_slab(struct folio *folio)
+{
+	VM_BUG_ON(folio_test_anon(folio));
+	__clear_bit(PG_slab, folio_flags(folio, FOLIO_PF_NO_TAIL));
+}
+
+static __always_inline void __ClearPageSlab(struct page *page)
+{
+	VM_BUG_ON_PGFLAGS(PageAnon(page), page);
+	__clear_bit(PG_slab, &PF_NO_TAIL(page, 1)->flags);
+}
+
+static __always_inline int PageAnonExclusive(struct page *page)
+{
+	VM_BUG_ON_PGFLAGS(!PageAnon(page), page);
+	VM_BUG_ON_PGFLAGS(PageHuge(page) && !PageHead(page), page);
+	return test_bit(PG_anon_exclusive, &PF_ANY(page, 1)->flags);
+}
+
+static __always_inline void SetPageAnonExclusive(struct page *page)
+{
+	VM_BUG_ON_PGFLAGS(!PageAnon(page) || PageKsm(page), page);
+	VM_BUG_ON_PGFLAGS(PageHuge(page) && !PageHead(page), page);
+	set_bit(PG_anon_exclusive, &PF_ANY(page, 1)->flags);
+}
+
+static __always_inline void ClearPageAnonExclusive(struct page *page)
+{
+	VM_BUG_ON_PGFLAGS(!PageAnon(page) || PageKsm(page), page);
+	VM_BUG_ON_PGFLAGS(PageHuge(page) && !PageHead(page), page);
+	clear_bit(PG_anon_exclusive, &PF_ANY(page, 1)->flags);
+}
+
+static __always_inline void __ClearPageAnonExclusive(struct page *page)
+{
+	VM_BUG_ON_PGFLAGS(!PageAnon(page), page);
+	VM_BUG_ON_PGFLAGS(PageHuge(page) && !PageHead(page), page);
+	__clear_bit(PG_anon_exclusive, &PF_ANY(page, 1)->flags);
+}
+
 #ifdef CONFIG_MMU
 #define __PG_MLOCKED		(1UL << PG_mlocked)
 #else
diff --git a/include/trace/events/mmflags.h b/include/trace/events/mmflags.h
index 116ed4d5d0f8..5662f74e027f 100644
--- a/include/trace/events/mmflags.h
+++ b/include/trace/events/mmflags.h
@@ -103,7 +103,7 @@
 	{1UL << PG_lru,			"lru"		},		\
 	{1UL << PG_active,		"active"	},		\
 	{1UL << PG_workingset,		"workingset"	},		\
-	{1UL << PG_slab,		"slab"		},		\
+	{1UL << PG_slab,		"slab/anon_exclusive" },	\
 	{1UL << PG_owner_priv_1,	"owner_priv_1"	},		\
 	{1UL << PG_arch_1,		"arch_1"	},		\
 	{1UL << PG_reserved,		"reserved"	},		\
diff --git a/mm/hugetlb.c b/mm/hugetlb.c
index 9fb990d95dab..bf5ce91c623c 100644
--- a/mm/hugetlb.c
+++ b/mm/hugetlb.c
@@ -1669,6 +1669,10 @@ void free_huge_page(struct page *page)
 	VM_BUG_ON_PAGE(page_mapcount(page), page);
 
 	hugetlb_set_page_subpool(page, NULL);
+	if (PageAnon(page)) {
+		__ClearPageAnonExclusive(page);
+		wmb(); /* avoid PageSlab() false positives */
+	}
 	page->mapping = NULL;
 	restore_reserve = HPageRestoreReserve(page);
 	ClearHPageRestoreReserve(page);
diff --git a/mm/memory-failure.c b/mm/memory-failure.c
index 97a9ed8f87a9..176fa2fbd699 100644
--- a/mm/memory-failure.c
+++ b/mm/memory-failure.c
@@ -1470,17 +1470,29 @@ static int identify_page_state(unsigned long pfn, struct page *p,
 	 * The first check uses the current page flags which may not have any
 	 * relevant information. The second check with the saved page flags is
 	 * carried out only if the first check can't determine the page status.
+	 *
+	 * Note that PG_slab is also used as PG_anon_exclusive for PageAnon()
+	 * pages. Most of these pages should have been handled previously,
+	 * however, let's play safe and verify via PageAnon().
 	 */
-	for (ps = error_states;; ps++)
-		if ((p->flags & ps->mask) == ps->res)
-			break;
+	for (ps = error_states;; ps++) {
+		if ((p->flags & ps->mask) != ps->res)
+			continue;
+		if ((ps->type == MF_MSG_SLAB) && PageAnon(p))
+			continue;
+		break;
+	}
 
 	page_flags |= (p->flags & (1UL << PG_dirty));
 
 	if (!ps->mask)
-		for (ps = error_states;; ps++)
-			if ((page_flags & ps->mask) == ps->res)
-				break;
+		for (ps = error_states;; ps++) {
+			if ((page_flags & ps->mask) != ps->res)
+				continue;
+			if ((ps->type == MF_MSG_SLAB) && PageAnon(p))
+				continue;
+			break;
+		}
 	return page_action(ps, p, pfn);
 }
 
diff --git a/mm/memremap.c b/mm/memremap.c
index 6aa5f0c2d11f..a6b82ae8b3e6 100644
--- a/mm/memremap.c
+++ b/mm/memremap.c
@@ -478,6 +478,17 @@ void free_devmap_managed_page(struct page *page)
 
 	mem_cgroup_uncharge(page_folio(page));
 
+	/*
+	 * Note: we don't expect anonymous compound pages yet. Once supported
+	 * and we could PTE-map them similar to THP, we'd have to clear
+	 * PG_anon_exclusive on all tail pages.
+	 */
+	VM_BUG_ON_PAGE(PageAnon(page) && PageCompound(page), page);
+	if (PageAnon(page)) {
+		__ClearPageAnonExclusive(page);
+		wmb(); /* avoid PageSlab() false positives */
+	}
+
 	/*
 	 * When a device_private page is freed, the page->mapping field
 	 * may still contain a (stale) mapping value. For example, the
diff --git a/mm/page_alloc.c b/mm/page_alloc.c
index 3589febc6d31..e59e68a61d1a 100644
--- a/mm/page_alloc.c
+++ b/mm/page_alloc.c
@@ -1297,6 +1297,7 @@ static __always_inline bool free_pages_prepare(struct page *page,
 {
 	int bad = 0;
 	bool skip_kasan_poison = should_skip_kasan_poison(page, fpi_flags);
+	const bool anon = PageAnon(page);
 
 	VM_BUG_ON_PAGE(PageTail(page), page);
 
@@ -1329,6 +1330,14 @@ static __always_inline bool free_pages_prepare(struct page *page,
 			ClearPageHasHWPoisoned(page);
 		}
 		for (i = 1; i < (1 << order); i++) {
+			/*
+			 * Freeing a previously PTE-mapped THP can have
+			 * PG_anon_exclusive set on tail pages. Clear it
+			 * manually as it's overloaded with PG_slab that we
+			 * want to catch in check_free_page().
+			 */
+			if (anon)
+				__ClearPageAnonExclusive(page + i);
 			if (compound)
 				bad += free_tail_pages_check(page, page + i);
 			if (unlikely(check_free_page(page + i))) {
@@ -1338,6 +1347,10 @@ static __always_inline bool free_pages_prepare(struct page *page,
 			(page + i)->flags &= ~PAGE_FLAGS_CHECK_AT_PREP;
 		}
 	}
+	if (anon) {
+		__ClearPageAnonExclusive(page);
+		wmb(); /* avoid PageSlab() false positives */
+	}
 	if (PageMappingFlags(page))
 		page->mapping = NULL;
 	if (memcg_kmem_enabled() && PageMemcgKmem(page))
