diff options
author | Neal Cardwell <ncardwell@google.com> | 2012-12-08 14:43:21 -0500 |
---|---|---|
committer | David S. Miller <davem@davemloft.net> | 2012-12-09 18:59:37 -0500 |
commit | 1c95df85ca49640576de2f0a850925957b547b84 (patch) | |
tree | f39f7537be01cf536c4f125e0f3671dc83ddc04b | |
parent | ed23ec4f0a510528e0ffe415f9394107418ae854 (diff) |
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: Neal Cardwell <ncardwell@google.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
-rw-r--r-- | net/ipv4/inet_diag.c | 53 |
1 files changed, 39 insertions, 14 deletions
diff --git a/net/ipv4/inet_diag.c b/net/ipv4/inet_diag.c index 0c34bfabc11f..16cfa42cfd99 100644 --- a/net/ipv4/inet_diag.c +++ b/net/ipv4/inet_diag.c | |||
@@ -44,6 +44,10 @@ struct inet_diag_entry { | |||
44 | u16 dport; | 44 | u16 dport; |
45 | u16 family; | 45 | u16 family; |
46 | u16 userlocks; | 46 | u16 userlocks; |
47 | #if IS_ENABLED(CONFIG_IPV6) | ||
48 | struct in6_addr saddr_storage; /* for IPv4-mapped-IPv6 addresses */ | ||
49 | struct in6_addr daddr_storage; /* for IPv4-mapped-IPv6 addresses */ | ||
50 | #endif | ||
47 | }; | 51 | }; |
48 | 52 | ||
49 | static DEFINE_MUTEX(inet_diag_table_mutex); | 53 | static DEFINE_MUTEX(inet_diag_table_mutex); |
@@ -596,6 +600,36 @@ static int inet_twsk_diag_dump(struct inet_timewait_sock *tw, | |||
596 | cb->nlh->nlmsg_seq, NLM_F_MULTI, cb->nlh); | 600 | cb->nlh->nlmsg_seq, NLM_F_MULTI, cb->nlh); |
597 | } | 601 | } |
598 | 602 | ||
603 | /* Get the IPv4, IPv6, or IPv4-mapped-IPv6 local and remote addresses | ||
604 | * from a request_sock. For IPv4-mapped-IPv6 we must map IPv4 to IPv6. | ||
605 | */ | ||
606 | static inline void inet_diag_req_addrs(const struct sock *sk, | ||
607 | const struct request_sock *req, | ||
608 | struct inet_diag_entry *entry) | ||
609 | { | ||
610 | struct inet_request_sock *ireq = inet_rsk(req); | ||
611 | |||
612 | #if IS_ENABLED(CONFIG_IPV6) | ||
613 | if (sk->sk_family == AF_INET6) { | ||
614 | if (req->rsk_ops->family == AF_INET6) { | ||
615 | entry->saddr = inet6_rsk(req)->loc_addr.s6_addr32; | ||
616 | entry->daddr = inet6_rsk(req)->rmt_addr.s6_addr32; | ||
617 | } else if (req->rsk_ops->family == AF_INET) { | ||
618 | ipv6_addr_set_v4mapped(ireq->loc_addr, | ||
619 | &entry->saddr_storage); | ||
620 | ipv6_addr_set_v4mapped(ireq->rmt_addr, | ||
621 | &entry->daddr_storage); | ||
622 | entry->saddr = entry->saddr_storage.s6_addr32; | ||
623 | entry->daddr = entry->daddr_storage.s6_addr32; | ||
624 | } | ||
625 | } else | ||
626 | #endif | ||
627 | { | ||
628 | entry->saddr = &ireq->loc_addr; | ||
629 | entry->daddr = &ireq->rmt_addr; | ||
630 | } | ||
631 | } | ||
632 | |||
599 | static int inet_diag_fill_req(struct sk_buff *skb, struct sock *sk, | 633 | static int inet_diag_fill_req(struct sk_buff *skb, struct sock *sk, |
600 | struct request_sock *req, | 634 | struct request_sock *req, |
601 | struct user_namespace *user_ns, | 635 | struct user_namespace *user_ns, |
@@ -637,8 +671,10 @@ static int inet_diag_fill_req(struct sk_buff *skb, struct sock *sk, | |||
637 | r->idiag_inode = 0; | 671 | r->idiag_inode = 0; |
638 | #if IS_ENABLED(CONFIG_IPV6) | 672 | #if IS_ENABLED(CONFIG_IPV6) |
639 | if (r->idiag_family == AF_INET6) { | 673 | if (r->idiag_family == AF_INET6) { |
640 | *(struct in6_addr *)r->id.idiag_src = inet6_rsk(req)->loc_addr; | 674 | struct inet_diag_entry entry; |
641 | *(struct in6_addr *)r->id.idiag_dst = inet6_rsk(req)->rmt_addr; | 675 | inet_diag_req_addrs(sk, req, &entry); |
676 | memcpy(r->id.idiag_src, entry.saddr, sizeof(struct in6_addr)); | ||
677 | memcpy(r->id.idiag_dst, entry.daddr, sizeof(struct in6_addr)); | ||
642 | } | 678 | } |
643 | #endif | 679 | #endif |
644 | 680 | ||
@@ -691,18 +727,7 @@ static int inet_diag_dump_reqs(struct sk_buff *skb, struct sock *sk, | |||
691 | continue; | 727 | continue; |
692 | 728 | ||
693 | if (bc) { | 729 | if (bc) { |
694 | entry.saddr = | 730 | inet_diag_req_addrs(sk, req, &entry); |
695 | #if IS_ENABLED(CONFIG_IPV6) | ||
696 | (entry.family == AF_INET6) ? | ||
697 | inet6_rsk(req)->loc_addr.s6_addr32 : | ||
698 | #endif | ||
699 | &ireq->loc_addr; | ||
700 | entry.daddr = | ||
701 | #if IS_ENABLED(CONFIG_IPV6) | ||
702 | (entry.family == AF_INET6) ? | ||
703 | inet6_rsk(req)->rmt_addr.s6_addr32 : | ||
704 | #endif | ||
705 | &ireq->rmt_addr; | ||
706 | entry.dport = ntohs(ireq->rmt_port); | 731 | entry.dport = ntohs(ireq->rmt_port); |
707 | 732 | ||
708 | if (!inet_diag_bc_run(bc, &entry)) | 733 | if (!inet_diag_bc_run(bc, &entry)) |