Commit 8b8cd4be authored by Linus Torvalds's avatar Linus Torvalds
Browse files

Merge tag '6.7-rc5-ksmbd-server-fixes' of git://git.samba.org/ksmbd

Pull smb server fixes from Steve French:

 - Memory leak fix (in lock error path)

 - Two fixes for create with allocation size

 - FIx for potential UAF in lease break error path

 - Five directory lease (caching) fixes found during additional recent
   testing

* tag '6.7-rc5-ksmbd-server-fixes' of git://git.samba.org/ksmbd:
  ksmbd: fix wrong name of SMB2_CREATE_ALLOCATION_SIZE
  ksmbd: fix wrong allocation size update in smb2_open()
  ksmbd: avoid duplicate opinfo_put() call on error of smb21_lease_break_ack()
  ksmbd: lazy v2 lease break on smb2_write()
  ksmbd: send v2 lease break notification for directory
  ksmbd: downgrade RWH lease caching state to RH for directory
  ksmbd: set v2 lease capability
  ksmbd: set epoch in create context v2 lease
  ksmbd: fix memory leak in smb2_lock()
parents 26aff849 13736654
Loading
Loading
Loading
Loading
+2 −1
Original line number Diff line number Diff line
@@ -1145,7 +1145,7 @@ struct smb2_server_client_notification {
#define SMB2_CREATE_SD_BUFFER			"SecD" /* security descriptor */
#define SMB2_CREATE_DURABLE_HANDLE_REQUEST	"DHnQ"
#define SMB2_CREATE_DURABLE_HANDLE_RECONNECT	"DHnC"
#define SMB2_CREATE_ALLOCATION_SIZE		"AISi"
#define SMB2_CREATE_ALLOCATION_SIZE		"AlSi"
#define SMB2_CREATE_QUERY_MAXIMAL_ACCESS_REQUEST "MxAc"
#define SMB2_CREATE_TIMEWARP_REQUEST		"TWrp"
#define SMB2_CREATE_QUERY_ON_DISK_ID		"QFid"
@@ -1253,6 +1253,7 @@ struct create_mxac_rsp {
#define SMB2_LEASE_WRITE_CACHING_LE		cpu_to_le32(0x04)

#define SMB2_LEASE_FLAG_BREAK_IN_PROGRESS_LE	cpu_to_le32(0x02)
#define SMB2_LEASE_FLAG_PARENT_LEASE_KEY_SET_LE	cpu_to_le32(0x04)

#define SMB2_LEASE_KEY_SIZE			16

+103 −12
Original line number Diff line number Diff line
@@ -102,9 +102,10 @@ static int alloc_lease(struct oplock_info *opinfo, struct lease_ctx_info *lctx)
	lease->new_state = 0;
	lease->flags = lctx->flags;
	lease->duration = lctx->duration;
	lease->is_dir = lctx->is_dir;
	memcpy(lease->parent_lease_key, lctx->parent_lease_key, SMB2_LEASE_KEY_SIZE);
	lease->version = lctx->version;
	lease->epoch = 0;
	lease->epoch = le16_to_cpu(lctx->epoch);
	INIT_LIST_HEAD(&opinfo->lease_entry);
	opinfo->o_lease = lease;

@@ -395,8 +396,8 @@ void close_id_del_oplock(struct ksmbd_file *fp)
{
	struct oplock_info *opinfo;

	if (S_ISDIR(file_inode(fp->filp)->i_mode))
		return;
	if (fp->reserve_lease_break)
		smb_lazy_parent_lease_break_close(fp);

	opinfo = opinfo_get(fp);
	if (!opinfo)
@@ -543,12 +544,13 @@ static struct oplock_info *same_client_has_lease(struct ksmbd_inode *ci,
			/* upgrading lease */
			if ((atomic_read(&ci->op_count) +
			     atomic_read(&ci->sop_count)) == 1) {
				if (lease->state ==
				    (lctx->req_state & lease->state)) {
				if (lease->state != SMB2_LEASE_NONE_LE &&
				    lease->state == (lctx->req_state & lease->state)) {
					lease->state |= lctx->req_state;
					if (lctx->req_state &
						SMB2_LEASE_WRITE_CACHING_LE)
						lease_read_to_write(opinfo);

				}
			} else if ((atomic_read(&ci->op_count) +
				    atomic_read(&ci->sop_count)) > 1) {
@@ -900,7 +902,8 @@ static int oplock_break(struct oplock_info *brk_opinfo, int req_op_level)
					lease->new_state =
						SMB2_LEASE_READ_CACHING_LE;
			} else {
				if (lease->state & SMB2_LEASE_HANDLE_CACHING_LE)
				if (lease->state & SMB2_LEASE_HANDLE_CACHING_LE &&
						!lease->is_dir)
					lease->new_state =
						SMB2_LEASE_READ_CACHING_LE;
				else
@@ -1032,6 +1035,7 @@ static void copy_lease(struct oplock_info *op1, struct oplock_info *op2)
	       SMB2_LEASE_KEY_SIZE);
	lease2->duration = lease1->duration;
	lease2->flags = lease1->flags;
	lease2->epoch = lease1->epoch++;
}

static int add_lease_global_list(struct oplock_info *opinfo)
@@ -1081,6 +1085,89 @@ static void set_oplock_level(struct oplock_info *opinfo, int level,
	}
}

void smb_send_parent_lease_break_noti(struct ksmbd_file *fp,
				      struct lease_ctx_info *lctx)
{
	struct oplock_info *opinfo;
	struct ksmbd_inode *p_ci = NULL;

	if (lctx->version != 2)
		return;

	p_ci = ksmbd_inode_lookup_lock(fp->filp->f_path.dentry->d_parent);
	if (!p_ci)
		return;

	read_lock(&p_ci->m_lock);
	list_for_each_entry(opinfo, &p_ci->m_op_list, op_entry) {
		if (!opinfo->is_lease)
			continue;

		if (opinfo->o_lease->state != SMB2_OPLOCK_LEVEL_NONE &&
		    (!(lctx->flags & SMB2_LEASE_FLAG_PARENT_LEASE_KEY_SET_LE) ||
		     !compare_guid_key(opinfo, fp->conn->ClientGUID,
				      lctx->parent_lease_key))) {
			if (!atomic_inc_not_zero(&opinfo->refcount))
				continue;

			atomic_inc(&opinfo->conn->r_count);
			if (ksmbd_conn_releasing(opinfo->conn)) {
				atomic_dec(&opinfo->conn->r_count);
				continue;
			}

			read_unlock(&p_ci->m_lock);
			oplock_break(opinfo, SMB2_OPLOCK_LEVEL_NONE);
			opinfo_conn_put(opinfo);
			read_lock(&p_ci->m_lock);
		}
	}
	read_unlock(&p_ci->m_lock);

	ksmbd_inode_put(p_ci);
}

void smb_lazy_parent_lease_break_close(struct ksmbd_file *fp)
{
	struct oplock_info *opinfo;
	struct ksmbd_inode *p_ci = NULL;

	rcu_read_lock();
	opinfo = rcu_dereference(fp->f_opinfo);
	rcu_read_unlock();

	if (!opinfo->is_lease || opinfo->o_lease->version != 2)
		return;

	p_ci = ksmbd_inode_lookup_lock(fp->filp->f_path.dentry->d_parent);
	if (!p_ci)
		return;

	read_lock(&p_ci->m_lock);
	list_for_each_entry(opinfo, &p_ci->m_op_list, op_entry) {
		if (!opinfo->is_lease)
			continue;

		if (opinfo->o_lease->state != SMB2_OPLOCK_LEVEL_NONE) {
			if (!atomic_inc_not_zero(&opinfo->refcount))
				continue;

			atomic_inc(&opinfo->conn->r_count);
			if (ksmbd_conn_releasing(opinfo->conn)) {
				atomic_dec(&opinfo->conn->r_count);
				continue;
			}
			read_unlock(&p_ci->m_lock);
			oplock_break(opinfo, SMB2_OPLOCK_LEVEL_NONE);
			opinfo_conn_put(opinfo);
			read_lock(&p_ci->m_lock);
		}
	}
	read_unlock(&p_ci->m_lock);

	ksmbd_inode_put(p_ci);
}

/**
 * smb_grant_oplock() - handle oplock/lease request on file open
 * @work:		smb work
@@ -1104,10 +1191,6 @@ int smb_grant_oplock(struct ksmbd_work *work, int req_op_level, u64 pid,
	bool prev_op_has_lease;
	__le32 prev_op_state = 0;

	/* not support directory lease */
	if (S_ISDIR(file_inode(fp->filp)->i_mode))
		return 0;

	opinfo = alloc_opinfo(work, pid, tid);
	if (!opinfo)
		return -ENOMEM;
@@ -1364,6 +1447,7 @@ void create_lease_buf(u8 *rbuf, struct lease *lease)
		memcpy(buf->lcontext.LeaseKey, lease->lease_key,
		       SMB2_LEASE_KEY_SIZE);
		buf->lcontext.LeaseFlags = lease->flags;
		buf->lcontext.Epoch = cpu_to_le16(++lease->epoch);
		buf->lcontext.LeaseState = lease->state;
		memcpy(buf->lcontext.ParentLeaseKey, lease->parent_lease_key,
		       SMB2_LEASE_KEY_SIZE);
@@ -1400,10 +1484,11 @@ void create_lease_buf(u8 *rbuf, struct lease *lease)
/**
 * parse_lease_state() - parse lease context containted in file open request
 * @open_req:	buffer containing smb2 file open(create) request
 * @is_dir:	whether leasing file is directory
 *
 * Return:  oplock state, -ENOENT if create lease context not found
 */
struct lease_ctx_info *parse_lease_state(void *open_req)
struct lease_ctx_info *parse_lease_state(void *open_req, bool is_dir)
{
	struct create_context *cc;
	struct smb2_create_req *req = (struct smb2_create_req *)open_req;
@@ -1421,8 +1506,14 @@ struct lease_ctx_info *parse_lease_state(void *open_req)
		struct create_lease_v2 *lc = (struct create_lease_v2 *)cc;

		memcpy(lreq->lease_key, lc->lcontext.LeaseKey, SMB2_LEASE_KEY_SIZE);
		if (is_dir) {
			lreq->req_state = lc->lcontext.LeaseState &
				~SMB2_LEASE_WRITE_CACHING_LE;
			lreq->is_dir = true;
		} else
			lreq->req_state = lc->lcontext.LeaseState;
		lreq->flags = lc->lcontext.LeaseFlags;
		lreq->epoch = lc->lcontext.Epoch;
		lreq->duration = lc->lcontext.LeaseDuration;
		memcpy(lreq->parent_lease_key, lc->lcontext.ParentLeaseKey,
				SMB2_LEASE_KEY_SIZE);
+7 −1
Original line number Diff line number Diff line
@@ -34,7 +34,9 @@ struct lease_ctx_info {
	__le32			flags;
	__le64			duration;
	__u8			parent_lease_key[SMB2_LEASE_KEY_SIZE];
	__le16			epoch;
	int			version;
	bool			is_dir;
};

struct lease_table {
@@ -53,6 +55,7 @@ struct lease {
	__u8			parent_lease_key[SMB2_LEASE_KEY_SIZE];
	int			version;
	unsigned short		epoch;
	bool			is_dir;
	struct lease_table	*l_lb;
};

@@ -108,7 +111,7 @@ void opinfo_put(struct oplock_info *opinfo);

/* Lease related functions */
void create_lease_buf(u8 *rbuf, struct lease *lease);
struct lease_ctx_info *parse_lease_state(void *open_req);
struct lease_ctx_info *parse_lease_state(void *open_req, bool is_dir);
__u8 smb2_map_lease_to_oplock(__le32 lease_state);
int lease_read_to_write(struct oplock_info *opinfo);

@@ -124,4 +127,7 @@ struct oplock_info *lookup_lease_in_table(struct ksmbd_conn *conn,
int find_same_lease_key(struct ksmbd_session *sess, struct ksmbd_inode *ci,
			struct lease_ctx_info *lctx);
void destroy_lease_table(struct ksmbd_conn *conn);
void smb_send_parent_lease_break_noti(struct ksmbd_file *fp,
				      struct lease_ctx_info *lctx);
void smb_lazy_parent_lease_break_close(struct ksmbd_file *fp);
#endif /* __KSMBD_OPLOCK_H */
+6 −3
Original line number Diff line number Diff line
@@ -221,7 +221,8 @@ void init_smb3_0_server(struct ksmbd_conn *conn)
	conn->signing_algorithm = SIGNING_ALG_AES_CMAC_LE;

	if (server_conf.flags & KSMBD_GLOBAL_FLAG_SMB2_LEASES)
		conn->vals->capabilities |= SMB2_GLOBAL_CAP_LEASING;
		conn->vals->capabilities |= SMB2_GLOBAL_CAP_LEASING |
			SMB2_GLOBAL_CAP_DIRECTORY_LEASING;

	if (server_conf.flags & KSMBD_GLOBAL_FLAG_SMB2_ENCRYPTION &&
	    conn->cli_cap & SMB2_GLOBAL_CAP_ENCRYPTION)
@@ -245,7 +246,8 @@ void init_smb3_02_server(struct ksmbd_conn *conn)
	conn->signing_algorithm = SIGNING_ALG_AES_CMAC_LE;

	if (server_conf.flags & KSMBD_GLOBAL_FLAG_SMB2_LEASES)
		conn->vals->capabilities |= SMB2_GLOBAL_CAP_LEASING;
		conn->vals->capabilities |= SMB2_GLOBAL_CAP_LEASING |
			SMB2_GLOBAL_CAP_DIRECTORY_LEASING;

	if (server_conf.flags & KSMBD_GLOBAL_FLAG_SMB2_ENCRYPTION ||
	    (!(server_conf.flags & KSMBD_GLOBAL_FLAG_SMB2_ENCRYPTION_OFF) &&
@@ -270,7 +272,8 @@ int init_smb3_11_server(struct ksmbd_conn *conn)
	conn->signing_algorithm = SIGNING_ALG_AES_CMAC_LE;

	if (server_conf.flags & KSMBD_GLOBAL_FLAG_SMB2_LEASES)
		conn->vals->capabilities |= SMB2_GLOBAL_CAP_LEASING;
		conn->vals->capabilities |= SMB2_GLOBAL_CAP_LEASING |
			SMB2_GLOBAL_CAP_DIRECTORY_LEASING;

	if (server_conf.flags & KSMBD_GLOBAL_FLAG_SMB2_ENCRYPTION ||
	    (!(server_conf.flags & KSMBD_GLOBAL_FLAG_SMB2_ENCRYPTION_OFF) &&
+35 −27
Original line number Diff line number Diff line
@@ -2516,7 +2516,7 @@ static void smb2_new_xattrs(struct ksmbd_tree_connect *tcon, const struct path *
	da.flags = XATTR_DOSINFO_ATTRIB | XATTR_DOSINFO_CREATE_TIME |
		XATTR_DOSINFO_ITIME;

	rc = ksmbd_vfs_set_dos_attrib_xattr(mnt_idmap(path->mnt), path, &da, false);
	rc = ksmbd_vfs_set_dos_attrib_xattr(mnt_idmap(path->mnt), path, &da, true);
	if (rc)
		ksmbd_debug(SMB, "failed to store file attribute into xattr\n");
}
@@ -2732,10 +2732,6 @@ int smb2_open(struct ksmbd_work *work)
		}
	}

	req_op_level = req->RequestedOplockLevel;
	if (req_op_level == SMB2_OPLOCK_LEVEL_LEASE)
		lc = parse_lease_state(req);

	if (le32_to_cpu(req->ImpersonationLevel) > le32_to_cpu(IL_DELEGATE)) {
		pr_err("Invalid impersonationlevel : 0x%x\n",
		       le32_to_cpu(req->ImpersonationLevel));
@@ -3189,23 +3185,6 @@ int smb2_open(struct ksmbd_work *work)
		goto err_out;
	}

	rc = ksmbd_vfs_getattr(&path, &stat);
	if (rc)
		goto err_out;

	if (stat.result_mask & STATX_BTIME)
		fp->create_time = ksmbd_UnixTimeToNT(stat.btime);
	else
		fp->create_time = ksmbd_UnixTimeToNT(stat.ctime);
	if (req->FileAttributes || fp->f_ci->m_fattr == 0)
		fp->f_ci->m_fattr =
			cpu_to_le32(smb2_get_dos_mode(&stat, le32_to_cpu(req->FileAttributes)));

	if (!created)
		smb2_update_xattrs(tcon, &path, fp);
	else
		smb2_new_xattrs(tcon, &path, fp);

	if (file_present || created)
		ksmbd_vfs_kern_path_unlock(&parent_path, &path);

@@ -3215,6 +3194,10 @@ int smb2_open(struct ksmbd_work *work)
		need_truncate = 1;
	}

	req_op_level = req->RequestedOplockLevel;
	if (req_op_level == SMB2_OPLOCK_LEVEL_LEASE)
		lc = parse_lease_state(req, S_ISDIR(file_inode(filp)->i_mode));

	share_ret = ksmbd_smb_check_shared_mode(fp->filp, fp);
	if (!test_share_config_flag(work->tcon->share_conf, KSMBD_SHARE_FLAG_OPLOCKS) ||
	    (req_op_level == SMB2_OPLOCK_LEVEL_LEASE &&
@@ -3225,6 +3208,13 @@ int smb2_open(struct ksmbd_work *work)
		}
	} else {
		if (req_op_level == SMB2_OPLOCK_LEVEL_LEASE) {
			/*
			 * Compare parent lease using parent key. If there is no
			 * a lease that has same parent key, Send lease break
			 * notification.
			 */
			smb_send_parent_lease_break_noti(fp, lc);

			req_op_level = smb2_map_lease_to_oplock(lc->req_state);
			ksmbd_debug(SMB,
				    "lease req for(%s) req oplock state 0x%x, lease state 0x%x\n",
@@ -3295,6 +3285,23 @@ int smb2_open(struct ksmbd_work *work)
		}
	}

	rc = ksmbd_vfs_getattr(&path, &stat);
	if (rc)
		goto err_out1;

	if (stat.result_mask & STATX_BTIME)
		fp->create_time = ksmbd_UnixTimeToNT(stat.btime);
	else
		fp->create_time = ksmbd_UnixTimeToNT(stat.ctime);
	if (req->FileAttributes || fp->f_ci->m_fattr == 0)
		fp->f_ci->m_fattr =
			cpu_to_le32(smb2_get_dos_mode(&stat, le32_to_cpu(req->FileAttributes)));

	if (!created)
		smb2_update_xattrs(tcon, &path, fp);
	else
		smb2_new_xattrs(tcon, &path, fp);

	memcpy(fp->client_guid, conn->ClientGUID, SMB2_CLIENT_GUID_SIZE);

	rsp->StructureSize = cpu_to_le16(89);
@@ -7080,6 +7087,7 @@ int smb2_lock(struct ksmbd_work *work)
						      smb2_remove_blocked_lock,
						      argv);
				if (rc) {
					kfree(argv);
					err = -ENOMEM;
					goto out;
				}
@@ -8211,6 +8219,11 @@ static void smb21_lease_break_ack(struct ksmbd_work *work)
			    le32_to_cpu(req->LeaseState));
	}

	if (ret < 0) {
		rsp->hdr.Status = err;
		goto err_out;
	}

	lease_state = lease->state;
	opinfo->op_state = OPLOCK_STATE_NONE;
	wake_up_interruptible_all(&opinfo->oplock_q);
@@ -8218,11 +8231,6 @@ static void smb21_lease_break_ack(struct ksmbd_work *work)
	wake_up_interruptible_all(&opinfo->oplock_brk);
	opinfo_put(opinfo);

	if (ret < 0) {
		rsp->hdr.Status = err;
		goto err_out;
	}

	rsp->StructureSize = cpu_to_le16(36);
	rsp->Reserved = 0;
	rsp->Flags = 0;
Loading