Unverified Commit 098e2550 authored by openeuler-ci-bot's avatar openeuler-ci-bot Committed by Gitee
Browse files

!9635 fix CVE-2022-48721

Merge Pull Request from: @ci-robot 
 
PR sync from: Zhengchao Shao <shaozhengchao@huawei.com>
https://mailweb.openeuler.org/hyperkitty/list/kernel@openeuler.org/message/AH5VDQMTGHRUL5KZKYYN3IDNNG6FMEDE/ 
Fix CVE-2022-48721.

Wen Gu (4):
  net/smc: Forward wakeup to smc socket waitqueue after fallback
  net/smc: Avoid overwriting the copies of clcsock callback functions
  net/smc: Only save the original clcsock callback functions
  net/smc: Fix slab-out-of-bounds issue in fallback


-- 
2.34.1
 
https://gitee.com/src-openeuler/kernel/issues/IA72JN 
 
Link:https://gitee.com/openeuler/kernel/pulls/9635

 

Reviewed-by: default avatarYue Haibing <yuehaibing@huawei.com>
Signed-off-by: default avatarJialin Zhang <zhangjialin11@huawei.com>
parents 40ff2274 468aae00
Loading
Loading
Loading
Loading
+175 −18
Original line number Diff line number Diff line
@@ -128,11 +128,27 @@ struct proto smc_proto6 = {
};
EXPORT_SYMBOL_GPL(smc_proto6);

static void smc_fback_restore_callbacks(struct smc_sock *smc)
{
	struct sock *clcsk = smc->clcsock->sk;

	write_lock_bh(&clcsk->sk_callback_lock);
	clcsk->sk_user_data = NULL;

	smc_clcsock_restore_cb(&clcsk->sk_state_change, &smc->clcsk_state_change);
	smc_clcsock_restore_cb(&clcsk->sk_data_ready, &smc->clcsk_data_ready);
	smc_clcsock_restore_cb(&clcsk->sk_write_space, &smc->clcsk_write_space);
	smc_clcsock_restore_cb(&clcsk->sk_error_report, &smc->clcsk_error_report);

	write_unlock_bh(&clcsk->sk_callback_lock);
}

static void smc_restore_fallback_changes(struct smc_sock *smc)
{
	if (smc->clcsock->file) { /* non-accepted sockets have no file yet */
		smc->clcsock->file->private_data = smc->sk.sk_socket;
		smc->clcsock->file = NULL;
		smc_fback_restore_callbacks(smc);
	}
}

@@ -258,6 +274,7 @@ static struct sock *smc_sock_alloc(struct net *net, struct socket *sock,
	sk->sk_prot->hash(sk);
	sk_refcnt_debug_inc(sk);
	mutex_init(&smc->clcsock_release_lock);
	smc_init_saved_callbacks(smc);

	return sk;
}
@@ -523,12 +540,134 @@ static void smc_link_save_peer_info(struct smc_link *link,
	link->peer_mtu = clc->r0.qp_mtu;
}

static void smc_switch_to_fallback(struct smc_sock *smc)
/* must be called under rcu read lock */
static void smc_fback_wakeup_waitqueue(struct smc_sock *smc, void *key)
{
	struct socket_wq *wq;
	__poll_t flags;

	wq = rcu_dereference(smc->sk.sk_wq);
	if (!skwq_has_sleeper(wq))
		return;

	/* wake up smc sk->sk_wq */
	if (!key) {
		/* sk_state_change */
		wake_up_interruptible_all(&wq->wait);
	} else {
		flags = key_to_poll(key);
		if (flags & (EPOLLIN | EPOLLOUT))
			/* sk_data_ready or sk_write_space */
			wake_up_interruptible_sync_poll(&wq->wait, flags);
		else if (flags & EPOLLERR)
			/* sk_error_report */
			wake_up_interruptible_poll(&wq->wait, flags);
	}
}

