Commit 34892992 authored by Linus Torvalds's avatar Linus Torvalds
Browse files

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
parents 46b51325 beef2634
Loading
Loading
Loading
Loading
+45 −27
Original line number Diff line number Diff line
@@ -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 @@ int smb_grant_oplock(struct ksmbd_work *work, int req_op_level, u64 pid,
	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;
}

+52 −21
Original line number Diff line number Diff line
@@ -1939,8 +1939,14 @@ int smb2_sess_setup(struct ksmbd_work *work)
			if (sess->user && sess->user->flags & KSMBD_USER_FLAG_DELAY_SESSION)
				try_delay = true;

			/*
			 * 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,7 +4452,8 @@ 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,
		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;
@@ -4714,7 +4721,8 @@ static int smb2_get_ea(struct ksmbd_work *work, struct ksmbd_file *fp,
	}

	buf_free_len =
		smb2_calc_max_out_buf_len(work, 8,
		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,7 +5061,8 @@ 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,
		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 @@ int smb2_lock(struct ksmbd_work *work)
		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 @@ int smb2_lock(struct ksmbd_work *work)
				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,6 +7717,7 @@ int smb2_lock(struct ksmbd_work *work)
		struct file_lock *rlock = NULL;

		rlock = smb_flock_init(filp);
		if (rlock) {
			rlock->c.flc_type = F_UNLCK;
			rlock->fl_start = smb_lock->start;
			rlock->fl_end = smb_lock->end;
@@ -7699,6 +7725,9 @@ int smb2_lock(struct ksmbd_work *work)
			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,6 +7737,7 @@ int smb2_lock(struct ksmbd_work *work)
		spin_unlock(&work->conn->llist_lock);

		locks_free_lock(smb_lock->fl);
		if (rlock)
			locks_free_lock(rlock);
		kfree(smb_lock);
	}
@@ -8191,7 +8221,8 @@ 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,
	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;