Merge tag 'mm-hotfixes-stable-2026-08-06-18-44' of git://git.kernel.org/pub/scm/linux/kernel/git/akpm/mm

Pull MM fixes from Andrew Morton:
 "17 hotfixes.  15 are cc:stable.  16 are for MM.

  There's a patch series from Lorenzo "mm: fix UAF caused by race
  between ptdump and vmap pgtable freeing" which addresses a quite old
  bug in the ptdump code.

  And another series also from Lorenzo which fixes a four year old bug
  in the huge_zero_folio handling.

  A series from SJ fixes a few possible divide-by-zero issues which
  Sashiko sniffed out. And a series which fixes handling of the
  commit_inputs parameters.

  The remainder are singletons, please see their changelogs for details"

* tag 'mm-hotfixes-stable-2026-08-06-18-44' of git://git.kernel.org/pub/scm/linux/kernel/git/akpm/mm:
  mm/damon: adjust isolated pages stat for DAMOS_MIGRATE_{HOT,COLD}
  mm/damon/ops-common: putback folios on invalid migrate nid
  mm/huge_memory: initialise workingset state before folio split
  mm/page_table_check: skip special zero mappings
  mm/damon/lru_sort: skip damon_call() if ctx has not started
  mm/damon/reclaim: skip damon_call() if ctx has not started
  mm/damon/lru_sort: error out for >10000 active_mem_bp
  samples/damon/mtier: error out for zero quota goal target values
  mailmap: map old addresses to Danila Tikhonov
  mm/huge_memory: separate out CONFIG_PERSISTENT_HUGE_ZERO_FOLIO logic
  mm/huge_memory: fix huge_zero_pfn race
  MAINTAINERS: update address for Brendan Jackman
  mm/filemap: __filemap_add_folio() restore index before retrying
  microblaze: restore the page alignment of swapper_pg_dir
  arm64: remove redundant concurrent ptdump UAF mitigation
  mm/ptdump: always stabilise against page table freeing using init_mm
  mm/vmalloc: acquire init_mm lock on huge vmap to avoid ptdump UAF