static int smc_fback_mark_woken(wait_queue_entry_t *wait,
				unsigned int mode, int sync, void *key)
{
	struct smc_mark_woken *mark =
			container_of(wait, struct smc_mark_woken, wait_entry);

	mark->woken = true;
	mark->key = key;
	return 0;
}

static void smc_fback_forward_wakeup(struct smc_sock *smc, struct sock *clcsk,
				     void (*clcsock_callback)(struct sock *sk))
{
	struct smc_mark_woken mark = { .woken = false };
	struct socket_wq *wq;

	init_waitqueue_func_entry(&mark.wait_entry,
				  smc_fback_mark_woken);

	rcu_read_lock();
	wq = rcu_dereference(clcsk->sk_wq);
	if (!wq)
		goto out;
	add_wait_queue(sk_sleep(clcsk), &mark.wait_entry);
	clcsock_callback(clcsk);
	remove_wait_queue(sk_sleep(clcsk), &mark.wait_entry);

	if (mark.woken)
		smc_fback_wakeup_waitqueue(smc, mark.key);
out:
	rcu_read_unlock();
}

static void smc_fback_state_change(struct sock *clcsk)
{
	struct smc_sock *smc;

	read_lock_bh(&clcsk->sk_callback_lock);
	smc = smc_clcsock_user_data(clcsk);
	if (smc)
		smc_fback_forward_wakeup(smc, clcsk,
					 smc->clcsk_state_change);
	read_unlock_bh(&clcsk->sk_callback_lock);
}

static void smc_fback_data_ready(struct sock *clcsk)
{
	struct smc_sock *smc;

	read_lock_bh(&clcsk->sk_callback_lock);
	smc = smc_clcsock_user_data(clcsk);
	if (smc)
		smc_fback_forward_wakeup(smc, clcsk,
					 smc->clcsk_data_ready);
	read_unlock_bh(&clcsk->sk_callback_lock);
}

static void smc_fback_write_space(struct sock *clcsk)
{
	struct smc_sock *smc;

	read_lock_bh(&clcsk->sk_callback_lock);
	smc = smc_clcsock_user_data(clcsk);
	if (smc)
		smc_fback_forward_wakeup(smc, clcsk,
					 smc->clcsk_write_space);
	read_unlock_bh(&clcsk->sk_callback_lock);
}

static void smc_fback_error_report(struct sock *clcsk)
{
	wait_queue_head_t *smc_wait = sk_sleep(&smc->sk);
	wait_queue_head_t *clc_wait = sk_sleep(smc->clcsock->sk);
	unsigned long flags;
	struct smc_sock *smc;

	read_lock_bh(&clcsk->sk_callback_lock);
	smc = smc_clcsock_user_data(clcsk);
	if (smc)
		smc_fback_forward_wakeup(smc, clcsk,
					 smc->clcsk_error_report);
	read_unlock_bh(&clcsk->sk_callback_lock);
}

static void smc_fback_replace_callbacks(struct smc_sock *smc)
{
	struct sock *clcsk = smc->clcsock->sk;

	write_lock_bh(&clcsk->sk_callback_lock);
	clcsk->sk_user_data = (void *)((uintptr_t)smc | SK_USER_DATA_NOCOPY);

	smc_clcsock_replace_cb(&clcsk->sk_state_change, smc_fback_state_change,
			       &smc->clcsk_state_change);
	smc_clcsock_replace_cb(&clcsk->sk_data_ready, smc_fback_data_ready,
			       &smc->clcsk_data_ready);
	smc_clcsock_replace_cb(&clcsk->sk_write_space, smc_fback_write_space,
			       &smc->clcsk_write_space);
	smc_clcsock_replace_cb(&clcsk->sk_error_report, smc_fback_error_report,
			       &smc->clcsk_error_report);

	write_unlock_bh(&clcsk->sk_callback_lock);
}

static void smc_switch_to_fallback(struct smc_sock *smc)
{
	smc->use_fallback = true;
	if (smc->sk.sk_socket && smc->sk.sk_socket->file) {
		smc->clcsock->file = smc->sk.sk_socket->file;
@@ -536,15 +675,11 @@ static void smc_switch_to_fallback(struct smc_sock *smc)
		smc->clcsock->wq.fasync_list =
			smc->sk.sk_socket->wq.fasync_list;

		/* There may be some entries remaining in
		 * smc socket->wq, which should be removed
		 * to clcsocket->wq during the fallback.
		/* There might be some wait entries remaining
		 * in smc sk->sk_wq and they should be woken up
		 * as clcsock's wait queue is woken up.
		 */
		spin_lock_irqsave(&smc_wait->lock, flags);
		spin_lock_nested(&clc_wait->lock, SINGLE_DEPTH_NESTING);
		list_splice_init(&smc_wait->head, &clc_wait->head);
		spin_unlock(&clc_wait->lock);
		spin_unlock_irqrestore(&smc_wait->lock, flags);
		smc_fback_replace_callbacks(smc);
	}
}

@@ -1183,6 +1318,19 @@ static int smc_clcsock_accept(struct smc_sock *lsmc, struct smc_sock **new_smc)
	 * function; switch it back to the original sk_data_ready function
	 */
	new_clcsock->sk->sk_data_ready = lsmc->clcsk_data_ready;

	/* if new clcsock has also inherited the fallback-specific callback
	 * functions, switch them back to the original ones.
	 */
	if (lsmc->use_fallback) {
		if (lsmc->clcsk_state_change)
			new_clcsock->sk->sk_state_change = lsmc->clcsk_state_change;
		if (lsmc->clcsk_write_space)
			new_clcsock->sk->sk_write_space = lsmc->clcsk_write_space;
		if (lsmc->clcsk_error_report)
			new_clcsock->sk->sk_error_report = lsmc->clcsk_error_report;
	}

	(*new_smc)->clcsock = new_clcsock;
out:
	return rc;
@@ -1845,16 +1993,18 @@ static void smc_clcsock_data_ready(struct sock *listen_clcsock)
{
	struct smc_sock *lsmc;

	lsmc = (struct smc_sock *)
	       ((uintptr_t)listen_clcsock->sk_user_data & ~SK_USER_DATA_NOCOPY);
	read_lock_bh(&listen_clcsock->sk_callback_lock);
	lsmc = smc_clcsock_user_data(listen_clcsock);
	if (!lsmc)
		return;
		goto out;
	lsmc->clcsk_data_ready(listen_clcsock);
	if (lsmc->sk.sk_state == SMC_LISTEN) {
		sock_hold(&lsmc->sk); /* sock_put in smc_tcp_listen_work() */
		if (!queue_work(smc_hs_wq, &lsmc->tcp_listen_work))
			sock_put(&lsmc->sk);
	}
out:
	read_unlock_bh(&listen_clcsock->sk_callback_lock);
}

static int smc_listen(struct socket *sock, int backlog)
@@ -1886,13 +2036,20 @@ static int smc_listen(struct socket *sock, int backlog)
	/* save original sk_data_ready function and establish
	 * smc-specific sk_data_ready function
	 */
	smc->clcsk_data_ready = smc->clcsock->sk->sk_data_ready;
	smc->clcsock->sk->sk_data_ready = smc_clcsock_data_ready;
	write_lock_bh(&smc->clcsock->sk->sk_callback_lock);
	smc->clcsock->sk->sk_user_data =
		(void *)((uintptr_t)smc | SK_USER_DATA_NOCOPY);
	smc_clcsock_replace_cb(&smc->clcsock->sk->sk_data_ready,
			       smc_clcsock_data_ready, &smc->clcsk_data_ready);
	write_unlock_bh(&smc->clcsock->sk->sk_callback_lock);

	rc = kernel_listen(smc->clcsock, backlog);
	if (rc) {
		smc->clcsock->sk->sk_data_ready = smc->clcsk_data_ready;
		write_lock_bh(&smc->clcsock->sk->sk_callback_lock);
		smc_clcsock_restore_cb(&smc->clcsock->sk->sk_data_ready,
				       &smc->clcsk_data_ready);
		smc->clcsock->sk->sk_user_data = NULL;
		write_unlock_bh(&smc->clcsock->sk->sk_callback_lock);
		goto out;
	}
	sk->sk_max_ack_backlog = backlog;
