* [PATCH RFC 1/5] memcg: move memcg private ID refcount to objcg
2026-08-13 8:52 [PATCH RFC 0/5] memcg: fix dying memcg pinned by swapped out shmem pages Bingfang Guo via B4 Relay
@ 2026-08-13 8:52 ` Bingfang Guo via B4 Relay
2026-08-13 15:09 ` Bingfang Guo
2026-08-13 8:52 ` [PATCH RFC 2/5] memcg: get stable memcg first before getting memcgid reference Bingfang Guo via B4 Relay
` (4 subsequent siblings)
5 siblings, 1 reply; 8+ messages in thread
From: Bingfang Guo via B4 Relay @ 2026-08-13 8:52 UTC (permalink / raw)
To: Johannes Weiner, Michal Hocko, Roman Gushchin, Shakeel Butt,
Muchun Song, Andrew Morton, Dave Chinner, Qi Zheng,
David Hildenbrand, Lorenzo Stoakes, Kairui Song, Barry Song,
Axel Rasmussen, Yuanchu Xie, Wei Xu
Cc: cgroups, linux-mm, linux-kernel, Bingfang Guo, Bingfang Guo
From: Bingfang Guo <bingfangguo@tencent.com>
In the previous series by Muchun Song and Qi Zheng, folios are charged
to the objcg and reparented as the memcg offlines. Same can be done to
memcg private ID and its main user: swap entries. Make the memcgid
xarray hold a pointer and a reference to an objcg of the memcg, which is
used to find the memcg (or its parent) later on. The online state now
pins the objcg instead of the css, so swapped out pages no longer pin
the dying memcg.
The id reference held by the online state is released in css_released()
after reparenting instead of in css_offline(). This is the key
invariant the rest of the series builds on: css_offline() runs while
other css references may still be held, but css_released() only runs
once the last reference is gone, so a caller holding a memcg reference
can always count on the id refcount being alive. To prevent races
between memcgid put in css offline and memcgid get, the release is put
off till css_released, which could delay the release of the memcgid and
the objcg it pins, but overall it should be fine.
After reparenting, the objcg points to a live ancestor, so
mem_cgroup_from_private_id() now returns that ancestor instead of the
memcg the ID originally belonged to. Callers that need the exact memcg
are fixed in patch 5.
Signed-off-by: Bingfang Guo <bingfangguo@tencent.com>
---
include/linux/memcontrol.h | 10 +++--
mm/memcontrol.c | 99 ++++++++++++++++++++++++++++++++--------------
2 files changed, 76 insertions(+), 33 deletions(-)
diff --git a/include/linux/memcontrol.h b/include/linux/memcontrol.h
index 8170bb8066a22..c33ec7efad50b 100644
--- a/include/linux/memcontrol.h
+++ b/include/linux/memcontrol.h
@@ -191,6 +191,7 @@ struct obj_cgroup {
struct rcu_head rcu;
};
bool is_root;
+ refcount_t id_ref;
};
/*
@@ -202,8 +203,8 @@ struct obj_cgroup {
struct mem_cgroup {
struct cgroup_subsys_state css;
- /* Private memcg ID. Used to ID objects that outlive the cgroup */
- struct mem_cgroup_private_id id;
+ /* The objcg holding private memcg ID. */
+ struct obj_cgroup *id_objcg;
/* Accounted resources */
struct page_counter memory; /* Both v1 & v2 */
@@ -270,6 +271,9 @@ struct mem_cgroup {
#endif
int kmemcg_id;
+ /* Private memcg ID. Used to ID objects that outlive the cgroup */
+ int id;
+
struct memcg_vmstats_percpu __percpu *vmstats_percpu;
#ifdef CONFIG_CGROUP_WRITEBACK
@@ -820,7 +824,7 @@ static inline unsigned short mem_cgroup_private_id(struct mem_cgroup *memcg)
if (mem_cgroup_disabled())
return 0;
- return memcg->id.id;
+ return memcg->id;
}
struct mem_cgroup *mem_cgroup_from_private_id(unsigned short id);
diff --git a/mm/memcontrol.c b/mm/memcontrol.c
index 8319ad8c5c23a..5f30e76ee93d7 100644
--- a/mm/memcontrol.c
+++ b/mm/memcontrol.c
@@ -3697,7 +3697,7 @@ static void memcg_online_kmem(struct mem_cgroup *memcg)
static_branch_enable(&memcg_kmem_online_key);
- memcg->kmemcg_id = memcg->id.id;
+ memcg->kmemcg_id = memcg->id;
}
static void memcg_offline_kmem(struct mem_cgroup *memcg)
@@ -3956,25 +3956,40 @@ static DEFINE_XARRAY_ALLOC1(mem_cgroup_private_ids);
static void mem_cgroup_private_id_remove(struct mem_cgroup *memcg)
{
- if (memcg->id.id > 0) {
- xa_erase(&mem_cgroup_private_ids, memcg->id.id);
- memcg->id.id = 0;
+ if (memcg->id > 0) {
+ xa_erase(&mem_cgroup_private_ids, memcg->id);
+ memcg->id = 0;
}
}
-static inline void mem_cgroup_private_id_put(struct mem_cgroup *memcg, unsigned int n)
+/**
+ * @objcg: the objcg returned by mem_cgroup_private_id_objcg
+ * @id: the corresponding memcg private id
+ */
+static void __mem_cgroup_private_id_put(struct obj_cgroup *objcg,
+ unsigned short id, unsigned int n)
{
- if (refcount_sub_and_test(n, &memcg->id.ref)) {
- mem_cgroup_private_id_remove(memcg);
+ struct obj_cgroup *objcg_free;
- /* Memcg ID pins CSS */
- css_put(&memcg->css);
+ if (refcount_sub_and_test(n, &objcg->id_ref)) {
+ objcg_free = xa_erase(&mem_cgroup_private_ids, id);
+ VM_WARN_ON(objcg_free != objcg);
+
+ /* Memcg ID pins the objcg */
+ obj_cgroup_put(objcg);
}
}
+static inline void mem_cgroup_private_id_put(struct mem_cgroup *memcg, unsigned int n)
+{
+ __mem_cgroup_private_id_put(memcg->id_objcg, memcg->id, n);
+}
+
struct mem_cgroup *mem_cgroup_private_id_get_online(struct mem_cgroup *memcg, unsigned int n)
{
- while (!refcount_add_not_zero(n, &memcg->id.ref)) {
+ struct obj_cgroup *objcg = memcg->id_objcg;
+
+ while (!refcount_add_not_zero(n, &objcg->id_ref)) {
/*
* The root cgroup cannot be destroyed, so it's refcount must
* always be >= 1.
@@ -3984,6 +3999,7 @@ struct mem_cgroup *mem_cgroup_private_id_get_online(struct mem_cgroup *memcg, un
break;
}
memcg = parent_mem_cgroup(memcg);
+ objcg = memcg->id_objcg;
}
return memcg;
}
@@ -3996,8 +4012,29 @@ struct mem_cgroup *mem_cgroup_private_id_get_online(struct mem_cgroup *memcg, un
*/
struct mem_cgroup *mem_cgroup_from_private_id(unsigned short id)
{
+ struct obj_cgroup *objcg;
WARN_ON_ONCE(!rcu_read_lock_held());
- return xa_load(&mem_cgroup_private_ids, id);
+
+ objcg = xa_load(&mem_cgroup_private_ids, id);
+ if (!objcg)
+ return NULL;
+
+ return obj_cgroup_memcg(objcg);
+}
+
+static struct mem_cgroup *mem_cgroup_take_from_private_id(unsigned short id, unsigned int n)
+{
+ struct obj_cgroup *objcg;
+ struct mem_cgroup *memcg;
+
+ objcg = xa_load(&mem_cgroup_private_ids, id);
+ if (!objcg)
+ return NULL;
+
+ memcg = get_mem_cgroup_from_objcg(objcg);
+
+ __mem_cgroup_private_id_put(objcg, id, n);
+ return memcg;
}
struct mem_cgroup *mem_cgroup_get_from_id(u64 id)
@@ -4098,7 +4135,7 @@ static struct mem_cgroup *mem_cgroup_alloc(struct mem_cgroup *parent)
if (!memcg)
return ERR_PTR(-ENOMEM);
- error = xa_alloc(&mem_cgroup_private_ids, &memcg->id.id, NULL,
+ error = xa_alloc(&mem_cgroup_private_ids, &memcg->id, NULL,
XA_LIMIT(1, MEM_CGROUP_ID_MAX), GFP_KERNEL);
if (error)
goto fail;
@@ -4243,9 +4280,10 @@ static int mem_cgroup_css_online(struct cgroup_subsys_state *css)
FLUSH_TIME);
lru_gen_online_memcg(memcg);
- /* Online state pins memcg ID, memcg ID pins CSS */
- refcount_set(&memcg->id.ref, 1);
- css_get(css);
+ /* CSS pins memcg ID, memcg ID pins obj cgroup */
+ memcg->id_objcg = memcg->nodeinfo[0]->objcg;
+ refcount_set(&memcg->id_objcg->id_ref, 1);
+ obj_cgroup_get(memcg->id_objcg);
/*
* Ensure mem_cgroup_from_private_id() works once we're fully online.
@@ -4257,7 +4295,7 @@ static int mem_cgroup_css_online(struct cgroup_subsys_state *css)
* publish it here at the end of onlining. This matches the
* regular ID destruction during offlining.
*/
- xa_store(&mem_cgroup_private_ids, memcg->id.id, memcg, GFP_KERNEL);
+ xa_store(&mem_cgroup_private_ids, memcg->id, objcg, GFP_KERNEL);
return 0;
free_objcg:
@@ -4308,8 +4346,6 @@ static void mem_cgroup_css_offline(struct cgroup_subsys_state *css)
lru_gen_offline_memcg(memcg);
drain_all_stock(memcg);
-
- mem_cgroup_private_id_put(memcg, 1);
}
static void mem_cgroup_css_released(struct cgroup_subsys_state *css)
@@ -4318,6 +4354,9 @@ static void mem_cgroup_css_released(struct cgroup_subsys_state *css)
invalidate_reclaim_iterators(memcg);
lru_gen_release_memcg(memcg);
+
+ mem_cgroup_private_id_put(memcg, 1);
+ memcg->id_objcg = NULL;
}
static void mem_cgroup_css_free(struct cgroup_subsys_state *css)
@@ -5651,19 +5690,19 @@ void __mem_cgroup_uncharge_swap(unsigned short id, unsigned int nr_pages)
{
struct mem_cgroup *memcg;
- rcu_read_lock();
- memcg = mem_cgroup_from_private_id(id);
- if (memcg) {
- if (!mem_cgroup_is_root(memcg)) {
- if (do_memsw_account())
- page_counter_uncharge(&memcg->memsw, nr_pages);
- else
- page_counter_uncharge(&memcg->swap, nr_pages);
- }
- mod_memcg_state(memcg, MEMCG_SWAP, -nr_pages);
- mem_cgroup_private_id_put(memcg, nr_pages);
+ memcg = mem_cgroup_take_from_private_id(id, nr_pages);
+ if (!memcg)
+ return;
+
+ if (!mem_cgroup_is_root(memcg)) {
+ if (do_memsw_account())
+ page_counter_uncharge(&memcg->memsw, nr_pages);
+ else
+ page_counter_uncharge(&memcg->swap, nr_pages);
}
- rcu_read_unlock();
+ mod_memcg_state(memcg, MEMCG_SWAP, -nr_pages);
+
+ mem_cgroup_put(memcg);
}
long mem_cgroup_get_nr_swap_pages(struct mem_cgroup *memcg)
--
2.43.7
^ permalink raw reply related [flat|nested] 8+ messages in thread* Re: [PATCH RFC 1/5] memcg: move memcg private ID refcount to objcg
2026-08-13 8:52 ` [PATCH RFC 1/5] memcg: move memcg private ID refcount to objcg Bingfang Guo via B4 Relay
@ 2026-08-13 15:09 ` Bingfang Guo
0 siblings, 0 replies; 8+ messages in thread
From: Bingfang Guo @ 2026-08-13 15:09 UTC (permalink / raw)
To: BINGFANG GUO
Cc: Johannes Weiner, Michal Hocko, Roman Gushchin, Shakeel Butt,
Muchun Song, Andrew Morton, Dave Chinner, Qi Zheng,
David Hildenbrand, Lorenzo Stoakes, Kairui Song, Barry Song,
Axel Rasmussen, Yuanchu Xie, Wei Xu, cgroups, linux-mm,
linux-kernel
> 2026年8月13日 16:52,Bingfang Guo via B4 Relay <devnull+bingfangguo.tencent.com@kernel.org> 写道:
>
> From: Bingfang Guo <bingfangguo@tencent.com>
>
> In the previous series by Muchun Song and Qi Zheng, folios are charged
> to the objcg and reparented as the memcg offlines. Same can be done to
> memcg private ID and its main user: swap entries. Make the memcgid
> xarray hold a pointer and a reference to an objcg of the memcg, which is
> used to find the memcg (or its parent) later on. The online state now
> pins the objcg instead of the css, so swapped out pages no longer pin
> the dying memcg.
>
> The id reference held by the online state is released in css_released()
> after reparenting instead of in css_offline(). This is the key
> invariant the rest of the series builds on: css_offline() runs while
> other css references may still be held, but css_released() only runs
> once the last reference is gone, so a caller holding a memcg reference
> can always count on the id refcount being alive. To prevent races
> between memcgid put in css offline and memcgid get, the release is put
> off till css_released, which could delay the release of the memcgid and
> the objcg it pins, but overall it should be fine.
>
> After reparenting, the objcg points to a live ancestor, so
> mem_cgroup_from_private_id() now returns that ancestor instead of the
> memcg the ID originally belonged to. Callers that need the exact memcg
> are fixed in patch 5.
>
> Signed-off-by: Bingfang Guo <bingfangguo@tencent.com>
> ---
> include/linux/memcontrol.h | 10 +++--
> mm/memcontrol.c | 99 ++++++++++++++++++++++++++++++++--------------
> 2 files changed, 76 insertions(+), 33 deletions(-)
>
> diff --git a/include/linux/memcontrol.h b/include/linux/memcontrol.h
> index 8170bb8066a22..c33ec7efad50b 100644
> --- a/include/linux/memcontrol.h
> +++ b/include/linux/memcontrol.h
> @@ -191,6 +191,7 @@ struct obj_cgroup {
> struct rcu_head rcu;
> };
> bool is_root;
> + refcount_t id_ref;
> };
>
> /*
> @@ -202,8 +203,8 @@ struct obj_cgroup {
> struct mem_cgroup {
> struct cgroup_subsys_state css;
>
> - /* Private memcg ID. Used to ID objects that outlive the cgroup */
> - struct mem_cgroup_private_id id;
> + /* The objcg holding private memcg ID. */
> + struct obj_cgroup *id_objcg;
>
> /* Accounted resources */
> struct page_counter memory; /* Both v1 & v2 */
> @@ -270,6 +271,9 @@ struct mem_cgroup {
> #endif
> int kmemcg_id;
>
> + /* Private memcg ID. Used to ID objects that outlive the cgroup */
> + int id;
> +
> struct memcg_vmstats_percpu __percpu *vmstats_percpu;
>
> #ifdef CONFIG_CGROUP_WRITEBACK
> @@ -820,7 +824,7 @@ static inline unsigned short mem_cgroup_private_id(struct mem_cgroup *memcg)
> if (mem_cgroup_disabled())
> return 0;
>
> - return memcg->id.id;
> + return memcg->id;
> }
> struct mem_cgroup *mem_cgroup_from_private_id(unsigned short id);
>
> diff --git a/mm/memcontrol.c b/mm/memcontrol.c
> index 8319ad8c5c23a..5f30e76ee93d7 100644
> --- a/mm/memcontrol.c
> +++ b/mm/memcontrol.c
> @@ -3697,7 +3697,7 @@ static void memcg_online_kmem(struct mem_cgroup *memcg)
>
> static_branch_enable(&memcg_kmem_online_key);
>
> - memcg->kmemcg_id = memcg->id.id;
> + memcg->kmemcg_id = memcg->id;
> }
>
> static void memcg_offline_kmem(struct mem_cgroup *memcg)
> @@ -3956,25 +3956,40 @@ static DEFINE_XARRAY_ALLOC1(mem_cgroup_private_ids);
>
> static void mem_cgroup_private_id_remove(struct mem_cgroup *memcg)
> {
> - if (memcg->id.id > 0) {
> - xa_erase(&mem_cgroup_private_ids, memcg->id.id);
> - memcg->id.id = 0;
> + if (memcg->id > 0) {
> + xa_erase(&mem_cgroup_private_ids, memcg->id);
> + memcg->id = 0;
> }
> }
>
> -static inline void mem_cgroup_private_id_put(struct mem_cgroup *memcg, unsigned int n)
> +/**
> + * @objcg: the objcg returned by mem_cgroup_private_id_objcg
> + * @id: the corresponding memcg private id
> + */
> +static void __mem_cgroup_private_id_put(struct obj_cgroup *objcg,
> + unsigned short id, unsigned int n)
> {
> - if (refcount_sub_and_test(n, &memcg->id.ref)) {
> - mem_cgroup_private_id_remove(memcg);
> + struct obj_cgroup *objcg_free;
>
> - /* Memcg ID pins CSS */
> - css_put(&memcg->css);
> + if (refcount_sub_and_test(n, &objcg->id_ref)) {
> + objcg_free = xa_erase(&mem_cgroup_private_ids, id);
> + VM_WARN_ON(objcg_free != objcg);
> +
> + /* Memcg ID pins the objcg */
> + obj_cgroup_put(objcg);
> }
> }
>
> +static inline void mem_cgroup_private_id_put(struct mem_cgroup *memcg, unsigned int n)
> +{
> + __mem_cgroup_private_id_put(memcg->id_objcg, memcg->id, n);
> +}
> +
> struct mem_cgroup *mem_cgroup_private_id_get_online(struct mem_cgroup *memcg, unsigned int n)
> {
> - while (!refcount_add_not_zero(n, &memcg->id.ref)) {
> + struct obj_cgroup *objcg = memcg->id_objcg;
> +
> + while (!refcount_add_not_zero(n, &objcg->id_ref)) {
> /*
> * The root cgroup cannot be destroyed, so it's refcount must
> * always be >= 1.
> @@ -3984,6 +3999,7 @@ struct mem_cgroup *mem_cgroup_private_id_get_online(struct mem_cgroup *memcg, un
> break;
> }
> memcg = parent_mem_cgroup(memcg);
> + objcg = memcg->id_objcg;
> }
> return memcg;
> }
> @@ -3996,8 +4012,29 @@ struct mem_cgroup *mem_cgroup_private_id_get_online(struct mem_cgroup *memcg, un
> */
> struct mem_cgroup *mem_cgroup_from_private_id(unsigned short id)
> {
> + struct obj_cgroup *objcg;
> WARN_ON_ONCE(!rcu_read_lock_held());
> - return xa_load(&mem_cgroup_private_ids, id);
> +
> + objcg = xa_load(&mem_cgroup_private_ids, id);
> + if (!objcg)
> + return NULL;
> +
> + return obj_cgroup_memcg(objcg);
> +}
> +
> +static struct mem_cgroup *mem_cgroup_take_from_private_id(unsigned short id, unsigned int n)
> +{
> + struct obj_cgroup *objcg;
> + struct mem_cgroup *memcg;
> +
> + objcg = xa_load(&mem_cgroup_private_ids, id);
> + if (!objcg)
> + return NULL;
> +
> + memcg = get_mem_cgroup_from_objcg(objcg);
> +
> + __mem_cgroup_private_id_put(objcg, id, n);
> + return memcg;
> }
>
> struct mem_cgroup *mem_cgroup_get_from_id(u64 id)
> @@ -4098,7 +4135,7 @@ static struct mem_cgroup *mem_cgroup_alloc(struct mem_cgroup *parent)
> if (!memcg)
> return ERR_PTR(-ENOMEM);
>
> - error = xa_alloc(&mem_cgroup_private_ids, &memcg->id.id, NULL,
> + error = xa_alloc(&mem_cgroup_private_ids, &memcg->id, NULL,
> XA_LIMIT(1, MEM_CGROUP_ID_MAX), GFP_KERNEL);
> if (error)
> goto fail;
> @@ -4243,9 +4280,10 @@ static int mem_cgroup_css_online(struct cgroup_subsys_state *css)
> FLUSH_TIME);
> lru_gen_online_memcg(memcg);
>
> - /* Online state pins memcg ID, memcg ID pins CSS */
> - refcount_set(&memcg->id.ref, 1);
> - css_get(css);
> + /* CSS pins memcg ID, memcg ID pins obj cgroup */
> + memcg->id_objcg = memcg->nodeinfo[0]->objcg;
> + refcount_set(&memcg->id_objcg->id_ref, 1);
> + obj_cgroup_get(memcg->id_objcg);
>
> /*
> * Ensure mem_cgroup_from_private_id() works once we're fully online.
> @@ -4257,7 +4295,7 @@ static int mem_cgroup_css_online(struct cgroup_subsys_state *css)
> * publish it here at the end of onlining. This matches the
> * regular ID destruction during offlining.
> */
> - xa_store(&mem_cgroup_private_ids, memcg->id.id, memcg, GFP_KERNEL);
> + xa_store(&mem_cgroup_private_ids, memcg->id, objcg, GFP_KERNEL);
Sashiko pointed out that objcg here is pointing to the wrong node here.
I made a mistake here while rebasing the patch set. Also the problem reported by syzbot and in
patch 2 is also caused by this…
It should be like this:
@@ -4257,7 +4295,7 @@ static int mem_cgroup_css_online(struct cgroup_subsys_state *css)
* publish it here at the end of onlining. This matches the
* regular ID destruction during offlining.
*/
- xa_store(&mem_cgroup_private_ids, memcg->id.id, memcg, GFP_KERNEL);
+ xa_store(&mem_cgroup_private_ids, memcg->id, memcg->id_objcg, GFP_KERNEL);
But it also says that it could go wrong if node 0 is not present on sparse NUMA setups, so I
think it might be better to just use the last objcg set up above:
@@ -4294,7 +4295,7 @@ static int mem_cgroup_css_online(struct cgroup_subsys_state *css)
lru_gen_online_memcg(memcg);
/* CSS pins memcg ID, memcg ID pins obj cgroup */
- memcg->id_objcg = memcg->nodeinfo[0]->objcg;
+ memcg->id_objcg = objcg;
refcount_set(&memcg->id_objcg->id_ref, 1);
obj_cgroup_get(memcg->id_objcg);
>
> return 0;
> free_objcg:
> @@ -4308,8 +4346,6 @@ static void mem_cgroup_css_offline(struct cgroup_subsys_state *css)
> lru_gen_offline_memcg(memcg);
>
> drain_all_stock(memcg);
> -
> - mem_cgroup_private_id_put(memcg, 1);
> }
>
> static void mem_cgroup_css_released(struct cgroup_subsys_state *css)
> @@ -4318,6 +4354,9 @@ static void mem_cgroup_css_released(struct cgroup_subsys_state *css)
>
> invalidate_reclaim_iterators(memcg);
> lru_gen_release_memcg(memcg);
> +
> + mem_cgroup_private_id_put(memcg, 1);
Sashiko says that if memcg onlining fails early, this could result in null pointer dereference.
I think we can fix it like this since mem_cgroup_private_id_put is only used in css releasing:
@@ -3980,9 +3980,13 @@ static void __mem_cgroup_private_id_put(struct obj_cgroup *objcg,
}
}
-static inline void mem_cgroup_private_id_put(struct mem_cgroup *memcg, unsigned int n)
+static inline void mem_cgroup_private_id_release(struct mem_cgroup *memcg, unsigned int n)
{
+ if (!memcg->id_objcg)
+ return;
+
__mem_cgroup_private_id_put(memcg->id_objcg, memcg->id, n);
+ memcg->id_objcg = NULL;
}
void mem_cgroup_private_id_get(struct mem_cgroup *memcg, unsigned int n)
@@ -4367,9 +4371,7 @@ static void mem_cgroup_css_released(struct cgroup_subsys_state *css)
invalidate_reclaim_iterators(memcg);
lru_gen_release_memcg(memcg);
-
- mem_cgroup_private_id_put(memcg, 1);
- memcg->id_objcg = NULL;
+ mem_cgroup_private_id_release(memcg, 1);
}
static void mem_cgroup_css_free(struct cgroup_subsys_state *css)
> + memcg->id_objcg = NULL;
> }
>
> static void mem_cgroup_css_free(struct cgroup_subsys_state *css)
> @@ -5651,19 +5690,19 @@ void __mem_cgroup_uncharge_swap(unsigned short id, unsigned int nr_pages)
> {
> struct mem_cgroup *memcg;
>
> - rcu_read_lock();
> - memcg = mem_cgroup_from_private_id(id);
> - if (memcg) {
> - if (!mem_cgroup_is_root(memcg)) {
> - if (do_memsw_account())
> - page_counter_uncharge(&memcg->memsw, nr_pages);
> - else
> - page_counter_uncharge(&memcg->swap, nr_pages);
> - }
> - mod_memcg_state(memcg, MEMCG_SWAP, -nr_pages);
> - mem_cgroup_private_id_put(memcg, nr_pages);
> + memcg = mem_cgroup_take_from_private_id(id, nr_pages);
> + if (!memcg)
> + return;
> +
> + if (!mem_cgroup_is_root(memcg)) {
> + if (do_memsw_account())
> + page_counter_uncharge(&memcg->memsw, nr_pages);
> + else
> + page_counter_uncharge(&memcg->swap, nr_pages);
> }
> - rcu_read_unlock();
> + mod_memcg_state(memcg, MEMCG_SWAP, -nr_pages);
> +
> + mem_cgroup_put(memcg);
> }
>
> long mem_cgroup_get_nr_swap_pages(struct mem_cgroup *memcg)
>
> --
> 2.43.7
>
>
^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH RFC 2/5] memcg: get stable memcg first before getting memcgid reference
2026-08-13 8:52 [PATCH RFC 0/5] memcg: fix dying memcg pinned by swapped out shmem pages Bingfang Guo via B4 Relay
2026-08-13 8:52 ` [PATCH RFC 1/5] memcg: move memcg private ID refcount to objcg Bingfang Guo via B4 Relay
@ 2026-08-13 8:52 ` Bingfang Guo via B4 Relay
2026-08-13 8:52 ` [PATCH RFC 3/5] memcg: remove retry logic in mem_cgroup_private_id_get_online Bingfang Guo via B4 Relay
` (3 subsequent siblings)
5 siblings, 0 replies; 8+ messages in thread
From: Bingfang Guo via B4 Relay @ 2026-08-13 8:52 UTC (permalink / raw)
To: Johannes Weiner, Michal Hocko, Roman Gushchin, Shakeel Butt,
Muchun Song, Andrew Morton, Dave Chinner, Qi Zheng,
David Hildenbrand, Lorenzo Stoakes, Kairui Song, Barry Song,
Axel Rasmussen, Yuanchu Xie, Wei Xu
Cc: cgroups, linux-mm, linux-kernel, Bingfang Guo, Bingfang Guo
From: Bingfang Guo <bingfangguo@tencent.com>
Storing the memcg private ID in a swap entry used to take the ID
reference under the RCU read lock and rely on
mem_cgroup_private_id_get_online() to hand back a usable (possibly
parent) memcg.
Now that the ID refcount lives on the objcg and stays alive until
css_released(), holding a memcg reference is enough to pin the ID. Both
__memcg1_swapout() and __mem_cgroup_try_charge_swap() take a stable
memcg reference first via get_mem_cgroup_from_objcg() and pin the ID
afterwards, dropping the rcu_read_lock() usage and the get-error-put
handling, and recording exactly the memcg the folio belongs to in the
swap entry.
Signed-off-by: Bingfang Guo <bingfangguo@tencent.com>
---
mm/memcontrol-v1.c | 23 +++++++++--------------
mm/memcontrol.c | 9 ++++++---
2 files changed, 15 insertions(+), 17 deletions(-)
diff --git a/mm/memcontrol-v1.c b/mm/memcontrol-v1.c
index 2dc599484d006..a913d32ad1e17 100644
--- a/mm/memcontrol-v1.c
+++ b/mm/memcontrol-v1.c
@@ -618,7 +618,7 @@ void memcg1_commit_charge(struct folio *folio, struct mem_cgroup *memcg)
*/
void __memcg1_swapout(struct folio *folio, struct swap_cluster_info *ci)
{
- struct mem_cgroup *memcg, *swap_memcg;
+ struct mem_cgroup *memcg;
struct obj_cgroup *objcg;
unsigned int nr_entries;
@@ -638,19 +638,20 @@ void __memcg1_swapout(struct folio *folio, struct swap_cluster_info *ci)
if (!objcg)
return;
- rcu_read_lock();
- memcg = obj_cgroup_memcg(objcg);
/*
* In case the memcg owning these pages has been offlined and doesn't
* have an ID allocated to it anymore, charge the closest online
- * ancestor for the swap instead and transfer the memory+swap charge.
+ * ancestor for the swap instead.
*/
+ memcg = get_mem_cgroup_from_objcg(objcg);
nr_entries = folio_nr_pages(folio);
- swap_memcg = mem_cgroup_private_id_get_online(memcg, nr_entries);
- mod_memcg_state(swap_memcg, MEMCG_SWAP, nr_entries);
+ mod_memcg_state(memcg, MEMCG_SWAP, nr_entries);
+
+ /* we have a reference to it, so we should get exact memcg itself */
+ mem_cgroup_private_id_get_online(memcg, nr_entries);
__swap_cgroup_set(ci, swp_cluster_offset(folio->swap), nr_entries,
- mem_cgroup_private_id(swap_memcg));
+ mem_cgroup_private_id(memcg));
folio_unqueue_deferred_split(folio);
folio->memcg_data = 0;
@@ -658,12 +659,6 @@ void __memcg1_swapout(struct folio *folio, struct swap_cluster_info *ci)
if (!obj_cgroup_is_root(objcg))
page_counter_uncharge(&memcg->memory, nr_entries);
- if (memcg != swap_memcg) {
- if (!mem_cgroup_is_root(swap_memcg))
- page_counter_charge(&swap_memcg->memsw, nr_entries);
- page_counter_uncharge(&memcg->memsw, nr_entries);
- }
-
/*
* The caller must hold the swap cluster lock with IRQ off. It is
* important here to have the interrupts disabled because it is the
@@ -675,7 +670,7 @@ void __memcg1_swapout(struct folio *folio, struct swap_cluster_info *ci)
preempt_enable_nested();
memcg1_check_events(memcg, folio_nid(folio));
- rcu_read_unlock();
+ mem_cgroup_put(memcg);
obj_cgroup_put(objcg);
}
diff --git a/mm/memcontrol.c b/mm/memcontrol.c
index 5f30e76ee93d7..a210fe2501219 100644
--- a/mm/memcontrol.c
+++ b/mm/memcontrol.c
@@ -5660,24 +5660,27 @@ int __mem_cgroup_try_charge_swap(struct folio *folio)
return 0;
}
- memcg = mem_cgroup_private_id_get_online(memcg, nr_pages);
- /* memcg is pined by memcg ID. */
+ memcg = get_mem_cgroup_from_objcg(objcg);
rcu_read_unlock();
if (!mem_cgroup_is_root(memcg) &&
!page_counter_try_charge(&memcg->swap, nr_pages, &counter)) {
memcg_memory_event(memcg, MEMCG_SWAP_MAX);
memcg_memory_event(memcg, MEMCG_SWAP_FAIL);
- mem_cgroup_private_id_put(memcg, nr_pages);
+ mem_cgroup_put(memcg);
return -ENOMEM;
}
mod_memcg_state(memcg, MEMCG_SWAP, nr_pages);
+ /* we have a reference to it, so we should get exact memcg itself */
+ mem_cgroup_private_id_get_online(memcg, nr_pages);
+
ci = swap_cluster_get_and_lock(folio);
__swap_cgroup_set(ci, swp_cluster_offset(folio->swap), nr_pages,
mem_cgroup_private_id(memcg));
swap_cluster_unlock(ci);
+ mem_cgroup_put(memcg);
return 0;
}
--
2.43.7
^ permalink raw reply related [flat|nested] 8+ messages in thread* [PATCH RFC 3/5] memcg: remove retry logic in mem_cgroup_private_id_get_online
2026-08-13 8:52 [PATCH RFC 0/5] memcg: fix dying memcg pinned by swapped out shmem pages Bingfang Guo via B4 Relay
2026-08-13 8:52 ` [PATCH RFC 1/5] memcg: move memcg private ID refcount to objcg Bingfang Guo via B4 Relay
2026-08-13 8:52 ` [PATCH RFC 2/5] memcg: get stable memcg first before getting memcgid reference Bingfang Guo via B4 Relay
@ 2026-08-13 8:52 ` Bingfang Guo via B4 Relay
2026-08-13 8:52 ` [PATCH RFC 4/5] memcg: add a helper to get online memcg from memcgid Bingfang Guo via B4 Relay
` (2 subsequent siblings)
5 siblings, 0 replies; 8+ messages in thread
From: Bingfang Guo via B4 Relay @ 2026-08-13 8:52 UTC (permalink / raw)
To: Johannes Weiner, Michal Hocko, Roman Gushchin, Shakeel Butt,
Muchun Song, Andrew Morton, Dave Chinner, Qi Zheng,
David Hildenbrand, Lorenzo Stoakes, Kairui Song, Barry Song,
Axel Rasmussen, Yuanchu Xie, Wei Xu
Cc: cgroups, linux-mm, linux-kernel, Bingfang Guo, Bingfang Guo
From: Bingfang Guo <bingfangguo@tencent.com>
With the ID released only in css_released() (patch 1) and every caller
holding a stable reference obtained from get_mem_cgroup_from_objcg()
(patch 2), the id refcount is guaranteed to be non-zero whenever the ID
is taken, so the retry loop that walked up the parent chain can never
trigger.
Remove the retry logic, rename the function to
mem_cgroup_private_id_get() and turn the fallible return value into a
VM_WARN_ON() that documents the invariant.
Signed-off-by: Bingfang Guo <bingfangguo@tencent.com>
---
mm/memcontrol-v1.c | 2 +-
mm/memcontrol-v1.h | 3 +--
mm/memcontrol.c | 20 +++++---------------
3 files changed, 7 insertions(+), 18 deletions(-)
diff --git a/mm/memcontrol-v1.c b/mm/memcontrol-v1.c
index a913d32ad1e17..e5161e061bd11 100644
--- a/mm/memcontrol-v1.c
+++ b/mm/memcontrol-v1.c
@@ -648,7 +648,7 @@ void __memcg1_swapout(struct folio *folio, struct swap_cluster_info *ci)
mod_memcg_state(memcg, MEMCG_SWAP, nr_entries);
/* we have a reference to it, so we should get exact memcg itself */
- mem_cgroup_private_id_get_online(memcg, nr_entries);
+ mem_cgroup_private_id_get(memcg, nr_entries);
__swap_cgroup_set(ci, swp_cluster_offset(folio->swap), nr_entries,
mem_cgroup_private_id(memcg));
diff --git a/mm/memcontrol-v1.h b/mm/memcontrol-v1.h
index 0f703f239c80f..9c74400aa7ddb 100644
--- a/mm/memcontrol-v1.h
+++ b/mm/memcontrol-v1.h
@@ -21,8 +21,7 @@ void drain_all_stock(struct mem_cgroup *root_memcg);
int memory_stat_show(struct seq_file *m, void *v);
-struct mem_cgroup *mem_cgroup_private_id_get_online(struct mem_cgroup *memcg,
- unsigned int n);
+void mem_cgroup_private_id_get(struct mem_cgroup *memcg, unsigned int n);
/* Cgroup v1-specific declarations */
#ifdef CONFIG_MEMCG_V1
diff --git a/mm/memcontrol.c b/mm/memcontrol.c
index a210fe2501219..12545ca48194d 100644
--- a/mm/memcontrol.c
+++ b/mm/memcontrol.c
@@ -3985,23 +3985,13 @@ static inline void mem_cgroup_private_id_put(struct mem_cgroup *memcg, unsigned
__mem_cgroup_private_id_put(memcg->id_objcg, memcg->id, n);
}
-struct mem_cgroup *mem_cgroup_private_id_get_online(struct mem_cgroup *memcg, unsigned int n)
+void mem_cgroup_private_id_get(struct mem_cgroup *memcg, unsigned int n)
{
+ bool success;
struct obj_cgroup *objcg = memcg->id_objcg;
- while (!refcount_add_not_zero(n, &objcg->id_ref)) {
- /*
- * The root cgroup cannot be destroyed, so it's refcount must
- * always be >= 1.
- */
- if (WARN_ON_ONCE(mem_cgroup_is_root(memcg))) {
- VM_BUG_ON(1);
- break;
- }
- memcg = parent_mem_cgroup(memcg);
- objcg = memcg->id_objcg;
- }
- return memcg;
+ success = refcount_add_not_zero(n, &objcg->id_ref);
+ VM_WARN_ON(!success);
}
/**
@@ -5673,7 +5663,7 @@ int __mem_cgroup_try_charge_swap(struct folio *folio)
mod_memcg_state(memcg, MEMCG_SWAP, nr_pages);
/* we have a reference to it, so we should get exact memcg itself */
- mem_cgroup_private_id_get_online(memcg, nr_pages);
+ mem_cgroup_private_id_get(memcg, nr_pages);
ci = swap_cluster_get_and_lock(folio);
__swap_cgroup_set(ci, swp_cluster_offset(folio->swap), nr_pages,
--
2.43.7
^ permalink raw reply related [flat|nested] 8+ messages in thread* [PATCH RFC 4/5] memcg: add a helper to get online memcg from memcgid
2026-08-13 8:52 [PATCH RFC 0/5] memcg: fix dying memcg pinned by swapped out shmem pages Bingfang Guo via B4 Relay
` (2 preceding siblings ...)
2026-08-13 8:52 ` [PATCH RFC 3/5] memcg: remove retry logic in mem_cgroup_private_id_get_online Bingfang Guo via B4 Relay
@ 2026-08-13 8:52 ` Bingfang Guo via B4 Relay
2026-08-13 8:52 ` [PATCH RFC 5/5] memcg: filter out reparented memcgs got using memcgid Bingfang Guo via B4 Relay
2026-08-13 13:26 ` [syzbot ci] Re: memcg: fix dying memcg pinned by swapped out shmem pages syzbot ci
5 siblings, 0 replies; 8+ messages in thread
From: Bingfang Guo via B4 Relay @ 2026-08-13 8:52 UTC (permalink / raw)
To: Johannes Weiner, Michal Hocko, Roman Gushchin, Shakeel Butt,
Muchun Song, Andrew Morton, Dave Chinner, Qi Zheng,
David Hildenbrand, Lorenzo Stoakes, Kairui Song, Barry Song,
Axel Rasmussen, Yuanchu Xie, Wei Xu
Cc: cgroups, linux-mm, linux-kernel, Bingfang Guo, Bingfang Guo
From: Bingfang Guo <bingfangguo@tencent.com>
When swapping in, the folio is charged back to the memcg that swapped
it out, or to one of its ancestors if that memcg is gone.
mem_cgroup_swapin_charge_folio() currently does the id lookup and the
css_tryget_online() check by hand under the RCU read lock.
The objcg behind the id is reparented to an online memcg when its own
memcg is destroyed, so looking the id up and taking a reference
through the objcg is enough to guarantee an online memcg. Add
mem_cgroup_from_private_id_online() for that purpose and use it in
mem_cgroup_swapin_charge_folio(), dropping the RCU read lock usage.
Signed-off-by: Bingfang Guo <bingfangguo@tencent.com>
---
include/linux/memcontrol.h | 1 +
mm/memcontrol.c | 22 ++++++++++++++++++----
2 files changed, 19 insertions(+), 4 deletions(-)
diff --git a/include/linux/memcontrol.h b/include/linux/memcontrol.h
index c33ec7efad50b..fef8a1c4191b1 100644
--- a/include/linux/memcontrol.h
+++ b/include/linux/memcontrol.h
@@ -827,6 +827,7 @@ static inline unsigned short mem_cgroup_private_id(struct mem_cgroup *memcg)
return memcg->id;
}
struct mem_cgroup *mem_cgroup_from_private_id(unsigned short id);
+struct mem_cgroup *mem_cgroup_from_private_id_online(unsigned short id);
static inline u64 mem_cgroup_id(struct mem_cgroup *memcg)
{
diff --git a/mm/memcontrol.c b/mm/memcontrol.c
index 12545ca48194d..fdf2e0d1f17e5 100644
--- a/mm/memcontrol.c
+++ b/mm/memcontrol.c
@@ -4012,6 +4012,22 @@ struct mem_cgroup *mem_cgroup_from_private_id(unsigned short id)
return obj_cgroup_memcg(objcg);
}
+/**
+ * mem_cgroup_from_private_id - look up an online memcg from a memcg id
+ * and get a reference.
+ * @id: the memcg id to look up
+ */
+struct mem_cgroup *mem_cgroup_from_private_id_online(unsigned short id)
+{
+ struct obj_cgroup *objcg;
+
+ objcg = xa_load(&mem_cgroup_private_ids, id);
+ if (!objcg)
+ return NULL;
+
+ return get_mem_cgroup_from_objcg(objcg);
+}
+
static struct mem_cgroup *mem_cgroup_take_from_private_id(unsigned short id, unsigned int n)
{
struct obj_cgroup *objcg;
@@ -5248,11 +5264,9 @@ int mem_cgroup_swapin_charge_folio(struct folio *folio, unsigned short id,
if (mem_cgroup_disabled())
return 0;
- rcu_read_lock();
- memcg = mem_cgroup_from_private_id(id);
- if (!memcg || !css_tryget_online(&memcg->css))
+ memcg = mem_cgroup_from_private_id_online(id);
+ if (!memcg)
memcg = get_mem_cgroup_from_mm(mm);
- rcu_read_unlock();
ret = charge_memcg(folio, memcg, gfp);
--
2.43.7
^ permalink raw reply related [flat|nested] 8+ messages in thread* [PATCH RFC 5/5] memcg: filter out reparented memcgs got using memcgid
2026-08-13 8:52 [PATCH RFC 0/5] memcg: fix dying memcg pinned by swapped out shmem pages Bingfang Guo via B4 Relay
` (3 preceding siblings ...)
2026-08-13 8:52 ` [PATCH RFC 4/5] memcg: add a helper to get online memcg from memcgid Bingfang Guo via B4 Relay
@ 2026-08-13 8:52 ` Bingfang Guo via B4 Relay
2026-08-13 13:26 ` [syzbot ci] Re: memcg: fix dying memcg pinned by swapped out shmem pages syzbot ci
5 siblings, 0 replies; 8+ messages in thread
From: Bingfang Guo via B4 Relay @ 2026-08-13 8:52 UTC (permalink / raw)
To: Johannes Weiner, Michal Hocko, Roman Gushchin, Shakeel Butt,
Muchun Song, Andrew Morton, Dave Chinner, Qi Zheng,
David Hildenbrand, Lorenzo Stoakes, Kairui Song, Barry Song,
Axel Rasmussen, Yuanchu Xie, Wei Xu
Cc: cgroups, linux-mm, linux-kernel, Bingfang Guo, Bingfang Guo
From: Bingfang Guo <bingfangguo@tencent.com>
mem_cgroup_from_private_id() looks up the objcg that owns the id and
returns the objcg's current memcg. After reparenting, that memcg can
differ from the one the id originally belonged to.
Callers such as the list lru and workingset refault code expect to get
back exactly the memcg referred to by the memcgid, so check that the
returned memcg still owns the id and return NULL otherwise, letting
the callers skip the entry.
Signed-off-by: Bingfang Guo <bingfangguo@tencent.com>
---
mm/list_lru.c | 2 +-
mm/memcontrol.c | 9 ++++++++-
mm/workingset.c | 6 +++++-
3 files changed, 14 insertions(+), 3 deletions(-)
diff --git a/mm/list_lru.c b/mm/list_lru.c
index 36662d02ff963..bc956267f6835 100644
--- a/mm/list_lru.c
+++ b/mm/list_lru.c
@@ -428,7 +428,7 @@ unsigned long list_lru_walk_node(struct list_lru *lru, int nid,
xa_for_each(&lru->xa, index, mlru) {
rcu_read_lock();
memcg = mem_cgroup_from_private_id(index);
- if (!mem_cgroup_tryget(memcg)) {
+ if (!memcg || !mem_cgroup_tryget(memcg)) {
rcu_read_unlock();
continue;
}
diff --git a/mm/memcontrol.c b/mm/memcontrol.c
index fdf2e0d1f17e5..e7555eca77019 100644
--- a/mm/memcontrol.c
+++ b/mm/memcontrol.c
@@ -3999,17 +3999,24 @@ void mem_cgroup_private_id_get(struct mem_cgroup *memcg, unsigned int n)
* @id: the memcg id to look up
*
* Caller must hold rcu_read_lock().
+ *
+ * @return: the memcg, or NULL if the memcg is already reparented.
*/
struct mem_cgroup *mem_cgroup_from_private_id(unsigned short id)
{
struct obj_cgroup *objcg;
+ struct mem_cgroup *memcg;
WARN_ON_ONCE(!rcu_read_lock_held());
objcg = xa_load(&mem_cgroup_private_ids, id);
if (!objcg)
return NULL;
- return obj_cgroup_memcg(objcg);
+ memcg = obj_cgroup_memcg(objcg);
+ if (mem_cgroup_private_id(memcg) != id)
+ return NULL;
+
+ return memcg;
}
/**
diff --git a/mm/workingset.c b/mm/workingset.c
index f351798e723ac..b6e22536a5240 100644
--- a/mm/workingset.c
+++ b/mm/workingset.c
@@ -283,6 +283,10 @@ static bool lru_gen_test_recent(void *shadow, struct lruvec **lruvec,
memcg = mem_cgroup_from_private_id(memcg_id);
*lruvec = mem_cgroup_lruvec(memcg, pgdat);
+ /* reparented memcg loses its max_seq */
+ if (!memcg)
+ return false;
+
max_seq = READ_ONCE((*lruvec)->lrugen.max_seq);
max_seq &= (file ? EVICTION_MASK : EVICTION_MASK_ANON) >> LRU_REFS_WIDTH;
@@ -470,7 +474,7 @@ bool workingset_test_recent(void *shadow, bool file, bool *workingset,
* configurations instead.
*/
eviction_memcg = mem_cgroup_from_private_id(memcgid);
- if (!mem_cgroup_tryget(eviction_memcg))
+ if (!eviction_memcg || !mem_cgroup_tryget(eviction_memcg))
eviction_memcg = NULL;
rcu_read_unlock();
--
2.43.7
^ permalink raw reply related [flat|nested] 8+ messages in thread* [syzbot ci] Re: memcg: fix dying memcg pinned by swapped out shmem pages
2026-08-13 8:52 [PATCH RFC 0/5] memcg: fix dying memcg pinned by swapped out shmem pages Bingfang Guo via B4 Relay
` (4 preceding siblings ...)
2026-08-13 8:52 ` [PATCH RFC 5/5] memcg: filter out reparented memcgs got using memcgid Bingfang Guo via B4 Relay
@ 2026-08-13 13:26 ` syzbot ci
5 siblings, 0 replies; 8+ messages in thread
From: syzbot ci @ 2026-08-13 13:26 UTC (permalink / raw)
To: akpm, axelrasmussen, baohua, bfguo, bingfangguo, cgroups, david,
david, devnull, hannes, kasong, linux-kernel, linux-mm, ljs,
mhocko, muchun.song, qi.zheng, roman.gushchin, shakeel.butt,
weixugc, yuanchu
Cc: syzbot, syzkaller-bugs
syzbot ci has tested the following series
[v1] memcg: fix dying memcg pinned by swapped out shmem pages
https://lore.kernel.org/all/20260813-memcgid-objcg-v1-0-83d21c685b77@tencent.com
* [PATCH RFC 1/5] memcg: move memcg private ID refcount to objcg
* [PATCH RFC 2/5] memcg: get stable memcg first before getting memcgid reference
* [PATCH RFC 3/5] memcg: remove retry logic in mem_cgroup_private_id_get_online
* [PATCH RFC 4/5] memcg: add a helper to get online memcg from memcgid
* [PATCH RFC 5/5] memcg: filter out reparented memcgs got using memcgid
and found the following issue:
WARNING: refcount bug in __mem_cgroup_uncharge_swap
Full report is available here:
https://ci.syzbot.org/series/3a8a2a5f-8b6d-4abf-954e-a1a18ee62748
***
WARNING: refcount bug in __mem_cgroup_uncharge_swap
tree: linux-next
URL: https://kernel.googlesource.com/pub/scm/linux/kernel/git/next/linux-next
base: 288058d8db5d35623228d84f48d9bea3707d5c85
arch: amd64
compiler: Debian clang version 22.1.8 (++20260613092233+e80beda6e255-1~exp1~20260613092250.77), Debian LLD 22.1.8
config: https://ci.syzbot.org/builds/35dac6f6-b44a-4ea8-9adf-2cdda5584e45/config
syz repro: https://ci.syzbot.org/findings/6e117c0b-d6e2-44a6-8e35-161cb33f2aff/syz_repro
------------[ cut here ]------------
refcount_t: underflow; use-after-free.
WARNING: lib/refcount.c:28 at refcount_warn_saturate+0xb2/0x110 lib/refcount.c:28, CPU#0: syz.2.19/5863
Modules linked in:
CPU: 0 UID: 0 PID: 5863 Comm: syz.2.19 Not tainted syzkaller #0 PREEMPT(full)
Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.2-debian-1.16.2-1 04/01/2014
RIP: 0010:refcount_warn_saturate+0xb2/0x110 lib/refcount.c:28
Code: 64 84 8e 0b 67 48 0f b9 3a eb 4a e8 58 fa f2 fc 48 8d 3d 61 84 8e 0b 67 48 0f b9 3a eb 37 e8 45 fa f2 fc 48 8d 3d 5e 84 8e 0b <67> 48 0f b9 3a eb 24 e8 32 fa f2 fc 48 8d 3d 5b 84 8e 0b 67 48 0f
RSP: 0018:ffffc90003bbf1d0 EFLAGS: 00010293
RAX: ffffffff84d3ed9b RBX: 0000000000000003 RCX: ffff8881bdf58000
RDX: 0000000000000000 RSI: ffffffff8f363380 RDI: ffffffff90627200
RBP: 1ffffffff20ae968 R08: ffff8881bdf58000 R09: 0000000000000005
R10: 0000000000000004 R11: 0000000000000000 R12: ffff8881026f8000
R13: ffff88810a914100 R14: ffff88810a914134 R15: 1ffff110204df009
FS: 00007fe2ee63d6c0(0000) GS:ffff88818d960000(0000) knlGS:0000000000000000
CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 000056066247f0b8 CR3: 000000000e946000 CR4: 00000000000006f0
Call Trace:
<TASK>
mem_cgroup_take_from_private_id mm/memcontrol.c:4049 [inline]
__mem_cgroup_uncharge_swap+0x204/0x2a0 mm/memcontrol.c:5707
mem_cgroup_uncharge_swap include/linux/swap.h:520 [inline]
__swap_cluster_free_entries+0x735/0xc50 mm/swapfile.c:1955
swap_put_entries_cluster+0x3b1/0x4b0 mm/swapfile.c:1629
swap_put_entries_direct+0x137/0x210 mm/swapfile.c:2139
zap_nonpresent_ptes mm/memory.c:1838 [inline]
do_zap_pte_range mm/memory.c:1905 [inline]
zap_pte_range mm/memory.c:2003 [inline]
zap_pmd_range mm/memory.c:2089 [inline]
zap_pud_range mm/memory.c:2117 [inline]
zap_p4d_range mm/memory.c:2138 [inline]
__zap_vma_range+0x1d9d/0x4f10 mm/memory.c:2178
unmap_vmas+0x390/0x550 mm/memory.c:2247
exit_mmap+0x293/0x9f0 mm/mmap.c:1315
__mmput+0x118/0x420 kernel/fork.c:1187
exit_mm+0x221/0x2d0 kernel/exit.c:615
do_exit+0x6cd/0x2360 kernel/exit.c:997
do_group_exit+0x22d/0x2f0 kernel/exit.c:1152
get_signal+0x121b/0x12c0 kernel/signal.c:3046
arch_do_signal_or_restart+0xbb/0x860 arch/x86/kernel/signal.c:337
__exit_to_user_mode_loop kernel/entry/common.c:66 [inline]
exit_to_user_mode_loop+0x104/0x730 kernel/entry/common.c:101
__exit_to_user_mode_prepare include/linux/irq-entry-common.h:207 [inline]
syscall_exit_to_user_mode_prepare include/linux/irq-entry-common.h:230 [inline]
syscall_exit_to_user_mode include/linux/entry-common.h:318 [inline]
do_syscall_64+0x353/0x580 arch/x86/entry/syscall_64.c:100
entry_SYSCALL_64_after_hwframe+0x77/0x7f
RIP: 0033:0x7fe2ed79e0d9
Code: Unable to access opcode bytes at 0x7fe2ed79e0af.
RSP: 002b:00007fe2ee63d0e8 EFLAGS: 00000246 ORIG_RAX: 00000000000000ca
RAX: fffffffffffffe00 RBX: 00007fe2eda25fa8 RCX: 00007fe2ed79e0d9
RDX: 0000000000000000 RSI: 0000000000000080 RDI: 00007fe2eda25fa8
RBP: 00007fe2eda25fa0 R08: 0000000000000000 R09: 0000000000000000
R10: 0000000000000000 R11: 0000000000000246 R12: 0000000000000000
R13: 00007fe2eda26038 R14: 00007fff0bed41b0 R15: 00007fff0bed4298
</TASK>
----------------
Code disassembly (best guess):
0: 64 84 8e 0b 67 48 0f test %cl,%fs:0xf48670b(%rsi)
7: b9 3a eb 4a e8 mov $0xe84aeb3a,%ecx
c: 58 pop %rax
d: fa cli
e: f2 fc repnz cld
10: 48 8d 3d 61 84 8e 0b lea 0xb8e8461(%rip),%rdi # 0xb8e8478
17: 67 48 0f b9 3a ud1 (%edx),%rdi
1c: eb 37 jmp 0x55
1e: e8 45 fa f2 fc call 0xfcf2fa68
23: 48 8d 3d 5e 84 8e 0b lea 0xb8e845e(%rip),%rdi # 0xb8e8488
* 2a: 67 48 0f b9 3a ud1 (%edx),%rdi <-- trapping instruction
2f: eb 24 jmp 0x55
31: e8 32 fa f2 fc call 0xfcf2fa68
36: 48 8d 3d 5b 84 8e 0b lea 0xb8e845b(%rip),%rdi # 0xb8e8498
3d: 67 addr32
3e: 48 rex.W
3f: 0f .byte 0xf
***
If these findings have caused you to resend the series or submit a
separate fix, please add the following tag to your commit message:
Tested-by: syzbot@syzkaller.appspotmail.com
---
This report is generated by a bot. It may contain errors.
syzbot ci engineers can be reached at syzkaller@googlegroups.com.
To test a fix for this bug, please reply with `#syz test`
(on a separate line) and attach the patch to the email.
Notes:
- The patch will be applied on top of the tested series (as an
incremental fix).
- To test a new version of the whole series, please send it directly
to syzbot@lists.linux.dev.
- Arguments like custom git repos and branches are not supported.
^ permalink raw reply [flat|nested] 8+ messages in thread