162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci 362306a36Sopenharmony_ci/* 462306a36Sopenharmony_ci * Copyright (c) 2021, Google LLC. 562306a36Sopenharmony_ci * Pasha Tatashin <pasha.tatashin@soleen.com> 662306a36Sopenharmony_ci */ 762306a36Sopenharmony_ci#include <linux/kstrtox.h> 862306a36Sopenharmony_ci#include <linux/mm.h> 962306a36Sopenharmony_ci#include <linux/page_table_check.h> 1062306a36Sopenharmony_ci 1162306a36Sopenharmony_ci#undef pr_fmt 1262306a36Sopenharmony_ci#define pr_fmt(fmt) "page_table_check: " fmt 1362306a36Sopenharmony_ci 1462306a36Sopenharmony_cistruct page_table_check { 1562306a36Sopenharmony_ci atomic_t anon_map_count; 1662306a36Sopenharmony_ci atomic_t file_map_count; 1762306a36Sopenharmony_ci}; 1862306a36Sopenharmony_ci 1962306a36Sopenharmony_cistatic bool __page_table_check_enabled __initdata = 2062306a36Sopenharmony_ci IS_ENABLED(CONFIG_PAGE_TABLE_CHECK_ENFORCED); 2162306a36Sopenharmony_ci 2262306a36Sopenharmony_ciDEFINE_STATIC_KEY_TRUE(page_table_check_disabled); 2362306a36Sopenharmony_ciEXPORT_SYMBOL(page_table_check_disabled); 2462306a36Sopenharmony_ci 2562306a36Sopenharmony_cistatic int __init early_page_table_check_param(char *buf) 2662306a36Sopenharmony_ci{ 2762306a36Sopenharmony_ci return kstrtobool(buf, &__page_table_check_enabled); 2862306a36Sopenharmony_ci} 2962306a36Sopenharmony_ci 3062306a36Sopenharmony_ciearly_param("page_table_check", early_page_table_check_param); 3162306a36Sopenharmony_ci 3262306a36Sopenharmony_cistatic bool __init need_page_table_check(void) 3362306a36Sopenharmony_ci{ 3462306a36Sopenharmony_ci return __page_table_check_enabled; 3562306a36Sopenharmony_ci} 3662306a36Sopenharmony_ci 3762306a36Sopenharmony_cistatic void __init init_page_table_check(void) 3862306a36Sopenharmony_ci{ 3962306a36Sopenharmony_ci if (!__page_table_check_enabled) 4062306a36Sopenharmony_ci return; 4162306a36Sopenharmony_ci static_branch_disable(&page_table_check_disabled); 4262306a36Sopenharmony_ci} 4362306a36Sopenharmony_ci 4462306a36Sopenharmony_cistruct page_ext_operations page_table_check_ops = { 4562306a36Sopenharmony_ci .size = sizeof(struct page_table_check), 4662306a36Sopenharmony_ci .need = need_page_table_check, 4762306a36Sopenharmony_ci .init = init_page_table_check, 4862306a36Sopenharmony_ci .need_shared_flags = false, 4962306a36Sopenharmony_ci}; 5062306a36Sopenharmony_ci 5162306a36Sopenharmony_cistatic struct page_table_check *get_page_table_check(struct page_ext *page_ext) 5262306a36Sopenharmony_ci{ 5362306a36Sopenharmony_ci BUG_ON(!page_ext); 5462306a36Sopenharmony_ci return page_ext_data(page_ext, &page_table_check_ops); 5562306a36Sopenharmony_ci} 5662306a36Sopenharmony_ci 5762306a36Sopenharmony_ci/* 5862306a36Sopenharmony_ci * An entry is removed from the page table, decrement the counters for that page 5962306a36Sopenharmony_ci * verify that it is of correct type and counters do not become negative. 6062306a36Sopenharmony_ci */ 6162306a36Sopenharmony_cistatic void page_table_check_clear(unsigned long pfn, unsigned long pgcnt) 6262306a36Sopenharmony_ci{ 6362306a36Sopenharmony_ci struct page_ext *page_ext; 6462306a36Sopenharmony_ci struct page *page; 6562306a36Sopenharmony_ci unsigned long i; 6662306a36Sopenharmony_ci bool anon; 6762306a36Sopenharmony_ci 6862306a36Sopenharmony_ci if (!pfn_valid(pfn)) 6962306a36Sopenharmony_ci return; 7062306a36Sopenharmony_ci 7162306a36Sopenharmony_ci page = pfn_to_page(pfn); 7262306a36Sopenharmony_ci page_ext = page_ext_get(page); 7362306a36Sopenharmony_ci 7462306a36Sopenharmony_ci BUG_ON(PageSlab(page)); 7562306a36Sopenharmony_ci anon = PageAnon(page); 7662306a36Sopenharmony_ci 7762306a36Sopenharmony_ci for (i = 0; i < pgcnt; i++) { 7862306a36Sopenharmony_ci struct page_table_check *ptc = get_page_table_check(page_ext); 7962306a36Sopenharmony_ci 8062306a36Sopenharmony_ci if (anon) { 8162306a36Sopenharmony_ci BUG_ON(atomic_read(&ptc->file_map_count)); 8262306a36Sopenharmony_ci BUG_ON(atomic_dec_return(&ptc->anon_map_count) < 0); 8362306a36Sopenharmony_ci } else { 8462306a36Sopenharmony_ci BUG_ON(atomic_read(&ptc->anon_map_count)); 8562306a36Sopenharmony_ci BUG_ON(atomic_dec_return(&ptc->file_map_count) < 0); 8662306a36Sopenharmony_ci } 8762306a36Sopenharmony_ci page_ext = page_ext_next(page_ext); 8862306a36Sopenharmony_ci } 8962306a36Sopenharmony_ci page_ext_put(page_ext); 9062306a36Sopenharmony_ci} 9162306a36Sopenharmony_ci 9262306a36Sopenharmony_ci/* 9362306a36Sopenharmony_ci * A new entry is added to the page table, increment the counters for that page 9462306a36Sopenharmony_ci * verify that it is of correct type and is not being mapped with a different 9562306a36Sopenharmony_ci * type to a different process. 9662306a36Sopenharmony_ci */ 9762306a36Sopenharmony_cistatic void page_table_check_set(unsigned long pfn, unsigned long pgcnt, 9862306a36Sopenharmony_ci bool rw) 9962306a36Sopenharmony_ci{ 10062306a36Sopenharmony_ci struct page_ext *page_ext; 10162306a36Sopenharmony_ci struct page *page; 10262306a36Sopenharmony_ci unsigned long i; 10362306a36Sopenharmony_ci bool anon; 10462306a36Sopenharmony_ci 10562306a36Sopenharmony_ci if (!pfn_valid(pfn)) 10662306a36Sopenharmony_ci return; 10762306a36Sopenharmony_ci 10862306a36Sopenharmony_ci page = pfn_to_page(pfn); 10962306a36Sopenharmony_ci page_ext = page_ext_get(page); 11062306a36Sopenharmony_ci 11162306a36Sopenharmony_ci BUG_ON(PageSlab(page)); 11262306a36Sopenharmony_ci anon = PageAnon(page); 11362306a36Sopenharmony_ci 11462306a36Sopenharmony_ci for (i = 0; i < pgcnt; i++) { 11562306a36Sopenharmony_ci struct page_table_check *ptc = get_page_table_check(page_ext); 11662306a36Sopenharmony_ci 11762306a36Sopenharmony_ci if (anon) { 11862306a36Sopenharmony_ci BUG_ON(atomic_read(&ptc->file_map_count)); 11962306a36Sopenharmony_ci BUG_ON(atomic_inc_return(&ptc->anon_map_count) > 1 && rw); 12062306a36Sopenharmony_ci } else { 12162306a36Sopenharmony_ci BUG_ON(atomic_read(&ptc->anon_map_count)); 12262306a36Sopenharmony_ci BUG_ON(atomic_inc_return(&ptc->file_map_count) < 0); 12362306a36Sopenharmony_ci } 12462306a36Sopenharmony_ci page_ext = page_ext_next(page_ext); 12562306a36Sopenharmony_ci } 12662306a36Sopenharmony_ci page_ext_put(page_ext); 12762306a36Sopenharmony_ci} 12862306a36Sopenharmony_ci 12962306a36Sopenharmony_ci/* 13062306a36Sopenharmony_ci * page is on free list, or is being allocated, verify that counters are zeroes 13162306a36Sopenharmony_ci * crash if they are not. 13262306a36Sopenharmony_ci */ 13362306a36Sopenharmony_civoid __page_table_check_zero(struct page *page, unsigned int order) 13462306a36Sopenharmony_ci{ 13562306a36Sopenharmony_ci struct page_ext *page_ext; 13662306a36Sopenharmony_ci unsigned long i; 13762306a36Sopenharmony_ci 13862306a36Sopenharmony_ci BUG_ON(PageSlab(page)); 13962306a36Sopenharmony_ci 14062306a36Sopenharmony_ci page_ext = page_ext_get(page); 14162306a36Sopenharmony_ci BUG_ON(!page_ext); 14262306a36Sopenharmony_ci for (i = 0; i < (1ul << order); i++) { 14362306a36Sopenharmony_ci struct page_table_check *ptc = get_page_table_check(page_ext); 14462306a36Sopenharmony_ci 14562306a36Sopenharmony_ci BUG_ON(atomic_read(&ptc->anon_map_count)); 14662306a36Sopenharmony_ci BUG_ON(atomic_read(&ptc->file_map_count)); 14762306a36Sopenharmony_ci page_ext = page_ext_next(page_ext); 14862306a36Sopenharmony_ci } 14962306a36Sopenharmony_ci page_ext_put(page_ext); 15062306a36Sopenharmony_ci} 15162306a36Sopenharmony_ci 15262306a36Sopenharmony_civoid __page_table_check_pte_clear(struct mm_struct *mm, pte_t pte) 15362306a36Sopenharmony_ci{ 15462306a36Sopenharmony_ci if (&init_mm == mm) 15562306a36Sopenharmony_ci return; 15662306a36Sopenharmony_ci 15762306a36Sopenharmony_ci if (pte_user_accessible_page(pte)) { 15862306a36Sopenharmony_ci page_table_check_clear(pte_pfn(pte), PAGE_SIZE >> PAGE_SHIFT); 15962306a36Sopenharmony_ci } 16062306a36Sopenharmony_ci} 16162306a36Sopenharmony_ciEXPORT_SYMBOL(__page_table_check_pte_clear); 16262306a36Sopenharmony_ci 16362306a36Sopenharmony_civoid __page_table_check_pmd_clear(struct mm_struct *mm, pmd_t pmd) 16462306a36Sopenharmony_ci{ 16562306a36Sopenharmony_ci if (&init_mm == mm) 16662306a36Sopenharmony_ci return; 16762306a36Sopenharmony_ci 16862306a36Sopenharmony_ci if (pmd_user_accessible_page(pmd)) { 16962306a36Sopenharmony_ci page_table_check_clear(pmd_pfn(pmd), PMD_SIZE >> PAGE_SHIFT); 17062306a36Sopenharmony_ci } 17162306a36Sopenharmony_ci} 17262306a36Sopenharmony_ciEXPORT_SYMBOL(__page_table_check_pmd_clear); 17362306a36Sopenharmony_ci 17462306a36Sopenharmony_civoid __page_table_check_pud_clear(struct mm_struct *mm, pud_t pud) 17562306a36Sopenharmony_ci{ 17662306a36Sopenharmony_ci if (&init_mm == mm) 17762306a36Sopenharmony_ci return; 17862306a36Sopenharmony_ci 17962306a36Sopenharmony_ci if (pud_user_accessible_page(pud)) { 18062306a36Sopenharmony_ci page_table_check_clear(pud_pfn(pud), PUD_SIZE >> PAGE_SHIFT); 18162306a36Sopenharmony_ci } 18262306a36Sopenharmony_ci} 18362306a36Sopenharmony_ciEXPORT_SYMBOL(__page_table_check_pud_clear); 18462306a36Sopenharmony_ci 18562306a36Sopenharmony_civoid __page_table_check_ptes_set(struct mm_struct *mm, pte_t *ptep, pte_t pte, 18662306a36Sopenharmony_ci unsigned int nr) 18762306a36Sopenharmony_ci{ 18862306a36Sopenharmony_ci unsigned int i; 18962306a36Sopenharmony_ci 19062306a36Sopenharmony_ci if (&init_mm == mm) 19162306a36Sopenharmony_ci return; 19262306a36Sopenharmony_ci 19362306a36Sopenharmony_ci for (i = 0; i < nr; i++) 19462306a36Sopenharmony_ci __page_table_check_pte_clear(mm, ptep_get(ptep + i)); 19562306a36Sopenharmony_ci if (pte_user_accessible_page(pte)) 19662306a36Sopenharmony_ci page_table_check_set(pte_pfn(pte), nr, pte_write(pte)); 19762306a36Sopenharmony_ci} 19862306a36Sopenharmony_ciEXPORT_SYMBOL(__page_table_check_ptes_set); 19962306a36Sopenharmony_ci 20062306a36Sopenharmony_civoid __page_table_check_pmd_set(struct mm_struct *mm, pmd_t *pmdp, pmd_t pmd) 20162306a36Sopenharmony_ci{ 20262306a36Sopenharmony_ci if (&init_mm == mm) 20362306a36Sopenharmony_ci return; 20462306a36Sopenharmony_ci 20562306a36Sopenharmony_ci __page_table_check_pmd_clear(mm, *pmdp); 20662306a36Sopenharmony_ci if (pmd_user_accessible_page(pmd)) { 20762306a36Sopenharmony_ci page_table_check_set(pmd_pfn(pmd), PMD_SIZE >> PAGE_SHIFT, 20862306a36Sopenharmony_ci pmd_write(pmd)); 20962306a36Sopenharmony_ci } 21062306a36Sopenharmony_ci} 21162306a36Sopenharmony_ciEXPORT_SYMBOL(__page_table_check_pmd_set); 21262306a36Sopenharmony_ci 21362306a36Sopenharmony_civoid __page_table_check_pud_set(struct mm_struct *mm, pud_t *pudp, pud_t pud) 21462306a36Sopenharmony_ci{ 21562306a36Sopenharmony_ci if (&init_mm == mm) 21662306a36Sopenharmony_ci return; 21762306a36Sopenharmony_ci 21862306a36Sopenharmony_ci __page_table_check_pud_clear(mm, *pudp); 21962306a36Sopenharmony_ci if (pud_user_accessible_page(pud)) { 22062306a36Sopenharmony_ci page_table_check_set(pud_pfn(pud), PUD_SIZE >> PAGE_SHIFT, 22162306a36Sopenharmony_ci pud_write(pud)); 22262306a36Sopenharmony_ci } 22362306a36Sopenharmony_ci} 22462306a36Sopenharmony_ciEXPORT_SYMBOL(__page_table_check_pud_set); 22562306a36Sopenharmony_ci 22662306a36Sopenharmony_civoid __page_table_check_pte_clear_range(struct mm_struct *mm, 22762306a36Sopenharmony_ci unsigned long addr, 22862306a36Sopenharmony_ci pmd_t pmd) 22962306a36Sopenharmony_ci{ 23062306a36Sopenharmony_ci if (&init_mm == mm) 23162306a36Sopenharmony_ci return; 23262306a36Sopenharmony_ci 23362306a36Sopenharmony_ci if (!pmd_bad(pmd) && !pmd_leaf(pmd)) { 23462306a36Sopenharmony_ci pte_t *ptep = pte_offset_map(&pmd, addr); 23562306a36Sopenharmony_ci unsigned long i; 23662306a36Sopenharmony_ci 23762306a36Sopenharmony_ci if (WARN_ON(!ptep)) 23862306a36Sopenharmony_ci return; 23962306a36Sopenharmony_ci for (i = 0; i < PTRS_PER_PTE; i++) { 24062306a36Sopenharmony_ci __page_table_check_pte_clear(mm, ptep_get(ptep)); 24162306a36Sopenharmony_ci addr += PAGE_SIZE; 24262306a36Sopenharmony_ci ptep++; 24362306a36Sopenharmony_ci } 24462306a36Sopenharmony_ci pte_unmap(ptep - PTRS_PER_PTE); 24562306a36Sopenharmony_ci } 24662306a36Sopenharmony_ci} 247