Commit message: NFC: llcp: fix info leaks via msg_name in llcp_sock_recvmsg()

The code in llcp_sock_recvmsg() does not initialize all the members of
struct sockaddr_nfc_llcp when filling the sockaddr info. Nor does it
initialize the padding bytes of the structure inserted by the compiler
for alignment.

Also, if the socket is in state LLCP_CLOSED or is shutting down during
receive the msg_namelen member is not updated to 0 while otherwise
returning with 0, i.e. "success". The msg_namelen update is also
missing for stream and seqpacket sockets which don't fill the sockaddr
info.

Both issues lead to the fact that the code will leak uninitialized
kernel stack bytes in net/socket.c.

Fix the first issue by initializing the memory used for sockaddr info
with memset(0). Fix the second one by setting msg_namelen to 0 early.
It will be updated later if we're going to fill the msg_name member.

Cc: Lauro Ramos Venancio <lauro.venancio@openbossa.org>
Cc: Aloisio Almeida Jr <aloisio.almeida@openbossa.org>
Cc: Samuel Ortiz <sameo@linux.intel.com>
Signed-off-by: Mathias Krause <minipli@googlemail.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
CVE: CVE-2013-3233
CWE: CWE-200
Vulnerability description: The llcp_sock_recvmsg function in net/nfc/llcp/sock.c in the Linux kernel before 3.9-rc7 does not initialize a certain length variable and a certain data structure, which allows local users to obtain sensitive information from kernel stack memory via a crafted recvmsg or recvfrom system call.

Project: linux
File: llcp_sock.c

static inline unsigned int llcp_accept_poll(struct sock *parent)
{
	struct nfc_llcp_sock *llcp_sock, *n, *parent_sock;
	struct sock *sk;

	parent_sock = nfc_llcp_sock(parent);

	list_for_each_entry_safe(llcp_sock, n, &parent_sock->accept_queue,
				 accept_queue) {
		sk = &llcp_sock->sk;

		if (sk->sk_state == LLCP_CONNECTED)
			return POLLIN | POLLRDNORM;
	}

	return 0;
}

Project: linux
File: llcp_sock.c

static int llcp_sock_accept(struct socket *sock, struct socket *newsock,
			    int flags)
{
	DECLARE_WAITQUEUE(wait, current);
	struct sock *sk = sock->sk, *new_sk;
	long timeo;
	int ret = 0;

	pr_debug("parent %p\n", sk);

	lock_sock_nested(sk, SINGLE_DEPTH_NESTING);

	if (sk->sk_state != LLCP_LISTEN) {
		ret = -EBADFD;
		goto error;
	}

	timeo = sock_rcvtimeo(sk, flags & O_NONBLOCK);

	/* Wait for an incoming connection. */
	add_wait_queue_exclusive(sk_sleep(sk), &wait);
	while (!(new_sk = nfc_llcp_accept_dequeue(sk, newsock))) {
		set_current_state(TASK_INTERRUPTIBLE);

		if (!timeo) {
			ret = -EAGAIN;
			break;
		}

		if (signal_pending(current)) {
			ret = sock_intr_errno(timeo);
			break;
		}

		release_sock(sk);
		timeo = schedule_timeout(timeo);
		lock_sock_nested(sk, SINGLE_DEPTH_NESTING);
	}
	__set_current_state(TASK_RUNNING);
	remove_wait_queue(sk_sleep(sk), &wait);

	if (ret)
		goto error;

	newsock->state = SS_CONNECTED;

	pr_debug("new socket %p\n", new_sk);

error:
	release_sock(sk);

	return ret;
}

Project: linux
File: llcp_sock.c

