Commit cfaf3c09 authored by Litao Jiao's avatar Litao Jiao
Browse files

net/smc: Transitional solution for clcsock race issue

mainline inclusion
from mainline-v5.17-rc2
commit c0bf3d8a
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=c0bf3d8a943b6f2e912b7c1de03e2ef28e76f760

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

We encountered a crash in smc_setsockopt() and it is caused by
accessing smc->clcsock after clcsock was released.

 BUG: kernel NULL pointer dereference, address: 0000000000000020
 #PF: supervisor read access in kernel mode
 #PF: error_code(0x0000) - not-present page
 PGD 0 P4D 0
 Oops: 0000 [#1] PREEMPT SMP PTI
 CPU: 1 PID: 50309 Comm: nginx Kdump: loaded Tainted: G E     5.16.0-rc4+ #53
 RIP: 0010:smc_setsockopt+0x59/0x280 [smc]
 Call Trace:
  <TASK>
  __sys_setsockopt+0xfc/0x190
  __x64_sys_setsockopt+0x20/0x30
  do_syscall_64+0x34/0x90
  entry_SYSCALL_64_after_hwframe+0x44/0xae
 RIP: 0033:0x7f16ba83918e
  </TASK>

This patch tries to fix it by holding clcsock_release_lock and
checking whether clcsock has already been released before access.

In case that a crash of the same reason happens in smc_getsockopt()
or smc_switch_to_fallback(), this patch also checkes smc->clcsock
in them too. And the caller of smc_switch_to_fallback() will identify
whether fallback succeeds according to the return value.

Fixes: fd57770d ("net/smc: wait for pending work before clcsock release_sock")
Link: https://lore.kernel.org/lkml/5dd7ffd1-28e2-24cc-9442-1defec27375e@linux.ibm.com/T/


Signed-off-by: default avatarWen Gu <guwen@linux.alibaba.com>
Acked-by: default avatarKarsten Graul <kgraul@linux.ibm.com>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
Signed-off-by: default avatarLitao Jiao <jiaolitao@sangfor.com.cn>
parent 1dd3c566
Loading
Loading
Loading
Loading
+50 −12
Original line number Original line Diff line number Diff line
@@ -703,10 +703,15 @@ static void smc_fback_error_report(struct sock *clcsk)
	smc_fback_forward_wakeup(smc, clcsk, smc->clcsk_error_report);
	smc_fback_forward_wakeup(smc, clcsk, smc->clcsk_error_report);
}
}


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


	mutex_lock(&smc->clcsock_release_lock);
	if (!smc->clcsock) {
		mutex_unlock(&smc->clcsock_release_lock);
		return -EBADF;
	}
	clcsk = smc->clcsock->sk;
	clcsk = smc->clcsock->sk;


	smc->use_fallback = true;
	smc->use_fallback = true;
@@ -733,12 +738,21 @@ static void smc_switch_to_fallback(struct smc_sock *smc)
		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);
	}
	}
	mutex_unlock(&smc->clcsock_release_lock);
	return 0;
}
}


