Commit 3571e8b0 authored by Linus Torvalds's avatar Linus Torvalds
Browse files

Merge tag 'v6.14-rc6-smb3-server-fixes' of git://git.samba.org/ksmbd

Pull smb server fixes from Steve French:

 - Two fixes for oplock break/lease races

* tag 'v6.14-rc6-smb3-server-fixes' of git://git.samba.org/ksmbd:
  ksmbd: prevent connection release during oplock break notification
  ksmbd: fix use-after-free in ksmbd_free_work_struct
parents a29967be 3aa660c0
Loading
Loading
Loading
Loading
+20 −0
Original line number Diff line number Diff line
@@ -433,6 +433,26 @@ void ksmbd_conn_init_server_callbacks(struct ksmbd_conn_ops *ops)
	default_conn_ops.terminate_fn = ops->terminate_fn;
}

void ksmbd_conn_r_count_inc(struct ksmbd_conn *conn)
{
	atomic_inc(&conn->r_count);
}

void ksmbd_conn_r_count_dec(struct ksmbd_conn *conn)
{
	/*
	 * Checking waitqueue to dropping pending requests on
	 * disconnection. waitqueue_active is safe because it
	 * uses atomic operation for condition.
	 */
	atomic_inc(&conn->refcnt);
	if (!atomic_dec_return(&conn->r_count) && waitqueue_active(&conn->r_count_q))
		wake_up(&conn->r_count_q);

	if (atomic_dec_and_test(&conn->refcnt))
		kfree(conn);
}

int ksmbd_conn_transport_init(void)
{
	int ret;
+2 −0
Original line number Diff line number Diff line
@@ -168,6 +168,8 @@ int ksmbd_conn_transport_init(void);
void ksmbd_conn_transport_destroy(void);
void ksmbd_conn_lock(struct ksmbd_conn *conn);
void ksmbd_conn_unlock(struct ksmbd_conn *conn);
void ksmbd_conn_r_count_inc(struct ksmbd_conn *conn);
void ksmbd_conn_r_count_dec(struct ksmbd_conn *conn);

/*
 * WARNING
+0 −3
Original line number Diff line number Diff line
@@ -26,7 +26,6 @@ struct ksmbd_work *ksmbd_alloc_work_struct(void)
		INIT_LIST_HEAD(&work->request_entry);
		INIT_LIST_HEAD(&work->async_request_entry);
		INIT_LIST_HEAD(&work->fp_entry);
		INIT_LIST_HEAD(&work->interim_entry);
		INIT_LIST_HEAD(&work->aux_read_list);
		work->iov_alloc_cnt = 4;
		work->iov = kcalloc(work->iov_alloc_cnt, sizeof(struct kvec),
@@ -56,8 +55,6 @@ void ksmbd_free_work_struct(struct ksmbd_work *work)
	kfree(work->tr_buf);
	kvfree(work->request_buf);
	kfree(work->iov);
	if (!list_empty(&work->interim_entry))
		list_del(&work->interim_entry);

	if (work->async_id)
		ksmbd_release_id(&work->conn->async_ida, work->async_id);
+0 −1
Original line number Diff line number Diff line
@@ -89,7 +89,6 @@ struct ksmbd_work {
	/* List head at conn->async_requests */
	struct list_head                async_request_entry;
	struct list_head                fp_entry;
	struct list_head                interim_entry;
};