static int llcp_sock_bind(struct socket *sock, struct sockaddr *addr, int alen)
{
	struct sock *sk = sock->sk;
	struct nfc_llcp_sock *llcp_sock = nfc_llcp_sock(sk);
	struct nfc_llcp_local *local;
	struct nfc_dev *dev;
	struct sockaddr_nfc_llcp llcp_addr;
	int len, ret = 0;

	if (!addr || addr->sa_family != AF_NFC)
		return -EINVAL;

	pr_debug("sk %p addr %p family %d\n", sk, addr, addr->sa_family);

	memset(&llcp_addr, 0, sizeof(llcp_addr));
	len = min_t(unsigned int, sizeof(llcp_addr), alen);
	memcpy(&llcp_addr, addr, len);

	/* This is going to be a listening socket, dsap must be 0 */
	if (llcp_addr.dsap != 0)
		return -EINVAL;

	lock_sock(sk);

	if (sk->sk_state != LLCP_CLOSED) {
		ret = -EBADFD;
		goto error;
	}

	dev = nfc_get_device(llcp_addr.dev_idx);
	if (dev == NULL) {
		ret = -ENODEV;
		goto error;
	}

	local = nfc_llcp_find_local(dev);
	if (local == NULL) {
		ret = -ENODEV;
		goto put_dev;
	}

	llcp_sock->dev = dev;
	llcp_sock->local = nfc_llcp_local_get(local);
	llcp_sock->nfc_protocol = llcp_addr.nfc_protocol;
	llcp_sock->service_name_len = min_t(unsigned int,
					    llcp_addr.service_name_len,
					    NFC_LLCP_MAX_SERVICE_NAME);
	llcp_sock->service_name = kmemdup(llcp_addr.service_name,
					  llcp_sock->service_name_len,
					  GFP_KERNEL);

	llcp_sock->ssap = nfc_llcp_get_sdp_ssap(local, llcp_sock);
	if (llcp_sock->ssap == LLCP_SAP_MAX) {
		ret = -EADDRINUSE;
		goto put_dev;
	}

	llcp_sock->reserved_ssap = llcp_sock->ssap;

	nfc_llcp_sock_link(&local->sockets, sk);

	pr_debug("Socket bound to SAP %d\n", llcp_sock->ssap);

	sk->sk_state = LLCP_BOUND;

put_dev:
	nfc_put_device(dev);

error:
	release_sock(sk);
	return ret;
}

Project: linux
File: sock.c

static int llcp_sock_connect(struct socket *sock, struct sockaddr *_addr,
			     int len, int flags)
{
	struct sock *sk = sock->sk;
	struct nfc_llcp_sock *llcp_sock = nfc_llcp_sock(sk);
	struct sockaddr_nfc_llcp *addr = (struct sockaddr_nfc_llcp *)_addr;
	struct nfc_dev *dev;
	struct nfc_llcp_local *local;
	int ret = 0;

	pr_debug("sock %p sk %p flags 0x%x\n", sock, sk, flags);

	if (!addr || len < sizeof(struct sockaddr_nfc) ||
	    addr->sa_family != AF_NFC)
		return -EINVAL;

	if (addr->service_name_len == 0 && addr->dsap == 0)
		return -EINVAL;

	pr_debug("addr dev_idx=%u target_idx=%u protocol=%u\n", addr->dev_idx,
		 addr->target_idx, addr->nfc_protocol);

	lock_sock(sk);

	if (sk->sk_state == LLCP_CONNECTED) {
		ret = -EISCONN;
		goto error;
	}

	dev = nfc_get_device(addr->dev_idx);
	if (dev == NULL) {
		ret = -ENODEV;
		goto error;
	}

	local = nfc_llcp_find_local(dev);
	if (local == NULL) {
		ret = -ENODEV;
		goto put_dev;
	}

	device_lock(&dev->dev);
	if (dev->dep_link_up == false) {
		ret = -ENOLINK;
		device_unlock(&dev->dev);
		goto put_dev;
	}
	device_unlock(&dev->dev);

	if (local->rf_mode == NFC_RF_INITIATOR &&
	    addr->target_idx != local->target_idx) {
		ret = -ENOLINK;
		goto put_dev;
	}

	llcp_sock->dev = dev;
	llcp_sock->local = nfc_llcp_local_get(local);
	llcp_sock->miu = llcp_sock->local->remote_miu;
	llcp_sock->ssap = nfc_llcp_get_local_ssap(local);
	if (llcp_sock->ssap == LLCP_SAP_MAX) {
		ret = -ENOMEM;
		goto put_dev;
	}

	llcp_sock->reserved_ssap = llcp_sock->ssap;

	if (addr->service_name_len == 0)
		llcp_sock->dsap = addr->dsap;
	else
		llcp_sock->dsap = LLCP_SAP_SDP;
	llcp_sock->nfc_protocol = addr->nfc_protocol;
	llcp_sock->service_name_len = min_t(unsigned int,
					    addr->service_name_len,
					    NFC_LLCP_MAX_SERVICE_NAME);
	llcp_sock->service_name = kmemdup(addr->service_name,
					  llcp_sock->service_name_len,
					  GFP_KERNEL);

	nfc_llcp_sock_link(&local->connecting_sockets, sk);

	ret = nfc_llcp_send_connect(llcp_sock);
	if (ret)
		goto sock_unlink;

	ret = sock_wait_state(sk, LLCP_CONNECTED,
			      sock_sndtimeo(sk, flags & O_NONBLOCK));
	if (ret)
		goto sock_unlink;

	release_sock(sk);

	return 0;

sock_unlink:
	nfc_llcp_put_ssap(local, llcp_sock->ssap);

	nfc_llcp_sock_unlink(&local->connecting_sockets, sk);

put_dev:
	nfc_put_device(dev);

error:
	release_sock(sk);
	return ret;
}