+48 −1
Original line number Diff line number Diff line
@@ -129,6 +129,12 @@ enum smc_urg_state {
	SMC_URG_READ	= 3,			/* data was already read */
};

struct smc_mark_woken {
	bool woken;
	void *key;
	wait_queue_entry_t wait_entry;
};

struct smc_connection {
	struct rb_node		alert_node;
	struct smc_link_group	*lgr;		/* link group of connection */
@@ -217,8 +223,14 @@ struct smc_connection {
struct smc_sock {				/* smc sock container */
	struct sock		sk;
	struct socket		*clcsock;	/* internal tcp socket */
	void			(*clcsk_state_change)(struct sock *sk);
						/* original stat_change fct. */
	void			(*clcsk_data_ready)(struct sock *sk);
						/* original data_ready fct. **/
						/* original data_ready fct. */
	void			(*clcsk_write_space)(struct sock *sk);
						/* original write_space fct. */
	void			(*clcsk_error_report)(struct sock *sk);
						/* original error_report fct. */
	struct smc_connection	conn;		/* smc connection */
	struct smc_sock		*listen_smc;	/* listen parent */
	struct work_struct	connect_work;	/* handle non-blocking connect*/
@@ -253,6 +265,41 @@ static inline struct smc_sock *smc_sk(const struct sock *sk)
	return (struct smc_sock *)sk;
}

static inline void smc_init_saved_callbacks(struct smc_sock *smc)
{
	smc->clcsk_state_change = NULL;
	smc->clcsk_data_ready   = NULL;
	smc->clcsk_write_space  = NULL;
	smc->clcsk_error_report = NULL;
}

static inline struct smc_sock *smc_clcsock_user_data(struct sock *clcsk)
{
	return (struct smc_sock *)
	       ((uintptr_t)clcsk->sk_user_data & ~SK_USER_DATA_NOCOPY);
}

/* save target_cb in saved_cb, and replace target_cb with new_cb */
static inline void smc_clcsock_replace_cb(void (**target_cb)(struct sock *),
					  void (*new_cb)(struct sock *),
					  void (**saved_cb)(struct sock *))
{
	/* only save once */
	if (!*saved_cb)
		*saved_cb = *target_cb;
	*target_cb = new_cb;
}

/* restore target_cb to saved_cb, and reset saved_cb to NULL */
static inline void smc_clcsock_restore_cb(void (**target_cb)(struct sock *),
					  void (**saved_cb)(struct sock *))
{
	if (!*saved_cb)
		return;
	*target_cb = *saved_cb;
	*saved_cb = NULL;
}

extern struct workqueue_struct	*smc_hs_wq;	/* wq for handshake work */
extern struct workqueue_struct	*smc_close_wq;	/* wq for close work */

+4 −1
Original line number Diff line number Diff line
@@ -211,8 +211,11 @@ int smc_close_active(struct smc_sock *smc)
		sk->sk_state = SMC_CLOSED;
		sk->sk_state_change(sk); /* wake up accept */
		if (smc->clcsock && smc->clcsock->sk) {
			smc->clcsock->sk->sk_data_ready = smc->clcsk_data_ready;
			write_lock_bh(&smc->clcsock->sk->sk_callback_lock);
			smc_clcsock_restore_cb(&smc->clcsock->sk->sk_data_ready,
					       &smc->clcsk_data_ready);
			smc->clcsock->sk->sk_user_data = NULL;
			write_unlock_bh(&smc->clcsock->sk->sk_callback_lock);
			rc = kernel_sock_shutdown(smc->clcsock, SHUT_RDWR);
		}
		smc_close_cleanup_listen(sk);