/**
+21 −22
Original line number Diff line number Diff line
@@ -46,7 +46,6 @@ static struct oplock_info *alloc_opinfo(struct ksmbd_work *work,
	opinfo->fid = id;
	opinfo->Tid = Tid;
	INIT_LIST_HEAD(&opinfo->op_entry);
	INIT_LIST_HEAD(&opinfo->interim_list);
	init_waitqueue_head(&opinfo->oplock_q);
	init_waitqueue_head(&opinfo->oplock_brk);
	atomic_set(&opinfo->refcount, 1);
@@ -635,6 +634,7 @@ static void __smb2_oplock_break_noti(struct work_struct *wk)
{
	struct smb2_oplock_break *rsp = NULL;
	struct ksmbd_work *work = container_of(wk, struct ksmbd_work, work);
	struct ksmbd_conn *conn = work->conn;
	struct oplock_break_info *br_info = work->request_buf;
	struct smb2_hdr *rsp_hdr;
	struct ksmbd_file *fp;
@@ -690,6 +690,7 @@ static void __smb2_oplock_break_noti(struct work_struct *wk)

out:
	ksmbd_free_work_struct(work);
	ksmbd_conn_r_count_dec(conn);
}

/**
@@ -724,6 +725,7 @@ static int smb2_oplock_break_noti(struct oplock_info *opinfo)
	work->sess = opinfo->sess;

	if (opinfo->op_state == OPLOCK_ACK_WAIT) {
		ksmbd_conn_r_count_inc(conn);
		INIT_WORK(&work->work, __smb2_oplock_break_noti);
		ksmbd_queue_work(work);

@@ -745,6 +747,7 @@ static void __smb2_lease_break_noti(struct work_struct *wk)
{
	struct smb2_lease_break *rsp = NULL;
	struct ksmbd_work *work = container_of(wk, struct ksmbd_work, work);
	struct ksmbd_conn *conn = work->conn;
	struct lease_break_info *br_info = work->request_buf;
	struct smb2_hdr *rsp_hdr;

@@ -791,6 +794,7 @@ static void __smb2_lease_break_noti(struct work_struct *wk)

out:
	ksmbd_free_work_struct(work);
	ksmbd_conn_r_count_dec(conn);
}

/**
@@ -803,7 +807,6 @@ static void __smb2_lease_break_noti(struct work_struct *wk)
static int smb2_lease_break_noti(struct oplock_info *opinfo)
{
	struct ksmbd_conn *conn = opinfo->conn;
	struct list_head *tmp, *t;
	struct ksmbd_work *work;
	struct lease_break_info *br_info;
	struct lease *lease = opinfo->o_lease;
@@ -831,16 +834,7 @@ static int smb2_lease_break_noti(struct oplock_info *opinfo)
	work->sess = opinfo->sess;

	if (opinfo->op_state == OPLOCK_ACK_WAIT) {
		list_for_each_safe(tmp, t, &opinfo->interim_list) {
			struct ksmbd_work *in_work;

			in_work = list_entry(tmp, struct ksmbd_work,
					     interim_entry);
			setup_async_work(in_work, NULL, NULL);
			smb2_send_interim_resp(in_work, STATUS_PENDING);
			list_del_init(&in_work->interim_entry);
			release_async_work(in_work);
		}
		ksmbd_conn_r_count_inc(conn);
		INIT_WORK(&work->work, __smb2_lease_break_noti);
		ksmbd_queue_work(work);
		wait_for_break_ack(opinfo);
@@ -871,7 +865,8 @@ static void wait_lease_breaking(struct oplock_info *opinfo)
	}
}

static int oplock_break(struct oplock_info *brk_opinfo, int req_op_level)
static int oplock_break(struct oplock_info *brk_opinfo, int req_op_level,
			struct ksmbd_work *in_work)
{
	int err = 0;

@@ -914,9 +909,15 @@ static int oplock_break(struct oplock_info *brk_opinfo, int req_op_level)
		}

		if (lease->state & (SMB2_LEASE_WRITE_CACHING_LE |
				SMB2_LEASE_HANDLE_CACHING_LE))
				SMB2_LEASE_HANDLE_CACHING_LE)) {
			if (in_work) {
				setup_async_work(in_work, NULL, NULL);
				smb2_send_interim_resp(in_work, STATUS_PENDING);
				release_async_work(in_work);
			}

			brk_opinfo->op_state = OPLOCK_ACK_WAIT;
		else
		} else
			atomic_dec(&brk_opinfo->breaking_cnt);
	} else {
		err = oplock_break_pending(brk_opinfo, req_op_level);
@@ -1116,7 +1117,7 @@ void smb_send_parent_lease_break_noti(struct ksmbd_file *fp,
			if (ksmbd_conn_releasing(opinfo->conn))
				continue;

			oplock_break(opinfo, SMB2_OPLOCK_LEVEL_NONE);
			oplock_break(opinfo, SMB2_OPLOCK_LEVEL_NONE, NULL);
			opinfo_put(opinfo);
		}
	}
@@ -1152,7 +1153,7 @@ void smb_lazy_parent_lease_break_close(struct ksmbd_file *fp)

			if (ksmbd_conn_releasing(opinfo->conn))
				continue;
			oplock_break(opinfo, SMB2_OPLOCK_LEVEL_NONE);
			oplock_break(opinfo, SMB2_OPLOCK_LEVEL_NONE, NULL);
			opinfo_put(opinfo);
		}
	}
@@ -1252,8 +1253,7 @@ int smb_grant_oplock(struct ksmbd_work *work, int req_op_level, u64 pid,
		goto op_break_not_needed;
	}

	list_add(&work->interim_entry, &prev_opinfo->interim_list);
	err = oplock_break(prev_opinfo, SMB2_OPLOCK_LEVEL_II);
	err = oplock_break(prev_opinfo, SMB2_OPLOCK_LEVEL_II, work);
	opinfo_put(prev_opinfo);
	if (err == -ENOENT)
		goto set_lev;
@@ -1322,8 +1322,7 @@ static void smb_break_all_write_oplock(struct ksmbd_work *work,
	}

	brk_opinfo->open_trunc = is_trunc;
	list_add(&work->interim_entry, &brk_opinfo->interim_list);
	oplock_break(brk_opinfo, SMB2_OPLOCK_LEVEL_II);
	oplock_break(brk_opinfo, SMB2_OPLOCK_LEVEL_II, work);
	opinfo_put(brk_opinfo);
}

@@ -1386,7 +1385,7 @@ void smb_break_all_levII_oplock(struct ksmbd_work *work, struct ksmbd_file *fp,
			    SMB2_LEASE_KEY_SIZE))
			goto next;
		brk_op->open_trunc = is_trunc;
		oplock_break(brk_op, SMB2_OPLOCK_LEVEL_NONE);
		oplock_break(brk_op, SMB2_OPLOCK_LEVEL_NONE, NULL);
next:
		opinfo_put(brk_op);
		rcu_read_lock();
Loading