From: Siddharth Chintamaneni <sidchintamaneni@gmail.com>
To: stable@vger.kernel.org
Cc: pc@manguebit.org, sashal@kernel.org, gregkh@linuxfoundation.org,
wozizhi@huawei.com, lsahlber@redhat.com, sprasad@microsoft.com,
tom@talpey.com, glaming@microsoft.com,
linux-cifs@vger.kernel.org, apais@microsoft.com,
rlmenge@gmail.com, hargar@linux.microsoft.com,
Siddharth Chintamaneni <sidchintamaneni@gmail.com>
Subject: [PATCH 6.1.y] smb: client: fix busy dentry warning on unmount after DIO
Date: Wed, 4 Nov 2026 17:33:09 +0000 [thread overview]
Message-ID: <20261104173309.19150-1-sidchintamaneni@gmail.com> (raw)
commit 75f5c412fa867efa0bf9b646bffe0d912109e84a upstream.
We recently backported commit 75f5c412fa86 ("smb: client: fix busy
dentry warning on unmount after DIO") to the 6.6 stable kernel [1], and
the same issue exists in the 6.1 stable kernel.
In mainline, commit 75f5c412fa86 fixes a busy-dentry warning during
unmount by tracking in-flight netfs_io_request instances in
cifs_init_request() and cifs_free_request(), waiting for the counter to
reach zero in cifs_kill_sb() before kill_anon_super(), and then flushing
serverclose_wq and fileinfo_put_wq. Because mainline routes all CIFS I/O
(direct I/O, uncached I/O, buffered readahead, and buffered writeback)
through netfs_io_request, that single counter covers every path that
holds a cifsFileInfo reference across asynchronous I/O completion.
Like 6.6, kernel 6.1 predates the netfs I/O request conversion and
splits I/O across three reference-counted structures that each hold a
cifsFileInfo reference:
- struct cifs_aio_ctx, used by direct and uncached I/O (__cifs_readv()
and __cifs_writev()).
- struct cifs_readdata, allocated for uncached reads
(cifs_send_async_read()) and directly without a cifs_aio_ctx for
buffered readahead (cifs_readahead()).
- struct cifs_writedata, allocated for uncached writes
(cifs_write_from_iter(), cifs_writev_requeue()) and directly without a
cifs_aio_ctx for buffered writeback (cifs_writepages()).
In both the DIO/uncached paths (where collect_uncached_read_data() and
collect_uncached_write_data() signal completion via complete(&ctx->done)
or ctx->iocb->ki_complete() before the worker drops its rdata/wdata and
ctx references) and the buffered paths (where cifs_readv_complete()
unlocks pages and cifs_writev_complete() ends page writeback before
calling kref_put() on rdata/wdata to drop their cfile reference), a
racing close() and umount() can reach shrink_dcache_for_umount() before
the final cifsFileInfo_put() has been queued and flushed. This triggers:
BUG: Dentry ... still in use (1) [unmount of cifs cifs]
followed by a use-after-free when deferred cifsFileInfo_put_work runs
after superblock teardown.
Fix this in 6.1 using the same approach as the 6.6 backport, adapted for
6.1's page-based writeback (cifs_writepages()) and separate
cifs_uncached_readdata_release() callback:
1. Add cifs_sb->outstanding_aio and increment it whenever ctx->cfile,
rdata->cfile, or wdata->cfile is assigned.
2. Release wdata->cfile and rdata->cfile before dropping ctx->refcount
in cifs_uncached_writedata_release() and
cifs_uncached_readdata_release().
3. Decrement cifs_sb->outstanding_aio in cifs_aio_ctx_release(),
cifs_readdata_release(), and cifs_writedata_release() only after
cifsFileInfo_put() has been called, and wait for it to reach zero in
cifs_kill_sb() before flushing serverclose_wq and fileinfo_put_wq.
Link: https://lore.kernel.org/all/20261102201410.55089-1-sidchintamaneni@gmail.com/ [1]
Fixes: 340cea84f691 ("cifs: open files should not hold ref on superblock")
Signed-off-by: Siddharth Chintamaneni <sidchintamaneni@gmail.com>
---
fs/smb/client/cifs_fs_sb.h | 1 +
fs/smb/client/cifsfs.c | 11 +++++++++++
fs/smb/client/connect.c | 1 +
fs/smb/client/file.c | 34 +++++++++++++++++++++++++++++-----
fs/smb/client/misc.c | 4 ++++
5 files changed, 46 insertions(+), 5 deletions(-)
diff --git a/fs/smb/client/cifs_fs_sb.h b/fs/smb/client/cifs_fs_sb.h
index 013a4bd65280c..5c6498329b05e 100644
--- a/fs/smb/client/cifs_fs_sb.h
+++ b/fs/smb/client/cifs_fs_sb.h
@@ -54,6 +54,7 @@ struct cifs_sb_info {
struct nls_table *local_nls;
struct smb3_fs_context *ctx;
atomic_t active;
+ atomic_t outstanding_aio; /* nr of AIO contexts not yet fully deinitialized */
unsigned int mnt_cifs_flags;
struct delayed_work prune_tlinks;
struct rcu_head rcu;
diff --git a/fs/smb/client/cifsfs.c b/fs/smb/client/cifsfs.c
index c2a9d7004ff59..b08eb605a7b54 100644
--- a/fs/smb/client/cifsfs.c
+++ b/fs/smb/client/cifsfs.c
@@ -299,6 +299,17 @@ static void cifs_kill_sb(struct super_block *sb)
/* Wait for all opened files to release */
flush_workqueue(deferredclose_wq);
+ /*
+ * Wait for all in-flight AIO contexts to finish cleanup so
+ * that their cifsFileInfo final puts have been queued, then
+ * drain the workqueues so the dentry refs are dropped before
+ * kill_anon_super().
+ */
+ wait_var_event(&cifs_sb->outstanding_aio,
+ !atomic_read(&cifs_sb->outstanding_aio));
+ flush_workqueue(serverclose_wq);
+ flush_workqueue(fileinfo_put_wq);
+
/* finally release root dentry */
dput(cifs_sb->root);
cifs_sb->root = NULL;
diff --git a/fs/smb/client/connect.c b/fs/smb/client/connect.c
index a8f16cd8dca69..30e75d31b0ee7 100644
--- a/fs/smb/client/connect.c
+++ b/fs/smb/client/connect.c
@@ -3274,6 +3274,7 @@ int cifs_setup_cifs_sb(struct cifs_sb_info *cifs_sb)
spin_lock_init(&cifs_sb->tlink_tree_lock);
cifs_sb->tlink_tree = RB_ROOT;
+ atomic_set(&cifs_sb->outstanding_aio, 0);
cifs_dbg(FYI, "file mode: %04ho dir mode: %04ho\n",
ctx->file_mode, ctx->dir_mode);
diff --git a/fs/smb/client/file.c b/fs/smb/client/file.c
index caa55b5bc110c..84227c8393c59 100644
--- a/fs/smb/client/file.c
+++ b/fs/smb/client/file.c
@@ -2379,6 +2379,8 @@ cifs_writedata_release(struct kref *refcount)
{
struct cifs_writedata *wdata = container_of(refcount,
struct cifs_writedata, refcount);
+ struct cifs_sb_info *cifs_sb = NULL;
+
#ifdef CONFIG_CIFS_SMB_DIRECT
if (wdata->mr) {
smbd_deregister_mr(wdata->mr);
@@ -2386,11 +2388,16 @@ cifs_writedata_release(struct kref *refcount)
}
#endif
- if (wdata->cfile)
+ if (wdata->cfile) {
+ cifs_sb = CIFS_SB(wdata->cfile->dentry->d_sb);
cifsFileInfo_put(wdata->cfile);
+ }
kvfree(wdata->pages);
kfree(wdata);
+
+ if (cifs_sb && atomic_dec_and_test(&cifs_sb->outstanding_aio))
+ wake_up_var(&cifs_sb->outstanding_aio);
}
/*
@@ -2454,6 +2461,7 @@ cifs_writev_requeue(struct cifs_writedata *wdata)
if (!is_retryable_error(rc))
rc = -EBADF;
} else {
+ atomic_inc(&CIFS_SB(inode->i_sb)->outstanding_aio);
wdata2->pid = wdata2->cfile->pid;
rc = server->ops->async_writev(wdata2,
cifs_writedata_release);
@@ -2837,8 +2845,10 @@ static int cifs_writepages(struct address_space *mapping,
rc = get_file_rc;
else
rc = -EBADF;
- } else
+ } else {
+ atomic_inc(&cifs_sb->outstanding_aio);
rc = wdata_send_pages(wdata, nr_pages, mapping, wbc);
+ }
for (i = 0; i < nr_pages; ++i)
unlock_page(wdata->pages[i]);
@@ -3185,11 +3195,12 @@ cifs_uncached_writedata_release(struct kref *refcount)
int i;
struct cifs_writedata *wdata = container_of(refcount,
struct cifs_writedata, refcount);
+ struct cifs_aio_ctx *ctx = wdata->ctx;
- kref_put(&wdata->ctx->refcount, cifs_aio_ctx_release);
for (i = 0; i < wdata->nr_pages; i++)
put_page(wdata->pages[i]);
cifs_writedata_release(refcount);
+ kref_put(&ctx->refcount, cifs_aio_ctx_release);
}
static void collect_uncached_write_data(struct cifs_aio_ctx *ctx);
@@ -3455,6 +3466,7 @@ cifs_write_from_iter(loff_t offset, size_t len, struct iov_iter *from,
wdata->nr_pages = nr_pages;
wdata->offset = (__u64)offset;
wdata->cfile = cifsFileInfo_get(open_file);
+ atomic_inc(&cifs_sb->outstanding_aio);
wdata->server = server;
wdata->pid = pid;
wdata->bytes = cur_len;
@@ -3615,6 +3627,7 @@ static ssize_t __cifs_writev(
return -ENOMEM;
ctx->cfile = cifsFileInfo_get(cfile);
+ atomic_inc(&cifs_sb->outstanding_aio);
if (!is_sync_kiocb(iocb))
ctx->iocb = iocb;
@@ -3817,17 +3830,24 @@ cifs_readdata_release(struct kref *refcount)
{
struct cifs_readdata *rdata = container_of(refcount,
struct cifs_readdata, refcount);
+ struct cifs_sb_info *cifs_sb = NULL;
+
#ifdef CONFIG_CIFS_SMB_DIRECT
if (rdata->mr) {
smbd_deregister_mr(rdata->mr);
rdata->mr = NULL;
}
#endif
- if (rdata->cfile)
+ if (rdata->cfile) {
+ cifs_sb = CIFS_SB(rdata->cfile->dentry->d_sb);
cifsFileInfo_put(rdata->cfile);
+ }
kvfree(rdata->pages);
kfree(rdata);
+
+ if (cifs_sb && atomic_dec_and_test(&cifs_sb->outstanding_aio))
+ wake_up_var(&cifs_sb->outstanding_aio);
}
static int
@@ -3862,13 +3882,14 @@ cifs_uncached_readdata_release(struct kref *refcount)
{
struct cifs_readdata *rdata = container_of(refcount,
struct cifs_readdata, refcount);
+ struct cifs_aio_ctx *ctx = rdata->ctx;
unsigned int i;
- kref_put(&rdata->ctx->refcount, cifs_aio_ctx_release);
for (i = 0; i < rdata->nr_pages; i++) {
put_page(rdata->pages[i]);
}
cifs_readdata_release(refcount);
+ kref_put(&ctx->refcount, cifs_aio_ctx_release);
}
/**
@@ -4172,6 +4193,7 @@ cifs_send_async_read(loff_t offset, size_t len, struct cifsFileInfo *open_file,
rdata->server = server;
rdata->cfile = cifsFileInfo_get(open_file);
+ atomic_inc(&cifs_sb->outstanding_aio);
rdata->nr_pages = npages;
rdata->offset = offset;
rdata->bytes = cur_len;
@@ -4357,6 +4379,7 @@ static ssize_t __cifs_readv(
return -ENOMEM;
ctx->cfile = cifsFileInfo_get(cfile);
+ atomic_inc(&cifs_sb->outstanding_aio);
if (!is_sync_kiocb(iocb))
ctx->iocb = iocb;
@@ -4895,6 +4918,7 @@ static void cifs_readahead(struct readahead_control *ractl)
rdata->nr_pages = nr_pages;
rdata->bytes = readahead_batch_length(ractl);
rdata->cfile = cifsFileInfo_get(open_file);
+ atomic_inc(&cifs_sb->outstanding_aio);
rdata->server = server;
rdata->mapping = ractl->mapping;
rdata->offset = readahead_pos(ractl);
diff --git a/fs/smb/client/misc.c b/fs/smb/client/misc.c
index 049a14677c910..203baf11625b9 100644
--- a/fs/smb/client/misc.c
+++ b/fs/smb/client/misc.c
@@ -1040,6 +1040,7 @@ cifs_aio_ctx_release(struct kref *refcount)
{
struct cifs_aio_ctx *ctx = container_of(refcount,
struct cifs_aio_ctx, refcount);
+ struct cifs_sb_info *cifs_sb = CIFS_SB(ctx->cfile->dentry->d_sb);
cifsFileInfo_put(ctx->cfile);
@@ -1060,6 +1061,9 @@ cifs_aio_ctx_release(struct kref *refcount)
}
kfree(ctx);
+
+ if (atomic_dec_and_test(&cifs_sb->outstanding_aio))
+ wake_up_var(&cifs_sb->outstanding_aio);
}
#define CIFS_AIO_KMALLOC_LIMIT (1024 * 1024)
--
2.43.0
next reply other threads:[~2026-11-04 17:33 UTC|newest]
Thread overview: 2+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-04 17:33 Siddharth Chintamaneni [this message]
2026-11-05 18:39 ` [PATCH 6.1.y] smb: client: fix busy dentry warning on unmount after DIO Sasha Levin
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=20261104173309.19150-1-sidchintamaneni@gmail.com \
--to=sidchintamaneni@gmail.com \
--cc=apais@microsoft.com \
--cc=glaming@microsoft.com \
--cc=gregkh@linuxfoundation.org \
--cc=hargar@linux.microsoft.com \
--cc=linux-cifs@vger.kernel.org \
--cc=lsahlber@redhat.com \
--cc=pc@manguebit.org \
--cc=rlmenge@gmail.com \
--cc=sashal@kernel.org \
--cc=sprasad@microsoft.com \
--cc=stable@vger.kernel.org \
--cc=tom@talpey.com \
--cc=wozizhi@huawei.com \
/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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox