From: Namjae Jeon Date: Sun, 28 Jun 2026 00:30:00 +0000 (+0900) Subject: ksmbd: snapshot previous oplock state before durable checks X-Git-Tag: v7.2-rc2~9^2~1 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=5138c84dbb501363510f6f9c300797b240a119cb;p=thirdparty%2Fkernel%2Flinux.git ksmbd: snapshot previous oplock state before durable checks 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 Signed-off-by: Namjae Jeon Signed-off-by: Steve French --- diff --git a/fs/smb/server/oplock.c b/fs/smb/server/oplock.c index 64cc8c5805e3..3c55ae5d6a11 100644 --- a/fs/smb/server/oplock.c +++ b/fs/smb/server/oplock.c @@ -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 &&