From: Leon Romanovsky <leon@kernel.org>
To: Chengyu Liu <lui2012qwq@gmail.com>
Cc: jgg@ziepe.ca, linux-rdma@vger.kernel.org, stable@vger.kernel.org
Subject: Re: [PATCH] RDMA/ipoib: cancel the AH reaper work synchronously
Date: Wed, 4 Nov 2026 16:06:08 +0200 [thread overview]
Message-ID: <20261104140608.GF478286@unreal> (raw)
In-Reply-To: <20261103170703.1600310-1-lui2012qwq@gmail.com>
On Wed, Nov 04, 2026 at 01:07:03AM +0800, Chengyu Liu wrote:
> ipoib_stop_ah_reaper() uses cancel_delayed_work(), which cannot stop a
> callback that is already running. ipoib_reap_ah() re-arms itself after
> an unlocked test_bit(IPOIB_STOP_REAPER) check, so a callback that has
> passed the check but has not re-armed yet still queues the delayed work
> after the "stop":
>
> CPU0 (reaper) CPU1 (stop/teardown)
> test_bit(STOP_REAPER) == 0
> set_bit(STOP_REAPER)
> cancel_delayed_work() (no-op)
> ... destroy_workqueue(priv->wq)
> queue_delayed_work(priv->wq, ...) <- delayed work sits on the timer
>
> destroy_workqueue() cannot see a delayed work that is pending on the
> timer wheel, so the re-armed work fires after the workqueue is
> destroyed and priv is freed: both the queueing onto the dead queue and
> the callback's container_of() on freed memory are use-after-free.
>
> Use cancel_delayed_work_sync(), which waits for a running callback and
> prevents its re-arm from taking effect.
>
> The comment above claims ipoib_reap_dead_ahs() "ensures the work is
> really stopped", but it never touches the work item - it only walks the
> dead_ahs list under priv->lock. The stop guarantee comes from the
> cancel; fix the comment along with it.
>
> Found by static analysis. The race was reproduced on a KASAN build
> with a small module exercising this exact guard/cancel/free pattern
> (no Infiniband hardware available); PoC and splat are below.
Please revisit this once you can reproduce the issue with real IPoIB code,
not code that merely resembles it.
Dropped for now.
Thanks
>
> Cc: stable@vger.kernel.org
> Signed-off-by: Chengyu Liu <lui2012qwq@gmail.com>
> ---
>
> Reproducer notes (PoC source below; a module parameter selects the
> shape): mode=3 (cancel_delayed_work + lockless guard race, this bug's shape) produces
>
> [ 2.05] BUG: KASAN: slab-use-after-free in queue_delayed_work_on+0x27/0x70
> [ 2.05] Workqueue: events pm_td_cb [pm_flush_poc]
>
> after the teardown frees the container; mode=1
> (cancel_delayed_work_sync, i.e. this fix) stays clean.
>
> ---------------------------- reproducer: pm_flush_poc.c ----------------------------
> // SPDX-License-Identifier: GPL-2.0
> /*
> * pm_teardown_poc - mechanism PoC for the drv-teardown-sync C/D variants
> * (F-026 ipoib / F-028 epf-ntb / F-029 habanalabs).
> *
> * Modes (module parameter):
> * 0 F-029 vuln shape: flush_delayed_work + lockless guard
> * 1 control: cancel_delayed_work_sync (>=6.9 swallows re-arm)
> * 2 F-028 vuln shape: cancel_delayed_work (non-sync) + unconditional re-arm
> * 3 F-026 vuln shape: cancel_delayed_work (non-sync) + guard check/queue
> * race window (test passes, teardown slips in before
> * the queue_delayed_work)
> *
> * Every vuln mode must produce KASAN slab-use-after-free when the orphaned
> * work fires after kfree; the control mode must stay clean.
> */
> #include <linux/module.h>
> #include <linux/workqueue.h>
> #include <linux/slab.h>
> #include <linux/delay.h>
>
> static int mode;
> module_param(mode, int, 0444);
> MODULE_PARM_DESC(mode, "0=flush(F-029) 1=cancel_sync(ctrl) 2=cancel_nosync(F-028) 3=cancel_nosync+guard-race(F-026)");
>
> struct pm_td_ctx {
> bool fini_pending;
> struct delayed_work work;
> };
>
> static struct pm_td_ctx *ctx;
>
> static void pm_td_cb(struct work_struct *w)
> {
> struct pm_td_ctx *c = container_of(to_delayed_work(w),
> struct pm_td_ctx, work);
>
> pr_info("PM-POC: cb running, fini_pending=%d\n", c->fini_pending);
>
> switch (mode) {
> case 0: /* F-029: lockless guard then long body then re-arm */
> if (c->fini_pending)
> return;
> msleep(300);
> schedule_delayed_work(&c->work, HZ / 10);
> pr_info("PM-POC: re-armed (flush shape)\n");
> break;
> case 2: /* F-028: unconditional re-arm, cancel must catch running cb */
> msleep(300); /* long body so cancel lands mid-run */
> schedule_delayed_work(&c->work, HZ / 10);
> pr_info("PM-POC: re-armed (no guard)\n");
> break;
> case 3: /* F-026: guard check passes, THEN teardown slips in */
> if (c->fini_pending)
> return;
> /* widen the check->queue race window like ipoib's 724->725 */
> msleep(300);
> schedule_delayed_work(&c->work, HZ / 10);
> pr_info("PM-POC: re-armed past guard (test-queue race)\n");
> break;
> default:
> break;
> }
> }
>
> static int __init pm_poc_init(void)
> {
> ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);
> if (!ctx)
> return -ENOMEM;
> ctx->fini_pending = false;
> INIT_DELAYED_WORK(&ctx->work, pm_td_cb);
>
> pr_info("PM-POC: arming, mode=%d\n", mode);
> schedule_delayed_work(&ctx->work, 0);
>
> msleep(50); /* callback is mid-run now */
>
> /* teardown sequence for the selected variant */
> ctx->fini_pending = true;
> switch (mode) {
> case 1:
> cancel_delayed_work_sync(&ctx->work);
> break;
> case 0:
> flush_delayed_work(&ctx->work);
> break;
> default:
> cancel_delayed_work(&ctx->work); /* non-sync: C variants */
> break;
> }
>
> pr_info("PM-POC: teardown done, freeing ctx\n");
> kfree(ctx);
>
> msleep(600);
> pr_info("PM-POC: SURVIVED (no KASAN splat)\n");
> return 0;
> }
>
> static void __exit pm_poc_exit(void) { }
>
> module_init(pm_poc_init);
> module_exit(pm_poc_exit);
> MODULE_LICENSE("GPL");
> MODULE_DESCRIPTION("drv-teardown-sync C/D variant mechanism PoC (F-026/028/029)");
>
> drivers/infiniband/ulp/ipoib/ipoib_ib.c | 6 +++---
> 1 file changed, 3 insertions(+), 3 deletions(-)
>
> diff --git a/drivers/infiniband/ulp/ipoib/ipoib_ib.c b/drivers/infiniband/ulp/ipoib/ipoib_ib.c
> index 81bbb3f7c1..5ed4f709cb 100644
> --- a/drivers/infiniband/ulp/ipoib/ipoib_ib.c
> +++ b/drivers/infiniband/ulp/ipoib/ipoib_ib.c
> @@ -736,11 +736,11 @@ static void ipoib_start_ah_reaper(struct ipoib_dev_priv *priv)
> static void ipoib_stop_ah_reaper(struct ipoib_dev_priv *priv)
> {
> set_bit(IPOIB_STOP_REAPER, &priv->flags);
> - cancel_delayed_work(&priv->ah_reap_task);
> + cancel_delayed_work_sync(&priv->ah_reap_task);
> /*
> * After ipoib_stop_ah_reaper() we always go through
> - * ipoib_reap_dead_ahs() which ensures the work is really stopped and
> - * does a final flush out of the dead_ah's list
> + * ipoib_reap_dead_ahs() which does a final flush out of the
> + * dead_ah's list
> */
> }
>
> --
> 2.25.1
>
next prev parent reply other threads:[~2026-11-04 14:06 UTC|newest]
Thread overview: 4+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-03 17:07 [PATCH] RDMA/ipoib: cancel the AH reaper work synchronously Chengyu Liu
2026-11-03 17:13 ` sashiko-bot
2026-11-04 14:06 ` Leon Romanovsky [this message]
2026-11-04 18:18 ` Chengyu Liu
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261104140608.GF478286@unreal \
--to=leon@kernel.org \
--cc=jgg@ziepe.ca \
--cc=linux-rdma@vger.kernel.org \
--cc=lui2012qwq@gmail.com \
--cc=stable@vger.kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.