Project: linux
File: llcp_sock.c

static int llcp_sock_create(struct net *net, struct socket *sock,
			    const struct nfc_protocol *nfc_proto)
{
	struct sock *sk;

	pr_debug("%p\n", sock);

	if (sock->type != SOCK_STREAM &&
	    sock->type != SOCK_DGRAM &&
	    sock->type != SOCK_RAW)
		return -ESOCKTNOSUPPORT;

	if (sock->type == SOCK_RAW)
		sock->ops = &llcp_rawsock_ops;
	else
		sock->ops = &llcp_sock_ops;

	sk = nfc_llcp_sock_alloc(sock, sock->type, GFP_ATOMIC);
	if (sk == NULL)
		return -ENOMEM;

	return 0;
}

Project: linux
File: llcp_sock.c

static void llcp_sock_destruct(struct sock *sk)
{
	struct nfc_llcp_sock *llcp_sock = nfc_llcp_sock(sk);

	pr_debug("%p\n", sk);

	if (sk->sk_state == LLCP_CONNECTED)
		nfc_put_device(llcp_sock->dev);

	skb_queue_purge(&sk->sk_receive_queue);

	nfc_llcp_sock_free(llcp_sock);

	if (!sock_flag(sk, SOCK_DEAD)) {
		pr_err("Freeing alive NFC LLCP socket %p\n", sk);
		return;
	}
}

Project: linux
File: llcp_sock.c

static int llcp_sock_listen(struct socket *sock, int backlog)
{
	struct sock *sk = sock->sk;
	int ret = 0;

	pr_debug("sk %p backlog %d\n", sk, backlog);

	lock_sock(sk);

	if ((sock->type != SOCK_SEQPACKET && sock->type != SOCK_STREAM) ||
	    sk->sk_state != LLCP_BOUND) {
		ret = -EBADFD;
		goto error;
	}

	sk->sk_max_ack_backlog = backlog;
	sk->sk_ack_backlog = 0;

	pr_debug("Socket listening\n");
	sk->sk_state = LLCP_LISTEN;

error:
	release_sock(sk);

	return ret;
}

Project: linux
File: sock.c

static unsigned int llcp_sock_poll(struct file *file, struct socket *sock,
				   poll_table *wait)
{
	struct sock *sk = sock->sk;
	unsigned int mask = 0;

	pr_debug("%p\n", sk);

	sock_poll_wait(file, sk_sleep(sk), wait);

	if (sk->sk_state == LLCP_LISTEN)
		return llcp_accept_poll(sk);

	if (sk->sk_err || !skb_queue_empty(&sk->sk_error_queue))
		mask |= POLLERR;

	if (!skb_queue_empty(&sk->sk_receive_queue))
		mask |= POLLIN | POLLRDNORM;

	if (sk->sk_state == LLCP_CLOSED)
		mask |= POLLHUP;

	if (sk->sk_shutdown & RCV_SHUTDOWN)
		mask |= POLLRDHUP | POLLIN | POLLRDNORM;

	if (sk->sk_shutdown == SHUTDOWN_MASK)
		mask |= POLLHUP;

	if (sock_writeable(sk))
		mask |= POLLOUT | POLLWRNORM | POLLWRBAND;
	else
		set_bit(SOCK_ASYNC_NOSPACE, &sk->sk_socket->flags);

	pr_debug("mask 0x%x\n", mask);

	return mask;
}

Project: linux
File: llcp_sock.c

static int llcp_sock_sendmsg(struct kiocb *iocb, struct socket *sock,
			     struct msghdr *msg, size_t len)
{
	struct sock *sk = sock->sk;
	struct nfc_llcp_sock *llcp_sock = nfc_llcp_sock(sk);
	int ret;

	pr_debug("sock %p sk %p", sock, sk);

	ret = sock_error(sk);
	if (ret)
		return ret;

