mirror of
https://github.com/torvalds/linux.git
synced 2026-07-29 10:41:49 +02:00
13 hotfixes. All are cc:stable. 11 are for MM. All are singletons -
please see the changelogs for details. -----BEGIN PGP SIGNATURE----- iHUEABYKAB0WIQTTMBEPP41GrTpTJgfdBJ7gKXxAjgUCamfLHAAKCRDdBJ7gKXxA jrrMAQDQHf+VGi/lZaxtgfLv0OThODhzzckRI2dcTGG66YX/DQD/V4aTndWFUUpM j1aSODTTWeKi3Ija4E3dA7SIaZkqSAI= =s/6H -----END PGP SIGNATURE----- Merge tag 'mm-hotfixes-stable-2026-07-27-14-18' of git://git.kernel.org/pub/scm/linux/kernel/git/akpm/mm Pull misc fixes from Andrew Morton: "13 hotfixes. All are cc:stable. 11 are for MM. All are singletons - please see the changelogs for details" * tag 'mm-hotfixes-stable-2026-07-27-14-18' of git://git.kernel.org/pub/scm/linux/kernel/git/akpm/mm: fs/proc/task_mmu: fix PAGEMAP_SCAN written state for PMD holes mm/hugetlb: fix list corruption in allocate_file_region_entries() mm: mglru: fix stale batch updates after memcg reparenting selftest: fix headers in fclog.c ocfs2: fix boundary check in ocfs2_check_dir_entry() to use buffer offset mm/percpu-km: fix bitmap overflow and accounting in pcpu_create_chunk() mm/util: don't read __page_2 for order-1 folios in snapshot_page() mm/hugetlb: fix swap entry corruption when clearing uffd-wp at fork() mm: migrate_device: fix pte_pfn/pte_dirty called on non-present PTE fs/proc/task_mmu: fix PAGEMAP_SCAN written state for unpopulated ptes userfaultfd: wait on source PMD during UFFDIO_MOVE lib: test_hmm: use device devt for coherent device range selection mm/vmstat: fold stranded per-cpu node stats when a node comes online
This commit is contained in:
commit
62cc902415
|
|
@ -302,10 +302,11 @@ static int ocfs2_check_dir_entry(struct inode *dir,
|
|||
unsigned long offset)
|
||||
{
|
||||
const char *error_msg = NULL;
|
||||
unsigned long buf_offset = (char *)de - buf;
|
||||
unsigned long next_offset;
|
||||
int rlen;
|
||||
|
||||
if (offset > size - OCFS2_DIR_REC_LEN(1)) {
|
||||
if (buf_offset > size || size - buf_offset < OCFS2_DIR_REC_LEN(1)) {
|
||||
/* Dirent is (maybe partially) beyond the buffer
|
||||
* boundaries so touching 'de' members is unsafe.
|
||||
*/
|
||||
|
|
@ -316,7 +317,7 @@ static int ocfs2_check_dir_entry(struct inode *dir,
|
|||
}
|
||||
|
||||
rlen = le16_to_cpu(de->rec_len);
|
||||
next_offset = ((char *) de - buf) + rlen;
|
||||
next_offset = buf_offset + rlen;
|
||||
|
||||
if (unlikely(rlen < OCFS2_DIR_REC_LEN(1)))
|
||||
error_msg = "rec_len is smaller than minimal";
|
||||
|
|
|
|||
|
|
@ -2432,8 +2432,18 @@ static unsigned long pagemap_page_category(struct pagemap_scan_private *p,
|
|||
{
|
||||
unsigned long categories;
|
||||
|
||||
if (pte_none(pte))
|
||||
return 0;
|
||||
if (pte_none(pte)) {
|
||||
/*
|
||||
* An unpopulated pte carries no uffd-wp marker, i.e. it is not
|
||||
* write-protected, the same condition under which the present
|
||||
* and swap cases below report PAGE_IS_WRITTEN. Report it here
|
||||
* too so this generic path agrees with the PAGE_IS_WRITTEN fast
|
||||
* path in pagemap_scan_pmd_entry(), which reports pte_none as
|
||||
* written and, under PM_SCAN_WP_MATCHING, arms a marker. The
|
||||
* fast path applies no VMA test, so neither does this.
|
||||
*/
|
||||
return PAGE_IS_WRITTEN;
|
||||
}
|
||||
|
||||
if (pte_present(pte)) {
|
||||
struct page *page;
|
||||
|
|
@ -3039,12 +3049,28 @@ static int pagemap_scan_pte_hole(unsigned long addr, unsigned long end,
|
|||
{
|
||||
struct pagemap_scan_private *p = walk->private;
|
||||
struct vm_area_struct *vma = walk->vma;
|
||||
unsigned long categories;
|
||||
int ret, err;
|
||||
|
||||
if (!vma || !pagemap_scan_is_interesting_page(p->cur_vma_category, p))
|
||||
if (!vma)
|
||||
return 0;
|
||||
|
||||
ret = pagemap_scan_output(p->cur_vma_category, p, addr, &end);
|
||||
/*
|
||||
* In a uffd-wp VMA an unpopulated range is treated as written:
|
||||
* uffd-wp registration populates page tables and installs markers
|
||||
* with WP_UNPOPULATED, so a missing marker means the range was
|
||||
* zapped. See the pte_none() handling in pagemap_page_category().
|
||||
*
|
||||
* hugetlb differs, see pagemap_hugetlb_category().
|
||||
*/
|
||||
categories = p->cur_vma_category;
|
||||
if (userfaultfd_wp(vma) && !is_vm_hugetlb_page(vma))
|
||||
categories |= PAGE_IS_WRITTEN;
|
||||
|
||||
if (!pagemap_scan_is_interesting_page(categories, p))
|
||||
return 0;
|
||||
|
||||
ret = pagemap_scan_output(categories, p, addr, &end);
|
||||
if (addr == end)
|
||||
return ret;
|
||||
|
||||
|
|
|
|||
|
|
@ -1493,6 +1493,31 @@ static inline void lruvec_lock_irq(struct lruvec *lruvec)
|
|||
spin_lock_irq(&lruvec->lru_lock);
|
||||
}
|
||||
|
||||
static inline struct lruvec *lruvec_live_lock_irq(struct lruvec *lruvec)
|
||||
{
|
||||
#ifdef CONFIG_MEMCG
|
||||
struct pglist_data *pgdat = lruvec_pgdat(lruvec);
|
||||
struct mem_cgroup *memcg = lruvec_memcg(lruvec);
|
||||
|
||||
rcu_read_lock();
|
||||
|
||||
/*
|
||||
* The memcg can be NULL when the memory controller is disabled.
|
||||
* Otherwise, the caller keeps the memcg owning @lruvec alive.
|
||||
*/
|
||||
while (unlikely(memcg && css_is_dying(&memcg->css))) {
|
||||
memcg = parent_mem_cgroup(memcg);
|
||||
lruvec = mem_cgroup_lruvec(memcg, pgdat);
|
||||
}
|
||||
|
||||
spin_lock_irq(&lruvec->lru_lock);
|
||||
#else
|
||||
lruvec_lock_irq(lruvec);
|
||||
#endif
|
||||
|
||||
return lruvec;
|
||||
}
|
||||
|
||||
static inline void lruvec_unlock(struct lruvec *lruvec)
|
||||
{
|
||||
spin_unlock(&lruvec->lru_lock);
|
||||
|
|
|
|||
|
|
@ -581,7 +581,7 @@ static int dmirror_allocate_chunk(struct dmirror_device *mdevice,
|
|||
devmem->pagemap.type = MEMORY_DEVICE_PRIVATE;
|
||||
break;
|
||||
case HMM_DMIRROR_MEMORY_DEVICE_COHERENT:
|
||||
devmem->pagemap.range.start = (MINOR(mdevice->cdevice.dev) - 2) ?
|
||||
devmem->pagemap.range.start = (MINOR(mdevice->device.devt) - 2) ?
|
||||
spm_addr_dev0 :
|
||||
spm_addr_dev1;
|
||||
devmem->pagemap.range.end = devmem->pagemap.range.start +
|
||||
|
|
|
|||
|
|
@ -2774,7 +2774,7 @@ int move_pages_huge_pmd(struct mm_struct *mm, pmd_t *dst_pmd, pmd_t *src_pmd, pm
|
|||
if (!pmd_trans_huge(src_pmdval)) {
|
||||
spin_unlock(src_ptl);
|
||||
if (pmd_is_migration_entry(src_pmdval)) {
|
||||
pmd_migration_entry_wait(mm, &src_pmdval);
|
||||
pmd_migration_entry_wait(mm, src_pmd);
|
||||
return -EAGAIN;
|
||||
}
|
||||
return -ENOENT;
|
||||
|
|
|
|||
12
mm/hugetlb.c
12
mm/hugetlb.c
|
|
@ -693,7 +693,7 @@ static int allocate_file_region_entries(struct resv_map *resv,
|
|||
|
||||
spin_lock(&resv->lock);
|
||||
|
||||
list_splice(&allocated_regions, &resv->region_cache);
|
||||
list_splice_init(&allocated_regions, &resv->region_cache);
|
||||
resv->region_cache_count += to_allocate;
|
||||
}
|
||||
|
||||
|
|
@ -4917,8 +4917,12 @@ int copy_hugetlb_page_range(struct mm_struct *dst, struct mm_struct *src,
|
|||
|
||||
softleaf = softleaf_from_pte(entry);
|
||||
if (unlikely(softleaf_is_hwpoison(softleaf))) {
|
||||
if (!userfaultfd_wp(dst_vma))
|
||||
entry = huge_pte_clear_uffd_wp(entry);
|
||||
/*
|
||||
* A hwpoison entry never carries the uffd-wp bit: it is
|
||||
* installed fresh by make_hwpoison_entry() and
|
||||
* hugetlb_change_protection() leaves it untouched, so
|
||||
* there is nothing to clear for the child.
|
||||
*/
|
||||
set_huge_pte_at(dst, addr, dst_pte, entry, sz);
|
||||
} else if (unlikely(softleaf_is_migration(softleaf))) {
|
||||
bool uffd_wp = pte_swp_uffd_wp(entry);
|
||||
|
|
@ -4936,7 +4940,7 @@ int copy_hugetlb_page_range(struct mm_struct *dst, struct mm_struct *src,
|
|||
set_huge_pte_at(src, addr, src_pte, entry, sz);
|
||||
}
|
||||
if (!userfaultfd_wp(dst_vma))
|
||||
entry = huge_pte_clear_uffd_wp(entry);
|
||||
entry = pte_swp_clear_uffd_wp(entry);
|
||||
set_huge_pte_at(dst, addr, dst_pte, entry, sz);
|
||||
} else if (unlikely(pte_is_marker(entry))) {
|
||||
const pte_marker marker = copy_pte_marker(softleaf, dst_vma);
|
||||
|
|
|
|||
|
|
@ -401,7 +401,8 @@ static int migrate_vma_collect_pmd(pmd_t *pmdp,
|
|||
bool anon_exclusive;
|
||||
pte_t swp_pte;
|
||||
|
||||
flush_cache_page(vma, addr, pte_pfn(pte));
|
||||
if (pte_present(pte))
|
||||
flush_cache_page(vma, addr, pte_pfn(pte));
|
||||
anon_exclusive = folio_test_anon(folio) &&
|
||||
PageAnonExclusive(page);
|
||||
if (anon_exclusive) {
|
||||
|
|
@ -422,7 +423,7 @@ static int migrate_vma_collect_pmd(pmd_t *pmdp,
|
|||
migrate->cpages++;
|
||||
|
||||
/* Set the dirty flag on the folio now the pte is gone. */
|
||||
if (pte_dirty(pte))
|
||||
if (pte_present(pte) && pte_dirty(pte))
|
||||
folio_mark_dirty(folio);
|
||||
|
||||
/* Setup special migration page table entry */
|
||||
|
|
|
|||
15
mm/mm_init.c
15
mm/mm_init.c
|
|
@ -1540,7 +1540,7 @@ void __ref free_area_init_core_hotplug(struct pglist_data *pgdat)
|
|||
{
|
||||
int nid = pgdat->node_id;
|
||||
enum zone_type z;
|
||||
int cpu;
|
||||
int cpu, i;
|
||||
|
||||
pgdat_init_internals(pgdat);
|
||||
|
||||
|
|
@ -1558,10 +1558,17 @@ void __ref free_area_init_core_hotplug(struct pglist_data *pgdat)
|
|||
pgdat->node_start_pfn = 0;
|
||||
pgdat->node_present_pages = 0;
|
||||
|
||||
for_each_online_cpu(cpu) {
|
||||
struct per_cpu_nodestat *p;
|
||||
/*
|
||||
* Hot-unplug can leave per-cpu vmstat deltas unfolded (folders skip
|
||||
* offline nodes) - reconcile this at online. Foreign access to counters
|
||||
* is safe: the node is not online yet and we hold the hotplug lock.
|
||||
*/
|
||||
for_each_possible_cpu(cpu) {
|
||||
struct per_cpu_nodestat *p = per_cpu_ptr(pgdat->per_cpu_nodestats, cpu);
|
||||
|
||||
p = per_cpu_ptr(pgdat->per_cpu_nodestats, cpu);
|
||||
for (i = 0; i < NR_VM_NODE_STAT_ITEMS; i++)
|
||||
if (p->vm_node_stat_diff[i])
|
||||
node_page_state_add(p->vm_node_stat_diff[i], pgdat, i);
|
||||
memset(p, 0, sizeof(*p));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ static struct pcpu_chunk *pcpu_create_chunk(gfp_t gfp)
|
|||
chunk->base_addr = page_address(pages);
|
||||
|
||||
spin_lock_irqsave(&pcpu_lock, flags);
|
||||
pcpu_chunk_populated(chunk, 0, nr_pages);
|
||||
pcpu_chunk_populated(chunk, 0, chunk->nr_pages);
|
||||
spin_unlock_irqrestore(&pcpu_lock, flags);
|
||||
|
||||
pcpu_stats_chunk_alloc();
|
||||
|
|
|
|||
|
|
@ -1353,7 +1353,7 @@ void snapshot_page(struct page_snapshot *ps, const struct page *page)
|
|||
if (ps->idx < MAX_FOLIO_NR_PAGES) {
|
||||
memcpy(&ps->folio_snapshot, foliop, 2 * sizeof(struct page));
|
||||
nr_pages = folio_nr_pages(&ps->folio_snapshot);
|
||||
if (nr_pages > 1)
|
||||
if (nr_pages > 2)
|
||||
memcpy(&ps->folio_snapshot.__page_2, &foliop->__page_2,
|
||||
sizeof(struct page));
|
||||
set_ps_flags(ps, foliop, page);
|
||||
|
|
|
|||
11
mm/vmscan.c
11
mm/vmscan.c
|
|
@ -3265,7 +3265,7 @@ static void update_batch_size(struct lru_gen_mm_walk *walk, struct folio *folio,
|
|||
static void reset_batch_size(struct lru_gen_mm_walk *walk)
|
||||
{
|
||||
int gen, type, zone;
|
||||
struct lruvec *lruvec = walk->lruvec;
|
||||
struct lruvec *lruvec = lruvec_live_lock_irq(walk->lruvec);
|
||||
struct lru_gen_folio *lrugen = &lruvec->lrugen;
|
||||
|
||||
walk->batched = 0;
|
||||
|
|
@ -3285,6 +3285,8 @@ static void reset_batch_size(struct lru_gen_mm_walk *walk)
|
|||
lru += LRU_ACTIVE;
|
||||
__update_lru_size(lruvec, lru, zone, delta);
|
||||
}
|
||||
|
||||
lruvec_unlock_irq(lruvec);
|
||||
}
|
||||
|
||||
static int should_skip_vma(unsigned long start, unsigned long end, struct mm_walk *args)
|
||||
|
|
@ -3779,11 +3781,8 @@ static void walk_mm(struct mm_struct *mm, struct lru_gen_mm_walk *walk)
|
|||
mmap_read_unlock(mm);
|
||||
}
|
||||
|
||||
if (walk->batched) {
|
||||
lruvec_lock_irq(lruvec);
|
||||
if (walk->batched)
|
||||
reset_batch_size(walk);
|
||||
lruvec_unlock_irq(lruvec);
|
||||
}
|
||||
|
||||
cond_resched();
|
||||
} while (err == -EAGAIN);
|
||||
|
|
@ -4867,9 +4866,7 @@ static int evict_folios(unsigned long nr_to_scan, struct lruvec *lruvec,
|
|||
walk = current->reclaim_state->mm_walk;
|
||||
if (walk && walk->batched) {
|
||||
walk->lruvec = lruvec;
|
||||
lruvec_lock_irq(lruvec);
|
||||
reset_batch_size(walk);
|
||||
lruvec_unlock_irq(lruvec);
|
||||
}
|
||||
|
||||
mod_lruvec_state(lruvec, PGDEMOTE_KSWAPD + reclaimer_offset(sc),
|
||||
|
|
|
|||
|
|
@ -6,10 +6,8 @@
|
|||
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <sched.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/mount.h>
|
||||
|
||||
|
|
|
|||
|
|
@ -1051,6 +1051,57 @@ static void test_simple(void)
|
|||
ksft_test_result(i == TEST_ITERATIONS, "Test %s\n", __func__);
|
||||
}
|
||||
|
||||
/*
|
||||
* A range that was populated and then MADV_DONTNEED'd is genuine pte_none
|
||||
* with no uffd-wp marker. Such a pte must read the same regardless of which
|
||||
* PAGEMAP_SCAN path serves the request: both the PAGE_IS_WRITTEN fast path and
|
||||
* the generic path (reached e.g. via category_anyof_mask) must report every
|
||||
* page written.
|
||||
*/
|
||||
static void unpopulated_scan_test(void)
|
||||
{
|
||||
int npages = 16, i;
|
||||
long mem_size = npages * page_size;
|
||||
struct page_region regions[16];
|
||||
long fast = 0, slow = 0, ret;
|
||||
char *mem;
|
||||
|
||||
mem = mmap(NULL, mem_size, PROT_READ | PROT_WRITE,
|
||||
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
|
||||
if (mem == MAP_FAILED)
|
||||
ksft_exit_fail_msg("%s mmap failed\n", __func__);
|
||||
|
||||
wp_init(mem, mem_size);
|
||||
|
||||
/* Populate, then drop: the ptes become pte_none without a marker. */
|
||||
memset(mem, 1, mem_size);
|
||||
if (madvise(mem, mem_size, MADV_DONTNEED))
|
||||
ksft_exit_fail_msg("%s MADV_DONTNEED failed\n", __func__);
|
||||
|
||||
/* Fast path: category_mask == return_mask == PAGE_IS_WRITTEN. */
|
||||
ret = pagemap_ioctl(mem, mem_size, regions, npages, 0, 0,
|
||||
PAGE_IS_WRITTEN, 0, 0, PAGE_IS_WRITTEN);
|
||||
if (ret < 0)
|
||||
ksft_exit_fail_msg("%s fast scan failed\n", __func__);
|
||||
for (i = 0; i < ret; i++)
|
||||
fast += LEN(regions[i]);
|
||||
|
||||
/* Generic path: same query expressed via category_anyof_mask. */
|
||||
ret = pagemap_ioctl(mem, mem_size, regions, npages, 0, 0,
|
||||
0, PAGE_IS_WRITTEN, 0, PAGE_IS_WRITTEN);
|
||||
if (ret < 0)
|
||||
ksft_exit_fail_msg("%s generic scan failed\n", __func__);
|
||||
for (i = 0; i < ret; i++)
|
||||
slow += LEN(regions[i]);
|
||||
|
||||
ksft_test_result(fast == npages && slow == npages,
|
||||
"%s unpopulated ptes reported written by both paths (%ld, %ld of %d)\n",
|
||||
__func__, fast, slow, npages);
|
||||
|
||||
wp_free(mem, mem_size);
|
||||
munmap(mem, mem_size);
|
||||
}
|
||||
|
||||
int sanity_tests(void)
|
||||
{
|
||||
unsigned long long mem_size, vec_size;
|
||||
|
|
@ -1559,7 +1610,7 @@ int main(int __attribute__((unused)) argc, char *argv[])
|
|||
if (!hugetlb_setup_default(4))
|
||||
ksft_print_msg("HugeTLB test will be skipped\n");
|
||||
|
||||
ksft_set_plan(117);
|
||||
ksft_set_plan(118);
|
||||
|
||||
page_size = getpagesize();
|
||||
hpage_size = read_pmd_pagesize();
|
||||
|
|
@ -1737,6 +1788,9 @@ int main(int __attribute__((unused)) argc, char *argv[])
|
|||
/* 17. ZEROPFN tests */
|
||||
zeropfn_tests();
|
||||
|
||||
/* 18. Unpopulated pte scan-path consistency */
|
||||
unpopulated_scan_test();
|
||||
|
||||
close(pagemap_fd);
|
||||
ksft_finished();
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user