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)) |
