Commit 694d7a92 authored by Litao Jiao's avatar Litao Jiao
Browse files

net/smc: Fix slab-out-of-bounds issue in fallback

mainline inclusion
from mainline-v5.18-rc5
commit 0558226c
category: bugfix
bugzilla: https://gitee.com/openeuler/kernel/issues/I76JHC
CVE: NA

Reference: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/net/smc?id=0558226cebee256aa3f8ec0cc5a800a10bf120a6



--------------------------------

syzbot reported a slab-out-of-bounds/use-after-free issue,
which was caused by accessing an already freed smc sock in
fallback-specific callback functions of clcsock.

This patch fixes the issue by restoring fallback-specific
callback functions to original ones and resetting clcsock
sk_user_data to NULL before freeing smc sock.

Meanwhile, this patch introduces sk_callback_lock to make
the access and assignment to sk_user_data mutually exclusive.

Reported-by: default avatar <syzbot+b425899ed22c6943e00b@syzkaller.appspotmail.com>
Fixes: 341adeec ("net/smc: Forward wakeup to smc socket waitqueue after fallback")
Link: https://lore.kernel.org/r/00000000000013ca8105d7ae3ada@google.com/


Signed-off-by: default avatarWen Gu <guwen@linux.alibaba.com>
Acked-by: default avatarKarsten Graul <kgraul@linux.ibm.com>
Signed-off-by: default avatarJakub Kicinski <kuba@kernel.org>
Signed-off-by: default avatarLitao Jiao <jiaolitao@sangfor.com.cn>
parent 4440af3a
Loading
Loading
Loading
Loading
+57 −23
Original line number Original line Diff line number Diff line
@@ -146,11 +146,27 @@ struct proto smc_proto6 = {
};
};
EXPORT_SYMBOL_GPL(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)
static void smc_restore_fallback_changes(struct smc_sock *smc)
{
{
	if (smc->clcsock->file) { /* non-accepted sockets have no file yet */
	if (smc->clcsock->file) { /* non-accepted sockets have no file yet */
		smc->clcsock->file->private_data = smc->sk.sk_socket;
		smc->clcsock->file->private_data = smc->sk.sk_socket;
		smc->clcsock->file = NULL;
		smc->clcsock->file = NULL;
		smc_fback_restore_callbacks(smc);
	}
	}
}
}


@@ -666,48 +682,57 @@ static void smc_fback_forward_wakeup(struct smc_sock *smc, struct sock *clcsk,


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


	if (!smc)
	read_lock_bh(&clcsk->sk_callback_lock);
		return;
	smc = smc_clcsock_user_data(clcsk);
	smc_fback_forward_wakeup(smc, clcsk, smc->clcsk_state_change);
	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)
static void smc_fback_data_ready(struct sock *clcsk)
{
{
	struct smc_sock *smc =
	struct smc_sock *smc;
		smc_clcsock_user_data(clcsk);


	if (!smc)
	read_lock_bh(&clcsk->sk_callback_lock);
		return;
	smc = smc_clcsock_user_data(clcsk);
	smc_fback_forward_wakeup(smc, clcsk, smc->clcsk_data_ready);
	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)
static void smc_fback_write_space(struct sock *clcsk)
{
{
	struct smc_sock *smc =
	struct smc_sock *smc;
		smc_clcsock_user_data(clcsk);


	if (!smc)
	read_lock_bh(&clcsk->sk_callback_lock);
		return;
	smc = smc_clcsock_user_data(clcsk);
	smc_fback_forward_wakeup(smc, clcsk, smc->clcsk_write_space);
	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)
static void smc_fback_error_report(struct sock *clcsk)
{
{
	struct smc_sock *smc =
	struct smc_sock *smc;
		smc_clcsock_user_data(clcsk);


	if (!smc)
	read_lock_bh(&clcsk->sk_callback_lock);
		return;
	smc = smc_clcsock_user_data(clcsk);
	smc_fback_forward_wakeup(smc, clcsk, smc->clcsk_error_report);
	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)
static void smc_fback_replace_callbacks(struct smc_sock *smc)
{
{
	struct sock *clcsk = smc->clcsock->sk;
	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);
	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_clcsock_replace_cb(&clcsk->sk_state_change, smc_fback_state_change,
@@ -718,6 +743,8 @@ static void smc_fback_replace_callbacks(struct smc_sock *smc)
			       &smc->clcsk_write_space);
			       &smc->clcsk_write_space);
	smc_clcsock_replace_cb(&clcsk->sk_error_report, smc_fback_error_report,
	smc_clcsock_replace_cb(&clcsk->sk_error_report, smc_fback_error_report,
			       &smc->clcsk_error_report);
			       &smc->clcsk_error_report);

	write_unlock_bh(&clcsk->sk_callback_lock);
}
}


static int smc_switch_to_fallback(struct smc_sock *smc)
static int smc_switch_to_fallback(struct smc_sock *smc)
@@ -2115,11 +2142,12 @@ static void smc_tcp_listen_work(struct work_struct *work)


static void smc_clcsock_data_ready(struct sock *listen_clcsock)
static void smc_clcsock_data_ready(struct sock *listen_clcsock)
{
{
	struct smc_sock *lsmc =
	struct smc_sock *lsmc;
		smc_clcsock_user_data(listen_clcsock);


	read_lock_bh(&listen_clcsock->sk_callback_lock);
	lsmc = smc_clcsock_user_data(listen_clcsock);
	if (!lsmc)
	if (!lsmc)
		return;
		goto out;
	lsmc->clcsk_data_ready(listen_clcsock);
	lsmc->clcsk_data_ready(listen_clcsock);
	if (lsmc->sk.sk_state == SMC_LISTEN) {
	if (lsmc->sk.sk_state == SMC_LISTEN) {
		int idx = atomic_fetch_inc(&lsmc->tcp_listen_work_seq) %
		int idx = atomic_fetch_inc(&lsmc->tcp_listen_work_seq) %
@@ -2128,6 +2156,8 @@ static void smc_clcsock_data_ready(struct sock *listen_clcsock)
		if (!queue_work(smc_tcp_ls_wq, &lsmc->tcp_listen_works[idx].work))
		if (!queue_work(smc_tcp_ls_wq, &lsmc->tcp_listen_works[idx].work))
			sock_put(&lsmc->sk);
			sock_put(&lsmc->sk);
	}
	}
out:
	read_unlock_bh(&listen_clcsock->sk_callback_lock);
}
}


static int smc_listen(struct socket *sock, int backlog)
static int smc_listen(struct socket *sock, int backlog)
@@ -2159,16 +2189,20 @@ static int smc_listen(struct socket *sock, int backlog)
	/* save original sk_data_ready function and establish
	/* save original sk_data_ready function and establish
	 * smc-specific sk_data_ready function
	 * smc-specific sk_data_ready function
	 */
	 */
	write_lock_bh(&smc->clcsock->sk->sk_callback_lock);
	smc->clcsock->sk->sk_user_data =
	smc->clcsock->sk->sk_user_data =
		(void *)((uintptr_t)smc | SK_USER_DATA_NOCOPY);
		(void *)((uintptr_t)smc | SK_USER_DATA_NOCOPY);
	smc_clcsock_replace_cb(&smc->clcsock->sk->sk_data_ready,
	smc_clcsock_replace_cb(&smc->clcsock->sk->sk_data_ready,
			       smc_clcsock_data_ready, &smc->clcsk_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);
	rc = kernel_listen(smc->clcsock, backlog);
	if (rc) {
	if (rc) {
		write_lock_bh(&smc->clcsock->sk->sk_callback_lock);
		smc_clcsock_restore_cb(&smc->clcsock->sk->sk_data_ready,
		smc_clcsock_restore_cb(&smc->clcsock->sk->sk_data_ready,
				       &smc->clcsk_data_ready);
				       &smc->clcsk_data_ready);
		smc->clcsock->sk->sk_user_data = NULL;
		smc->clcsock->sk->sk_user_data = NULL;
		write_unlock_bh(&smc->clcsock->sk->sk_callback_lock);
		goto out;
		goto out;
	}
	}
	sk->sk_max_ack_backlog = backlog;
	sk->sk_max_ack_backlog = backlog;
+2 −0
Original line number Original line Diff line number Diff line
@@ -215,9 +215,11 @@ int smc_close_active(struct smc_sock *smc)
		sk->sk_state = SMC_CLOSED;
		sk->sk_state = SMC_CLOSED;
		sk->sk_state_change(sk); /* wake up accept */
		sk->sk_state_change(sk); /* wake up accept */
		if (smc->clcsock && smc->clcsock->sk) {
		if (smc->clcsock && smc->clcsock->sk) {
			write_lock_bh(&smc->clcsock->sk->sk_callback_lock);
			smc_clcsock_restore_cb(&smc->clcsock->sk->sk_data_ready,
			smc_clcsock_restore_cb(&smc->clcsock->sk->sk_data_ready,
					       &smc->clcsk_data_ready);
					       &smc->clcsk_data_ready);
			smc->clcsock->sk->sk_user_data = NULL;
			smc->clcsock->sk->sk_user_data = NULL;
			write_unlock_bh(&smc->clcsock->sk->sk_callback_lock);
			rc = kernel_sock_shutdown(smc->clcsock, SHUT_RDWR);
			rc = kernel_sock_shutdown(smc->clcsock, SHUT_RDWR);
		}
		}
		smc_close_cleanup_listen(sk);
		smc_close_cleanup_listen(sk);