| From 6cd2791a3f51b2a0edbde0d1f9b62954a5e88fe6 Mon Sep 17 00:00:00 2001 |
| From: Sasha Levin <sashal@kernel.org> |
| Date: Thu, 1 Oct 2026 12:22:32 +0800 |
| Subject: devlink: fix devlink_rel reference leak when notify work is pending |
| |
| From: Haishuang Yan <yanhaishuang@cmss.chinamobile.com> |
| |
| [ Upstream commit c5381ae1cd1481cc431c2bd79460472c6f1d3ce3 ] |
| |
| devlink_rel_nested_in_notify_work_schedule() takes a reference on the |
| devlink_rel for the notify work and then queues the work, ignoring the |
| return value of schedule_delayed_work(). If the work is already pending, |
| nothing new is queued, the work runs only once and drops only one |
| reference, so the extra one is leaked together with the devlink_rel and |
| its index in devlink_rels. |
| |
| This is easy to hit. devl_register() of a nested instance queues the |
| work, and if the instance is unregistered before the work has run, |
| devlink_rel_put() queues it again while it is still pending. The work |
| also keeps rescheduling itself for as long as the parent devlink lock |
| cannot be taken, which widens the window. Registering and unregistering |
| a nested devlink instance 100 times while holding the parent lock leaks |
| all 100 devlink_rel objects. |
| |
| The reschedule path in devlink_rel_nested_in_notify_work() has the same |
| problem: if the work was queued again while it was running, the |
| reference it holds is never dropped. |
| |
| Drop the reference in both places when the work was already pending. |
| The pending work holds its own reference, so this can not be the last |
| one. |
| |
| Fixes: c137743bce02 ("devlink: introduce object and nested devlink relationship infra") |
| Signed-off-by: Haishuang Yan <yanhaishuang@cmss.chinamobile.com> |
| Reviewed-by: Simon Horman <horms@kernel.org> |
| Link: https://patch.msgid.link/20261001042232.222987-1-yanhaishuang@cmss.chinamobile.com |
| Signed-off-by: Jakub Kicinski <kuba@kernel.org> |
| Signed-off-by: Sasha Levin <sashal@kernel.org> |
| --- |
| net/devlink/core.c | 10 ++++++++-- |
| 1 file changed, 8 insertions(+), 2 deletions(-) |
| |
| diff --git a/net/devlink/core.c b/net/devlink/core.c |
| index d8f875b229897..0a31f28c8128c 100644 |
| --- a/net/devlink/core.c |
| +++ b/net/devlink/core.c |
| @@ -96,13 +96,19 @@ static void devlink_rel_nested_in_notify_work(struct work_struct *work) |
| return; |
| |
| reschedule_work: |
| - schedule_delayed_work(&rel->nested_in.notify_work, 1); |
| + /* The work may have been queued again meanwhile, which took its own |
| + * reference. Drop ours in that case. |
| + */ |
| + if (!schedule_delayed_work(&rel->nested_in.notify_work, 1)) |
| + __devlink_rel_put(rel); |
| } |
| |
| static void devlink_rel_nested_in_notify_work_schedule(struct devlink_rel *rel) |
| { |
| __devlink_rel_get(rel); |
| - schedule_delayed_work(&rel->nested_in.notify_work, 0); |
| + /* The pending work holds a reference already, drop the new one. */ |
| + if (!schedule_delayed_work(&rel->nested_in.notify_work, 0)) |
| + __devlink_rel_put(rel); |
| } |
| |
| static struct devlink_rel *devlink_rel_alloc(void) |
| -- |
| 2.53.0 |
| |