summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2026-03-27 12:03:39 -0700
committerLinus Torvalds <torvalds@linux-foundation.org>2026-03-27 12:03:39 -0700
commit34892992d0ed45b4b0547f25e01887b56959fd5f (patch)
tree4ef1fbbc8978f459d9ec38df8019ebbc2a455a1a
parent46b513250491a7bfc97d98791dbe6a10bcc8129d (diff)
parentbeef2634f81f1c086208191f7228bce1d366493d (diff)
Merge tag 'v7.0-rc5-ksmbd-srv-fixes' of git://git.samba.org/ksmbd
Pull smb server fixes from Steve French: - Fix out of bounds write - Fix for better calculating max output buffers - Fix memory leaks in SMB2/SMB3 lock - Fix use after free - Multichannel fix * tag 'v7.0-rc5-ksmbd-srv-fixes' of git://git.samba.org/ksmbd: ksmbd: fix potencial OOB in get_file_all_info() for compound requests ksmbd: replace hardcoded hdr2_len with offsetof() in smb2_calc_max_out_buf_len() ksmbd: fix memory leaks and NULL deref in smb2_lock() ksmbd: fix use-after-free and NULL deref in smb_grant_oplock() ksmbd: do not expire session on binding failure
-rw-r--r--fs/smb/server/oplock.c72
-rw-r--r--fs/smb/server/smb2pdu.c73
2 files changed, 97 insertions, 48 deletions
diff --git a/fs/smb/server/oplock.c b/fs/smb/server/oplock.c
index 393a4ae47cc1..9b2bb8764a80 100644
--- a/fs/smb/server/oplock.c
+++ b/fs/smb/server/oplock.c
@@ -82,11 +82,19 @@ static void lease_del_list(struct oplock_info *opinfo)
spin_unlock(&lb->lb_lock);
}
-static void lb_add(struct lease_table *lb)
+static struct lease_table *alloc_lease_table(struct oplock_info *opinfo)
{
- write_lock(&lease_list_lock);
- list_add(&lb->l_entry, &lease_table_list);
- write_unlock(&lease_list_lock);
+ struct lease_table *lb;
+
+ lb = kmalloc_obj(struct lease_table, KSMBD_DEFAULT_GFP);
+ if (!lb)
+ return NULL;
+
+ memcpy(lb->client_guid, opinfo->conn->ClientGUID,
+ SMB2_CLIENT_GUID_SIZE);
+ INIT_LIST_HEAD(&lb->lease_list);
+ spin_lock_init(&lb->lb_lock);
+ return lb;
}
static int alloc_lease(struct oplock_info *opinfo, struct lease_ctx_info *lctx)
@@ -1042,34 +1050,27 @@ static void copy_lease(struct oplock_info *op1, struct oplock_info *op2)
lease2->version = lease1->version;
}
-static int add_lease_global_list(struct oplock_info *opinfo)
+static void add_lease_global_list(struct oplock_info *opinfo,
+ struct lease_table *new_lb)
{
struct lease_table *lb;
- read_lock(&lease_list_lock);
+ write_lock(&lease_list_lock);
list_for_each_entry(lb, &lease_table_list, l_entry) {
if (!memcmp(lb->client_guid, opinfo->conn->ClientGUID,
SMB2_CLIENT_GUID_SIZE)) {
opinfo->o_lease->l_lb = lb;
lease_add_list(opinfo);
- read_unlock(&lease_list_lock);
- return 0;
+ write_unlock(&lease_list_lock);
+ kfree(new_lb);
+ return;
}
}
- read_unlock(&lease_list_lock);
- lb = kmalloc_obj(struct lease_table, KSMBD_DEFAULT_GFP);
- if (!lb)
- return -ENOMEM;
-
- memcpy(lb->client_guid, opinfo->conn->ClientGUID,
- SMB2_CLIENT_GUID_SIZE);
- INIT_LIST_HEAD(&lb->lease_list);
- spin_lock_init(&lb->lb_lock);
- opinfo->o_lease->l_lb = lb;
+ opinfo->o_lease->l_lb = new_lb;
lease_add_list(opinfo);
- lb_add(lb);
- return 0;
+ list_add(&new_lb->l_entry, &lease_table_list);
+ write_unlock(&lease_list_lock);
}
static void set_oplock_level(struct oplock_info *opinfo, int level,
@@ -1189,6 +1190,7 @@ int smb_grant_oplock(struct ksmbd_work *work, int req_op_level, u64 pid,
int err = 0;
struct oplock_info *opinfo = NULL, *prev_opinfo = NULL;
struct ksmbd_inode *ci = fp->f_ci;
+ struct lease_table *new_lb = NULL;
bool prev_op_has_lease;
__le32 prev_op_state = 0;
@@ -1291,21 +1293,37 @@ set_lev:
set_oplock_level(opinfo, req_op_level, lctx);
out:
- opinfo_count_inc(fp);
- opinfo_add(opinfo, fp);
-
+ /*
+ * Set o_fp before any publication so that concurrent readers
+ * (e.g. find_same_lease_key() on the lease list) that
+ * dereference opinfo->o_fp don't hit a NULL pointer.
+ *
+ * Keep the original publication order so concurrent opens can
+ * still observe the in-flight grant via ci->m_op_list, but make
+ * everything after opinfo_add() no-fail by preallocating any new
+ * lease_table first.
+ */
+ opinfo->o_fp = fp;
if (opinfo->is_lease) {
- err = add_lease_global_list(opinfo);
- if (err)
+ new_lb = alloc_lease_table(opinfo);
+ if (!new_lb) {
+ err = -ENOMEM;
goto err_out;
+ }
}
+ opinfo_count_inc(fp);
+ opinfo_add(opinfo, fp);
+
+ if (opinfo->is_lease)
+ add_lease_global_list(opinfo, new_lb);
+
rcu_assign_pointer(fp->f_opinfo, opinfo);
- opinfo->o_fp = fp;
return 0;
err_out:
- __free_opinfo(opinfo);
+ kfree(new_lb);
+ opinfo_put(opinfo);
return err;
}
diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c
index 9c44e71e3c3b..6fb7a795ff5d 100644
--- a/fs/smb/server/smb2pdu.c
+++ b/fs/smb/server/smb2pdu.c
@@ -1939,8 +1939,14 @@ out_err:
if (sess->user && sess->user->flags & KSMBD_USER_FLAG_DELAY_SESSION)
try_delay = true;
- sess->last_active = jiffies;
- sess->state = SMB2_SESSION_EXPIRED;
+ /*
+ * For binding requests, session belongs to another
+ * connection. Do not expire it.
+ */
+ if (!(req->Flags & SMB2_SESSION_REQ_FLAG_BINDING)) {
+ sess->last_active = jiffies;
+ sess->state = SMB2_SESSION_EXPIRED;
+ }
ksmbd_user_session_put(sess);
work->sess = NULL;
if (try_delay) {
@@ -4446,8 +4452,9 @@ int smb2_query_dir(struct ksmbd_work *work)
d_info.wptr = (char *)rsp->Buffer;
d_info.rptr = (char *)rsp->Buffer;
d_info.out_buf_len =
- smb2_calc_max_out_buf_len(work, 8,
- le32_to_cpu(req->OutputBufferLength));
+ smb2_calc_max_out_buf_len(work,
+ offsetof(struct smb2_query_directory_rsp, Buffer),
+ le32_to_cpu(req->OutputBufferLength));
if (d_info.out_buf_len < 0) {
rc = -EINVAL;
goto err_out;
@@ -4714,8 +4721,9 @@ static int smb2_get_ea(struct ksmbd_work *work, struct ksmbd_file *fp,
}
buf_free_len =
- smb2_calc_max_out_buf_len(work, 8,
- le32_to_cpu(req->OutputBufferLength));
+ smb2_calc_max_out_buf_len(work,
+ offsetof(struct smb2_query_info_rsp, Buffer),
+ le32_to_cpu(req->OutputBufferLength));
if (buf_free_len < 0)
return -EINVAL;
@@ -4932,7 +4940,8 @@ static int get_file_all_info(struct ksmbd_work *work,
int conv_len;
char *filename;
u64 time;
- int ret;
+ int ret, buf_free_len, filename_len;
+ struct smb2_query_info_req *req = ksmbd_req_buf_next(work);
if (!(fp->daccess & FILE_READ_ATTRIBUTES_LE)) {
ksmbd_debug(SMB, "no right to read the attributes : 0x%x\n",
@@ -4944,6 +4953,16 @@ static int get_file_all_info(struct ksmbd_work *work,
if (IS_ERR(filename))
return PTR_ERR(filename);
+ filename_len = strlen(filename);
+ buf_free_len = smb2_calc_max_out_buf_len(work,
+ offsetof(struct smb2_query_info_rsp, Buffer) +
+ offsetof(struct smb2_file_all_info, FileName),
+ le32_to_cpu(req->OutputBufferLength));
+ if (buf_free_len < (filename_len + 1) * 2) {
+ kfree(filename);
+ return -EINVAL;
+ }
+
ret = vfs_getattr(&fp->filp->f_path, &stat, STATX_BASIC_STATS,
AT_STATX_SYNC_AS_STAT);
if (ret) {
@@ -4987,7 +5006,8 @@ static int get_file_all_info(struct ksmbd_work *work,
file_info->Mode = fp->coption;
file_info->AlignmentRequirement = 0;
conv_len = smbConvertToUTF16((__le16 *)file_info->FileName, filename,
- PATH_MAX, conn->local_nls, 0);
+ min(filename_len, PATH_MAX),
+ conn->local_nls, 0);
conv_len *= 2;
file_info->FileNameLength = cpu_to_le32(conv_len);
rsp->OutputBufferLength =
@@ -5041,8 +5061,9 @@ static int get_file_stream_info(struct ksmbd_work *work,
file_info = (struct smb2_file_stream_info *)rsp->Buffer;
buf_free_len =
- smb2_calc_max_out_buf_len(work, 8,
- le32_to_cpu(req->OutputBufferLength));
+ smb2_calc_max_out_buf_len(work,
+ offsetof(struct smb2_query_info_rsp, Buffer),
+ le32_to_cpu(req->OutputBufferLength));
if (buf_free_len < 0)
goto out;
@@ -7586,14 +7607,15 @@ retry:
rc = vfs_lock_file(filp, smb_lock->cmd, flock, NULL);
skip:
if (smb_lock->flags & SMB2_LOCKFLAG_UNLOCK) {
+ locks_free_lock(flock);
+ kfree(smb_lock);
if (!rc) {
ksmbd_debug(SMB, "File unlocked\n");
} else if (rc == -ENOENT) {
rsp->hdr.Status = STATUS_NOT_LOCKED;
+ err = rc;
goto out;
}
- locks_free_lock(flock);
- kfree(smb_lock);
} else {
if (rc == FILE_LOCK_DEFERRED) {
void **argv;
@@ -7662,6 +7684,9 @@ skip:
spin_unlock(&work->conn->llist_lock);
ksmbd_debug(SMB, "successful in taking lock\n");
} else {
+ locks_free_lock(flock);
+ kfree(smb_lock);
+ err = rc;
goto out;
}
}
@@ -7692,13 +7717,17 @@ out:
struct file_lock *rlock = NULL;
rlock = smb_flock_init(filp);
- rlock->c.flc_type = F_UNLCK;
- rlock->fl_start = smb_lock->start;
- rlock->fl_end = smb_lock->end;
+ if (rlock) {
+ rlock->c.flc_type = F_UNLCK;
+ rlock->fl_start = smb_lock->start;
+ rlock->fl_end = smb_lock->end;
- rc = vfs_lock_file(filp, F_SETLK, rlock, NULL);
- if (rc)
- pr_err("rollback unlock fail : %d\n", rc);
+ rc = vfs_lock_file(filp, F_SETLK, rlock, NULL);
+ if (rc)
+ pr_err("rollback unlock fail : %d\n", rc);
+ } else {
+ pr_err("rollback unlock alloc failed\n");
+ }
list_del(&smb_lock->llist);
spin_lock(&work->conn->llist_lock);
@@ -7708,7 +7737,8 @@ out:
spin_unlock(&work->conn->llist_lock);
locks_free_lock(smb_lock->fl);
- locks_free_lock(rlock);
+ if (rlock)
+ locks_free_lock(rlock);
kfree(smb_lock);
}
out2:
@@ -8191,8 +8221,9 @@ int smb2_ioctl(struct ksmbd_work *work)
buffer = (char *)req + le32_to_cpu(req->InputOffset);
cnt_code = le32_to_cpu(req->CtlCode);
- ret = smb2_calc_max_out_buf_len(work, 48,
- le32_to_cpu(req->MaxOutputResponse));
+ ret = smb2_calc_max_out_buf_len(work,
+ offsetof(struct smb2_ioctl_rsp, Buffer),
+ le32_to_cpu(req->MaxOutputResponse));
if (ret < 0) {
rsp->hdr.Status = STATUS_INVALID_PARAMETER;
goto out;