/* fall back during connect */
/* fall back during connect */
static int smc_connect_fallback(struct smc_sock *smc, int reason_code)
static int smc_connect_fallback(struct smc_sock *smc, int reason_code)
{
{
	smc_switch_to_fallback(smc);
	int rc = 0;

	rc = smc_switch_to_fallback(smc);
	if (rc) { /* fallback fails */
		if (smc->sk.sk_state == SMC_INIT)
			sock_put(&smc->sk); /* passive closing */
		return rc;
	}
	smc->fallback_rsn = reason_code;
	smc->fallback_rsn = reason_code;
	smc_copy_sock_settings_to_clc(smc);
	smc_copy_sock_settings_to_clc(smc);
	smc->connect_nonblock = 0;
	smc->connect_nonblock = 0;
@@ -1586,11 +1600,12 @@ static void smc_listen_decline(struct smc_sock *new_smc, int reason_code,
		smc_lgr_cleanup_early(&new_smc->conn);
		smc_lgr_cleanup_early(&new_smc->conn);
	else
	else
		smc_conn_free(&new_smc->conn);
		smc_conn_free(&new_smc->conn);
	if (reason_code < 0) { /* error, no fallback possible */
	if (reason_code < 0 ||
	    smc_switch_to_fallback(new_smc)) {
		/* error, no fallback possible */
		smc_listen_out_err(new_smc);
		smc_listen_out_err(new_smc);
		return;
		return;
	}
	}
	smc_switch_to_fallback(new_smc);
	new_smc->fallback_rsn = reason_code;
	new_smc->fallback_rsn = reason_code;
	if (reason_code && reason_code != SMC_CLC_DECL_PEERDECL) {
	if (reason_code && reason_code != SMC_CLC_DECL_PEERDECL) {
		if (smc_clc_send_decline(new_smc, reason_code, version) < 0) {
		if (smc_clc_send_decline(new_smc, reason_code, version) < 0) {
@@ -1951,9 +1966,13 @@ static void smc_listen_work(struct work_struct *work)


	/* check if peer is smc capable */
	/* check if peer is smc capable */
	if (!tcp_sk(newclcsock->sk)->syn_smc) {
	if (!tcp_sk(newclcsock->sk)->syn_smc) {
		smc_switch_to_fallback(new_smc);
		rc = smc_switch_to_fallback(new_smc);
		if (rc) {
			smc_listen_out_err(new_smc);
		} else {
			new_smc->fallback_rsn = SMC_CLC_DECL_PEERNOSMC;
			new_smc->fallback_rsn = SMC_CLC_DECL_PEERNOSMC;
			smc_listen_out_connected(new_smc);
			smc_listen_out_connected(new_smc);
		}
		return;
		return;
	}
	}


@@ -2237,7 +2256,9 @@ static int smc_sendmsg(struct socket *sock, struct msghdr *msg, size_t len)
	if (msg->msg_flags & MSG_FASTOPEN) {
	if (msg->msg_flags & MSG_FASTOPEN) {
		/* not connected yet, fallback */
		/* not connected yet, fallback */
		if (sk->sk_state == SMC_INIT && !smc->connect_nonblock) {
		if (sk->sk_state == SMC_INIT && !smc->connect_nonblock) {
			smc_switch_to_fallback(smc);
			rc = smc_switch_to_fallback(smc);
			if (rc)
				goto out;
			smc->fallback_rsn = SMC_CLC_DECL_OPTUNSUPP;
			smc->fallback_rsn = SMC_CLC_DECL_OPTUNSUPP;
		} else {
		} else {
			rc = -EINVAL;
			rc = -EINVAL;
@@ -2446,6 +2467,11 @@ static int smc_setsockopt(struct socket *sock, int level, int optname,
	/* generic setsockopts reaching us here always apply to the
	/* generic setsockopts reaching us here always apply to the
	 * CLC socket
	 * CLC socket
	 */
	 */
	mutex_lock(&smc->clcsock_release_lock);
	if (!smc->clcsock) {
		mutex_unlock(&smc->clcsock_release_lock);
		return -EBADF;
	}
	if (unlikely(!smc->clcsock->ops->setsockopt))
	if (unlikely(!smc->clcsock->ops->setsockopt))
		rc = -EOPNOTSUPP;
		rc = -EOPNOTSUPP;
	else
	else
@@ -2455,6 +2481,7 @@ static int smc_setsockopt(struct socket *sock, int level, int optname,
		sk->sk_err = smc->clcsock->sk->sk_err;
		sk->sk_err = smc->clcsock->sk->sk_err;
		sk->sk_error_report(sk);
		sk->sk_error_report(sk);
	}
	}
	mutex_unlock(&smc->clcsock_release_lock);


	if (optlen < sizeof(int))
	if (optlen < sizeof(int))
		return -EINVAL;
		return -EINVAL;
@@ -2471,7 +2498,8 @@ static int smc_setsockopt(struct socket *sock, int level, int optname,
	case TCP_FASTOPEN_NO_COOKIE:
	case TCP_FASTOPEN_NO_COOKIE:
		/* option not supported by SMC */
		/* option not supported by SMC */
		if (sk->sk_state == SMC_INIT && !smc->connect_nonblock) {
		if (sk->sk_state == SMC_INIT && !smc->connect_nonblock) {
			smc_switch_to_fallback(smc);
			rc = smc_switch_to_fallback(smc);
			if (!rc)
				smc->fallback_rsn = SMC_CLC_DECL_OPTUNSUPP;
				smc->fallback_rsn = SMC_CLC_DECL_OPTUNSUPP;
		} else {
		} else {
			rc = -EINVAL;
			rc = -EINVAL;
@@ -2513,13 +2541,23 @@ static int smc_getsockopt(struct socket *sock, int level, int optname,
			  char __user *optval, int __user *optlen)
			  char __user *optval, int __user *optlen)
{
{
	struct smc_sock *smc;
	struct smc_sock *smc;
	int rc;


	smc = smc_sk(sock->sk);
	smc = smc_sk(sock->sk);
	mutex_lock(&smc->clcsock_release_lock);
	if (!smc->clcsock) {
		mutex_unlock(&smc->clcsock_release_lock);
		return -EBADF;
	}
	/* socket options apply to the CLC socket */
	/* socket options apply to the CLC socket */
	if (unlikely(!smc->clcsock->ops->getsockopt))
	if (unlikely(!smc->clcsock->ops->getsockopt)) {
		mutex_unlock(&smc->clcsock_release_lock);
		return -EOPNOTSUPP;
		return -EOPNOTSUPP;
	return smc->clcsock->ops->getsockopt(smc->clcsock, level, optname,
	}
	rc = smc->clcsock->ops->getsockopt(smc->clcsock, level, optname,
					     optval, optlen);
					     optval, optlen);
	mutex_unlock(&smc->clcsock_release_lock);
	return rc;
}
}


static int smc_ioctl(struct socket *sock, unsigned int cmd,
static int smc_ioctl(struct socket *sock, unsigned int cmd,