mirror of
https://github.com/torvalds/linux.git
synced 2026-07-29 02:31:27 +02:00
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: 26fa88dc87 ("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>
This commit is contained in:
parent
c706195e5e
commit
5138c84dbb
|
|
@ -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 &&
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user