* [PATCH] ksmbd: fix use-after-free in oplock break notification
@ 2026-08-29 10:22 Abdifatah Suruur
2026-08-29 11:05 ` Namjae Jeon
0 siblings, 1 reply; 4+ messages in thread
From: Abdifatah Suruur @ 2026-08-29 10:22 UTC (permalink / raw)
To: linux-cifs; +Cc: linkinjeon, sfrench, senozhatsky, tom
smb2_oplock_break_noti() reads opinfo->conn without any lock and
dereferences it after two allocations which may sleep. When the
durable handle owning the oplock is disconnected, session_fd_check()
clears opinfo->conn and drops its conn reference under ci->m_lock, and
the last ksmbd_conn_put() frees the connection. A break triggered by
another connection that races with the teardown can then resurrect the
freed connection: ksmbd_conn_get() is a plain atomic_inc, and the
queued break work later dereferences the stale conn via
ksmbd_conn_write(), a use-after-free reachable by any authenticated
client holding a durable batch oplock.
Select and pin the connection under ci->m_lock before the allocations,
the same lock session_fd_check() uses to clear opinfo->conn, so a
concurrent teardown either loses the race to the clear or keeps the
connection alive until the notification work releases it. Transfer the
reference to the work item and release it on allocation failures.
Fixes: b003086d7696 ("ksmbd: fix NULL-deref of opinfo->conn in oplock/lease break notifiers")
Cc: stable@vger.kernel.org
Signed-off-by: Abdifatah Suruur <suruurism@gmail.com>
---
fs/smb/server/oplock.c | 36 ++++++++++++++++++++++++++++--------
1 file changed, 28 insertions(+), 8 deletions(-)
diff --git a/fs/smb/server/oplock.c b/fs/smb/server/oplock.c
@@ -924,6 +924,32 @@
ksmbd_conn_put(conn);
}
+/*
+ * Select and pin the connection used for an oplock break before doing any
+ * allocations which may sleep. opinfo->conn is cleared under ci->m_lock by
+ * session_fd_check() when the durable handle owning the oplock is
+ * disconnected, and the last ksmbd_conn_put() frees the connection.
+ */
+static struct ksmbd_conn *smb2_oplock_break_conn_get(struct oplock_info *opinfo)
+{
+ struct ksmbd_inode *ci;
+ struct ksmbd_conn *conn;
+
+ if (!opinfo->o_fp)
+ return NULL;
+ ci = opinfo->o_fp->f_ci;
+
+ down_read(&ci->m_lock);
+ conn = READ_ONCE(opinfo->conn);
+ if (conn && !ksmbd_conn_releasing(conn))
+ conn = ksmbd_conn_get(conn);
+ else
+ conn = NULL;
+ up_read(&ci->m_lock);
+
+ return conn;
+}
+
/**
* smb2_oplock_break_noti() - send smb2 exclusive/batch to level2 oplock
* break command from server to client
@@ -938,17 +964,20 @@
int ret = 0;
struct ksmbd_work *work;
- conn = READ_ONCE(opinfo->conn);
+ conn = smb2_oplock_break_conn_get(opinfo);
if (!conn)
return ksmbd_invalidate_durable_fd(opinfo->fid);
work = ksmbd_alloc_work_struct();
- if (!work)
+ if (!work) {
+ ksmbd_conn_put(conn);
return -ENOMEM;
+ }
br_info = kmalloc_obj(struct oplock_break_info, KSMBD_DEFAULT_GFP);
if (!br_info) {
ksmbd_free_work_struct(work);
+ ksmbd_conn_put(conn);
return -ENOMEM;
}
@@ -957,7 +986,8 @@
br_info->open_trunc = opinfo->open_trunc;
work->request_buf = (char *)br_info;
- work->conn = ksmbd_conn_get(conn);
+ /* Transfer the reference acquired by smb2_oplock_break_conn_get(). */
+ work->conn = conn;
work->sess = opinfo->sess;
ksmbd_conn_r_count_inc(conn);
^ permalink raw reply [flat|nested] 4+ messages in thread
* Re: [PATCH] ksmbd: fix use-after-free in oplock break notification
2026-08-29 10:22 Abdifatah Suruur
@ 2026-08-29 11:05 ` Namjae Jeon
0 siblings, 0 replies; 4+ messages in thread
From: Namjae Jeon @ 2026-08-29 11:05 UTC (permalink / raw)
To: Abdifatah Suruur; +Cc: linux-cifs, senozhatsky, tom
On Sat, Aug 29, 2026 at 7:22 PM Abdifatah Suruur <suruurism@gmail.com> wrote:
>
> smb2_oplock_break_noti() reads opinfo->conn without any lock and
> dereferences it after two allocations which may sleep. When the
> durable handle owning the oplock is disconnected, session_fd_check()
> clears opinfo->conn and drops its conn reference under ci->m_lock, and
> the last ksmbd_conn_put() frees the connection. A break triggered by
> another connection that races with the teardown can then resurrect the
> freed connection: ksmbd_conn_get() is a plain atomic_inc, and the
> queued break work later dereferences the stale conn via
> ksmbd_conn_write(), a use-after-free reachable by any authenticated
> client holding a durable batch oplock.
>
> Select and pin the connection under ci->m_lock before the allocations,
> the same lock session_fd_check() uses to clear opinfo->conn, so a
> concurrent teardown either loses the race to the clear or keeps the
> connection alive until the notification work releases it. Transfer the
> reference to the work item and release it on allocation failures.
>
> Fixes: b003086d7696 ("ksmbd: fix NULL-deref of opinfo->conn in oplock/lease break notifiers")
> Cc: stable@vger.kernel.org
> Signed-off-by: Abdifatah Suruur <suruurism@gmail.com>
I can not apply this patch...
Applying: ksmbd: fix use-after-free in oplock break notification
error: patch fragment without header at line 7: @@ -924,6 +924,32 @@
Patch failed at 0001 ksmbd: fix use-after-free in oplock break notification
>
> ---
> fs/smb/server/oplock.c | 36 ++++++++++++++++++++++++++++--------
> 1 file changed, 28 insertions(+), 8 deletions(-)
>
> diff --git a/fs/smb/server/oplock.c b/fs/smb/server/oplock.c
> @@ -924,6 +924,32 @@
> ksmbd_conn_put(conn);
> }
>
> +/*
> + * Select and pin the connection used for an oplock break before doing any
> + * allocations which may sleep. opinfo->conn is cleared under ci->m_lock by
> + * session_fd_check() when the durable handle owning the oplock is
> + * disconnected, and the last ksmbd_conn_put() frees the connection.
> + */
> +static struct ksmbd_conn *smb2_oplock_break_conn_get(struct oplock_info *opinfo)
> +{
> + struct ksmbd_inode *ci;
> + struct ksmbd_conn *conn;
> +
> + if (!opinfo->o_fp)
> + return NULL;
> + ci = opinfo->o_fp->f_ci;
opinfo reference does not pin opinfo->o_fp or o_fp->f_ci. opinfo can
still be valid after o_fp has been freed by concurrent close, so
dereferencing opinfo->o_fp here may cause a use-after-free. How is
o_fp protected here?
^ permalink raw reply [flat|nested] 4+ messages in thread
* [PATCH] ksmbd: fix use-after-free in oplock break notification
@ 2026-08-29 11:38 Abdifatah Suruur
0 siblings, 0 replies; 4+ messages in thread
From: Abdifatah Suruur @ 2026-08-29 11:38 UTC (permalink / raw)
To: linux-cifs; +Cc: linkinjeon, senozhatsky, tom
smb2_oplock_break_noti() reads opinfo->conn without any lock and
dereferences it after two allocations which may sleep. When the
durable handle owning the oplock is disconnected, session_fd_check()
clears opinfo->conn and drops its conn reference under ci->m_lock, and
the last ksmbd_conn_put() frees the connection. A break triggered by
another connection that races with the teardown can then resurrect the
freed connection: ksmbd_conn_get() is a plain atomic_inc, and the
queued break work later dereferences the stale conn via
ksmbd_conn_write(), a use-after-free reachable by any authenticated
client holding a durable batch oplock.
Give the oplock_info its own reference on the inode (o_ci) so the
notification path can take ci->m_lock without dereferencing
opinfo->o_fp, which is not pinned by the oplock_info reference and may
be freed by a concurrent close. Select and pin the connection under
ci->m_lock, the same lock session_fd_check() uses to clear
opinfo->conn, so a concurrent teardown either loses the race to the
clear or keeps the connection alive until the notification work
releases it. Transfer the reference to the work item and release it
on allocation failures.
Fixes: b003086d7696 ("ksmbd: fix NULL-deref of opinfo->conn in oplock/lease break notifiers")
Cc: stable@vger.kernel.org
Signed-off-by: Abdifatah Suruur <suruurism@gmail.com>
---
v2:
- The oplock_info now holds a reference on the inode (o_ci), taken
when the oplock is granted and released when the oplock_info is
freed, so smb2_oplock_break_conn_get() no longer dereferences
opinfo->o_fp, which a concurrent close may free (review from Namjae
Jeon).
---
fs/smb/server/oplock.c | 38 +++++++++++++++++++++++++++++++++++---
fs/smb/server/oplock.h | 1 +
fs/smb/server/vfs_cache.h | 6 ++++++
3 files changed, 42 insertions(+), 3 deletions(-)
diff --git a/fs/smb/server/oplock.c b/fs/smb/server/oplock.c
index 58af0fddf39f2..fcd1210de980c 100644
--- a/fs/smb/server/oplock.c
+++ b/fs/smb/server/oplock.c
@@ -9,6 +9,7 @@
#include "glob.h"
#include "oplock.h"
+#include "vfs_cache.h"
#include "smb_common.h"
#include "../common/smb2status.h"
@@ -236,6 +237,8 @@ static void __free_opinfo(struct oplock_info *opinfo)
{
if (opinfo->is_lease)
free_lease(opinfo);
+ if (opinfo->o_ci)
+ ksmbd_inode_put(opinfo->o_ci);
ksmbd_conn_put(opinfo->conn);
kfree(opinfo);
}
@@ -924,6 +927,30 @@ static void __smb2_oplock_break_noti(struct work_struct *wk)
ksmbd_conn_put(conn);
}
+/*
+ * Select and pin the connection used for an oplock break before doing any
+ * allocations which may sleep. opinfo->conn is cleared under ci->m_lock by
+ * session_fd_check() when the durable handle owning the oplock is
+ * disconnected, and the last ksmbd_conn_put() frees the connection. The
+ * oplock_info holds its own reference on the inode (o_ci, taken when the
+ * oplock was granted), so the lock is always reachable here without
+ * dereferencing opinfo->o_fp, which a concurrent close may free.
+ */
+static struct ksmbd_conn *smb2_oplock_break_conn_get(struct oplock_info *opinfo)
+{
+ struct ksmbd_conn *conn;
+
+ down_read(&opinfo->o_ci->m_lock);
+ conn = READ_ONCE(opinfo->conn);
+ if (conn && !ksmbd_conn_releasing(conn))
+ conn = ksmbd_conn_get(conn);
+ else
+ conn = NULL;
+ up_read(&opinfo->o_ci->m_lock);
+
+ return conn;
+}
+
/**
* smb2_oplock_break_noti() - send smb2 exclusive/batch to level2 oplock
* break command from server to client
@@ -938,17 +965,20 @@ static int smb2_oplock_break_noti(struct oplock_info *opinfo)
int ret = 0;
struct ksmbd_work *work;
- conn = READ_ONCE(opinfo->conn);
+ conn = smb2_oplock_break_conn_get(opinfo);
if (!conn)
return ksmbd_invalidate_durable_fd(opinfo->fid);
work = ksmbd_alloc_work_struct();
- if (!work)
+ if (!work) {
+ ksmbd_conn_put(conn);
return -ENOMEM;
+ }
br_info = kmalloc_obj(struct oplock_break_info, KSMBD_DEFAULT_GFP);
if (!br_info) {
ksmbd_free_work_struct(work);
+ ksmbd_conn_put(conn);
return -ENOMEM;
}
@@ -957,7 +987,8 @@ static int smb2_oplock_break_noti(struct oplock_info *opinfo)
br_info->open_trunc = opinfo->open_trunc;
work->request_buf = (char *)br_info;
- work->conn = ksmbd_conn_get(conn);
+ /* Transfer the reference acquired by smb2_oplock_break_conn_get(). */
+ work->conn = conn;
work->sess = opinfo->sess;
ksmbd_conn_r_count_inc(conn);
@@ -1724,6 +1755,7 @@ int smb_grant_oplock(struct ksmbd_work *work, int req_op_level, u64 pid,
* lease_table first.
*/
opinfo->o_fp = fp;
+ opinfo->o_ci = ksmbd_inode_ref(fp->f_ci);
if (new_lease) {
new_lb = alloc_lease_table(opinfo);
if (!new_lb) {
diff --git a/fs/smb/server/oplock.h b/fs/smb/server/oplock.h
index b08d21758e07a..3e9e63514896d 100644
--- a/fs/smb/server/oplock.h
+++ b/fs/smb/server/oplock.h
@@ -64,6 +64,7 @@ struct oplock_info {
struct ksmbd_session *sess;
struct ksmbd_work *work;
struct ksmbd_file *o_fp;
+ struct ksmbd_inode *o_ci; /* inode ref held while the oplock lives */
int level;
int op_state;
spinlock_t state_lock;
diff --git a/fs/smb/server/vfs_cache.h b/fs/smb/server/vfs_cache.h
index 502efb16f05fc..14d3111eb7334 100644
--- a/fs/smb/server/vfs_cache.h
+++ b/fs/smb/server/vfs_cache.h
@@ -206,6 +206,12 @@ struct ksmbd_file *ksmbd_file_get(struct ksmbd_file *fp);
void ksmbd_fd_put(struct ksmbd_work *work, struct ksmbd_file *fp);
struct ksmbd_inode *ksmbd_inode_lookup_lock(struct dentry *d);
void ksmbd_inode_put(struct ksmbd_inode *ci);
+
+static inline struct ksmbd_inode *ksmbd_inode_ref(struct ksmbd_inode *ci)
+{
+ atomic_inc(&ci->m_count);
+ return ci;
+}
bool ksmbd_close_disconnected_durable_delete_on_close(struct dentry *dentry);
struct ksmbd_file *ksmbd_lookup_global_fd(unsigned long long id);
struct ksmbd_file *ksmbd_lookup_durable_fd(unsigned long long id);
--
2.53.0
^ permalink raw reply related [flat|nested] 4+ messages in thread
* [PATCH] ksmbd: fix use-after-free in oplock break notification
@ 2026-08-29 11:38 Abdifatah Suruur
0 siblings, 0 replies; 4+ messages in thread
From: Abdifatah Suruur @ 2026-08-29 11:38 UTC (permalink / raw)
To: linux-cifs; +Cc: linkinjeon, senozhatsky, tom
smb2_oplock_break_noti() reads opinfo->conn without any lock and
dereferences it after two allocations which may sleep. When the
durable handle owning the oplock is disconnected, session_fd_check()
clears opinfo->conn and drops its conn reference under ci->m_lock, and
the last ksmbd_conn_put() frees the connection. A break triggered by
another connection that races with the teardown can then resurrect the
freed connection: ksmbd_conn_get() is a plain atomic_inc, and the
queued break work later dereferences the stale conn via
ksmbd_conn_write(), a use-after-free reachable by any authenticated
client holding a durable batch oplock.
Give the oplock_info its own reference on the inode (o_ci) so the
notification path can take ci->m_lock without dereferencing
opinfo->o_fp, which is not pinned by the oplock_info reference and may
be freed by a concurrent close. Select and pin the connection under
ci->m_lock, the same lock session_fd_check() uses to clear
opinfo->conn, so a concurrent teardown either loses the race to the
clear or keeps the connection alive until the notification work
releases it. Transfer the reference to the work item and release it
on allocation failures.
Fixes: b003086d7696 ("ksmbd: fix NULL-deref of opinfo->conn in oplock/lease break notifiers")
Cc: stable@vger.kernel.org
Signed-off-by: Abdifatah Suruur <suruurism@gmail.com>
---
v2:
- The oplock_info now holds a reference on the inode (o_ci), taken
when the oplock is granted and released when the oplock_info is
freed, so smb2_oplock_break_conn_get() no longer dereferences
opinfo->o_fp, which a concurrent close may free (review from Namjae
Jeon).
---
fs/smb/server/oplock.c | 38 +++++++++++++++++++++++++++++++++++---
fs/smb/server/oplock.h | 1 +
fs/smb/server/vfs_cache.h | 6 ++++++
3 files changed, 42 insertions(+), 3 deletions(-)
diff --git a/fs/smb/server/oplock.c b/fs/smb/server/oplock.c
index 58af0fddf39f2..fcd1210de980c 100644
--- a/fs/smb/server/oplock.c
+++ b/fs/smb/server/oplock.c
@@ -9,6 +9,7 @@
#include "glob.h"
#include "oplock.h"
+#include "vfs_cache.h"
#include "smb_common.h"
#include "../common/smb2status.h"
@@ -236,6 +237,8 @@ static void __free_opinfo(struct oplock_info *opinfo)
{
if (opinfo->is_lease)
free_lease(opinfo);
+ if (opinfo->o_ci)
+ ksmbd_inode_put(opinfo->o_ci);
ksmbd_conn_put(opinfo->conn);
kfree(opinfo);
}
@@ -924,6 +927,30 @@ static void __smb2_oplock_break_noti(struct work_struct *wk)
ksmbd_conn_put(conn);
}
+/*
+ * Select and pin the connection used for an oplock break before doing any
+ * allocations which may sleep. opinfo->conn is cleared under ci->m_lock by
+ * session_fd_check() when the durable handle owning the oplock is
+ * disconnected, and the last ksmbd_conn_put() frees the connection. The
+ * oplock_info holds its own reference on the inode (o_ci, taken when the
+ * oplock was granted), so the lock is always reachable here without
+ * dereferencing opinfo->o_fp, which a concurrent close may free.
+ */
+static struct ksmbd_conn *smb2_oplock_break_conn_get(struct oplock_info *opinfo)
+{
+ struct ksmbd_conn *conn;
+
+ down_read(&opinfo->o_ci->m_lock);
+ conn = READ_ONCE(opinfo->conn);
+ if (conn && !ksmbd_conn_releasing(conn))
+ conn = ksmbd_conn_get(conn);
+ else
+ conn = NULL;
+ up_read(&opinfo->o_ci->m_lock);
+
+ return conn;
+}
+
/**
* smb2_oplock_break_noti() - send smb2 exclusive/batch to level2 oplock
* break command from server to client
@@ -938,17 +965,20 @@ static int smb2_oplock_break_noti(struct oplock_info *opinfo)
int ret = 0;
struct ksmbd_work *work;
- conn = READ_ONCE(opinfo->conn);
+ conn = smb2_oplock_break_conn_get(opinfo);
if (!conn)
return ksmbd_invalidate_durable_fd(opinfo->fid);
work = ksmbd_alloc_work_struct();
- if (!work)
+ if (!work) {
+ ksmbd_conn_put(conn);
return -ENOMEM;
+ }
br_info = kmalloc_obj(struct oplock_break_info, KSMBD_DEFAULT_GFP);
if (!br_info) {
ksmbd_free_work_struct(work);
+ ksmbd_conn_put(conn);
return -ENOMEM;
}
@@ -957,7 +987,8 @@ static int smb2_oplock_break_noti(struct oplock_info *opinfo)
br_info->open_trunc = opinfo->open_trunc;
work->request_buf = (char *)br_info;
- work->conn = ksmbd_conn_get(conn);
+ /* Transfer the reference acquired by smb2_oplock_break_conn_get(). */
+ work->conn = conn;
work->sess = opinfo->sess;
ksmbd_conn_r_count_inc(conn);
@@ -1724,6 +1755,7 @@ int smb_grant_oplock(struct ksmbd_work *work, int req_op_level, u64 pid,
* lease_table first.
*/
opinfo->o_fp = fp;
+ opinfo->o_ci = ksmbd_inode_ref(fp->f_ci);
if (new_lease) {
new_lb = alloc_lease_table(opinfo);
if (!new_lb) {
diff --git a/fs/smb/server/oplock.h b/fs/smb/server/oplock.h
index b08d21758e07a..3e9e63514896d 100644
--- a/fs/smb/server/oplock.h
+++ b/fs/smb/server/oplock.h
@@ -64,6 +64,7 @@ struct oplock_info {
struct ksmbd_session *sess;
struct ksmbd_work *work;
struct ksmbd_file *o_fp;
+ struct ksmbd_inode *o_ci; /* inode ref held while the oplock lives */
int level;
int op_state;
spinlock_t state_lock;
diff --git a/fs/smb/server/vfs_cache.h b/fs/smb/server/vfs_cache.h
index 502efb16f05fc..14d3111eb7334 100644
--- a/fs/smb/server/vfs_cache.h
+++ b/fs/smb/server/vfs_cache.h
@@ -206,6 +206,12 @@ struct ksmbd_file *ksmbd_file_get(struct ksmbd_file *fp);
void ksmbd_fd_put(struct ksmbd_work *work, struct ksmbd_file *fp);
struct ksmbd_inode *ksmbd_inode_lookup_lock(struct dentry *d);
void ksmbd_inode_put(struct ksmbd_inode *ci);
+
+static inline struct ksmbd_inode *ksmbd_inode_ref(struct ksmbd_inode *ci)
+{
+ atomic_inc(&ci->m_count);
+ return ci;
+}
bool ksmbd_close_disconnected_durable_delete_on_close(struct dentry *dentry);
struct ksmbd_file *ksmbd_lookup_global_fd(unsigned long long id);
struct ksmbd_file *ksmbd_lookup_durable_fd(unsigned long long id);
--
2.53.0
^ permalink raw reply related [flat|nested] 4+ messages in thread
end of thread, other threads:[~2026-08-29 11:38 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-29 11:38 [PATCH] ksmbd: fix use-after-free in oplock break notification Abdifatah Suruur
-- strict thread matches above, loose matches on Subject: below --
2026-08-29 11:38 Abdifatah Suruur
2026-08-29 10:22 Abdifatah Suruur
2026-08-29 11:05 ` Namjae Jeon
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox