diff options
author | Claudio Jeker <claudio@cvs.openbsd.org> | 2019-01-06 11:05:10 +0000 |
---|---|---|
committer | Claudio Jeker <claudio@cvs.openbsd.org> | 2019-01-06 11:05:10 +0000 |
commit | d85710f6342b7a3ba89add222a8b7e2163f1b4b0 (patch) | |
tree | b59af2f580f25c271110f1123265b057db6c8479 /sys/netinet | |
parent | a7e276d2dd22a043f79e6c0a53b1d01549d585ac (diff) |
Rewrite ip_pcbopts() to fill a fresh mbuf with the ip options instead
of fiddling with the user supplied mbuf and then copy it at the end.
OK visa@
Diffstat (limited to 'sys/netinet')
-rw-r--r-- | sys/netinet/ip_output.c | 85 |
1 files changed, 49 insertions, 36 deletions
diff --git a/sys/netinet/ip_output.c b/sys/netinet/ip_output.c index ea9b1e98feb..e9c64fd2c75 100644 --- a/sys/netinet/ip_output.c +++ b/sys/netinet/ip_output.c @@ -1,4 +1,4 @@ -/* $OpenBSD: ip_output.c,v 1.350 2019/01/03 08:46:40 claudio Exp $ */ +/* $OpenBSD: ip_output.c,v 1.351 2019/01/06 11:05:09 claudio Exp $ */ /* $NetBSD: ip_output.c,v 1.28 1996/02/13 23:43:07 christos Exp $ */ /* @@ -1234,13 +1234,15 @@ ip_ctloutput(int op, struct socket *so, int level, int optname, int ip_pcbopts(struct mbuf **pcbopt, struct mbuf *m) { - int cnt, optlen; + struct mbuf *n; + struct ipoption *p; + int cnt, off, optlen; u_char *cp; u_char opt; /* turn off any old options */ m_free(*pcbopt); - *pcbopt = 0; + *pcbopt = NULL; if (m == NULL || m->m_len == 0) { /* * Only turning off any previous options. @@ -1248,38 +1250,36 @@ ip_pcbopts(struct mbuf **pcbopt, struct mbuf *m) return (0); } - if (m->m_len % sizeof(int32_t)) + if (m->m_len % sizeof(int32_t) || + m->m_len > MAX_IPOPTLEN + sizeof(struct in_addr)) return (EINVAL); - /* - * IP first-hop destination address will be stored before - * actual options; move other options back - * and clear it when none present. - */ - if (m_trailingspace(m) < sizeof(struct in_addr)) - return (EINVAL); + /* Don't sleep because NET_LOCK() is hold. */ + if ((n = m_get(M_NOWAIT, MT_SOOPTS)) == NULL) + return (ENOBUFS); + p = mtod(n, struct ipoption *); + p->ipopt_dst.s_addr = 0; + n->m_len = sizeof(struct in_addr); + + off = 0; cnt = m->m_len; - m->m_len += sizeof(struct in_addr); cp = mtod(m, u_char *) + sizeof(struct in_addr); - memmove((caddr_t)cp, mtod(m, caddr_t), (unsigned)cnt); - memset(mtod(m, caddr_t), 0, sizeof(struct in_addr)); - for (; cnt > 0; cnt -= optlen, cp += optlen) { + while (cnt > 0) { opt = cp[IPOPT_OPTVAL]; - if (opt == IPOPT_EOL) - break; - if (opt == IPOPT_NOP) + + if (opt == IPOPT_NOP || opt == IPOPT_EOL) optlen = 1; else { if (cnt < IPOPT_OLEN + sizeof(*cp)) - return (EINVAL); + goto bad; optlen = cp[IPOPT_OLEN]; if (optlen < IPOPT_OLEN + sizeof(*cp) || optlen > cnt) - return (EINVAL); + goto bad; } switch (opt) { - default: + memcpy(p->ipopt_list + off, cp, optlen); break; case IPOPT_LSRR: @@ -1293,33 +1293,46 @@ ip_pcbopts(struct mbuf **pcbopt, struct mbuf *m) * actual IP option, but is stored before the options. */ if (optlen < IPOPT_MINOFF - 1 + sizeof(struct in_addr)) - return (EINVAL); - m->m_len -= sizeof(struct in_addr); - cnt -= sizeof(struct in_addr); + goto bad; + + /* + * Optlen is smaller because first address is popped. + * Cnt and cp will be adjusted a bit later to reflect + * this. + */ optlen -= sizeof(struct in_addr); - cp[IPOPT_OLEN] = optlen; + p->ipopt_list[off + IPOPT_OPTVAL] = opt; + p->ipopt_list[off + IPOPT_OLEN] = optlen; + /* * Move first hop before start of options. */ - memcpy(mtod(m, caddr_t), &cp[IPOPT_OFFSET+1], + memcpy(&p->ipopt_dst, cp + IPOPT_OFFSET, sizeof(struct in_addr)); + cp += sizeof(struct in_addr); + cnt -= sizeof(struct in_addr); /* - * Then copy rest of options back - * to close up the deleted entry. + * Then copy rest of options */ - memmove((caddr_t)&cp[IPOPT_OFFSET+1], - (caddr_t)(&cp[IPOPT_OFFSET+1] + - sizeof(struct in_addr)), - (unsigned)cnt - (IPOPT_OFFSET+1)); + memcpy(p->ipopt_list + off + IPOPT_OFFSET, + cp + IPOPT_OFFSET, optlen - IPOPT_OFFSET); break; } + off += optlen; + cp += optlen; + cnt -= optlen; + + if (opt == IPOPT_EOL) + break; } - if (m->m_len > MAX_IPOPTLEN + sizeof(struct in_addr)) + n->m_len += off; + if (n->m_len > sizeof(*p)) { + bad: + m_freem(n); return (EINVAL); - *pcbopt = m_copym(m, 0, M_COPYALL, M_NOWAIT); - if (*pcbopt == NULL) - return (ENOBUFS); + } + *pcbopt = n; return (0); } |