Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 1c95df85 authored by Neal Cardwell's avatar Neal Cardwell Committed by David S. Miller
Browse files

inet_diag: fix oops for IPv4 AF_INET6 TCP SYN-RECV state



Fix inet_diag to be aware of the fact that AF_INET6 TCP connections
instantiated for IPv4 traffic and in the SYN-RECV state were actually
created with inet_reqsk_alloc(), instead of inet6_reqsk_alloc(). This
means that for such connections inet6_rsk(req) returns a pointer to a
random spot in memory up to roughly 64KB beyond the end of the
request_sock.

With this bug, for a server using AF_INET6 TCP sockets and serving
IPv4 traffic, an inet_diag user like `ss state SYN-RECV` would lead to
inet_diag_fill_req() causing an oops or the export to user space of 16
bytes of kernel memory as a garbage IPv6 address, depending on where
the garbage inet6_rsk(req) pointed.

Signed-off-by: default avatarNeal Cardwell <ncardwell@google.com>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent ed23ec4f
Loading
Loading
Loading
Loading
+39 −14
Original line number Original line Diff line number Diff line
@@ -44,6 +44,10 @@ struct inet_diag_entry {
	u16 dport;
	u16 dport;
	u16 family;
	u16 family;
	u16 userlocks;
	u16 userlocks;
#if IS_ENABLED(CONFIG_IPV6)
	struct in6_addr saddr_storage;	/* for IPv4-mapped-IPv6 addresses */
	struct in6_addr daddr_storage;	/* for IPv4-mapped-IPv6 addresses */
#endif
};
};


static DEFINE_MUTEX(inet_diag_table_mutex);
static DEFINE_MUTEX(inet_diag_table_mutex);
@@ -596,6 +600,36 @@ static int inet_twsk_diag_dump(struct inet_timewait_sock *tw,
				   cb->nlh->nlmsg_seq, NLM_F_MULTI, cb->nlh);
				   cb->nlh->nlmsg_seq, NLM_F_MULTI, cb->nlh);
}
}


/* Get the IPv4, IPv6, or IPv4-mapped-IPv6 local and remote addresses
 * from a request_sock. For IPv4-mapped-IPv6 we must map IPv4 to IPv6.
 */
static inline void inet_diag_req_addrs(const struct sock *sk,
				       const struct request_sock *req,
				       struct inet_diag_entry *entry)
{
	struct inet_request_sock *ireq = inet_rsk(req);

#if IS_ENABLED(CONFIG_IPV6)
	if (sk->sk_family == AF_INET6) {
		if (req->rsk_ops->family == AF_INET6) {
			entry->saddr = inet6_rsk(req)->loc_addr.s6_addr32;
			entry->daddr = inet6_rsk(req)->rmt_addr.s6_addr32;
		} else if (req->rsk_ops->family == AF_INET) {
			ipv6_addr_set_v4mapped(ireq->loc_addr,
					       &entry->saddr_storage);
			ipv6_addr_set_v4mapped(ireq->rmt_addr,
					       &entry->daddr_storage);
			entry->saddr = entry->saddr_storage.s6_addr32;
			entry->daddr = entry->daddr_storage.s6_addr32;
		}
	} else
#endif
	{
		entry->saddr = &ireq->loc_addr;
		entry->daddr = &ireq->rmt_addr;
	}
}

static int inet_diag_fill_req(struct sk_buff *skb, struct sock *sk,
static int inet_diag_fill_req(struct sk_buff *skb, struct sock *sk,
			      struct request_sock *req,
			      struct request_sock *req,
			      struct user_namespace *user_ns,
			      struct user_namespace *user_ns,
@@ -637,8 +671,10 @@ static int inet_diag_fill_req(struct sk_buff *skb, struct sock *sk,
	r->idiag_inode = 0;
	r->idiag_inode = 0;
#if IS_ENABLED(CONFIG_IPV6)
#if IS_ENABLED(CONFIG_IPV6)
	if (r->idiag_family == AF_INET6) {
	if (r->idiag_family == AF_INET6) {
		*(struct in6_addr *)r->id.idiag_src = inet6_rsk(req)->loc_addr;
		struct inet_diag_entry entry;
		*(struct in6_addr *)r->id.idiag_dst = inet6_rsk(req)->rmt_addr;
		inet_diag_req_addrs(sk, req, &entry);
		memcpy(r->id.idiag_src, entry.saddr, sizeof(struct in6_addr));
		memcpy(r->id.idiag_dst, entry.daddr, sizeof(struct in6_addr));
	}
	}
#endif
#endif


@@ -691,18 +727,7 @@ static int inet_diag_dump_reqs(struct sk_buff *skb, struct sock *sk,
				continue;
				continue;


			if (bc) {
			if (bc) {
				entry.saddr =
				inet_diag_req_addrs(sk, req, &entry);
#if IS_ENABLED(CONFIG_IPV6)
					(entry.family == AF_INET6) ?
					inet6_rsk(req)->loc_addr.s6_addr32 :
#endif
					&ireq->loc_addr;
				entry.daddr =
#if IS_ENABLED(CONFIG_IPV6)
					(entry.family == AF_INET6) ?
					inet6_rsk(req)->rmt_addr.s6_addr32 :
#endif
					&ireq->rmt_addr;
				entry.dport = ntohs(ireq->rmt_port);
				entry.dport = ntohs(ireq->rmt_port);


				if (!inet_diag_bc_run(bc, &entry))
				if (!inet_diag_bc_run(bc, &entry))