ksmbd: fix SMB2 byte-range lock end offset

SMB2 describes a byte-range lock using an offset and a length, while
Linux file_lock uses an inclusive end offset. smb2_lock() currently sets
fl_end to start + length and consequently locks one extra byte for every
nonzero-length request.

Translate nonzero lengths to start + length - 1 and reject ranges that
cannot be represented by loff_t instead of silently truncating them at
OFFSET_MAX. Track zero-length locks from the request length so one-byte
ranges are not mistaken for zero-length locks after endpoint conversion.

Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
This commit is contained in:
Namjae Jeon 2026-07-29 21:24:11 +09:00
parent b0148dc562
commit 54d90311f9

View File

@ -9131,7 +9131,7 @@ static int smb2_set_flock_flags(struct file_lock *flock, int flags)
}
static struct ksmbd_lock *smb2_lock_init(struct file_lock *flock,
unsigned int cmd, int flags,
unsigned int cmd, int flags, bool zero_len,
struct list_head *lock_list)
{
struct ksmbd_lock *lock;
@ -9145,8 +9145,7 @@ static struct ksmbd_lock *smb2_lock_init(struct file_lock *flock,
lock->start = flock->fl_start;
lock->end = flock->fl_end;
lock->flags = flags;
if (lock->start == lock->end)
lock->zero_len = 1;
lock->zero_len = zero_len;
INIT_LIST_HEAD(&lock->clist);
INIT_LIST_HEAD(&lock->flist);
INIT_LIST_HEAD(&lock->llist);
@ -9255,32 +9254,18 @@ int smb2_lock(struct ksmbd_work *work)
lock_start = le64_to_cpu(lock_ele[i].Offset);
lock_length = le64_to_cpu(lock_ele[i].Length);
if (lock_start > U64_MAX - lock_length) {
if (lock_start > OFFSET_MAX ||
(lock_length &&
lock_length - 1 > OFFSET_MAX - lock_start)) {
pr_err("Invalid lock range requested\n");
rsp->hdr.Status = STATUS_INVALID_LOCK_RANGE;
locks_free_lock(flock);
goto out;
}
if (lock_start > OFFSET_MAX)
flock->fl_start = OFFSET_MAX;
else
flock->fl_start = lock_start;
lock_length = le64_to_cpu(lock_ele[i].Length);
if (lock_length > OFFSET_MAX - flock->fl_start)
lock_length = OFFSET_MAX - flock->fl_start;
flock->fl_end = flock->fl_start + lock_length;
if (flock->fl_end < flock->fl_start) {
ksmbd_debug(SMB,
"the end offset(%llx) is smaller than the start offset(%llx)\n",
flock->fl_end, flock->fl_start);
rsp->hdr.Status = STATUS_INVALID_LOCK_RANGE;
locks_free_lock(flock);
goto out;
}
flock->fl_start = lock_start;
flock->fl_end = lock_length ?
flock->fl_start + lock_length - 1 : flock->fl_start;
/* Check conflict locks in one request */
list_for_each_entry(cmp_lock, &lock_list, llist) {
@ -9296,7 +9281,8 @@ int smb2_lock(struct ksmbd_work *work)
}
}
smb_lock = smb2_lock_init(flock, cmd, flags, &lock_list);
smb_lock = smb2_lock_init(flock, cmd, flags, !lock_length,
&lock_list);
if (!smb_lock) {
err = -EINVAL;
locks_free_lock(flock);
@ -9370,7 +9356,7 @@ int smb2_lock(struct ksmbd_work *work)
/* check zero byte lock range */
if (cmp_lock->zero_len && !smb_lock->zero_len &&
cmp_lock->start > smb_lock->start &&
cmp_lock->start < smb_lock->end) {
cmp_lock->start <= smb_lock->end) {
spin_unlock(&conn->llist_lock);
up_read(&conn_list_lock);
pr_err("previous lock conflict with zero byte lock range\n");
@ -9379,17 +9365,15 @@ int smb2_lock(struct ksmbd_work *work)
if (smb_lock->zero_len && !cmp_lock->zero_len &&
smb_lock->start > cmp_lock->start &&
smb_lock->start < cmp_lock->end) {
smb_lock->start <= cmp_lock->end) {
spin_unlock(&conn->llist_lock);
up_read(&conn_list_lock);
pr_err("current lock conflict with zero byte lock range\n");
goto out;
}
if (((cmp_lock->start <= smb_lock->start &&
cmp_lock->end > smb_lock->start) ||
(cmp_lock->start < smb_lock->end &&
cmp_lock->end >= smb_lock->end)) &&
if (cmp_lock->start <= smb_lock->end &&
smb_lock->start <= cmp_lock->end &&
!cmp_lock->zero_len && !smb_lock->zero_len) {
spin_unlock(&conn->llist_lock);
up_read(&conn_list_lock);