ksmbd: recognize replayed SMB2 lock sequences

A server returns success without processing a lock request when a valid
LockSequenceArray entry contains the same sequence number. The current
verifier only invalidates mismatched entries, so matching requests are
submitted to the VFS again and recorded as duplicate locks.

Make the verifier report matching sequences and skip lock processing for
those replays. Also correct the field comment to describe the sequence
and index bit layout used by the implementation and the protocol. Use
the capabilities advertised by the server when deciding whether lock
sequence verification applies to a multichannel connection.

Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
This commit is contained in:
Namjae Jeon 2026-07-29 21:24:54 +09:00
parent 54d90311f9
commit 5ce5227cd6
2 changed files with 25 additions and 13 deletions

View File

@ -847,8 +847,8 @@ struct smb2_lock_req {
__le16 StructureSize; /* Must be 48 */
__le16 LockCount;
/*
* The least significant four bits are the index, the other 28 bits are
* the lock sequence number (0 to 64). See MS-SMB2 2.2.26
* The least significant four bits are the lock sequence number. The
* other 28 bits are the index (0 to 64). See MS-SMB2 2.2.26.
*/
__le32 LockSequenceNumber;
__u64 PersistentFileId;

View File

@ -787,31 +787,38 @@ static bool smb2_lock_sequence_applicable(struct ksmbd_work *work,
{
return fp->is_resilient || fp->is_durable || fp->is_persistent ||
(work->conn->dialect >= SMB30_PROT_ID &&
(work->conn->cli_cap & SMB2_GLOBAL_CAP_MULTI_CHANNEL));
(work->conn->vals->req_capabilities &
SMB2_GLOBAL_CAP_MULTI_CHANNEL));
}
static void smb2_verify_lock_sequence(struct ksmbd_work *work,
struct ksmbd_file *fp,
struct smb2_lock_req *req)
static bool smb2_verify_lock_sequence(struct ksmbd_work *work,
struct ksmbd_file *fp,
struct smb2_lock_req *req)
{
u32 val, index;
u8 sequence;
bool replay = false;
if (work->conn->dialect == SMB20_PROT_ID ||
!smb2_lock_sequence_applicable(work, fp))
return;
return false;
val = le32_to_cpu(req->LockSequenceNumber);
sequence = val & 0xf;
index = val >> 4;
if (!index || index > KSMBD_LOCK_SEQ_ARRAY_SIZE)
return;
return false;
spin_lock(&fp->f_lock);
if (fp->lock_seq[index - 1].valid &&
fp->lock_seq[index - 1].sequence != sequence)
fp->lock_seq[index - 1].valid = false;
if (fp->lock_seq[index - 1].valid) {
if (fp->lock_seq[index - 1].sequence == sequence)
replay = true;
else
fp->lock_seq[index - 1].valid = false;
}
spin_unlock(&fp->f_lock);
return replay;
}
static void smb2_update_lock_sequence(struct ksmbd_work *work,
@ -9194,6 +9201,7 @@ int smb2_lock(struct ksmbd_work *work)
LIST_HEAD(rollback_list);
int prior_lock = 0, bkt;
unsigned int id = KSMBD_NO_FID, pid = KSMBD_NO_FID;
bool lock_replayed;
WORK_BUFFERS(work, req, rsp);
@ -9226,7 +9234,9 @@ int smb2_lock(struct ksmbd_work *work)
if (err)
goto out2;
smb2_verify_lock_sequence(work, fp, req);
lock_replayed = smb2_verify_lock_sequence(work, fp, req);
if (lock_replayed)
goto lock_success;
filp = fp->filp;
lock_count = le16_to_cpu(req->LockCount);
@ -9493,6 +9503,7 @@ int smb2_lock(struct ksmbd_work *work)
if (atomic_read(&fp->f_ci->op_count) > 1)
smb_break_all_oplock(work, fp);
lock_success:
rsp->StructureSize = cpu_to_le16(4);
ksmbd_debug(SMB, "successful in taking lock\n");
rsp->hdr.Status = STATUS_SUCCESS;
@ -9500,7 +9511,8 @@ int smb2_lock(struct ksmbd_work *work)
err = ksmbd_iov_pin_rsp(work, rsp, sizeof(struct smb2_lock_rsp));
if (err)
goto out;
smb2_update_lock_sequence(work, fp, req);
if (!lock_replayed)
smb2_update_lock_sequence(work, fp, req);
ksmbd_fd_put(work, fp);
return 0;