diff options
author | Mark Johnston <markj@FreeBSD.org> | 2021-05-03 16:51:04 +0000 |
---|---|---|
committer | Mark Johnston <markj@FreeBSD.org> | 2021-05-03 17:35:19 +0000 |
commit | f161d294b92732df6254a89f393ab24999e122bf (patch) | |
tree | 3b0c4e481060d66ae8cdad006c1569e3ca4a5520 /sys/netinet | |
parent | a3c7da3d08eec921f16a32c4c716b896385264e9 (diff) | |
download | src-f161d294b92732df6254a89f393ab24999e122bf.tar.gz src-f161d294b92732df6254a89f393ab24999e122bf.zip |
Add missing sockaddr length and family validation to various protocols
Several protocol methods take a sockaddr as input. In some cases the
sockaddr lengths were not being validated, or were validated after some
out-of-bounds accesses could occur. Add requisite checking to various
protocol entry points, and convert some existing checks to assertions
where appropriate.
Reported by: syzkaller+KASAN
Reviewed by: tuexen, melifaro
MFC after: 2 weeks
Sponsored by: The FreeBSD Foundation
Differential Revision: https://reviews.freebsd.org/D29519
Diffstat (limited to 'sys/netinet')
-rw-r--r-- | sys/netinet/in_pcb.c | 28 | ||||
-rw-r--r-- | sys/netinet/ip_divert.c | 21 | ||||
-rw-r--r-- | sys/netinet/raw_ip.c | 14 | ||||
-rw-r--r-- | sys/netinet/sctp_usrreq.c | 24 | ||||
-rw-r--r-- | sys/netinet/tcp_usrreq.c | 31 | ||||
-rw-r--r-- | sys/netinet/udp_usrreq.c | 28 |
6 files changed, 101 insertions, 45 deletions
diff --git a/sys/netinet/in_pcb.c b/sys/netinet/in_pcb.c index 43eb31c4e31d..4c21bdbf1347 100644 --- a/sys/netinet/in_pcb.c +++ b/sys/netinet/in_pcb.c @@ -654,6 +654,10 @@ in_pcbbind(struct inpcb *inp, struct sockaddr *nam, struct ucred *cred) { int anonport, error; + KASSERT(nam == NULL || nam->sa_family == AF_INET, + ("%s: invalid address family for %p", __func__, nam)); + KASSERT(nam == NULL || nam->sa_len == sizeof(struct sockaddr_in), + ("%s: invalid address length for %p", __func__, nam)); INP_WLOCK_ASSERT(inp); INP_HASH_WLOCK_ASSERT(inp->inp_pcbinfo); @@ -940,16 +944,11 @@ in_pcbbind_setup(struct inpcb *inp, struct sockaddr *nam, in_addr_t *laddrp, return (error); } else { sin = (struct sockaddr_in *)nam; - if (nam->sa_len != sizeof (*sin)) - return (EINVAL); -#ifdef notdef - /* - * We should check the family, but old programs - * incorrectly fail to initialize it. - */ - if (sin->sin_family != AF_INET) - return (EAFNOSUPPORT); -#endif + KASSERT(sin->sin_family == AF_INET, + ("%s: invalid family for address %p", __func__, sin)); + KASSERT(sin->sin_len == sizeof(*sin), + ("%s: invalid length for address %p", __func__, sin)); + error = prison_local_ip4(cred, &sin->sin_addr); if (error) return (error); @@ -1372,6 +1371,11 @@ in_pcbconnect_setup(struct inpcb *inp, struct sockaddr *nam, u_short lport, fport; int error; + KASSERT(sin->sin_family == AF_INET, + ("%s: invalid address family for %p", __func__, sin)); + KASSERT(sin->sin_len == sizeof(*sin), + ("%s: invalid address length for %p", __func__, sin)); + /* * Because a global state change doesn't actually occur here, a read * lock is sufficient. @@ -1382,10 +1386,6 @@ in_pcbconnect_setup(struct inpcb *inp, struct sockaddr *nam, if (oinpp != NULL) *oinpp = NULL; - if (nam->sa_len != sizeof (*sin)) - return (EINVAL); - if (sin->sin_family != AF_INET) - return (EAFNOSUPPORT); if (sin->sin_port == 0) return (EADDRNOTAVAIL); laddr.s_addr = *laddrp; diff --git a/sys/netinet/ip_divert.c b/sys/netinet/ip_divert.c index c3f9c43b8f70..65974051ad1f 100644 --- a/sys/netinet/ip_divert.c +++ b/sys/netinet/ip_divert.c @@ -327,6 +327,22 @@ div_output(struct socket *so, struct mbuf *m, struct sockaddr_in *sin, struct ipfw_rule_ref *dt; int error, family; + if (control) { + m_freem(control); /* XXX */ + control = NULL; + } + + if (sin != NULL) { + if (sin->sin_family != AF_INET) { + m_freem(m); + return (EAFNOSUPPORT); + } + if (sin->sin_len != sizeof(*sin)) { + m_freem(m); + return (EINVAL); + } + } + /* * An mbuf may hasn't come from userland, but we pretend * that it has. @@ -335,9 +351,6 @@ div_output(struct socket *so, struct mbuf *m, struct sockaddr_in *sin, m->m_nextpkt = NULL; M_SETFIB(m, so->so_fibnum); - if (control) - m_freem(control); /* XXX */ - mtag = m_tag_locate(m, MTAG_IPFW_RULE, 0, NULL); if (mtag == NULL) { /* this should be normal */ @@ -628,6 +641,8 @@ div_bind(struct socket *so, struct sockaddr *nam, struct thread *td) */ if (nam->sa_family != AF_INET) return EAFNOSUPPORT; + if (nam->sa_len != sizeof(struct sockaddr_in)) + return EINVAL; ((struct sockaddr_in *)nam)->sin_addr.s_addr = INADDR_ANY; INP_INFO_WLOCK(&V_divcbinfo); INP_WLOCK(inp); diff --git a/sys/netinet/raw_ip.c b/sys/netinet/raw_ip.c index c9def015343c..89ab6a6bbdad 100644 --- a/sys/netinet/raw_ip.c +++ b/sys/netinet/raw_ip.c @@ -996,6 +996,8 @@ rip_bind(struct socket *so, struct sockaddr *nam, struct thread *td) struct inpcb *inp; int error; + if (nam->sa_family != AF_INET) + return (EAFNOSUPPORT); if (nam->sa_len != sizeof(*addr)) return (EINVAL); @@ -1070,6 +1072,7 @@ rip_send(struct socket *so, int flags, struct mbuf *m, struct sockaddr *nam, { struct inpcb *inp; u_long dst; + int error; inp = sotoinpcb(so); KASSERT(inp != NULL, ("rip_send: inp == NULL")); @@ -1084,9 +1087,16 @@ rip_send(struct socket *so, int flags, struct mbuf *m, struct sockaddr *nam, } dst = inp->inp_faddr.s_addr; /* Unlocked read. */ } else { - if (nam == NULL) { + error = 0; + if (nam == NULL) + error = ENOTCONN; + else if (nam->sa_family != AF_INET) + error = EAFNOSUPPORT; + else if (nam->sa_len != sizeof(struct sockaddr_in)) + error = EINVAL; + if (error != 0) { m_freem(m); - return (ENOTCONN); + return (error); } dst = ((struct sockaddr_in *)nam)->sin_addr.s_addr; } diff --git a/sys/netinet/sctp_usrreq.c b/sys/netinet/sctp_usrreq.c index 49cc6f22cc5a..238c20c0e368 100644 --- a/sys/netinet/sctp_usrreq.c +++ b/sys/netinet/sctp_usrreq.c @@ -598,29 +598,27 @@ sctp_sendm(struct socket *so, int flags, struct mbuf *m, struct sockaddr *addr, ((inp->sctp_flags & SCTP_PCB_FLAGS_CONNECTED) || (inp->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE))) { goto connected_type; - } else if (addr == NULL) { + } + + error = 0; + if (addr == NULL) { SCTP_LTRACE_ERR_RET_PKT(m, inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EDESTADDRREQ); error = EDESTADDRREQ; - sctp_m_freem(m); - if (control) { - sctp_m_freem(control); - control = NULL; - } - return (error); + } else if (addr->sa_family != AF_INET) { + SCTP_LTRACE_ERR_RET_PKT(m, inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EAFNOSUPPORT); + error = EAFNOSUPPORT; + } else if (addr->sa_len != sizeof(struct sockaddr_in)) { + SCTP_LTRACE_ERR_RET_PKT(m, inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL); + error = EINVAL; } -#ifdef INET6 - if (addr->sa_family != AF_INET) { - /* must be a v4 address! */ - SCTP_LTRACE_ERR_RET_PKT(m, inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EDESTADDRREQ); + if (error != 0) { sctp_m_freem(m); if (control) { sctp_m_freem(control); control = NULL; } - error = EDESTADDRREQ; return (error); } -#endif /* INET6 */ connected_type: /* now what about control */ if (control) { diff --git a/sys/netinet/tcp_usrreq.c b/sys/netinet/tcp_usrreq.c index c4cfb5ea199f..cbc36860bf32 100644 --- a/sys/netinet/tcp_usrreq.c +++ b/sys/netinet/tcp_usrreq.c @@ -321,14 +321,16 @@ tcp_usr_bind(struct socket *so, struct sockaddr *nam, struct thread *td) struct sockaddr_in *sinp; sinp = (struct sockaddr_in *)nam; - if (nam->sa_len != sizeof (*sinp)) + if (nam->sa_family != AF_INET) + return (EAFNOSUPPORT); + if (nam->sa_len != sizeof(*sinp)) return (EINVAL); + /* * Must check for multicast addresses and disallow binding * to them. */ - if (sinp->sin_family == AF_INET && - IN_MULTICAST(ntohl(sinp->sin_addr.s_addr))) + if (IN_MULTICAST(ntohl(sinp->sin_addr.s_addr))) return (EAFNOSUPPORT); TCPDEBUG0; @@ -364,14 +366,16 @@ tcp6_usr_bind(struct socket *so, struct sockaddr *nam, struct thread *td) u_char vflagsav; sin6 = (struct sockaddr_in6 *)nam; - if (nam->sa_len != sizeof (*sin6)) + if (nam->sa_family != AF_INET6) + return (EAFNOSUPPORT); + if (nam->sa_len != sizeof(*sin6)) return (EINVAL); + /* * Must check for multicast addresses and disallow binding * to them. */ - if (sin6->sin6_family == AF_INET6 && - IN6_IS_ADDR_MULTICAST(&sin6->sin6_addr)) + if (IN6_IS_ADDR_MULTICAST(&sin6->sin6_addr)) return (EAFNOSUPPORT); TCPDEBUG0; @@ -542,16 +546,17 @@ tcp_usr_connect(struct socket *so, struct sockaddr *nam, struct thread *td) struct sockaddr_in *sinp; sinp = (struct sockaddr_in *)nam; + if (nam->sa_family != AF_INET) + return (EAFNOSUPPORT); if (nam->sa_len != sizeof (*sinp)) return (EINVAL); + /* * Must disallow TCP ``connections'' to multicast addresses. */ - if (sinp->sin_family == AF_INET - && IN_MULTICAST(ntohl(sinp->sin_addr.s_addr))) + if (IN_MULTICAST(ntohl(sinp->sin_addr.s_addr))) return (EAFNOSUPPORT); - if ((sinp->sin_family == AF_INET) && - (ntohl(sinp->sin_addr.s_addr) == INADDR_BROADCAST)) + if (ntohl(sinp->sin_addr.s_addr) == INADDR_BROADCAST) return (EACCES); if ((error = prison_remote_ip4(td->td_ucred, &sinp->sin_addr)) != 0) return (error); @@ -606,13 +611,15 @@ tcp6_usr_connect(struct socket *so, struct sockaddr *nam, struct thread *td) TCPDEBUG0; sin6 = (struct sockaddr_in6 *)nam; + if (nam->sa_family != AF_INET6) + return (EAFNOSUPPORT); if (nam->sa_len != sizeof (*sin6)) return (EINVAL); + /* * Must disallow TCP ``connections'' to multicast addresses. */ - if (sin6->sin6_family == AF_INET6 - && IN6_IS_ADDR_MULTICAST(&sin6->sin6_addr)) + if (IN6_IS_ADDR_MULTICAST(&sin6->sin6_addr)) return (EAFNOSUPPORT); inp = sotoinpcb(so); diff --git a/sys/netinet/udp_usrreq.c b/sys/netinet/udp_usrreq.c index c2ad9381850e..16ae0a89bb15 100644 --- a/sys/netinet/udp_usrreq.c +++ b/sys/netinet/udp_usrreq.c @@ -1626,6 +1626,12 @@ udp_bind(struct socket *so, struct sockaddr *nam, struct thread *td) pcbinfo = udp_get_inpcbinfo(so->so_proto->pr_protocol); inp = sotoinpcb(so); KASSERT(inp != NULL, ("udp_bind: inp == NULL")); + + if (nam->sa_family != AF_INET) + return (EAFNOSUPPORT); + if (nam->sa_len != sizeof(struct sockaddr_in)) + return (EINVAL); + INP_WLOCK(inp); INP_HASH_WLOCK(pcbinfo); error = in_pcbbind(inp, nam, td->td_ucred); @@ -1666,12 +1672,18 @@ udp_connect(struct socket *so, struct sockaddr *nam, struct thread *td) pcbinfo = udp_get_inpcbinfo(so->so_proto->pr_protocol); inp = sotoinpcb(so); KASSERT(inp != NULL, ("udp_connect: inp == NULL")); + + sin = (struct sockaddr_in *)nam; + if (sin->sin_family != AF_INET) + return (EAFNOSUPPORT); + if (sin->sin_len != sizeof(*sin)) + return (EINVAL); + INP_WLOCK(inp); if (inp->inp_faddr.s_addr != INADDR_ANY) { INP_WUNLOCK(inp); return (EISCONN); } - sin = (struct sockaddr_in *)nam; error = prison_remote_ip4(td->td_ucred, &sin->sin_addr); if (error != 0) { INP_WUNLOCK(inp); @@ -1741,9 +1753,23 @@ udp_send(struct socket *so, int flags, struct mbuf *m, struct sockaddr *addr, struct mbuf *control, struct thread *td) { struct inpcb *inp; + int error; inp = sotoinpcb(so); KASSERT(inp != NULL, ("udp_send: inp == NULL")); + + if (addr != NULL) { + error = 0; + if (addr->sa_family != AF_INET) + error = EAFNOSUPPORT; + else if (addr->sa_len != sizeof(struct sockaddr_in)) + error = EINVAL; + if (__predict_false(error != 0)) { + m_freem(control); + m_freem(m); + return (error); + } + } return (udp_output(inp, m, addr, control, td, flags)); } #endif /* INET */ |