Denial of service in listen system call.

Corresponding upstream changeset from
https://www.freebsd.org/security/advisories/FreeBSD-EN-18:11.listen.asc.

Refer: #329.
dev
fengbojiang(姜凤波) 2019-11-22 22:07:47 +08:00
parent 2d72fc9054
commit 6757379bb8
3 changed files with 63 additions and 11 deletions

View File

@ -358,6 +358,7 @@ tcp6_usr_bind(struct socket *so, struct sockaddr *nam, struct thread *td)
struct inpcb *inp; struct inpcb *inp;
struct tcpcb *tp = NULL; struct tcpcb *tp = NULL;
struct sockaddr_in6 *sin6p; struct sockaddr_in6 *sin6p;
u_char vflagsav;
sin6p = (struct sockaddr_in6 *)nam; sin6p = (struct sockaddr_in6 *)nam;
if (nam->sa_len != sizeof (*sin6p)) if (nam->sa_len != sizeof (*sin6p))
@ -374,6 +375,7 @@ tcp6_usr_bind(struct socket *so, struct sockaddr *nam, struct thread *td)
inp = sotoinpcb(so); inp = sotoinpcb(so);
KASSERT(inp != NULL, ("tcp6_usr_bind: inp == NULL")); KASSERT(inp != NULL, ("tcp6_usr_bind: inp == NULL"));
INP_WLOCK(inp); INP_WLOCK(inp);
vflagsav = inp->inp_vflag;
if (inp->inp_flags & (INP_TIMEWAIT | INP_DROPPED)) { if (inp->inp_flags & (INP_TIMEWAIT | INP_DROPPED)) {
error = EINVAL; error = EINVAL;
goto out; goto out;
@ -403,6 +405,8 @@ tcp6_usr_bind(struct socket *so, struct sockaddr *nam, struct thread *td)
error = in6_pcbbind(inp, nam, td->td_ucred); error = in6_pcbbind(inp, nam, td->td_ucred);
INP_HASH_WUNLOCK(&V_tcbinfo); INP_HASH_WUNLOCK(&V_tcbinfo);
out: out:
if (error != 0)
inp->inp_vflag = vflagsav;
TCPDEBUG2(PRU_BIND); TCPDEBUG2(PRU_BIND);
TCP_PROBE2(debug__user, tp, PRU_BIND); TCP_PROBE2(debug__user, tp, PRU_BIND);
INP_WUNLOCK(inp); INP_WUNLOCK(inp);
@ -466,6 +470,7 @@ tcp6_usr_listen(struct socket *so, int backlog, struct thread *td)
int error = 0; int error = 0;
struct inpcb *inp; struct inpcb *inp;
struct tcpcb *tp = NULL; struct tcpcb *tp = NULL;
u_char vflagsav;
TCPDEBUG0; TCPDEBUG0;
inp = sotoinpcb(so); inp = sotoinpcb(so);
@ -475,6 +480,7 @@ tcp6_usr_listen(struct socket *so, int backlog, struct thread *td)
error = EINVAL; error = EINVAL;
goto out; goto out;
} }
vflagsav = inp->inp_vflag;
tp = intotcpcb(inp); tp = intotcpcb(inp);
TCPDEBUG1(); TCPDEBUG1();
SOCK_LOCK(so); SOCK_LOCK(so);
@ -501,6 +507,9 @@ tcp6_usr_listen(struct socket *so, int backlog, struct thread *td)
if (tp->t_flags & TF_FASTOPEN) if (tp->t_flags & TF_FASTOPEN)
tp->t_tfo_pending = tcp_fastopen_alloc_counter(); tp->t_tfo_pending = tcp_fastopen_alloc_counter();
#endif #endif
if (error != 0)
inp->inp_vflag = vflagsav;
out: out:
TCPDEBUG2(PRU_LISTEN); TCPDEBUG2(PRU_LISTEN);
TCP_PROBE2(debug__user, tp, PRU_LISTEN); TCP_PROBE2(debug__user, tp, PRU_LISTEN);
@ -577,6 +586,8 @@ tcp6_usr_connect(struct socket *so, struct sockaddr *nam, struct thread *td)
struct inpcb *inp; struct inpcb *inp;
struct tcpcb *tp = NULL; struct tcpcb *tp = NULL;
struct sockaddr_in6 *sin6p; struct sockaddr_in6 *sin6p;
u_int8_t incflagsav;
u_char vflagsav;
TCPDEBUG0; TCPDEBUG0;
@ -593,6 +604,8 @@ tcp6_usr_connect(struct socket *so, struct sockaddr *nam, struct thread *td)
inp = sotoinpcb(so); inp = sotoinpcb(so);
KASSERT(inp != NULL, ("tcp6_usr_connect: inp == NULL")); KASSERT(inp != NULL, ("tcp6_usr_connect: inp == NULL"));
INP_WLOCK(inp); INP_WLOCK(inp);
vflagsav = inp->inp_vflag;
incflagsav = inp->inp_inc.inc_flags;
if (inp->inp_flags & INP_TIMEWAIT) { if (inp->inp_flags & INP_TIMEWAIT) {
error = EADDRINUSE; error = EADDRINUSE;
goto out; goto out;
@ -618,11 +631,11 @@ tcp6_usr_connect(struct socket *so, struct sockaddr *nam, struct thread *td)
} }
in6_sin6_2_sin(&sin, sin6p); in6_sin6_2_sin(&sin, sin6p);
inp->inp_vflag |= INP_IPV4;
inp->inp_vflag &= ~INP_IPV6;
if ((error = prison_remote_ip4(td->td_ucred, if ((error = prison_remote_ip4(td->td_ucred,
&sin.sin_addr)) != 0) &sin.sin_addr)) != 0)
goto out; goto out;
inp->inp_vflag |= INP_IPV4;
inp->inp_vflag &= ~INP_IPV6;
if ((error = tcp_connect(tp, (struct sockaddr *)&sin, td)) != 0) if ((error = tcp_connect(tp, (struct sockaddr *)&sin, td)) != 0)
goto out; goto out;
#ifdef TCP_OFFLOAD #ifdef TCP_OFFLOAD
@ -635,11 +648,11 @@ tcp6_usr_connect(struct socket *so, struct sockaddr *nam, struct thread *td)
goto out; goto out;
} }
#endif #endif
if ((error = prison_remote_ip6(td->td_ucred, &sin6p->sin6_addr)) != 0)
goto out;
inp->inp_vflag &= ~INP_IPV4; inp->inp_vflag &= ~INP_IPV4;
inp->inp_vflag |= INP_IPV6; inp->inp_vflag |= INP_IPV6;
inp->inp_inc.inc_flags |= INC_ISIPV6; 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) if ((error = tcp6_connect(tp, nam, td)) != 0)
goto out; goto out;
#ifdef TCP_OFFLOAD #ifdef TCP_OFFLOAD
@ -652,6 +665,15 @@ tcp6_usr_connect(struct socket *so, struct sockaddr *nam, struct thread *td)
error = tp->t_fb->tfb_tcp_output(tp); error = tp->t_fb->tfb_tcp_output(tp);
out: 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); TCPDEBUG2(PRU_CONNECT);
TCP_PROBE2(debug__user, tp, PRU_CONNECT); TCP_PROBE2(debug__user, tp, PRU_CONNECT);
INP_WUNLOCK(inp); INP_WUNLOCK(inp);

