]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
ksmbd: snapshot previous oplock state before durable checks
authorNamjae Jeon <linkinjeon@kernel.org>
Sun, 28 Jun 2026 00:30:00 +0000 (09:30 +0900)
committerSteve French <stfrench@microsoft.com>
Wed, 1 Jul 2026 02:29:46 +0000 (21:29 -0500)
smb_grant_oplock() checks the previous oplock holder's o_fp to decide
whether a durable handle should be invalidated when the oplock break
cannot be delivered. prev_opinfo is obtained with opinfo_get_list(),
which pins only the oplock_info. It does not pin the ksmbd_file stored
in opinfo->o_fp.

A concurrent last close can unlink the opinfo from ci->m_op_list under
ci->m_lock and then free the ksmbd_file. The oplock_info can still be
kept alive by the refcount taken by opinfo_get_list(), but o_fp may
already point at freed memory by the time smb_grant_oplock() reads
is_durable, conn, or tcon.

Snapshot the previous holder's durable state while ci->m_lock is held,
then use only the copied values after dropping the lock. This keeps the
o_fp lifetime tied to the inode lock without taking an extra ksmbd_file
reference. Taking such a reference is unsafe here because smb_grant_oplock()
does not necessarily have the previous holder's session work, and dropping
the temporary reference can otherwise become the final putter.

Fixes: 26fa88dc877c ("ksmbd: invalidate durable handles on oplock break")
Reported-by: Gil Portnoy <dddhkts1@gmail.com>
Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
Signed-off-by: Steve French <stfrench@microsoft.com>
fs/smb/server/oplock.c

index 64cc8c5805e3aa908a06d6a3b2ed27d9cc4f6a6d..3c55ae5d6a11b68bc9712199c101ebfecd861778 100644 (file)
@@ -278,10 +278,24 @@ struct oplock_info *opinfo_get(struct ksmbd_file *fp)
        return opinfo;
 }
 
-static struct oplock_info *opinfo_get_list(struct ksmbd_inode *ci)
+struct oplock_snapshot {
+       bool durable_open;
+       bool durable_detached;
+       unsigned long long fid;
+};
+
+static struct oplock_info *opinfo_get_list(struct ksmbd_inode *ci,
+                                          struct ksmbd_file *skip_fp,
+                                          struct oplock_snapshot *snapshot)
 {
        struct oplock_info *opinfo;
 
+       if (snapshot) {
+               snapshot->durable_open = false;
+               snapshot->durable_detached = false;
+               snapshot->fid = KSMBD_NO_FID;
+       }
+
        down_read(&ci->m_lock);
        opinfo = list_first_entry_or_null(&ci->m_op_list, struct oplock_info,
                                          op_entry);
@@ -295,6 +309,16 @@ static struct oplock_info *opinfo_get_list(struct ksmbd_inode *ci)
                                opinfo = NULL;
                        }
                }
+
+               if (opinfo && snapshot && opinfo->o_fp &&
+                   opinfo->o_fp != skip_fp &&
+                   READ_ONCE(opinfo->o_fp->is_durable)) {
+                       snapshot->durable_open = true;
+                       snapshot->durable_detached =
+                               !READ_ONCE(opinfo->o_fp->conn) ||
+                               !READ_ONCE(opinfo->o_fp->tcon);
+                       snapshot->fid = opinfo->fid;
+               }
        }
        up_read(&ci->m_lock);
 
@@ -314,7 +338,7 @@ void opinfo_put(struct oplock_info *opinfo)
 
 static bool ksmbd_inode_has_lease(struct ksmbd_inode *ci)
 {
-       struct oplock_info *opinfo = opinfo_get_list(ci);
+       struct oplock_info *opinfo = opinfo_get_list(ci, NULL, NULL);
        bool is_lease;
 
        if (!opinfo)
@@ -1421,6 +1445,7 @@ int smb_grant_oplock(struct ksmbd_work *work, int req_op_level, u64 pid,
        struct oplock_info *opinfo = NULL, *prev_opinfo = NULL;
        struct ksmbd_inode *ci = fp->f_ci;
        struct lease_table *new_lb = NULL;
+       struct oplock_snapshot prev_op_snapshot;
        bool prev_op_has_lease;
        bool prev_durable_open = false;
        bool prev_durable_detached = false;
@@ -1488,7 +1513,7 @@ int smb_grant_oplock(struct ksmbd_work *work, int req_op_level, u64 pid,
                        goto out;
                }
        }
-       prev_opinfo = opinfo_get_list(ci);
+       prev_opinfo = opinfo_get_list(ci, fp, &prev_op_snapshot);
        if (!prev_opinfo ||
            (prev_opinfo->level == SMB2_OPLOCK_LEVEL_NONE && lctx)) {
                opinfo_put(prev_opinfo);
@@ -1510,13 +1535,9 @@ int smb_grant_oplock(struct ksmbd_work *work, int req_op_level, u64 pid,
                goto op_break_not_needed;
        }
 
-       if (prev_opinfo->o_fp && prev_opinfo->o_fp != fp &&
-           prev_opinfo->o_fp->is_durable) {
-               prev_durable_open = true;
-               prev_durable_detached = !prev_opinfo->o_fp->conn ||
-                                       !prev_opinfo->o_fp->tcon;
-               prev_fid = prev_opinfo->fid;
-       }
+       prev_durable_open = prev_op_snapshot.durable_open;
+       prev_durable_detached = prev_op_snapshot.durable_detached;
+       prev_fid = prev_op_snapshot.fid;
 
        err = oplock_break(prev_opinfo, break_level, work,
                           share_ret < 0 && prev_opinfo->is_lease);
@@ -1607,7 +1628,7 @@ static bool smb_break_all_write_oplock(struct ksmbd_work *work,
        struct oplock_info *brk_opinfo;
        bool sent_break = false;
 
-       brk_opinfo = opinfo_get_list(fp->f_ci);
+       brk_opinfo = opinfo_get_list(fp->f_ci, NULL, NULL);
        if (!brk_opinfo)
                return false;
        if (brk_opinfo->level != SMB2_OPLOCK_LEVEL_BATCH &&