14 hotfixes. 10 are cc:stable. 11 are for MM.

Five are DAMON fixes.  One fixes an arm64 contpte bug where DAMON can
 write past the end of a page-table page, resulting in memory corruption
 and possible crashes.
 
 Two are hugetlb fixes.  One fixes an mremap() address calculation bug
 which can panic x86-64.
 
 There's also a missing anon_vma publication barrier which can result in
 hung tasks, and a writeback fix to keep long cgroup writeback drains from
 delaying Tasks-RCU grace periods.
 
 The remainder are smaller fixes and maintenance changes.
 -----BEGIN PGP SIGNATURE-----
 
 iHUEABYKAB0WIQTTMBEPP41GrTpTJgfdBJ7gKXxAjgUCarHHNAAKCRDdBJ7gKXxA
 jhb7AP4yE/k7RrZC6zWg4M9ejI3fVlHI9+EG1mCLGiV57jZ4mAEA9LLSZryOD6Nc
 zsaeCtZhHcEdxW6EhO18hMfH4b7oJA0=
 =alhC
 -----END PGP SIGNATURE-----

Merge tag 'mm-hotfixes-stable-2026-09-21-17-08' of git://git.kernel.org/pub/scm/linux/kernel/git/akpm/mm

Pull MM fixes from Andrew Morton:
 "14 hotfixes.  10 are cc:stable.  11 are for MM.

  Five DAMON fixes: one fixes an arm64 contpte bug where DAMON can write
  past the end of a page-table page, resulting in memory corruption and
  possible crashes.

  Two hugetlb fixes: one fixes an mremap() address calculation bug which
  can panic x86-64.

  There's also a missing anon_vma publication barrier which can result
  in hung tasks, and a writeback fix to keep long cgroup writeback
  drains from delaying Tasks-RCU grace periods.

  The remainder are smaller fixes and maintenance changes"

* tag 'mm-hotfixes-stable-2026-09-21-17-08' of git://git.kernel.org/pub/scm/linux/kernel/git/akpm/mm:
  MAINTAINERS: update Xu Xin's email
  writeback: report a Tasks-RCU quiescent state per cgwb drain pass
  mm/damon/core: reset invalid quota->charge_target_from
  MAINTAINERS: add Baoquan and Baolin as MGLRU reviewers
  mm/rmap: fix missing barrier between anon_vma init and vma->anon_vma publish
  mm/hugetlb: preserve mremap address delta when skipping page tables
  mm/damon/core: fix unconditionally skip last region
  mm/damon/vaddr: avoid hw-driven pte updates during damon_hugetlb_mkold()
  mm/damon/core: allow esz to be set to zero
  mm/damon/ops-common: use a page-aligned address in damon_ptep_mkold()
  ocfs2: make ocfs2_calc_xattr_init() return void
  mailmap: update Haowen Bai's email address
  selftests/cgroup: account for zswap shrinker writeback
  mm/hugetlb: do not dissolve gigantic pages without runtime support
This commit is contained in:
Linus Torvalds 2026-09-22 10:03:33 -07:00
commit a52a93358a
13 changed files with 121 additions and 51 deletions

View File

@ -355,6 +355,8 @@ Hans Verkuil <hverkuil@kernel.org> <hverkuil-cisco@xs4all.nl>
Hans Verkuil <hverkuil@kernel.org> <hansverk@cisco.com>
Hans Verkuil <hverkuil@kernel.org> <hans.verkuil@cisco.com>
Hao Ge <hao.ge@linux.dev> <gehao@kylinos.cn>
Haowen Bai <baihaowen88@gmail.com> <baihaowen@meizu.com>
Haowen Bai <baihaowen88@gmail.com> <calvin.bai@ugreen.com>
Harry Yoo <harry.yoo@oracle.com> <42.hyeyoo@gmail.com>
Harry Yoo <harry@kernel.org> <harry.yoo@oracle.com>
Heiko Carstens <hca@linux.ibm.com> <h.carstens@de.ibm.com>
@ -982,6 +984,8 @@ Wesley Cheng <quic_wcheng@quicinc.com> <wcheng@codeaurora.org>
Will Deacon <will@kernel.org> <will.deacon@arm.com>
Wolfram Sang <wsa@kernel.org> <w.sang@pengutronix.de>
Wolfram Sang <wsa@kernel.org> <wsa@the-dreams.de>
Xu Xin <xu.xin@linux.dev> <xu.xin16@zte.com.cn>
Xu Xin <xu.xin@linux.dev> xu xin <cgel.zte@gmail.com>
Yakir Yang <kuankuan.y@gmail.com> <ykk@rock-chips.com>
Yanteng Si <si.yanteng@linux.dev> <siyanteng@loongson.cn>
Ying Huang <huang.ying.caritas@gmail.com> <ying.huang@intel.com>

View File

@ -17194,7 +17194,7 @@ F: tools/testing/selftests/mm/gup_test.c
MEMORY MANAGEMENT - KSM (Kernel Samepage Merging)
M: Andrew Morton <akpm@linux-foundation.org>
M: David Hildenbrand <david@kernel.org>
R: Xu Xin <xu.xin16@zte.com.cn>
R: Xu Xin <xu.xin@linux.dev>
R: Chengming Zhou <chengming.zhou@linux.dev>
L: linux-mm@kvack.org
S: Maintained
@ -17242,6 +17242,8 @@ R: Barry Song <baohua@kernel.org>
R: Axel Rasmussen <axelrasmussen@google.com>
R: Yuanchu Xie <yuanchu@google.com>
R: Wei Xu <weixugc@google.com>
R: Baoquan He <baoquan.he@linux.dev>
R: Baolin Wang <baolin.wang@linux.alibaba.com>
L: linux-mm@kvack.org
S: Maintained
W: http://www.linux-mm.org

View File

@ -336,13 +336,8 @@ static int ocfs2_mknod(struct mnt_idmap *idmap,
goto leave;
/* calculate meta data/clusters for setting security and acl xattr */
status = ocfs2_calc_xattr_init(dir, mode, &si, &want_clusters,
&xattr_credits, &want_meta,
&acl_state);
if (status < 0) {
mlog_errno(status);
goto leave;
}
ocfs2_calc_xattr_init(dir, mode, &si, &want_clusters, &xattr_credits,
&want_meta, &acl_state);
/* Reserve a cluster if creating an extent based directory. */
if (S_ISDIR(mode) && !ocfs2_supports_inline_data(osb)) {

View File

@ -635,12 +635,11 @@ int ocfs2_calc_security_init(struct inode *dir,
return ret;
}
int ocfs2_calc_xattr_init(struct inode *dir, umode_t mode,
struct ocfs2_security_xattr_info *si,
int *want_clusters, int *xattr_credits,
int *want_meta, struct ocfs2_acl_state *acl_state)
void ocfs2_calc_xattr_init(struct inode *dir, umode_t mode,
struct ocfs2_security_xattr_info *si,
int *want_clusters, int *xattr_credits,
int *want_meta, struct ocfs2_acl_state *acl_state)
{
int ret = 0;
struct ocfs2_super *osb = OCFS2_SB(dir->i_sb);
int s_size = 0, a_size = 0, acl_len = 0, new_clusters;
@ -662,7 +661,7 @@ int ocfs2_calc_xattr_init(struct inode *dir, umode_t mode,
}
if (!(s_size + a_size))
return ret;
return;
/*
* The max space of security xattr taken inline is
@ -728,8 +727,6 @@ int ocfs2_calc_xattr_init(struct inode *dir, umode_t mode,
}
}
}
return ret;
}
static int ocfs2_xattr_extend_allocation(struct inode *inode,

View File

@ -59,10 +59,10 @@ int ocfs2_calc_security_init(struct inode *,
int *, int *, struct ocfs2_alloc_context **);
struct ocfs2_acl_state;
int ocfs2_calc_xattr_init(struct inode *dir, umode_t mode,
struct ocfs2_security_xattr_info *si,
int *want_clusters, int *xattr_credits,
int *want_meta, struct ocfs2_acl_state *acl_state);
void ocfs2_calc_xattr_init(struct inode *dir, umode_t mode,
struct ocfs2_security_xattr_info *si,
int *want_clusters, int *xattr_credits,
int *want_meta, struct ocfs2_acl_state *acl_state);
/*
* xattrs can live inside an inode, as part of an external xattr block,

View File

@ -910,8 +910,9 @@ static void cleanup_offline_cgwbs_workfn(struct work_struct *work)
continue;
spin_unlock_irq(&cgwb_lock);
while (cleanup_offline_cgwb(wb))
cond_resched();
do {
cond_resched_tasks_rcu_qs();
} while (cleanup_offline_cgwb(wb));
spin_lock_irq(&cgwb_lock);
wb_put(wb);

View File

@ -2342,36 +2342,39 @@ static bool damos_skip_charged_region(struct damon_target *t,
{
struct damos_quota *quota = &s->quota;
unsigned long sz_to_skip;
bool skip = false;
/* Skip previously charged regions */
if (quota->charge_target_from) {
if (t != quota->charge_target_from)
return true;
if (r == damon_last_region(t)) {
quota->charge_target_from = NULL;
quota->charge_addr_from = 0;
return true;
}
if (quota->charge_addr_from &&
r->ar.end <= quota->charge_addr_from)
return true;
r->ar.end <= quota->charge_addr_from) {
skip = true;
goto out;
}
if (quota->charge_addr_from && r->ar.start <
quota->charge_addr_from) {
sz_to_skip = ALIGN_DOWN(quota->charge_addr_from -
r->ar.start, min_region_sz);
if (!sz_to_skip) {
if (damon_sz_region(r) <= min_region_sz)
return true;
if (damon_sz_region(r) <= min_region_sz) {
skip = true;
goto out;
}
sz_to_skip = min_region_sz;
}
damon_split_region_at(t, r, sz_to_skip);
return true;
skip = true;
}
}
out:
if (r == damon_last_region(t)) {
quota->charge_target_from = NULL;
quota->charge_addr_from = 0;
}
return false;
return skip;
}
static void damos_update_stat(struct damos *s,
@ -3091,6 +3094,7 @@ static void damos_set_effective_quota(struct damon_ctx *ctx, struct damos *s)
struct damos_quota *quota = &s->quota;
unsigned long throughput;
unsigned long esz = ULONG_MAX;
unsigned long esz_time;
if (!quota->ms && list_empty(&quota->goals)) {
quota->esz = quota->sz;
@ -3111,8 +3115,8 @@ static void damos_set_effective_quota(struct damon_ctx *ctx, struct damos *s)
1000000, quota->total_charged_ns);
else
throughput = PAGE_SIZE * 1024;
esz = min(throughput * quota->ms, esz);
esz = max(ctx->min_region_sz, esz);
esz_time = max(throughput * quota->ms, ctx->min_region_sz);
esz = min(esz_time, esz);
}
if (quota->sz && quota->sz < esz)
@ -3241,8 +3245,15 @@ static void kdamond_apply_schemes(struct damon_ctx *c)
max_region_sz = damon_region_sz_limit(c);
mutex_lock(&c->walk_control_lock);
damon_for_each_target(t, c) {
if (c->ops.target_valid && c->ops.target_valid(t) == false)
if (c->ops.target_valid && c->ops.target_valid(t) == false) {
damon_for_each_scheme(s, c) {
if (s->quota.charge_target_from != t)
continue;
s->quota.charge_target_from = NULL;
s->quota.charge_addr_from = 0;
}
continue;
}
damos_apply_target(c, t, max_region_sz);
}

View File

@ -61,7 +61,12 @@ void damon_ptep_mkold(pte_t *pte, struct vm_area_struct *vma, unsigned long addr
* device aspects.
*/
if (likely(pte_present(pteval)))
young |= ptep_test_and_clear_young(vma, addr, pte);
/*
* Arch implementation of ptep_test_and_clear_young() may
* require aligned @addr
*/
young |= ptep_test_and_clear_young(vma, PAGE_ALIGN_DOWN(addr),
pte);
young |= mmu_notifier_clear_young(vma->vm_mm, addr, addr + PAGE_SIZE);
if (young)
folio_set_young(folio);

View File

@ -293,22 +293,29 @@ static int damon_mkold_pmd_entry(pmd_t *pmd, unsigned long addr,
}
#ifdef CONFIG_HUGETLB_PAGE
static bool damon_hugetlb_ptep_mkold(pte_t *pte, struct mm_struct *mm,
struct vm_area_struct *vma, unsigned long addr, pte_t *entry)
{
unsigned long psize = huge_page_size(hstate_vma(vma));
if (!pte_young(*entry))
return false;
*entry = huge_ptep_get_and_clear(mm, addr, pte, psize);
*entry = pte_mkold(*entry);
set_huge_pte_at(mm, addr, pte, *entry, psize);
return true;
}
static void damon_hugetlb_mkold(pte_t *pte, struct mm_struct *mm,
struct vm_area_struct *vma, unsigned long addr)
{
bool referenced = false;
pte_t entry = huge_ptep_get(mm, addr, pte);
struct folio *folio = pfn_folio(pte_pfn(entry));
unsigned long psize = huge_page_size(hstate_vma(vma));
folio_get(folio);
if (pte_young(entry)) {
referenced = true;
entry = pte_mkold(entry);
set_huge_pte_at(mm, addr, pte, entry, psize);
}
referenced = damon_hugetlb_ptep_mkold(pte, mm, vma, addr, &entry);
if (mmu_notifier_clear_young(mm, addr,
addr + huge_page_size(hstate_vma(vma))))
referenced = true;

View File

@ -1967,6 +1967,15 @@ int dissolve_free_hugetlb_folio(struct folio *folio)
struct hstate *h = folio_hstate(folio);
bool adjust_surplus = false;
/*
* remove_hugetlb_folio()/update_and_free_hugetlb_folio() bail
* for gigantic hstates without runtime support, so dissolving one
* here would leave it on the free list and, on vmemmap restore
* failure, the add_hugetlb_folio() rollback corrupts that list.
*/
if (hstate_is_gigantic_no_runtime(h))
goto out;
if (!available_huge_pages(h))
goto out;
@ -5161,18 +5170,21 @@ int move_hugetlb_page_tables(struct vm_area_struct *vma,
hugetlb_vma_lock_write(vma);
i_mmap_lock_write(mapping);
for (; old_addr < old_end; old_addr += sz, new_addr += sz) {
const unsigned long offset_to_last_entry =
(old_addr | last_addr_mask) - old_addr;
src_pte = hugetlb_walk(vma, old_addr, sz);
if (!src_pte) {
old_addr |= last_addr_mask;
new_addr |= last_addr_mask;
old_addr += offset_to_last_entry;
new_addr += offset_to_last_entry;
continue;
}
if (huge_pte_none(huge_ptep_get(mm, old_addr, src_pte)))
continue;
if (huge_pmd_unshare(&tlb, vma, old_addr, src_pte)) {
old_addr |= last_addr_mask;
new_addr |= last_addr_mask;
old_addr += offset_to_last_entry;
new_addr += offset_to_last_entry;
continue;
}

View File

@ -209,7 +209,11 @@ int __anon_vma_prepare(struct vm_area_struct *vma)
/* page_table_lock to protect against threads */
spin_lock(&mm->page_table_lock);
if (likely(!vma->anon_vma)) {
vma->anon_vma = anon_vma;
/*
* Make anon_vma fields visible before anon_vma is published.
* Paired with an address dependency in reusable_anon_vma().
*/
smp_store_release(&vma->anon_vma, anon_vma);
anon_vma_chain_assign(vma, avc, anon_vma);
anon_rmap_tree_insert(avc, anon_vma);
anon_vma->num_active_vmas++;

View File

@ -2094,6 +2094,13 @@ static int anon_vma_compatible(struct vm_area_struct *a, struct vm_area_struct *
* acceptable for merging, so we can do all of this optimistically. But
* we do that READ_ONCE() to make sure that we never re-load the pointer.
*
* The READ_ONCE() establishes an address dependency between anon_vma and
* any access to its fields, which pairs with the assignment to
* vma->anon_vma performed with release semantics in __anon_vma_prepare().
*
* This is especially important as anon_vma's are SLAB_TYPESAFE_BY_RCU so
* accessing an uninitialised anon_vma's fields may result in a UAF.
*
* IOW: that the "list_is_singular()" test on the anon_vma_chain only
* matters for the 'stable anon_vma' case (ie the thing we want to avoid
* is to return an anon_vma that is "complex" due to having gone through
@ -2108,6 +2115,7 @@ static struct anon_vma *reusable_anon_vma(struct vm_area_struct *old,
struct vm_area_struct *b)
{
if (anon_vma_compatible(a, b)) {
/* Paired with a memory barrier in __anon_vma_prepare(). */
struct anon_vma *anon_vma = READ_ONCE(old->anon_vma);
if (anon_vma && list_is_singular(&old->anon_vma_chain))

View File

@ -20,6 +20,7 @@ static int page_size;
#define PATH_ZSWAP "/sys/module/zswap"
#define PATH_ZSWAP_ENABLED "/sys/module/zswap/parameters/enabled"
#define PATH_ZSWAP_SHRINKER_ENABLED "/sys/module/zswap/parameters/shrinker_enabled"
#define PATH_ZSWAP_STORED_PAGES "/sys/kernel/debug/zswap/stored_pages"
static int read_int(const char *path, size_t *value)
@ -444,6 +445,16 @@ static int test_zswap_writeback_disabled(const char *root)
return test_zswap_writeback(root, false);
}
static bool zswap_shrinker_enabled(void)
{
char value[2];
if (read_text(PATH_ZSWAP_SHRINKER_ENABLED, value, sizeof(value)) <= 0)
return 0;
return value[0] == 'Y';
}
/*
* When trying to store a memcg page in zswap, if the memcg hits its memory
* limit in zswap, writeback should affect only the zswapped pages of that
@ -453,6 +464,7 @@ static int test_no_invasive_cgroup_shrink(const char *root)
{
int ret = KSFT_FAIL;
unsigned int off;
long zswpwb_before, zswpwb_after, zswpwb_target;
size_t allocation_size = page_size * 1024;
unsigned int nr_pages = allocation_size / page_size;
char zswap_max_buf[32], mem_max_buf[32];
@ -488,6 +500,14 @@ static int test_no_invasive_cgroup_shrink(const char *root)
if (cg_read_key_long(zw_group, "memory.stat", "zswapped") < 1)
goto out;
/* If the shrinker is enabled, try to let the writebacks finish first */
if (zswap_shrinker_enabled())
sleep(5);
zswpwb_before = get_cg_wb_count(zw_group);
if (zswpwb_before < 0)
goto out;
/* Push wb_group memory into zswap with hard-to-compress data to trigger wb */
if (cg_enter_current(wb_group))
goto out;
@ -500,9 +520,13 @@ static int test_no_invasive_cgroup_shrink(const char *root)
getrandom(&wb_allocation[off], page_size/4, 0);
}
/* Verify that only zswapped memory from gwb_group has been written back */
if (wait_for_writeback(wb_group, 5000) > 0 && get_cg_wb_count(zw_group) == 0)
/* Verify that only zswapped memory from wb_group has been written back */
zswpwb_target = wait_for_writeback(wb_group, 5000);
zswpwb_after = get_cg_wb_count(zw_group);
if (zswpwb_target > 0 && zswpwb_before == zswpwb_after)
ret = KSFT_PASS;
out:
cg_enter_current(root);
if (zw_group) {