Merge tag 'mm-hotfixes-stable-2026-07-06-17-49' of git://git.kernel.org/pub/scm/linux/kernel/git/akpm/mm

Pull misc fixes from Andrew Morton:
 "20 hotfixes. 17 are for MM. 12 are cc:stable and the remaining 8
  address post-7.1 issues or aren't considered suitable for backporting.

  Two patches from SJ addresses a couple of quite old DAMON issues. And
  two patches from Yichong Chen fixes tools/virtio build issues. The
  remaining patches are singletons"

* tag 'mm-hotfixes-stable-2026-07-06-17-49' of git://git.kernel.org/pub/scm/linux/kernel/git/akpm/mm:
  tools/include: include stdint.h for SIZE_MAX in overflow.h
  tools/virtio: add missing compat definitions for vhost_net_test
  mm: do file ownership checks with the proper mount idmap
  samples/damon/mtier: fail early if address range parameters are invalid
  mm: a second pagecache maintainer
  mm/damon: add a kernel-doc comment for damon_ctx->rnd_state
  mm/damon: add a kernel-doc comment for damon_ctx->probes
  mailmap: add entries for Radu Rendec
  selftests/mm: hmm-tests: include linux/mman.h to access MADV_COLLAPSE
  selftests/mm: pagemap_ioctl: use the correct page size for transact_test()
  fs/proc: fix KPF_KSM reported for all anonymous pages
  mm: page_ext: add count limit to page_ext_iter_next to prevent invalid PFN access
  mm/damon/ops-common: handle extreme intervals in damon_hot_score()
  MAINTAINERS: add Lance as an rmap reviewer
  mm/compaction: handle free_pages_prepare() properly in compaction_free()
  mm/damon/sysfs-schemes: put stats for scheme_add_dirs() internal error
  mm/damon/sysfs-schemes: fix dir put orders in access_pattern_add_dirs()
  mm: shrinker: fix NULL pointer dereference in debugfs
  mm: shrinker: fix shrinker_info teardown race with expansion
  selftests/mm: fix ksft_process_madv.sh test category
This commit is contained in:
Linus Torvalds
2026-07-06 18:51:36 -07:00
25 changed files with 187 additions and 43 deletions

View File

@@ -708,6 +708,10 @@ Qi Zheng <qi.zheng@linux.dev> <zhengqi.arch@bytedance.com>
Quentin Monnet <qmo@kernel.org> <quentin.monnet@netronome.com>
Quentin Monnet <qmo@kernel.org> <quentin@isovalent.com>
Quentin Perret <qperret@qperret.net> <quentin.perret@arm.com>
Radu Rendec <radu@rendec.net> <radu.rendec@ines.ro>
Radu Rendec <radu@rendec.net> <rrendec@arista.com>
Radu Rendec <radu@rendec.net> <radu.rendec@gmail.com>
Radu Rendec <radu@rendec.net> <rrendec@redhat.com>
Rae Moar <raemoar63@gmail.com> <rmoar@google.com>
Rafael J. Wysocki <rjw@rjwysocki.net> <rjw@sisk.pl>
Rajeev Nandan <quic_rajeevny@quicinc.com> <rajeevny@codeaurora.org>

View File

@@ -17204,6 +17204,7 @@ R: Liam R. Howlett <liam@infradead.org>
R: Vlastimil Babka <vbabka@kernel.org>
R: Harry Yoo <harry@kernel.org>
R: Jann Horn <jannh@google.com>
R: Lance Yang <lance.yang@linux.dev>
L: linux-mm@kvack.org
S: Maintained
F: include/linux/rmap.h
@@ -20405,7 +20406,7 @@ F: kernel/padata.c
PAGE CACHE
M: Matthew Wilcox (Oracle) <willy@infradead.org>
R: Jan Kara <jack@suse.cz>
M: Jan Kara <jack@suse.cz>
L: linux-fsdevel@vger.kernel.org
L: linux-mm@kvack.org
S: Supported

View File

@@ -173,7 +173,7 @@ u64 stable_page_flags(const struct page *page)
u |= 1 << KPF_MMAP;
if (is_anon) {
u |= 1 << KPF_ANON;
if (mapping & FOLIO_MAPPING_KSM)
if ((mapping & FOLIO_MAPPING_FLAGS) == FOLIO_MAPPING_KSM)
u |= 1 << KPF_KSM;
}

