From 16981b16f338577fa04c1eac9b41f5b53a5a5c63 Mon Sep 17 00:00:00 2001 From: Michael Tuexen Date: Fri, 25 Oct 2019 18:46:53 +0000 Subject: [PATCH] MFS r354090: Ensure that the flags indicating IPv4/IPv6 are not changed by failing bind() calls. This would lead to inconsistent state resulting in a panic. A fix for stable/11 was committed in https://svnweb.freebsd.org/base?view=revision&revision=338986 Reported by: syzbot+2609a378d89264ff5a42@syzkaller.appspotmail.com Obtained from: jtl@ Sponsored by: Netflix, Inc. Approved by: re (gjb@) --- sys/netinet/tcp_usrreq.c | 62 ++++++++++++++++++++++++++++++++++--- sys/netinet6/sctp6_usrreq.c | 13 ++++++-- sys/netinet6/udp6_usrreq.c | 31 ++++++++++++++++--- 3 files changed, 95 insertions(+), 11 deletions(-) diff --git a/sys/netinet/tcp_usrreq.c b/sys/netinet/tcp_usrreq.c index dee08a2180c..b1145e697e8 100644 --- a/sys/netinet/tcp_usrreq.c +++ b/sys/netinet/tcp_usrreq.c @@ -345,6 +345,7 @@ tcp6_usr_bind(struct socket *so, struct sockaddr *nam, struct thread *td) struct inpcb *inp; struct tcpcb *tp = NULL; struct sockaddr_in6 *sin6p; + u_char vflagsav; sin6p = (struct sockaddr_in6 *)nam; if (nam->sa_len != sizeof (*sin6p)) @@ -361,6 +362,7 @@ tcp6_usr_bind(struct socket *so, struct sockaddr *nam, struct thread *td) inp = sotoinpcb(so); KASSERT(inp != NULL, ("tcp6_usr_bind: inp == NULL")); INP_WLOCK(inp); + vflagsav = inp->inp_vflag; if (inp->inp_flags & (INP_TIMEWAIT | INP_DROPPED)) { error = EINVAL; goto out; @@ -395,6 +397,8 @@ tcp6_usr_bind(struct socket *so, struct sockaddr *nam, struct thread *td) error = in6_pcbbind(inp, nam, td->td_ucred); INP_HASH_WUNLOCK(&V_tcbinfo); out: + if (error != 0) + inp->inp_vflag = vflagsav; TCPDEBUG2(PRU_BIND); TCP_PROBE2(debug__user, tp, PRU_BIND); INP_WUNLOCK(inp); @@ -457,6 +461,7 @@ tcp6_usr_listen(struct socket *so, int backlog, struct thread *td) int error = 0; struct inpcb *inp; struct tcpcb *tp = NULL; + u_char vflagsav; TCPDEBUG0; inp = sotoinpcb(so); @@ -466,6 +471,7 @@ tcp6_usr_listen(struct socket *so, int backlog, struct thread *td) error = EINVAL; goto out; } + vflagsav = inp->inp_vflag; tp = intotcpcb(inp); TCPDEBUG1(); SOCK_LOCK(so); @@ -491,6 +497,9 @@ tcp6_usr_listen(struct socket *so, int backlog, struct thread *td) if (IS_FASTOPEN(tp->t_flags)) tp->t_tfo_pending = tcp_fastopen_alloc_counter(); + if (error != 0) + inp->inp_vflag = vflagsav; + out: TCPDEBUG2(PRU_LISTEN); TCP_PROBE2(debug__user, tp, PRU_LISTEN); @@ -567,6 +576,8 @@ tcp6_usr_connect(struct socket *so, struct sockaddr *nam, struct thread *td) struct inpcb *inp; struct tcpcb *tp = NULL; struct sockaddr_in6 *sin6p; + u_int8_t incflagsav; + u_char vflagsav; TCPDEBUG0; @@ -583,6 +594,8 @@ tcp6_usr_connect(struct socket *so, struct sockaddr *nam, struct thread *td) inp = sotoinpcb(so); KASSERT(inp != NULL, ("tcp6_usr_connect: inp == NULL")); INP_WLOCK(inp); + vflagsav = inp->inp_vflag; + incflagsav = inp->inp_inc.inc_flags; if (inp->inp_flags & INP_TIMEWAIT) { error = EADDRINUSE; goto out; @@ -616,11 +629,11 @@ tcp6_usr_connect(struct socket *so, struct sockaddr *nam, struct thread *td) error = EAFNOSUPPORT; goto out; } - inp->inp_vflag |= INP_IPV4; - inp->inp_vflag &= ~INP_IPV6; if ((error = prison_remote_ip4(td->td_ucred, &sin.sin_addr)) != 0) goto out; + inp->inp_vflag |= INP_IPV4; + inp->inp_vflag &= ~INP_IPV6; if ((error = tcp_connect(tp, (struct sockaddr *)&sin, td)) != 0) goto out; #ifdef TCP_OFFLOAD @@ -638,11 +651,11 @@ tcp6_usr_connect(struct socket *so, struct sockaddr *nam, struct thread *td) } } #endif + if ((error = prison_remote_ip6(td->td_ucred, &sin6p->sin6_addr)) != 0) + goto out; inp->inp_vflag &= ~INP_IPV4; inp->inp_vflag |= INP_IPV6; inp->inp_inc.inc_flags |= INC_ISIPV6; - if ((error = prison_remote_ip6(td->td_ucred, &sin6p->sin6_addr)) != 0) - goto out; if ((error = tcp6_connect(tp, nam, td)) != 0) goto out; #ifdef TCP_OFFLOAD @@ -655,6 +668,15 @@ tcp6_usr_connect(struct socket *so, struct sockaddr *nam, struct thread *td) error = tp->t_fb->tfb_tcp_output(tp); out: + /* + * If the implicit bind in the connect call fails, restore + * the flags we modified. + */ + if (error != 0 && inp->inp_lport == 0) { + inp->inp_vflag = vflagsav; + inp->inp_inc.inc_flags = incflagsav; + } + TCPDEBUG2(PRU_CONNECT); TCP_PROBE2(debug__user, tp, PRU_CONNECT); INP_WUNLOCK(inp); @@ -910,6 +932,9 @@ tcp_usr_send(struct socket *so, int flags, struct mbuf *m, #ifdef INET6 int isipv6; #endif + u_int8_t incflagsav; + u_char vflagsav; + bool restoreflags; TCPDEBUG0; /* @@ -921,6 +946,9 @@ tcp_usr_send(struct socket *so, int flags, struct mbuf *m, inp = sotoinpcb(so); KASSERT(inp != NULL, ("tcp_usr_send: inp == NULL")); INP_WLOCK(inp); + vflagsav = inp->inp_vflag; + incflagsav = inp->inp_inc.inc_flags; + restoreflags = false; if (inp->inp_flags & (INP_TIMEWAIT | INP_DROPPED)) { if (control) m_freem(control); @@ -1001,6 +1029,7 @@ tcp_usr_send(struct socket *so, int flags, struct mbuf *m, m_freem(m); goto out; } + restoreflags = true; inp->inp_vflag &= ~INP_IPV6; sinp = &sin; in6_sin6_2_sin(sinp, sin6p); @@ -1031,6 +1060,7 @@ tcp_usr_send(struct socket *so, int flags, struct mbuf *m, error = EAFNOSUPPORT; goto out; } + restoreflags = true; inp->inp_vflag &= ~INP_IPV4; inp->inp_inc.inc_flags |= INC_ISIPV6; if ((error = prison_remote_ip6(td->td_ucred, @@ -1081,6 +1111,14 @@ tcp_usr_send(struct socket *so, int flags, struct mbuf *m, error = tcp_connect(tp, (struct sockaddr *)sinp, td); #endif + /* + * The bind operation in tcp_connect succeeded. We + * no longer want to restore the flags if later + * operations fail. + */ + if (error == 0 || inp->inp_lport != 0) + restoreflags = false; + if (error) goto out; if (IS_FASTOPEN(tp->t_flags)) @@ -1151,6 +1189,14 @@ tcp_usr_send(struct socket *so, int flags, struct mbuf *m, error = tcp_connect(tp, (struct sockaddr *)sinp, td); #endif + /* + * The bind operation in tcp_connect succeeded. We + * no longer want to restore the flags if later + * operations fail. + */ + if (error == 0 || inp->inp_lport != 0) + restoreflags = false; + if (error) goto out; tp->snd_wnd = TTCP_CLIENT_SND_WND; @@ -1169,6 +1215,14 @@ tcp_usr_send(struct socket *so, int flags, struct mbuf *m, TCP_LOG_USERSEND, error, 0, NULL, false); out: + /* + * If the request was unsuccessful and we changed flags, + * restore the original flags. + */ + if (error != 0 && restoreflags) { + inp->inp_vflag = vflagsav; + inp->inp_inc.inc_flags = incflagsav; + } TCPDEBUG2((flags & PRUS_OOB) ? PRU_SENDOOB : ((flags & PRUS_EOF) ? PRU_SEND_EOF : PRU_SEND)); TCP_PROBE2(debug__user, tp, (flags & PRUS_OOB) ? PRU_SENDOOB : diff --git a/sys/netinet6/sctp6_usrreq.c b/sys/netinet6/sctp6_usrreq.c index 0b7742a01d1..ded7ecdbf55 100644 --- a/sys/netinet6/sctp6_usrreq.c +++ b/sys/netinet6/sctp6_usrreq.c @@ -563,6 +563,7 @@ sctp6_bind(struct socket *so, struct sockaddr *addr, struct thread *p) struct sctp_inpcb *inp; struct in6pcb *inp6; int error; + u_char vflagsav; inp = (struct sctp_inpcb *)so->so_pcb; if (inp == NULL) { @@ -594,6 +595,7 @@ sctp6_bind(struct socket *so, struct sockaddr *addr, struct thread *p) } } inp6 = (struct in6pcb *)inp; + vflagsav = inp6->inp_vflag; inp6->inp_vflag &= ~INP_IPV4; inp6->inp_vflag |= INP_IPV6; if ((addr != NULL) && (SCTP_IPV6_V6ONLY(inp6) == 0)) { @@ -623,7 +625,7 @@ sctp6_bind(struct socket *so, struct sockaddr *addr, struct thread *p) inp6->inp_vflag |= INP_IPV4; inp6->inp_vflag &= ~INP_IPV6; error = sctp_inpcb_bind(so, (struct sockaddr *)&sin, NULL, p); - return (error); + goto out; } #endif break; @@ -640,7 +642,8 @@ sctp6_bind(struct socket *so, struct sockaddr *addr, struct thread *p) if (addr->sa_family == AF_INET) { /* can't bind v4 addr to v6 only socket! */ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP6_USRREQ, EINVAL); - return (EINVAL); + error = EINVAL; + goto out; } #endif sin6_p = (struct sockaddr_in6 *)addr; @@ -649,10 +652,14 @@ sctp6_bind(struct socket *so, struct sockaddr *addr, struct thread *p) /* can't bind v4-mapped addrs either! */ /* NOTE: we don't support SIIT */ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP6_USRREQ, EINVAL); - return (EINVAL); + error = EINVAL; + goto out; } } error = sctp_inpcb_bind(so, addr, NULL, p); +out: + if (error != 0) + inp6->inp_vflag = vflagsav; return (error); } diff --git a/sys/netinet6/udp6_usrreq.c b/sys/netinet6/udp6_usrreq.c index 6a46dedc1df..737dd2d759f 100644 --- a/sys/netinet6/udp6_usrreq.c +++ b/sys/netinet6/udp6_usrreq.c @@ -1143,6 +1143,7 @@ udp6_bind(struct socket *so, struct sockaddr *nam, struct thread *td) struct inpcb *inp; struct inpcbinfo *pcbinfo; int error; + u_char vflagsav; pcbinfo = udp_get_inpcbinfo(so->so_proto->pr_protocol); inp = sotoinpcb(so); @@ -1150,6 +1151,7 @@ udp6_bind(struct socket *so, struct sockaddr *nam, struct thread *td) INP_WLOCK(inp); INP_HASH_WLOCK(pcbinfo); + vflagsav = inp->inp_vflag; inp->inp_vflag &= ~INP_IPV4; inp->inp_vflag |= INP_IPV6; if ((inp->inp_flags & IN6P_IPV6_V6ONLY) == 0) { @@ -1177,6 +1179,8 @@ udp6_bind(struct socket *so, struct sockaddr *nam, struct thread *td) #ifdef INET out: #endif + if (error != 0) + inp->inp_vflag = vflagsav; INP_HASH_WUNLOCK(pcbinfo); INP_WUNLOCK(inp); return (error); @@ -1223,6 +1227,7 @@ udp6_connect(struct socket *so, struct sockaddr *nam, struct thread *td) struct inpcbinfo *pcbinfo; struct sockaddr_in6 *sin6; int error; + u_char vflagsav; pcbinfo = udp_get_inpcbinfo(so->so_proto->pr_protocol); inp = sotoinpcb(so); @@ -1250,17 +1255,26 @@ udp6_connect(struct socket *so, struct sockaddr *nam, struct thread *td) goto out; } in6_sin6_2_sin(&sin, sin6); - inp->inp_vflag |= INP_IPV4; - inp->inp_vflag &= ~INP_IPV6; error = prison_remote_ip4(td->td_ucred, &sin.sin_addr); if (error != 0) goto out; + vflagsav = inp->inp_vflag; + inp->inp_vflag |= INP_IPV4; + inp->inp_vflag &= ~INP_IPV6; INP_HASH_WLOCK(pcbinfo); error = in_pcbconnect(inp, (struct sockaddr *)&sin, td->td_ucred); INP_HASH_WUNLOCK(pcbinfo); + /* + * If connect succeeds, mark socket as connected. If + * connect fails and socket is unbound, reset inp_vflag + * field. + */ if (error == 0) soisconnected(so); + else if (inp->inp_laddr.s_addr == INADDR_ANY && + inp->inp_lport == 0) + inp->inp_vflag = vflagsav; goto out; } else { if ((inp->inp_vflag & INP_IPV6) == 0) { @@ -1273,16 +1287,25 @@ udp6_connect(struct socket *so, struct sockaddr *nam, struct thread *td) error = EISCONN; goto out; } - inp->inp_vflag &= ~INP_IPV4; - inp->inp_vflag |= INP_IPV6; error = prison_remote_ip6(td->td_ucred, &sin6->sin6_addr); if (error != 0) goto out; + vflagsav = inp->inp_vflag; + inp->inp_vflag &= ~INP_IPV4; + inp->inp_vflag |= INP_IPV6; INP_HASH_WLOCK(pcbinfo); error = in6_pcbconnect(inp, nam, td->td_ucred); INP_HASH_WUNLOCK(pcbinfo); + /* + * If connect succeeds, mark socket as connected. If + * connect fails and socket is unbound, reset inp_vflag + * field. + */ if (error == 0) soisconnected(so); + else if (IN6_IS_ADDR_UNSPECIFIED(&inp->in6p_laddr) && + inp->inp_lport == 0) + inp->inp_vflag = vflagsav; out: INP_WUNLOCK(inp); return (error); -- 2.45.0