| From 69944b743c24748cccab46063ba7e89b2706ac87 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 fe9f6a0a67d5b..42c16940e4c43 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 | |