Commit message: ipx: call ipxitf_put() in ioctl error path

We should call ipxitf_put() if the copy_to_user() fails.

Reported-by: 李强 <liqiang6-s@360.cn>
Signed-off-by: Dan Carpenter <dan.carpenter@oracle.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
CVE: CVE-2017-7487
CWE: CWE-416
Vulnerability description: The ipxitf_ioctl function in net/ipx/af_ipx.c in the Linux kernel through 4.11.1 mishandles reference counts, which allows local users to cause a denial of service (use-after-free) or possibly have unspecified other impact via a failed SIOCGIFADDR ioctl call for an IPX interface.

Project: linux

static void __ipxitf_put(struct ipx_interface *intrfc)
{
	if (atomic_dec_and_test(&intrfc->refcnt))
		__ipxitf_down(intrfc);
}

Project: linux

static int ipx_create(struct net *net, struct socket *sock, int protocol,
		      int kern)
{
	int rc = -ESOCKTNOSUPPORT;
	struct sock *sk;

	if (!net_eq(net, &init_net))
		return -EAFNOSUPPORT;

	/*
	 * SPX support is not anymore in the kernel sources. If you want to
	 * ressurrect it, completing it and making it understand shared skbs,
	 * be fully multithreaded, etc, grab the sources in an early 2.5 kernel
	 * tree.
	 */
	if (sock->type != SOCK_DGRAM)
		goto out;

	rc = -ENOMEM;
	sk = sk_alloc(net, PF_IPX, GFP_KERNEL, &ipx_proto, kern);
	if (!sk)
		goto out;

	sk_refcnt_debug_inc(sk);
	sock_init_data(sock, sk);
	sk->sk_no_check_tx = 1;		/* Checksum off by default */
	sock->ops = &ipx_dgram_ops;
	rc = 0;
out:
	return rc;
}

Project: linux

static int ipx_recvmsg(struct socket *sock, struct msghdr *msg, size_t size,
		       int flags)
{
	struct sock *sk = sock->sk;
	struct ipx_sock *ipxs = ipx_sk(sk);
	DECLARE_SOCKADDR(struct sockaddr_ipx *, sipx, msg->msg_name);
	struct ipxhdr *ipx = NULL;
	struct sk_buff *skb;
	int copied, rc;
	bool locked = true;

	lock_sock(sk);
	/* put the autobinding in */
	if (!ipxs->port) {
		struct sockaddr_ipx uaddr;

		uaddr.sipx_port		= 0;
		uaddr.sipx_network 	= 0;

#ifdef CONFIG_IPX_INTERN
		rc = -ENETDOWN;
		if (!ipxs->intrfc)
			goto out; /* Someone zonked the iface */
		memcpy(uaddr.sipx_node, ipxs->intrfc->if_node, IPX_NODE_LEN);
#endif	/* CONFIG_IPX_INTERN */

		rc = __ipx_bind(sock, (struct sockaddr *)&uaddr,
			      sizeof(struct sockaddr_ipx));
		if (rc)
			goto out;
	}

	rc = -ENOTCONN;
	if (sock_flag(sk, SOCK_ZAPPED))
		goto out;

	release_sock(sk);
	locked = false;
	skb = skb_recv_datagram(sk, flags & ~MSG_DONTWAIT,
				flags & MSG_DONTWAIT, &rc);
	if (!skb) {
		if (rc == -EAGAIN && (sk->sk_shutdown & RCV_SHUTDOWN))
			rc = 0;
		goto out;
	}

	ipx 	= ipx_hdr(skb);
	copied 	= ntohs(ipx->ipx_pktsize) - sizeof(struct ipxhdr);
	if (copied > size) {
		copied = size;
		msg->msg_flags |= MSG_TRUNC;
	}

	rc = skb_copy_datagram_msg(skb, sizeof(struct ipxhdr), msg, copied);
	if (rc)
		goto out_free;
	if (skb->tstamp)
		sk->sk_stamp = skb->tstamp;

	if (sipx) {
		sipx->sipx_family	= AF_IPX;
		sipx->sipx_port		= ipx->ipx_source.sock;
		memcpy(sipx->sipx_node, ipx->ipx_source.node, IPX_NODE_LEN);
		sipx->sipx_network	= IPX_SKB_CB(skb)->ipx_source_net;
		sipx->sipx_type 	= ipx->ipx_type;
		sipx->sipx_zero		= 0;
		msg->msg_namelen	= sizeof(*sipx);
	}
	rc = copied;

out_free:
	skb_free_datagram(sk, skb);
out:
	if (locked)
		release_sock(sk);
	return rc;
}