	if (msg->msg_flags & MSG_OOB)
		return -EOPNOTSUPP;

	lock_sock(sk);

	if (sk->sk_type == SOCK_DGRAM) {
		struct sockaddr_nfc_llcp *addr =
			(struct sockaddr_nfc_llcp *)msg->msg_name;

		if (msg->msg_namelen < sizeof(*addr)) {
			release_sock(sk);
			return -EINVAL;
		}

		release_sock(sk);

		return nfc_llcp_send_ui_frame(llcp_sock, addr->dsap, addr->ssap,
					      msg, len);
	}

	if (sk->sk_state != LLCP_CONNECTED) {
		release_sock(sk);
		return -ENOTCONN;
	}

	release_sock(sk);

	return nfc_llcp_send_i_frame(llcp_sock, msg, len);
}

Project: linux
File: llcp_sock.c

void nfc_llcp_accept_enqueue(struct sock *parent, struct sock *sk)
{
	struct nfc_llcp_sock *llcp_sock = nfc_llcp_sock(sk);
	struct nfc_llcp_sock *llcp_sock_parent = nfc_llcp_sock(parent);

	/* Lock will be free from unlink */
	sock_hold(sk);

	list_add_tail(&llcp_sock->accept_queue,
		      &llcp_sock_parent->accept_queue);
	llcp_sock->parent = parent;
	sk_acceptq_added(parent);
}

Project: linux
File: llcp_sock.c

void nfc_llcp_accept_unlink(struct sock *sk)
{
	struct nfc_llcp_sock *llcp_sock = nfc_llcp_sock(sk);

	pr_debug("state %d\n", sk->sk_state);

	list_del_init(&llcp_sock->accept_queue);
	sk_acceptq_removed(llcp_sock->parent);
	llcp_sock->parent = NULL;

	sock_put(sk);
}

Project: linux
File: sock.c

struct sock *nfc_llcp_sock_alloc(struct socket *sock, int type, gfp_t gfp)
{
	struct sock *sk;
	struct nfc_llcp_sock *llcp_sock;

	sk = sk_alloc(&init_net, PF_NFC, gfp, &llcp_sock_proto);
	if (!sk)
		return NULL;

	llcp_sock = nfc_llcp_sock(sk);

	sock_init_data(sock, sk);
	sk->sk_state = LLCP_CLOSED;
	sk->sk_protocol = NFC_SOCKPROTO_LLCP;
	sk->sk_type = type;
	sk->sk_destruct = llcp_sock_destruct;

	llcp_sock->ssap = 0;
	llcp_sock->dsap = LLCP_SAP_SDP;
	llcp_sock->rw = LLCP_DEFAULT_RW;
	llcp_sock->miu = LLCP_DEFAULT_MIU;
	llcp_sock->send_n = llcp_sock->send_ack_n = 0;
	llcp_sock->recv_n = llcp_sock->recv_ack_n = 0;
	llcp_sock->remote_ready = 1;
	llcp_sock->reserved_ssap = LLCP_SAP_MAX;
	skb_queue_head_init(&llcp_sock->tx_queue);
	skb_queue_head_init(&llcp_sock->tx_pending_queue);
	INIT_LIST_HEAD(&llcp_sock->accept_queue);

	if (sock != NULL)
		sock->state = SS_UNCONNECTED;

	return sk;
}

Project: linux
File: llcp_sock.c

void nfc_llcp_sock_exit(void)
{
	nfc_proto_unregister(&llcp_nfc_proto);
}

Project: linux
File: llcp_sock.c

static int sock_wait_state(struct sock *sk, int state, unsigned long timeo)
{
	DECLARE_WAITQUEUE(wait, current);
	int err = 0;

	pr_debug("sk %p", sk);

	add_wait_queue(sk_sleep(sk), &wait);
	set_current_state(TASK_INTERRUPTIBLE);

	while (sk->sk_state != state) {
		if (!timeo) {
			err = -EINPROGRESS;
			break;
		}

		if (signal_pending(current)) {
			err = sock_intr_errno(timeo);
			break;
		}

		release_sock(sk);
		timeo = schedule_timeout(timeo);
		lock_sock(sk);
		set_current_state(TASK_INTERRUPTIBLE);

		err = sock_error(sk);
		if (err)
			break;
	}

	__set_current_state(TASK_RUNNING);
	remove_wait_queue(sk_sleep(sk), &wait);
	return err;
}