View File

@ -573,6 +573,7 @@ sctp6_bind(struct socket *so, struct sockaddr *addr, struct thread *p)
struct sctp_inpcb *inp; struct sctp_inpcb *inp;
struct in6pcb *inp6; struct in6pcb *inp6;
int error; int error;
u_char vflagsav;
inp = (struct sctp_inpcb *)so->so_pcb; inp = (struct sctp_inpcb *)so->so_pcb;
if (inp == NULL) { if (inp == NULL) {
@ -603,6 +604,7 @@ sctp6_bind(struct socket *so, struct sockaddr *addr, struct thread *p)
} }
} }
inp6 = (struct in6pcb *)inp; inp6 = (struct in6pcb *)inp;
vflagsav = inp6->inp_vflag;
inp6->inp_vflag &= ~INP_IPV4; inp6->inp_vflag &= ~INP_IPV4;
inp6->inp_vflag |= INP_IPV6; inp6->inp_vflag |= INP_IPV6;
if ((addr != NULL) && (SCTP_IPV6_V6ONLY(inp6) == 0)) { if ((addr != NULL) && (SCTP_IPV6_V6ONLY(inp6) == 0)) {
@ -632,7 +634,7 @@ sctp6_bind(struct socket *so, struct sockaddr *addr, struct thread *p)
inp6->inp_vflag |= INP_IPV4; inp6->inp_vflag |= INP_IPV4;
inp6->inp_vflag &= ~INP_IPV6; inp6->inp_vflag &= ~INP_IPV6;
error = sctp_inpcb_bind(so, (struct sockaddr *)&sin, NULL, p); error = sctp_inpcb_bind(so, (struct sockaddr *)&sin, NULL, p);
return (error); goto out;
} }
#endif #endif
break; break;
@ -649,7 +651,8 @@ sctp6_bind(struct socket *so, struct sockaddr *addr, struct thread *p)
if (addr->sa_family == AF_INET) { if (addr->sa_family == AF_INET) {
/* can't bind v4 addr to v6 only socket! */ /* can't bind v4 addr to v6 only socket! */
SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP6_USRREQ, EINVAL); SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP6_USRREQ, EINVAL);
return (EINVAL); error = EINVAL;
goto out;
} }
#endif #endif
sin6_p = (struct sockaddr_in6 *)addr; sin6_p = (struct sockaddr_in6 *)addr;
@ -658,10 +661,14 @@ sctp6_bind(struct socket *so, struct sockaddr *addr, struct thread *p)
/* can't bind v4-mapped addrs either! */ /* can't bind v4-mapped addrs either! */
/* NOTE: we don't support SIIT */ /* NOTE: we don't support SIIT */
SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP6_USRREQ, EINVAL); 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); error = sctp_inpcb_bind(so, addr, NULL, p);
out:
if (error != 0)
inp6->inp_vflag = vflagsav;
return (error); return (error);
} }