Project: linux

static int ipx_release(struct socket *sock)
{
	struct sock *sk = sock->sk;

	if (!sk)
		goto out;

	lock_sock(sk);
	sk->sk_shutdown = SHUTDOWN_MASK;
	if (!sock_flag(sk, SOCK_DEAD))
		sk->sk_state_change(sk);

	sock_set_flag(sk, SOCK_DEAD);
	sock->sk = NULL;
	sk_refcnt_debug_release(sk);
	ipx_destroy_socket(sk);
	release_sock(sk);
	sock_put(sk);
out:
	return 0;
}

Project: linux

static int ipx_sendmsg(struct socket *sock, struct msghdr *msg, size_t len)
{
	struct sock *sk = sock->sk;
	struct ipx_sock *ipxs = ipx_sk(sk);
	DECLARE_SOCKADDR(struct sockaddr_ipx *, usipx, msg->msg_name);
	struct sockaddr_ipx local_sipx;
	int rc = -EINVAL;
	int flags = msg->msg_flags;

	lock_sock(sk);
	/* Socket gets bound below anyway */
/*	if (sk->sk_zapped)
		return -EIO; */	/* Socket not bound */
	if (flags & ~(MSG_DONTWAIT|MSG_CMSG_COMPAT))
		goto out;

	/* Max possible packet size limited by 16 bit pktsize in header */
	if (len >= 65535 - sizeof(struct ipxhdr))
		goto out;

	if (usipx) {
		if (!ipxs->port) {
			struct sockaddr_ipx uaddr;

			uaddr.sipx_port		= 0;
			uaddr.sipx_network	= 0;
#ifdef CONFIG_IPX_INTERN
			rc = -ENETDOWN;
			if (!ipxs->intrfc)
				goto out; /* Someone zonked the iface */
			memcpy(uaddr.sipx_node, ipxs->intrfc->if_node,
				IPX_NODE_LEN);
#endif
			rc = __ipx_bind(sock, (struct sockaddr *)&uaddr,
					sizeof(struct sockaddr_ipx));
			if (rc)
				goto out;
		}

		rc = -EINVAL;
		if (msg->msg_namelen < sizeof(*usipx) ||
		    usipx->sipx_family != AF_IPX)
			goto out;
	} else {
		rc = -ENOTCONN;
		if (sk->sk_state != TCP_ESTABLISHED)
			goto out;

		usipx = &local_sipx;
		usipx->sipx_family 	= AF_IPX;
		usipx->sipx_type 	= ipxs->type;
		usipx->sipx_port 	= ipxs->dest_addr.sock;
		usipx->sipx_network 	= ipxs->dest_addr.net;
		memcpy(usipx->sipx_node, ipxs->dest_addr.node, IPX_NODE_LEN);
	}

	rc = ipxrtr_route_packet(sk, usipx, msg, len, flags & MSG_DONTWAIT);
	if (rc >= 0)
		rc = len;
out:
	release_sock(sk);
	return rc;
}

Project: linux

static int ipx_shutdown(struct socket *sock, int mode)
{
	struct sock *sk = sock->sk;

	if (mode < SHUT_RD || mode > SHUT_RDWR)
		return -EINVAL;
	/* This maps:
	 * SHUT_RD   (0) -> RCV_SHUTDOWN  (1)
	 * SHUT_WR   (1) -> SEND_SHUTDOWN (2)
	 * SHUT_RDWR (2) -> SHUTDOWN_MASK (3)
	 */
	++mode;

	lock_sock(sk);
	sk->sk_shutdown |= mode;
	release_sock(sk);
	sk->sk_state_change(sk);

	return 0;
}