View File

@@ -843,11 +843,13 @@ struct damon_attrs {
* including damon_call() and damos_walk().
*
* @ops: Set of monitoring operations for given use cases.
* @probes: Head of probes (&damon_probe) list.
* @addr_unit: Scale factor for core to ops address conversion.
* @min_region_sz: Minimum region size.
* @pause: Pause kdamond main loop.
* @adaptive_targets: Head of monitoring targets (&damon_target) list.
* @schemes: Head of schemes (&damos) list.
* @rnd_state: Per-ctx PRNG state for damon_rand().
*/
struct damon_ctx {
struct damon_attrs attrs;
@@ -905,7 +907,6 @@ struct damon_ctx {
struct list_head adaptive_targets;
struct list_head schemes;
/* Per-ctx PRNG state for damon_rand(); kdamond is the sole consumer. */
struct rnd_state rnd_state;
};
@@ -1065,9 +1066,13 @@ static inline bool damon_target_has_pid(const struct damon_ctx *ctx)
static inline unsigned int damon_max_nr_accesses(const struct damon_attrs *attrs)
{
/* {aggr,sample}_interval are unsigned long, hence could overflow */
return min(attrs->aggr_interval / attrs->sample_interval,
unsigned long sample_interval;
unsigned long max_nr_accesses;
sample_interval = attrs->sample_interval ? : 1;
max_nr_accesses = min(attrs->aggr_interval / sample_interval,
(unsigned long)UINT_MAX);
return max_nr_accesses ? : 1;
}

View File

@@ -2444,6 +2444,11 @@ static inline struct mnt_idmap *file_mnt_idmap(const struct file *file)
return mnt_idmap(file->f_path.mnt);
}
static inline bool file_owner_or_capable(const struct file *file)
{
return inode_owner_or_capable(file_mnt_idmap(file), file_inode(file));
}
/**
* is_idmapped_mnt - check whether a mount is mapped
* @mnt: the mount to check

View File

@@ -120,14 +120,18 @@ struct page_ext_iter {
* page_ext_iter_begin() - Prepare for iterating through page extensions.
* @iter: page extension iterator.
* @pfn: PFN of the page we're interested in.
* @count: maximum number of page extensions to return.
*
* Must be called with RCU read lock taken.
*
* Return: NULL if no page_ext exists for this page.
*/
static inline struct page_ext *page_ext_iter_begin(struct page_ext_iter *iter,
unsigned long pfn)
unsigned long pfn, unsigned long count)
{
if (!count)
return NULL;
iter->index = 0;
iter->start_pfn = pfn;
iter->page_ext = page_ext_lookup(pfn);
@@ -138,19 +142,22 @@ static inline struct page_ext *page_ext_iter_begin(struct page_ext_iter *iter,
/**
* page_ext_iter_next() - Get next page extension
* @iter: page extension iterator.
* @count: maximum number of page extensions to return.
*
* Must be called with RCU read lock taken.
*
* Return: NULL if no next page_ext exists.
*/
static inline struct page_ext *page_ext_iter_next(struct page_ext_iter *iter)
static inline struct page_ext *page_ext_iter_next(struct page_ext_iter *iter,
unsigned long count)
{
unsigned long pfn;
if (WARN_ON_ONCE(!iter->page_ext))
return NULL;
iter->index++;
if (++iter->index >= count)
return NULL;
pfn = iter->start_pfn + iter->index;
if (page_ext_iter_next_fast_possible(pfn))
@@ -183,9 +190,9 @@ static inline struct page_ext *page_ext_iter_get(const struct page_ext_iter *ite
* IMPORTANT: must be called with RCU read lock taken.
*/
#define for_each_page_ext(__page, __pgcount, __page_ext, __iter) \
for (__page_ext = page_ext_iter_begin(&__iter, page_to_pfn(__page));\
__page_ext && __iter.index < __pgcount; \
__page_ext = page_ext_iter_next(&__iter))
for (__page_ext = page_ext_iter_begin(&__iter, page_to_pfn(__page), __pgcount); \
__page_ext; \
__page_ext = page_ext_iter_next(&__iter, __pgcount))
#else /* !CONFIG_PAGE_EXTENSION */
struct page_ext;

View File

@@ -1875,15 +1875,14 @@ static void compaction_free(struct folio *dst, unsigned long data)
int order = folio_order(dst);
struct page *page = &dst->page;
if (folio_put_testzero(dst)) {
free_pages_prepare(page, order);
if (folio_put_testzero(dst) && free_pages_prepare(page, order)) {
list_add(&dst->lru, &cc->freepages[order]);
cc->nr_freepages += 1 << order;
}
cc->nr_migratepages += 1 << order;
/*
* someone else has referenced the page, we cannot take it back to our
* free list.
* someone else has referenced the page or free_pages_prepare() fails,
* we cannot take it back to our free list.
*/
}

View File

@@ -143,6 +143,7 @@ int damon_hot_score(struct damon_ctx *c, struct damon_region *r,
* Transform it to fit in [0, DAMOS_MAX_SCORE]
*/
hotness = hotness * DAMOS_MAX_SCORE / DAMON_MAX_SUBSCORE;
hotness = max(min(hotness, DAMOS_MAX_SCORE), 0);
return hotness;
}

View File

@@ -1993,22 +1993,19 @@ static int damon_sysfs_access_pattern_add_dirs(
err = damon_sysfs_access_pattern_add_range_dir(access_pattern,
&access_pattern->sz, "sz");
if (err)
goto put_sz_out;
return err;
err = damon_sysfs_access_pattern_add_range_dir(access_pattern,
&access_pattern->nr_accesses, "nr_accesses");
if (err)
goto put_nr_accesses_sz_out;
goto put_sz_out;
err = damon_sysfs_access_pattern_add_range_dir(access_pattern,
&access_pattern->age, "age");
if (err)
goto put_age_nr_accesses_sz_out;
goto put_nr_accesses_sz_out;
return 0;
put_age_nr_accesses_sz_out:
kobject_put(&access_pattern->age->kobj);
access_pattern->age = NULL;
put_nr_accesses_sz_out:
kobject_put(&access_pattern->nr_accesses->kobj);
access_pattern->nr_accesses = NULL;
@@ -2516,12 +2513,12 @@ static int damon_sysfs_scheme_add_dirs(struct damon_sysfs_scheme *scheme)
goto put_filters_watermarks_quotas_access_pattern_out;
err = damon_sysfs_scheme_set_tried_regions(scheme);
if (err)
goto put_tried_regions_out;
goto put_stats_out;
return 0;
put_tried_regions_out:
kobject_put(&scheme->tried_regions->kobj);
scheme->tried_regions = NULL;
put_stats_out:
kobject_put(&scheme->stats->kobj);
scheme->stats = NULL;
put_filters_watermarks_quotas_access_pattern_out:
kobject_put(&scheme->ops_filters->kobj);
scheme->ops_filters = NULL;

View File

@@ -4704,7 +4704,7 @@ static inline bool can_do_cachestat(struct file *f)
{
if (f->f_mode & FMODE_WRITE)
return true;
if (inode_owner_or_capable(file_mnt_idmap(f), file_inode(f)))
if (file_owner_or_capable(f))
return true;
return file_permission(f, MAY_WRITE) == 0;
}

View File

@@ -336,8 +336,7 @@ static inline bool can_do_file_pageout(struct vm_area_struct *vma)
* otherwise we'd be including shared non-exclusive mappings, which
* opens a side channel.
*/
return inode_owner_or_capable(&nop_mnt_idmap,
file_inode(vma->vm_file)) ||
return file_owner_or_capable(vma->vm_file) ||
file_permission(vma->vm_file, MAY_WRITE) == 0;
}

View File

@@ -227,8 +227,7 @@ static inline bool can_do_mincore(struct vm_area_struct *vma)
* for writing; otherwise we'd be including shared non-exclusive
* mappings, which opens a side channel.
*/
return inode_owner_or_capable(&nop_mnt_idmap,
file_inode(vma->vm_file)) ||
return file_owner_or_capable(vma->vm_file) ||
file_permission(vma->vm_file, MAY_WRITE) == 0;
}

View File

@@ -59,12 +59,14 @@ static inline int shrinker_unit_alloc(struct shrinker_info *new,
return 0;
}
void free_shrinker_info(struct mem_cgroup *memcg)
static void __free_shrinker_info(struct mem_cgroup *memcg)
{
struct mem_cgroup_per_node *pn;
struct shrinker_info *info;
int nid;
lockdep_assert_held(&shrinker_mutex);
for_each_node(nid) {
pn = memcg->nodeinfo[nid];
info = rcu_dereference_protected(pn->shrinker_info, true);
@@ -74,6 +76,13 @@ void free_shrinker_info(struct mem_cgroup *memcg)
}
}
void free_shrinker_info(struct mem_cgroup *memcg)
{
mutex_lock(&shrinker_mutex);
__free_shrinker_info(memcg);
mutex_unlock(&shrinker_mutex);
}
int alloc_shrinker_info(struct mem_cgroup *memcg)
{
int nid, ret = 0;
@@ -98,8 +107,8 @@ int alloc_shrinker_info(struct mem_cgroup *memcg)
return ret;
err:
__free_shrinker_info(memcg);
mutex_unlock(&shrinker_mutex);
free_shrinker_info(memcg);
return -ENOMEM;
}

View File

@@ -183,10 +183,12 @@ int shrinker_debugfs_add(struct shrinker *shrinker)
}
shrinker->debugfs_entry = entry;
debugfs_create_file("count", 0440, entry, shrinker,
&shrinker_debugfs_count_fops);
debugfs_create_file("scan", 0220, entry, shrinker,
&shrinker_debugfs_scan_fops);
if (shrinker->count_objects)
debugfs_create_file("count", 0440, entry, shrinker,
&shrinker_debugfs_count_fops);
if (shrinker->scan_objects)
debugfs_create_file("scan", 0220, entry, shrinker,
&shrinker_debugfs_scan_fops);
return 0;
}

View File

@@ -120,6 +120,9 @@ static struct damon_ctx *damon_sample_mtier_build_ctx(bool promote)
addr.end = promote ? node1_end_addr : node0_end_addr;
}
if (addr.start >= addr.end)
goto free_out;
range.start = addr.start;
range.end = addr.end;

View File

@@ -1,4 +1,5 @@
/* SPDX-License-Identifier: GPL-2.0 OR MIT */
#include <stdint.h>
#ifndef __LINUX_OVERFLOW_H
#define __LINUX_OVERFLOW_H

View File

@@ -23,6 +23,7 @@
#include <time.h>
#include <pthread.h>
#include <limits.h>
#include <linux/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>

View File

@@ -1,4 +1,4 @@
#!/bin/sh -e
# SPDX-License-Identifier: GPL-2.0
./run_vmtests.sh -t mmap
./run_vmtests.sh -t process_madv

View File

@@ -1368,7 +1368,7 @@ void *thread_proc(void *mem)
ksft_exit_fail_msg("pthread_barrier_wait\n");
for (i = 0; i < access_per_thread; ++i)
__atomic_add_fetch(m + i * (0x1000 / sizeof(*m)), 1, __ATOMIC_SEQ_CST);
__atomic_add_fetch(m + i * (page_size / sizeof(*m)), 1, __ATOMIC_SEQ_CST);
ret = pthread_barrier_wait(&end_barrier);
if (ret && ret != PTHREAD_BARRIER_SERIAL_THREAD)
@@ -1403,15 +1403,15 @@ static void transact_test(int page_size)
if (pthread_barrier_init(&end_barrier, NULL, nthreads + 1))
ksft_exit_fail_msg("pthread_barrier_init\n");
mem = mmap(NULL, 0x1000 * nthreads * pages_per_thread, PROT_READ | PROT_WRITE,
mem = mmap(NULL, page_size * nthreads * pages_per_thread, PROT_READ | PROT_WRITE,
MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
if (mem == MAP_FAILED)
ksft_exit_fail_msg("Error mmap %s.\n", strerror(errno));
wp_init(mem, 0x1000 * nthreads * pages_per_thread);
wp_addr_range(mem, 0x1000 * nthreads * pages_per_thread);
wp_init(mem, page_size * nthreads * pages_per_thread);
wp_addr_range(mem, page_size * nthreads * pages_per_thread);
memset(mem, 0, 0x1000 * nthreads * pages_per_thread);
memset(mem, 0, page_size * nthreads * pages_per_thread);
count = get_dirty_pages_reset(mem, nthreads * pages_per_thread, 1, page_size);
ksft_test_result(count > 0, "%s count %u\n", __func__, count);
@@ -1420,7 +1420,7 @@ static void transact_test(int page_size)
finish = 0;
for (i = 0; i < nthreads; ++i)
pthread_create(&th, NULL, thread_proc, mem + 0x1000 * i * pages_per_thread);
pthread_create(&th, NULL, thread_proc, mem + page_size * i * pages_per_thread);
extra_pages = 0;
for (i = 0; i < iter_count; ++i) {

View File

@@ -0,0 +1,7 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef _ASM_PERCPU_TYPES_H
#define _ASM_PERCPU_TYPES_H
#define __percpu_qual
#endif /* _ASM_PERCPU_TYPES_H */

View File

@@ -0,0 +1,9 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef _LINUX_COMPLETION_H
#define _LINUX_COMPLETION_H
struct completion {
unsigned int done;
};
#endif /* _LINUX_COMPLETION_H */

View File

@@ -1,4 +1,5 @@
#ifndef LINUX_DEVICE_H
#define LINUX_DEVICE_H
struct device {
void *parent;

View File

@@ -61,5 +61,6 @@ enum dma_data_direction {
#define DMA_MAPPING_ERROR (~(dma_addr_t)0)
#define DMA_ATTR_CPU_CACHE_CLEAN (1UL << 11)
#define DMA_ATTR_DEBUGGING_IGNORE_CACHELINES 0
#endif

View File

@@ -0,0 +1,14 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef _LINUX_MOD_DEVICETABLE_H
#define _LINUX_MOD_DEVICETABLE_H
#include <linux/types.h>
struct virtio_device_id {
__u32 device;
__u32 vendor;
};
#define VIRTIO_DEV_ANY_ID 0xffffffff
#endif /* _LINUX_MOD_DEVICETABLE_H */

View File

@@ -0,0 +1,79 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef _LINUX_VIRTIO_FEATURES_H
#define _LINUX_VIRTIO_FEATURES_H
#include <linux/bug.h>
#include <linux/string.h>
#include <linux/types.h>
#define VIRTIO_FEATURES_U64S 2
#define VIRTIO_FEATURES_BITS (VIRTIO_FEATURES_U64S * 64)
#define VIRTIO_BIT(b) (1ULL << ((b) & 0x3f))
#define VIRTIO_U64(b) ((b) >> 6)
#define VIRTIO_DECLARE_FEATURES(name) \
union { \
u64 name; \
u64 name##_array[VIRTIO_FEATURES_U64S];\
}
static inline bool virtio_features_chk_bit(unsigned int bit)
{
return bit < VIRTIO_FEATURES_BITS;
}
static inline bool virtio_features_test_bit(const u64 *features,
unsigned int bit)
{
return virtio_features_chk_bit(bit) &&
!!(features[VIRTIO_U64(bit)] & VIRTIO_BIT(bit));
}
static inline void virtio_features_set_bit(u64 *features, unsigned int bit)
{
if (virtio_features_chk_bit(bit))
features[VIRTIO_U64(bit)] |= VIRTIO_BIT(bit);
}
static inline void virtio_features_clear_bit(u64 *features, unsigned int bit)
{
if (virtio_features_chk_bit(bit))
features[VIRTIO_U64(bit)] &= ~VIRTIO_BIT(bit);
}
static inline void virtio_features_zero(u64 *features)
{
memset(features, 0, sizeof(features[0]) * VIRTIO_FEATURES_U64S);
}
static inline void virtio_features_from_u64(u64 *features, u64 from)
{
virtio_features_zero(features);
features[0] = from;
}
static inline bool virtio_features_equal(const u64 *f1, const u64 *f2)
{
int i;
for (i = 0; i < VIRTIO_FEATURES_U64S; ++i)
if (f1[i] != f2[i])
return false;
return true;
}
static inline void virtio_features_copy(u64 *to, const u64 *from)
{
memcpy(to, from, sizeof(to[0]) * VIRTIO_FEATURES_U64S);
}
static inline void virtio_features_andnot(u64 *to, const u64 *f1, const u64 *f2)
{
int i;
for (i = 0; i < VIRTIO_FEATURES_U64S; i++)
to[i] = f1[i] & ~f2[i];
}
#endif /* _LINUX_VIRTIO_FEATURES_H */