View File

@ -1007,6 +1007,7 @@ udp6_bind(struct socket *so, struct sockaddr *nam, struct thread *td)
struct inpcb *inp; struct inpcb *inp;
struct inpcbinfo *pcbinfo; struct inpcbinfo *pcbinfo;
int error; int error;
u_char vflagsav;
pcbinfo = udp_get_inpcbinfo(so->so_proto->pr_protocol); pcbinfo = udp_get_inpcbinfo(so->so_proto->pr_protocol);
inp = sotoinpcb(so); inp = sotoinpcb(so);
@ -1014,6 +1015,7 @@ udp6_bind(struct socket *so, struct sockaddr *nam, struct thread *td)
INP_WLOCK(inp); INP_WLOCK(inp);
INP_HASH_WLOCK(pcbinfo); INP_HASH_WLOCK(pcbinfo);
vflagsav = inp->inp_vflag;
inp->inp_vflag &= ~INP_IPV4; inp->inp_vflag &= ~INP_IPV4;
inp->inp_vflag |= INP_IPV6; inp->inp_vflag |= INP_IPV6;
if ((inp->inp_flags & IN6P_IPV6_V6ONLY) == 0) { if ((inp->inp_flags & IN6P_IPV6_V6ONLY) == 0) {
@ -1041,6 +1043,8 @@ udp6_bind(struct socket *so, struct sockaddr *nam, struct thread *td)
#ifdef INET #ifdef INET
out: out:
#endif #endif
if (error != 0)
inp->inp_vflag = vflagsav;
INP_HASH_WUNLOCK(pcbinfo); INP_HASH_WUNLOCK(pcbinfo);
INP_WUNLOCK(inp); INP_WUNLOCK(inp);
return (error); return (error);
@ -1087,6 +1091,7 @@ udp6_connect(struct socket *so, struct sockaddr *nam, struct thread *td)
struct inpcbinfo *pcbinfo; struct inpcbinfo *pcbinfo;
struct sockaddr_in6 *sin6; struct sockaddr_in6 *sin6;
int error; int error;
u_char vflagsav;
pcbinfo = udp_get_inpcbinfo(so->so_proto->pr_protocol); pcbinfo = udp_get_inpcbinfo(so->so_proto->pr_protocol);
inp = sotoinpcb(so); inp = sotoinpcb(so);
@ -1110,17 +1115,26 @@ udp6_connect(struct socket *so, struct sockaddr *nam, struct thread *td)
goto out; goto out;
} }
in6_sin6_2_sin(&sin, sin6); 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); error = prison_remote_ip4(td->td_ucred, &sin.sin_addr);
if (error != 0) if (error != 0)
goto out; goto out;
vflagsav = inp->inp_vflag;
inp->inp_vflag |= INP_IPV4;
inp->inp_vflag &= ~INP_IPV6;
INP_HASH_WLOCK(pcbinfo); INP_HASH_WLOCK(pcbinfo);
error = in_pcbconnect(inp, (struct sockaddr *)&sin, error = in_pcbconnect(inp, (struct sockaddr *)&sin,
td->td_ucred); td->td_ucred);
INP_HASH_WUNLOCK(pcbinfo); 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) if (error == 0)
soisconnected(so); soisconnected(so);
else if (inp->inp_laddr.s_addr == INADDR_ANY &&
inp->inp_lport == 0)
inp->inp_vflag = vflagsav;
goto out; goto out;
} }
#endif #endif
@ -1128,16 +1142,25 @@ udp6_connect(struct socket *so, struct sockaddr *nam, struct thread *td)
error = EISCONN; error = EISCONN;
goto out; goto out;
} }
inp->inp_vflag &= ~INP_IPV4;
inp->inp_vflag |= INP_IPV6;
error = prison_remote_ip6(td->td_ucred, &sin6->sin6_addr); error = prison_remote_ip6(td->td_ucred, &sin6->sin6_addr);
if (error != 0) if (error != 0)
goto out; goto out;
vflagsav = inp->inp_vflag;
inp->inp_vflag &= ~INP_IPV4;
inp->inp_vflag |= INP_IPV6;
INP_HASH_WLOCK(pcbinfo); INP_HASH_WLOCK(pcbinfo);
error = in6_pcbconnect(inp, nam, td->td_ucred); error = in6_pcbconnect(inp, nam, td->td_ucred);
INP_HASH_WUNLOCK(pcbinfo); 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) if (error == 0)
soisconnected(so); soisconnected(so);
else if (IN6_IS_ADDR_UNSPECIFIED(&inp->in6p_laddr) &&
inp->inp_lport == 0)
inp->inp_vflag = vflagsav;
out: out:
INP_WUNLOCK(inp); INP_WUNLOCK(inp);
return (error); return (error);