This commit is contained in:
Linus Torvalds
2026-08-06 20:29:38 -07:00
19 changed files with 246 additions and 165 deletions
+3
View File
@@ -171,6 +171,7 @@ Boris Brezillon <bbrezillon@kernel.org> <b.brezillon@overkiz.com>
Boris Brezillon <bbrezillon@kernel.org> <boris.brezillon@bootlin.com>
Boris Brezillon <bbrezillon@kernel.org> <boris.brezillon@free-electrons.com>
Brendan Higgins <brendan.higgins@linux.dev> <brendanhiggins@google.com>
Brendan Jackman <brendan.jackman@linux.dev> <jackmanb@google.com>
Brian Avery <b.avery@hp.com>
Brian Cain <bcain@kernel.org> <brian.cain@oss.qualcomm.com>
Brian Cain <bcain@kernel.org> <bcain@quicinc.com>
@@ -233,6 +234,8 @@ Daniel Lezcano <daniel.lezcano@kernel.org> <daniel.lezcano@linexp.org>
Daniel Lezcano <daniel.lezcano@kernel.org> <dlezcano@fr.ibm.com>
Daniel Thompson <danielt@kernel.org> <daniel.thompson@linaro.org>
Daniele Alessandrelli <daniele.alessandrelli@gmail.com> <daniele.alessandrelli@intel.com>
Danila Tikhonov <danila@mainlining.org> <danila@jiaxyga.com>
Danila Tikhonov <danila@mainlining.org> <JIaxyga@protonmail.com>
Danilo Krummrich <dakr@kernel.org> <dakr@redhat.com>
David Brownell <david-b@pacbell.net>
David Collins <quic_collinsd@quicinc.com> <collinsd@codeaurora.org>
+1 -1
View File
@@ -17152,7 +17152,7 @@ M: Andrew Morton <akpm@linux-foundation.org>
M: Vlastimil Babka <vbabka@kernel.org>
R: Suren Baghdasaryan <surenb@google.com>
R: Michal Hocko <mhocko@suse.com>
R: Brendan Jackman <jackmanb@google.com>
R: Brendan Jackman <brendan.jackman@linux.dev>
R: Johannes Weiner <hannes@cmpxchg.org>
R: Zi Yan <ziy@nvidia.com>
L: linux-mm@kvack.org
-2
View File
@@ -7,8 +7,6 @@
#include <linux/ptdump.h>
DECLARE_STATIC_KEY_FALSE(arm64_ptdump_lock_key);
#ifdef CONFIG_PTDUMP
#include <linux/mm_types.h>
+4 -39
View File
@@ -49,8 +49,6 @@
#define NO_CONT_MAPPINGS BIT(1)
#define NO_EXEC_MAPPINGS BIT(2) /* assumes FEAT_HPDS is not used */
DEFINE_STATIC_KEY_FALSE(arm64_ptdump_lock_key);
u64 kimage_voffset __ro_after_init;
EXPORT_SYMBOL(kimage_voffset);
@@ -1864,8 +1862,7 @@ int pmd_clear_huge(pmd_t *pmdp)
return 1;
}
static int __pmd_free_pte_page(pmd_t *pmdp, unsigned long addr,
bool acquire_mmap_lock)
int pmd_free_pte_page(pmd_t *pmdp, unsigned long addr)
{
pte_t *table;
pmd_t pmd;
@@ -1877,25 +1874,13 @@ static int __pmd_free_pte_page(pmd_t *pmdp, unsigned long addr,
return 1;
}
/* See comment in pud_free_pmd_page for static key logic */
table = pte_offset_kernel(pmdp, addr);
pmd_clear(pmdp);
__flush_tlb_kernel_pgtable(addr);
if (static_branch_unlikely(&arm64_ptdump_lock_key) && acquire_mmap_lock) {
mmap_read_lock(&init_mm);
mmap_read_unlock(&init_mm);
}
pte_free_kernel(NULL, table);
return 1;
}
int pmd_free_pte_page(pmd_t *pmdp, unsigned long addr)
{
/* If ptdump is walking the pagetables, acquire init_mm.mmap_lock */
return __pmd_free_pte_page(pmdp, addr, /* acquire_mmap_lock = */ true);
}
int pud_free_pmd_page(pud_t *pudp, unsigned long addr)
{
pmd_t *table;
@@ -1911,36 +1896,16 @@ int pud_free_pmd_page(pud_t *pudp, unsigned long addr)
}
table = pmd_offset(pudp, addr);
/*
* Our objective is to prevent ptdump from reading a PMD table which has
* been freed. In this race, if pud_free_pmd_page observes the key on
* (which got flipped by ptdump) then the mmap lock sequence here will,
* as a result of the mmap write lock/unlock sequence in ptdump, give
* us the correct synchronization. If not, this means that ptdump has
* yet not started walking the pagetables - the sequence of barriers
* issued by __flush_tlb_kernel_pgtable() guarantees that ptdump will
* observe an empty PUD.
*/
pud_clear(pudp);
__flush_tlb_kernel_pgtable(addr);
if (static_branch_unlikely(&arm64_ptdump_lock_key)) {
mmap_read_lock(&init_mm);
mmap_read_unlock(&init_mm);
}
pmdp = table;
next = addr;
end = addr + PUD_SIZE;
do {
if (pmd_present(pmdp_get(pmdp)))
/*
* PMD has been isolated, so ptdump won't see it. No
* need to acquire init_mm.mmap_lock.
*/
__pmd_free_pte_page(pmdp, next, /* acquire_mmap_lock = */ false);
pmd_free_pte_page(pmdp, next);
} while (pmdp++, next += PMD_SIZE, next != end);
pud_clear(pudp);
__flush_tlb_kernel_pgtable(addr);
pmd_free(NULL, table);
return 1;
}
+2 -9
View File
@@ -283,13 +283,6 @@ void note_page_flush(struct ptdump_state *pt_st)
note_page(pt_st, 0, -1, pte_val(pte_zero));
}
static void arm64_ptdump_walk_pgd(struct ptdump_state *st, struct mm_struct *mm)
{
static_branch_inc(&arm64_ptdump_lock_key);
ptdump_walk_pgd(st, mm, NULL);
static_branch_dec(&arm64_ptdump_lock_key);
}
void ptdump_walk(struct seq_file *s, struct ptdump_info *info)
{
unsigned long end = ~0UL;
@@ -318,7 +311,7 @@ void ptdump_walk(struct seq_file *s, struct ptdump_info *info)
}
};
arm64_ptdump_walk_pgd(&st.ptdump, info->mm);
ptdump_walk_pgd(&st.ptdump, info->mm, NULL);
}
static void __init ptdump_initialize(void)
@@ -360,7 +353,7 @@ bool ptdump_check_wx(void)
}
};
arm64_ptdump_walk_pgd(&st.ptdump, &init_mm);
ptdump_walk_pgd(&st.ptdump, &init_mm, NULL);
if (st.wx_pages || st.uxn_pages) {
pr_warn("Checked W+X mappings: FAILED, %lu W+X pages found, %lu non-UXN pages found\n",
+2
View File
@@ -39,6 +39,8 @@
#include <asm/processor.h>
.section .data
/* The MMU requires a page aligned page directory. */
.align 12
.global swapper_pg_dir
swapper_pg_dir:
.space PAGE_SIZE
+1
View File
@@ -621,6 +621,7 @@ static inline void mmap_read_unlock(struct mm_struct *mm)
DEFINE_GUARD(mmap_read_lock, struct mm_struct *,
mmap_read_lock(_T), mmap_read_unlock(_T))
DEFINE_GUARD_COND(mmap_read_lock, _try, mmap_read_trylock(_T))
static inline void mmap_read_unlock_non_owner(struct mm_struct *mm)
{
+8 -5
View File
@@ -233,6 +233,8 @@ static int damon_lru_sort_add_quota_goals(struct damos *hot_scheme,
if (!active_mem_bp)
return 0;
if (10000 < active_mem_bp)
return -EINVAL;
goal = damos_new_quota_goal(DAMOS_QUOTA_ACTIVE_MEM_BP, active_mem_bp);
if (!goal)
return -ENOMEM;
@@ -349,6 +351,8 @@ static int damon_lru_sort_commit_inputs_fn(void *arg)
return damon_lru_sort_apply_parameters();
}
static bool damon_lru_sort_damon_has_started;
static int damon_lru_sort_commit_inputs_store(const char *val,
const struct kernel_param *kp)
{
@@ -369,11 +373,8 @@ static int damon_lru_sort_commit_inputs_store(const char *val,
if (!commit_inputs_request)
return 0;
/*
* Skip damon_call() if ctx is not initialized to avoid
* NULL pointer dereference.
*/
if (!ctx)
/* Skip damon_call() if ctx has not successfully started. */
if (!damon_lru_sort_damon_has_started)
return -EINVAL;
err = damon_call(ctx, &control);
@@ -424,6 +425,8 @@ static int damon_lru_sort_turn(bool on)
err = damon_start(&ctx, 1, true);
if (err)
return err;
if (!damon_lru_sort_damon_has_started)
damon_lru_sort_damon_has_started = true;
return damon_call(ctx, &call_control);
}
+12 -1
View File
@@ -375,6 +375,8 @@ keep:
while (!list_empty(folio_list)) {
folio = lru_to_folio(folio_list);
list_del(&folio->lru);
node_stat_sub_folio(folio, NR_ISOLATED_ANON +
folio_is_file_lru(folio));
folio_putback_lru(folio);
}
@@ -392,8 +394,17 @@ unsigned long damon_migrate_pages(struct list_head *folio_list, int target_nid)
return nr_migrated;
if (target_nid < 0 || target_nid >= MAX_NUMNODES ||
!node_state(target_nid, N_MEMORY))
!node_state(target_nid, N_MEMORY)) {
while (!list_empty(folio_list)) {
struct folio *folio = lru_to_folio(folio_list);
list_del(&folio->lru);
node_stat_sub_folio(folio, NR_ISOLATED_ANON +
folio_is_file_lru(folio));
folio_putback_lru(folio);
}
return nr_migrated;
}
noreclaim_flag = memalloc_noreclaim_save();
+2
View File
@@ -350,6 +350,8 @@ static unsigned long damon_pa_migrate(struct damon_region *r,
if (!folio_isolate_lru(folio))
goto put_folio;
node_stat_add_folio(folio, NR_ISOLATED_ANON +
folio_is_file_lru(folio));
list_add(&folio->lru, &folio_list);
put_folio:
addr += folio_size(folio);
+6 -5
View File
@@ -276,6 +276,8 @@ static int damon_reclaim_commit_inputs_fn(void *arg)
return damon_reclaim_apply_parameters();
}
static bool damon_reclaim_damon_has_started;
static int damon_reclaim_commit_inputs_store(const char *val,
const struct kernel_param *kp)
{
@@ -296,11 +298,8 @@ static int damon_reclaim_commit_inputs_store(const char *val,
if (!commit_inputs_request)
return 0;
/*
* Skip damon_call() if ctx is not initialized to avoid
* NULL pointer dereference.
*/
if (!ctx)
/* Skip damon_call() if ctx has not successfully started. */
if (!damon_reclaim_damon_has_started)
return -EINVAL;
err = damon_call(ctx, &control);
@@ -347,6 +346,8 @@ static int damon_reclaim_turn(bool on)
err = damon_start(&ctx, 1, true);
if (err)
return err;
if (!damon_reclaim_damon_has_started)
damon_reclaim_damon_has_started = true;
return damon_call(ctx, &call_control);
}
+2 -1
View File
@@ -649,7 +649,8 @@ static void damos_va_migrate_dests_add(struct folio *folio,
isolate:
if (!folio_isolate_lru(folio))
return;
node_stat_add_folio(folio, NR_ISOLATED_ANON +
folio_is_file_lru(folio));
list_add(&folio->lru, &migration_lists[i]);
}
+6
View File
@@ -931,6 +931,12 @@ unlock:
if (!xas_nomem(&xas, gfp))
break;
/*
* Lock has been dropped: start again with the original index
* and order (but now with the memory reserved by xas_nomem()).
*/
xas_set_order(&xas, index, forder);
}
if (xas_error(&xas))
+119 -74
View File
@@ -41,6 +41,7 @@
#include <linux/pgalloc.h>
#include <linux/pgalloc_tag.h>
#include <linux/pagewalk.h>
#include <linux/cleanup.h>
#include <asm/tlb.h>
#include "internal.h"
@@ -77,9 +78,15 @@ static unsigned long deferred_split_scan(struct shrinker *shrink,
struct shrink_control *sc);
static bool split_underused_thp = true;
static atomic_t huge_zero_refcount;
#define HUGE_ZERO_UNSET_PFN (~0UL)
struct folio *huge_zero_folio __read_mostly;
unsigned long huge_zero_pfn __read_mostly = ~0UL;
unsigned long huge_zero_pfn __read_mostly = HUGE_ZERO_UNSET_PFN;
#ifndef CONFIG_PERSISTENT_HUGE_ZERO_FOLIO
static atomic_t huge_zero_refcount;
static DEFINE_SPINLOCK(huge_zero_lock);
static struct shrinker *huge_zero_folio_shrinker;
#endif
unsigned long huge_anon_orders_always __read_mostly;
unsigned long huge_anon_orders_madvise __read_mostly;
unsigned long huge_anon_orders_inherit __read_mostly;
@@ -221,33 +228,74 @@ unsigned long __thp_vma_allowable_orders(struct vm_area_struct *vma,
return orders;
}
static bool get_huge_zero_folio(void)
static struct folio *alloc_huge_zero_folio(void)
{
struct folio *zero_folio;
retry:
if (likely(atomic_inc_not_zero(&huge_zero_refcount)))
return true;
zero_folio = folio_alloc((GFP_TRANSHUGE | __GFP_ZERO | __GFP_ZEROTAGS) &
~__GFP_MOVABLE,
HPAGE_PMD_ORDER);
if (!zero_folio) {
count_vm_event(THP_ZERO_PAGE_ALLOC_FAILED);
return false;
return NULL;
}
/* Ensure zero folio won't have large_rmappable flag set. */
folio_clear_large_rmappable(zero_folio);
preempt_disable();
if (cmpxchg(&huge_zero_folio, NULL, zero_folio)) {
preempt_enable();
folio_clear_large_rmappable(zero_folio); /* Explicitly not rmappable. */
return zero_folio;
}
#ifdef CONFIG_PERSISTENT_HUGE_ZERO_FOLIO
static int __init huge_zero_init(void)
{
huge_zero_folio = alloc_huge_zero_folio();
if (!huge_zero_folio) {
pr_warn("Allocating persistent huge zero folio failed\n");
} else {
huge_zero_pfn = folio_pfn(huge_zero_folio);
count_vm_event(THP_ZERO_PAGE_ALLOC);
}
return 0;
}
static void __init huge_zero_shrinker_exit(void)
{
}
struct folio *mm_get_huge_zero_folio(struct mm_struct *mm)
{
return huge_zero_folio;
}
void mm_put_huge_zero_folio(struct mm_struct *mm)
{
}
#else
static bool get_huge_zero_folio(void)
{
struct folio *zero_folio;
/* Paired with atomic_set_release(). */
if (likely(atomic_inc_not_zero(&huge_zero_refcount)))
return true;
zero_folio = alloc_huge_zero_folio();
if (unlikely(!zero_folio))
return false;
/* Paired with critical section in shrink_huge_zero_folio_scan(). */
spin_lock(&huge_zero_lock);
if (huge_zero_folio) {
/* Somebody else already installed it. */
atomic_inc(&huge_zero_refcount);
spin_unlock(&huge_zero_lock);
folio_put(zero_folio);
goto retry;
return true;
}
WRITE_ONCE(huge_zero_folio, zero_folio);
WRITE_ONCE(huge_zero_pfn, folio_pfn(zero_folio));
/* Paired with atomic_inc_not_zero(). +1 for shrinker pin. */
atomic_set_release(&huge_zero_refcount, 2);
spin_unlock(&huge_zero_lock);
/* We take additional reference here. It will be put back by shrinker */
atomic_set(&huge_zero_refcount, 2);
preempt_enable();
count_vm_event(THP_ZERO_PAGE_ALLOC);
return true;
}
@@ -258,14 +306,59 @@ static void put_huge_zero_folio(void)
* Counter should never go to zero here. Only shrinker can put
* last reference.
*/
BUG_ON(atomic_dec_and_test(&huge_zero_refcount));
WARN_ON_ONCE(atomic_dec_and_test(&huge_zero_refcount));
}
static unsigned long shrink_huge_zero_folio_count(struct shrinker *shrink,
struct shrink_control *sc)
{
/* we can free zero page only if last reference remains */
return atomic_read(&huge_zero_refcount) == 1 ? HPAGE_PMD_NR : 0;
}
static unsigned long shrink_huge_zero_folio_scan(struct shrinker *shrink,
struct shrink_control *sc)
{
struct folio *zero_folio;
/* Paired with critical section in get_huge_zero_folio(). */
scoped_guard(spinlock, &huge_zero_lock) {
/* Paired with atomic_inc_not_zero() in get_huge_zero_folio(). */
if (atomic_cmpxchg(&huge_zero_refcount, 1, 0) != 1)
return 0;
zero_folio = huge_zero_folio;
VM_WARN_ON_ONCE(!zero_folio);
WRITE_ONCE(huge_zero_folio, NULL);
WRITE_ONCE(huge_zero_pfn, HUGE_ZERO_UNSET_PFN);
}
folio_put(zero_folio);
return HPAGE_PMD_NR;
}
static int __init huge_zero_init(void)
{
huge_zero_folio_shrinker = shrinker_alloc(0, "thp-zero");
if (!huge_zero_folio_shrinker) {
shrinker_free(deferred_split_shrinker);
list_lru_destroy(&deferred_split_lru);
return -ENOMEM;
}
huge_zero_folio_shrinker->count_objects = shrink_huge_zero_folio_count;
huge_zero_folio_shrinker->scan_objects = shrink_huge_zero_folio_scan;
shrinker_register(huge_zero_folio_shrinker);
return 0;
}
static void __init huge_zero_shrinker_exit(void)
{
shrinker_free(huge_zero_folio_shrinker);
}
struct folio *mm_get_huge_zero_folio(struct mm_struct *mm)
{
if (IS_ENABLED(CONFIG_PERSISTENT_HUGE_ZERO_FOLIO))
return huge_zero_folio;
if (mm_flags_test(MMF_HUGE_ZERO_FOLIO, mm))
return READ_ONCE(huge_zero_folio);
@@ -280,35 +373,10 @@ struct folio *mm_get_huge_zero_folio(struct mm_struct *mm)
void mm_put_huge_zero_folio(struct mm_struct *mm)
{
if (IS_ENABLED(CONFIG_PERSISTENT_HUGE_ZERO_FOLIO))
return;
if (mm_flags_test(MMF_HUGE_ZERO_FOLIO, mm))
put_huge_zero_folio();
}
static unsigned long shrink_huge_zero_folio_count(struct shrinker *shrink,
struct shrink_control *sc)
{
/* we can free zero page only if last reference remains */
return atomic_read(&huge_zero_refcount) == 1 ? HPAGE_PMD_NR : 0;
}
static unsigned long shrink_huge_zero_folio_scan(struct shrinker *shrink,
struct shrink_control *sc)
{
if (atomic_cmpxchg(&huge_zero_refcount, 1, 0) == 1) {
struct folio *zero_folio = xchg(&huge_zero_folio, NULL);
BUG_ON(zero_folio == NULL);
WRITE_ONCE(huge_zero_pfn, ~0UL);
folio_put(zero_folio);
return HPAGE_PMD_NR;
}
return 0;
}
static struct shrinker *huge_zero_folio_shrinker;
#endif /* CONFIG_PERSISTENT_HUGE_ZERO_FOLIO */
#ifdef CONFIG_SYSFS
static ssize_t enabled_show(struct kobject *kobj,
@@ -972,39 +1040,14 @@ static int __init thp_shrinker_init(void)
deferred_split_shrinker->scan_objects = deferred_split_scan;
shrinker_register(deferred_split_shrinker);
if (IS_ENABLED(CONFIG_PERSISTENT_HUGE_ZERO_FOLIO)) {
/*
* Bump the reference of the huge_zero_folio and do not
* initialize the shrinker.
*
* huge_zero_folio will always be NULL on failure. We assume
* that get_huge_zero_folio() will most likely not fail as
* thp_shrinker_init() is invoked early on during boot.
*/
if (!get_huge_zero_folio())
pr_warn("Allocating persistent huge zero folio failed\n");
return 0;
}
huge_zero_folio_shrinker = shrinker_alloc(0, "thp-zero");
if (!huge_zero_folio_shrinker) {
shrinker_free(deferred_split_shrinker);
list_lru_destroy(&deferred_split_lru);
return -ENOMEM;
}
huge_zero_folio_shrinker->count_objects = shrink_huge_zero_folio_count;
huge_zero_folio_shrinker->scan_objects = shrink_huge_zero_folio_scan;
shrinker_register(huge_zero_folio_shrinker);
return 0;
return huge_zero_init();
}
static void __init thp_shrinker_exit(void)
{
shrinker_free(huge_zero_folio_shrinker);
shrinker_free(deferred_split_shrinker);
list_lru_destroy(&deferred_split_lru);
huge_zero_shrinker_exit();
}
static int __init hugepage_init(void)
@@ -4033,7 +4076,7 @@ static int __folio_split(struct folio *folio, unsigned int new_order,
gfp_t gfp;
mapping = folio->mapping;
min_order = mapping_min_folio_order(folio->mapping);
min_order = mapping_min_folio_order(mapping);
if (new_order < min_order) {
ret = -EINVAL;
goto out;
@@ -4047,6 +4090,8 @@ static int __folio_split(struct folio *folio, unsigned int new_order,
goto out;
}
mapping_set_update(&xas, mapping);
if (split_type == SPLIT_TYPE_UNIFORM) {
xas_set_order(&xas, folio->index, new_order);
xas_split_alloc(&xas, folio, old_order, gfp);
+16 -4
View File
@@ -151,18 +151,29 @@ void __page_table_check_pte_clear(struct mm_struct *mm, unsigned long addr,
if (&init_mm == mm)
return;
if (pte_user_accessible_page(mm, addr, pte))
if (pte_user_accessible_page(mm, addr, pte) && !pte_special(pte))
page_table_check_clear(pte_pfn(pte), PAGE_SIZE >> PAGE_SHIFT);
}
EXPORT_SYMBOL(__page_table_check_pte_clear);
static inline bool page_table_check_huge_zero_pmd(pmd_t pmd)
{
unsigned long pfn = pmd_pfn(pmd);
if (!pfn_valid(pfn))
return false;
return is_huge_zero_folio(page_folio(pfn_to_page(pfn)));
}
void __page_table_check_pmd_clear(struct mm_struct *mm, unsigned long addr,
pmd_t pmd)
{
if (&init_mm == mm)
return;
if (pmd_user_accessible_page(mm, addr, pmd))
if (pmd_user_accessible_page(mm, addr, pmd) &&
!page_table_check_huge_zero_pmd(pmd))
page_table_check_clear(pmd_pfn(pmd), PMD_SIZE >> PAGE_SHIFT);
}
EXPORT_SYMBOL(__page_table_check_pmd_clear);
@@ -208,7 +219,7 @@ void __page_table_check_ptes_set(struct mm_struct *mm, unsigned long addr,
for (i = 0; i < nr; i++)
__page_table_check_pte_clear(mm, addr + PAGE_SIZE * i, ptep_get(ptep + i));
if (pte_user_accessible_page(mm, addr, pte))
if (pte_user_accessible_page(mm, addr, pte) && !pte_special(pte))
page_table_check_set(pte_pfn(pte), nr, pte_write(pte));
}
EXPORT_SYMBOL(__page_table_check_ptes_set);
@@ -238,7 +249,8 @@ void __page_table_check_pmds_set(struct mm_struct *mm, unsigned long addr,
for (i = 0; i < nr; i++)
__page_table_check_pmd_clear(mm, addr + PMD_SIZE * i, *(pmdp + i));
if (pmd_user_accessible_page(mm, addr, pmd))
if (pmd_user_accessible_page(mm, addr, pmd) &&
!page_table_check_huge_zero_pmd(pmd))
page_table_check_set(pmd_pfn(pmd), stride * nr, pmd_write(pmd));
}
EXPORT_SYMBOL(__page_table_check_pmds_set);
+21 -15
View File
@@ -678,6 +678,8 @@ int walk_kernel_page_table_range_lockless(unsigned long start, unsigned long end
* will also not lock the PTEs for the pte_entry() callback.
*
* This is for debugging purposes ONLY.
*
* The mmap write lock must be held.
*/
int walk_page_range_debug(struct mm_struct *mm, unsigned long start,
unsigned long end, const struct mm_walk_ops *ops,
@@ -691,25 +693,29 @@ int walk_page_range_debug(struct mm_struct *mm, unsigned long start,
.no_vma = true
};
/* For convenience, we allow traversal of kernel mappings. */
if (mm == &init_mm)
return walk_kernel_page_table_range(start, end, ops,
pgd, private);
if (start >= end || !walk.mm)
/*
* When walking userland page tables, an mmap write lock must be held to
* account for munmap() downgrading to an mmap read lock when tearing
* down page tables.
*
* When walking kernel page tables, an mmap write lock must also be held
* to account for page table freeing on vmap huge page mapping.
*/
mmap_assert_write_locked(mm);
/*
* x86, arm64 ptdump allow walks of efi mm's and x86 ptdump allows walks
* of arbitrary mm's.
*
* However, they both must also hold the init_mm lock to account for
* concurrent kernel page table freeing.
*/
mmap_assert_write_locked(&init_mm);
if (start >= end)
return -EINVAL;
if (!check_ops_safe(ops))
return -EINVAL;
/*
* The mmap lock protects the page walker from changes to the page
* tables during the walk. However a read lock is insufficient to
* protect those areas which don't have a VMA as munmap() detaches
* the VMAs before downgrading to a read lock and actually tearing
* down PTEs/page tables. In which case, the mmap write lock should
* be held.
*/
mmap_assert_write_locked(mm);
return walk_pgd_range(start, end, &walk);
}
+7
View File
@@ -178,11 +178,18 @@ void ptdump_walk_pgd(struct ptdump_state *st, struct mm_struct *mm, pgd_t *pgd)
get_online_mems();
mmap_write_lock(mm);
/* To stabilise kernel page tables we must hold the init_mm lock too. */
if (mm != &init_mm)
mmap_write_lock_nested(&init_mm, SINGLE_DEPTH_NESTING);
while (range->start != range->end) {
walk_page_range_debug(mm, range->start, range->end,
&ptdump_ops, pgd, st);
range++;
}
if (mm != &init_mm)
mmap_write_unlock(&init_mm);
mmap_write_unlock(mm);
put_online_mems();
+31 -9
View File
@@ -43,6 +43,7 @@
#include <asm/tlbflush.h>
#include <asm/shmparam.h>
#include <linux/page_owner.h>
#include <linux/cleanup.h>
#define CREATE_TRACE_POINTS
#include <trace/events/vmalloc.h>
@@ -158,10 +159,21 @@ static int vmap_try_huge_pmd(pmd_t *pmd, unsigned long addr, unsigned long end,
if (!IS_ALIGNED(phys_addr, PMD_SIZE))
return 0;
if (pmd_present(*pmd) && !pmd_free_pte_page(pmd, addr))
return 0;
if (!pmd_present(*pmd))
return pmd_set_huge(pmd, phys_addr, prot);
return pmd_set_huge(pmd, phys_addr, prot);
/*
* Acquire the mmap read lock to exclude ptdump, which walks
* kernel page tables it does not own under the mmap write lock.
*
* Concurrent read lock holders are safe: each exclusively owns
* the range it operates on and cannot reach this page table.
*/
scoped_cond_guard(mmap_read_lock_try, return 0, &init_mm) {
if (!pmd_free_pte_page(pmd, addr))
return 0;
return pmd_set_huge(pmd, phys_addr, prot);
}
}
static int vmap_pmd_range(pud_t *pud, unsigned long addr, unsigned long end,
@@ -210,10 +222,15 @@ static int vmap_try_huge_pud(pud_t *pud, unsigned long addr, unsigned long end,
if (!IS_ALIGNED(phys_addr, PUD_SIZE))
return 0;
if (pud_present(*pud) && !pud_free_pmd_page(pud, addr))
return 0;
if (!pud_present(*pud))
return pud_set_huge(pud, phys_addr, prot);
return pud_set_huge(pud, phys_addr, prot);
/* See comment in vmap_try_huge_pmd(). */
scoped_cond_guard(mmap_read_lock_try, return 0, &init_mm) {
if (!pud_free_pmd_page(pud, addr))
return 0;
return pud_set_huge(pud, phys_addr, prot);
}
}
static int vmap_pud_range(p4d_t *p4d, unsigned long addr, unsigned long end,
@@ -262,10 +279,15 @@ static int vmap_try_huge_p4d(p4d_t *p4d, unsigned long addr, unsigned long end,
if (!IS_ALIGNED(phys_addr, P4D_SIZE))
return 0;
if (p4d_present(*p4d) && !p4d_free_pud_page(p4d, addr))
return 0;
if (!p4d_present(*p4d))
return p4d_set_huge(p4d, phys_addr, prot);
return p4d_set_huge(p4d, phys_addr, prot);
/* See comment in vmap_try_huge_pmd(). */
scoped_cond_guard(mmap_read_lock_try, return 0, &init_mm) {
if (!p4d_free_pud_page(p4d, addr))
return 0;
return p4d_set_huge(p4d, phys_addr, prot);
}
}
static int vmap_p4d_range(pgd_t *pgd, unsigned long addr, unsigned long end,
+3
View File
@@ -156,6 +156,9 @@ static struct damon_ctx *damon_sample_mtier_build_ctx(bool promote)
if (!scheme)
goto free_out;
damon_set_schemes(ctx, &scheme, 1);
/* zero target value causes division by zero in damos_quota_store() */
if (!node0_mem_used_bp || !node0_mem_free_bp)
goto free_out;
quota_goal = damos_new_quota_goal(
promote ? DAMOS_QUOTA_NODE_MEM_USED_BP :
DAMOS_QUOTA_NODE_MEM_FREE_BP,