The branch main has been updated by kp: URL: https://cgit.FreeBSD.org/src/commit/?id=c114db294d5d0cf82eb010c09061330aa0fdc925
commit c114db294d5d0cf82eb010c09061330aa0fdc925 Author: Kristof Provost <k...@freebsd.org> AuthorDate: 2025-06-24 11:27:56 +0000 Commit: Kristof Provost <k...@freebsd.org> CommitDate: 2025-06-27 14:55:16 +0000 pf: Refactor the six ways to find TCP options into one new function. As a result: - MSS and WSCALE option candidates must now meet their min type length. - 'max-mss' is now more tolerant of malformed option lists. These changes were immaterial to the live traffic I've examined. OK sashan@ mpi@ Obtained from: OpenBSD, procter <proc...@openbsd.org>, 672fea2ccb Sponsored by: Rubicon Communications, LLC ("Netgate") --- sys/net/pfvar.h | 2 + sys/netpfil/pf/pf.c | 198 ++++++++++++++++++------------------- sys/netpfil/pf/pf_norm.c | 247 +++++++++++++++++++---------------------------- 3 files changed, 193 insertions(+), 254 deletions(-) diff --git a/sys/net/pfvar.h b/sys/net/pfvar.h index 9fc2a00dca77..71cb1862aabf 100644 --- a/sys/net/pfvar.h +++ b/sys/net/pfvar.h @@ -2601,6 +2601,8 @@ int pf_tag_packet(struct pf_pdesc *, int); int pf_addr_cmp(struct pf_addr *, struct pf_addr *, sa_family_t); +uint8_t* pf_find_tcpopt(u_int8_t *, u_int8_t *, size_t, + u_int8_t, u_int8_t); u_int16_t pf_get_mss(struct pf_pdesc *); u_int8_t pf_get_wscale(struct pf_pdesc *); struct mbuf *pf_build_tcp(const struct pf_krule *, sa_family_t, diff --git a/sys/netpfil/pf/pf.c b/sys/netpfil/pf/pf.c index 4ce2df2f0e31..f4d6f3dcb869 100644 --- a/sys/netpfil/pf/pf.c +++ b/sys/netpfil/pf/pf.c @@ -3933,55 +3933,42 @@ static int pf_modulate_sack(struct pf_pdesc *pd, struct tcphdr *th, struct pf_state_peer *dst) { - int hlen = (th->th_off << 2) - sizeof(*th), thoptlen = hlen; - u_int8_t opts[TCP_MAXOLEN], *opt = opts; - int copyback = 0, i, olen; - struct sackblk sack; - -#define TCPOLEN_SACKLEN (TCPOLEN_SACK + 2) - if (hlen < TCPOLEN_SACKLEN || hlen > MAX_TCPOPTLEN || - !pf_pull_hdr(pd->m, pd->off + sizeof(*th), opts, hlen, NULL, NULL, pd->af)) - return 0; - - while (hlen >= TCPOLEN_SACKLEN) { - size_t startoff = opt - opts; - olen = opt[1]; - switch (*opt) { - case TCPOPT_EOL: /* FALLTHROUGH */ - case TCPOPT_NOP: - opt++; - hlen--; - break; - case TCPOPT_SACK: - if (olen > hlen) - olen = hlen; - if (olen >= TCPOLEN_SACKLEN) { - for (i = 2; i + TCPOLEN_SACK <= olen; - i += TCPOLEN_SACK) { - memcpy(&sack, &opt[i], sizeof(sack)); - pf_patch_32(pd, - &sack.start, - htonl(ntohl(sack.start) - dst->seqdiff), - PF_ALGNMNT(startoff)); - pf_patch_32(pd, - &sack.end, - htonl(ntohl(sack.end) - dst->seqdiff), - PF_ALGNMNT(startoff)); - memcpy(&opt[i], &sack, sizeof(sack)); - copyback = 1; - } - } - /* FALLTHROUGH */ - default: - if (olen < 2) - olen = 2; - hlen -= olen; - opt += olen; + struct sackblk sack; + int copyback = 0, i; + int olen, optsoff; + uint8_t opts[MAX_TCPOPTLEN], *opt, *eoh; + + olen = (pd->hdr.tcp.th_off << 2) - sizeof(struct tcphdr); + optsoff = pd->off + sizeof(struct tcphdr); +#define TCPOLEN_MINSACK (TCPOLEN_SACK + 2) + if (olen < TCPOLEN_MINSACK || + !pf_pull_hdr(pd->m, optsoff, opts, olen, NULL, NULL, pd->af)) + return (0); + + eoh = opts + olen; + opt = opts; + while ((opt = pf_find_tcpopt(opt, opts, olen, + TCPOPT_SACK, TCPOLEN_MINSACK)) != NULL) + { + size_t safelen = MIN(opt[1], (eoh - opt)); + for (i = 2; i + TCPOLEN_SACK <= safelen; i += TCPOLEN_SACK) { + size_t startoff = (opt + i) - opts; + memcpy(&sack, &opt[i], sizeof(sack)); + pf_patch_32(pd, &sack.start, + htonl(ntohl(sack.start) - dst->seqdiff), + PF_ALGNMNT(startoff)); + pf_patch_32(pd, &sack.end, + htonl(ntohl(sack.end) - dst->seqdiff), + PF_ALGNMNT(startoff + sizeof(sack.start))); + memcpy(&opt[i], &sack, sizeof(sack)); } + copyback = 1; + opt += opt[1]; } if (copyback) - m_copyback(pd->m, pd->off + sizeof(*th), thoptlen, (caddr_t)opts); + m_copyback(pd->m, optsoff, olen, (caddr_t)opts); + return (copyback); } @@ -4965,83 +4952,86 @@ pf_socket_lookup(struct pf_pdesc *pd) return (1); } -u_int8_t -pf_get_wscale(struct pf_pdesc *pd) +/* post: r => (r[0] == type /\ r[1] >= min_typelen >= 2 "validity" + * /\ (eoh - r) >= min_typelen >= 2 "safety" ) + * + * warning: r + r[1] may exceed opts bounds for r[1] > min_typelen + */ +uint8_t* +pf_find_tcpopt(u_int8_t *opt, u_int8_t *opts, size_t hlen, u_int8_t type, + u_int8_t min_typelen) { - struct tcphdr *th = &pd->hdr.tcp; - int hlen; - u_int8_t hdr[60]; - u_int8_t *opt, optlen; - u_int8_t wscale = 0; + uint8_t *eoh = opts + hlen; - hlen = th->th_off << 2; /* hlen <= sizeof(hdr) */ - if (hlen <= sizeof(struct tcphdr)) - return (0); - if (!pf_pull_hdr(pd->m, pd->off, hdr, hlen, NULL, NULL, pd->af)) - return (0); - opt = hdr + sizeof(struct tcphdr); - hlen -= sizeof(struct tcphdr); - while (hlen >= 3) { + if (min_typelen < 2) + return (NULL); + + while ((eoh - opt) >= min_typelen) { switch (*opt) { case TCPOPT_EOL: + /* FALLTHROUGH - Workaround the failure of some + systems to NOP-pad their bzero'd option buffers, + producing spurious EOLs */ case TCPOPT_NOP: - ++opt; - --hlen; - break; - case TCPOPT_WINDOW: - wscale = opt[2]; - if (wscale > TCP_MAX_WINSHIFT) - wscale = TCP_MAX_WINSHIFT; - wscale |= PF_WSCALE_FLAG; - /* FALLTHROUGH */ + opt++; + continue; default: - optlen = opt[1]; - if (optlen < 2) - optlen = 2; - hlen -= optlen; - opt += optlen; - break; + if (opt[0] == type && + opt[1] >= min_typelen) + return (opt); } + + opt += MAX(opt[1], 2); /* evade infinite loops */ + } + + return (NULL); +} + +u_int8_t +pf_get_wscale(struct pf_pdesc *pd) +{ + int olen; + uint8_t opts[MAX_TCPOPTLEN], *opt; + uint8_t wscale = 0; + + olen = (pd->hdr.tcp.th_off << 2) - sizeof(struct tcphdr); + if (olen < TCPOLEN_WINDOW || !pf_pull_hdr(pd->m, + pd->off + sizeof(struct tcphdr), opts, olen, NULL, NULL, pd->af)) + return (0); + + opt = opts; + while ((opt = pf_find_tcpopt(opt, opts, olen, + TCPOPT_WINDOW, TCPOLEN_WINDOW)) != NULL) { + wscale = opt[2]; + wscale = MIN(wscale, TCP_MAX_WINSHIFT); + wscale |= PF_WSCALE_FLAG; + + opt += opt[1]; } + return (wscale); } u_int16_t pf_get_mss(struct pf_pdesc *pd) { - struct tcphdr *th = &pd->hdr.tcp; - int hlen; - u_int8_t hdr[60]; - u_int8_t *opt, optlen; + int olen; + uint8_t opts[MAX_TCPOPTLEN], *opt; u_int16_t mss = V_tcp_mssdflt; - hlen = th->th_off << 2; /* hlen <= sizeof(hdr) */ - if (hlen <= sizeof(struct tcphdr)) - return (0); - if (!pf_pull_hdr(pd->m, pd->off, hdr, hlen, NULL, NULL, pd->af)) + olen = (pd->hdr.tcp.th_off << 2) - sizeof(struct tcphdr); + if (olen < TCPOLEN_MAXSEG || !pf_pull_hdr(pd->m, + pd->off + sizeof(struct tcphdr), opts, olen, NULL, NULL, pd->af)) return (0); - opt = hdr + sizeof(struct tcphdr); - hlen -= sizeof(struct tcphdr); - while (hlen >= TCPOLEN_MAXSEG) { - switch (*opt) { - case TCPOPT_EOL: - case TCPOPT_NOP: - ++opt; - --hlen; - break; - case TCPOPT_MAXSEG: - memcpy(&mss, (opt + 2), 2); - mss = ntohs(mss); - /* FALLTHROUGH */ - default: - optlen = opt[1]; - if (optlen < 2) - optlen = 2; - hlen -= optlen; - opt += optlen; - break; - } + + opt = opts; + while ((opt = pf_find_tcpopt(opt, opts, olen, + TCPOPT_MAXSEG, TCPOLEN_MAXSEG)) != NULL) { + memcpy(&mss, (opt + 2), 2); + mss = ntohs(mss); + opt += opt[1]; } + return (mss); } diff --git a/sys/netpfil/pf/pf_norm.c b/sys/netpfil/pf/pf_norm.c index 94a436cdbfd6..369292ca365e 100644 --- a/sys/netpfil/pf/pf_norm.c +++ b/sys/netpfil/pf/pf_norm.c @@ -1499,8 +1499,8 @@ pf_normalize_tcp_init(struct pf_pdesc *pd, struct tcphdr *th, struct pf_state_peer *src) { u_int32_t tsval, tsecr; - u_int8_t hdr[60]; - u_int8_t *opt; + int olen; + uint8_t opts[MAX_TCPOPTLEN], *opt; KASSERT((src->scrub == NULL), ("pf_normalize_tcp_init: src->scrub != NULL")); @@ -1535,43 +1535,25 @@ pf_normalize_tcp_init(struct pf_pdesc *pd, struct tcphdr *th, if ((tcp_get_flags(th) & TH_SYN) == 0) return (0); - if (th->th_off > (sizeof(struct tcphdr) >> 2) && src->scrub && - pf_pull_hdr(pd->m, pd->off, hdr, th->th_off << 2, NULL, NULL, pd->af)) { - /* Diddle with TCP options */ - int hlen; - opt = hdr + sizeof(struct tcphdr); - hlen = (th->th_off << 2) - sizeof(struct tcphdr); - while (hlen >= TCPOLEN_TIMESTAMP) { - switch (*opt) { - case TCPOPT_EOL: /* FALLTHROUGH */ - case TCPOPT_NOP: - opt++; - hlen--; - break; - case TCPOPT_TIMESTAMP: - if (opt[1] >= TCPOLEN_TIMESTAMP) { - src->scrub->pfss_flags |= - PFSS_TIMESTAMP; - src->scrub->pfss_ts_mod = - arc4random(); - - /* note PFSS_PAWS not set yet */ - memcpy(&tsval, &opt[2], - sizeof(u_int32_t)); - memcpy(&tsecr, &opt[6], - sizeof(u_int32_t)); - src->scrub->pfss_tsval0 = ntohl(tsval); - src->scrub->pfss_tsval = ntohl(tsval); - src->scrub->pfss_tsecr = ntohl(tsecr); - getmicrouptime(&src->scrub->pfss_last); - } - /* FALLTHROUGH */ - default: - hlen -= MAX(opt[1], 2); - opt += MAX(opt[1], 2); - break; - } - } + olen = (th->th_off << 2) - sizeof(*th); + if (olen < TCPOLEN_TIMESTAMP || !pf_pull_hdr(pd->m, + pd->off + sizeof(*th), opts, olen, NULL, NULL, pd->af)) + return (0); + + opt = opts; + while ((opt = pf_find_tcpopt(opt, opts, olen, + TCPOPT_TIMESTAMP, TCPOLEN_TIMESTAMP)) != NULL) { + src->scrub->pfss_flags |= PFSS_TIMESTAMP; + src->scrub->pfss_ts_mod = arc4random(); + /* note PFSS_PAWS not set yet */ + memcpy(&tsval, &opt[2], sizeof(u_int32_t)); + memcpy(&tsecr, &opt[6], sizeof(u_int32_t)); + src->scrub->pfss_tsval0 = ntohl(tsval); + src->scrub->pfss_tsval = ntohl(tsval); + src->scrub->pfss_tsecr = ntohl(tsecr); + getmicrouptime(&src->scrub->pfss_last); + + opt += opt[1]; } return (0); @@ -1611,13 +1593,12 @@ pf_normalize_tcp_stateful(struct pf_pdesc *pd, struct pf_state_peer *src, struct pf_state_peer *dst, int *writeback) { struct timeval uptime; - u_int32_t tsval, tsecr; u_int tsval_from_last; - u_int8_t hdr[60]; - u_int8_t *opt; + uint32_t tsval, tsecr; int copyback = 0; int got_ts = 0; - size_t startoff; + int olen; + uint8_t opts[MAX_TCPOPTLEN], *opt; KASSERT((src->scrub || dst->scrub), ("%s: src->scrub && dst->scrub!", __func__)); @@ -1654,80 +1635,64 @@ pf_normalize_tcp_stateful(struct pf_pdesc *pd, unhandled_af(pd->af); } - if (th->th_off > (sizeof(struct tcphdr) >> 2) && + olen = (th->th_off << 2) - sizeof(*th); + + if (olen >= TCPOLEN_TIMESTAMP && ((src->scrub && (src->scrub->pfss_flags & PFSS_TIMESTAMP)) || (dst->scrub && (dst->scrub->pfss_flags & PFSS_TIMESTAMP))) && - pf_pull_hdr(pd->m, pd->off, hdr, th->th_off << 2, NULL, NULL, pd->af)) { - /* Diddle with TCP options */ - int hlen; - opt = hdr + sizeof(struct tcphdr); - hlen = (th->th_off << 2) - sizeof(struct tcphdr); - while (hlen >= TCPOLEN_TIMESTAMP) { - startoff = opt - (hdr + sizeof(struct tcphdr)); - switch (*opt) { - case TCPOPT_EOL: /* FALLTHROUGH */ - case TCPOPT_NOP: - opt++; - hlen--; - break; - case TCPOPT_TIMESTAMP: - /* Modulate the timestamps. Can be used for - * NAT detection, OS uptime determination or - * reboot detection. - */ - - if (got_ts) { - /* Huh? Multiple timestamps!? */ - if (V_pf_status.debug >= PF_DEBUG_MISC) { - DPFPRINTF(("multiple TS??\n")); - pf_print_state(state); - printf("\n"); - } - REASON_SET(reason, PFRES_TS); - return (PF_DROP); - } - if (opt[1] >= TCPOLEN_TIMESTAMP) { - memcpy(&tsval, &opt[2], - sizeof(u_int32_t)); - if (tsval && src->scrub && - (src->scrub->pfss_flags & - PFSS_TIMESTAMP)) { - tsval = ntohl(tsval); - copyback += pf_patch_32(pd, - &opt[2], - htonl(tsval + - src->scrub->pfss_ts_mod), - PF_ALGNMNT(startoff)); - } - - /* Modulate TS reply iff valid (!0) */ - memcpy(&tsecr, &opt[6], - sizeof(u_int32_t)); - if (tsecr && dst->scrub && - (dst->scrub->pfss_flags & - PFSS_TIMESTAMP)) { - tsecr = ntohl(tsecr) - - dst->scrub->pfss_ts_mod; - copyback += pf_patch_32(pd, - &opt[6], - htonl(tsecr), - PF_ALGNMNT(startoff)); - } - got_ts = 1; + pf_pull_hdr(pd->m, pd->off + sizeof(*th), opts, olen, NULL, NULL, pd->af)) { + /* Modulate the timestamps. Can be used for NAT detection, OS + * uptime determination or reboot detection. + */ + opt = opts; + while ((opt = pf_find_tcpopt(opt, opts, olen, + TCPOPT_TIMESTAMP, TCPOLEN_TIMESTAMP)) != NULL) { + uint8_t *ts = opt + 2; + uint8_t *tsr = opt + 6; + + if (got_ts) { + /* Huh? Multiple timestamps!? */ + if (V_pf_status.debug >= PF_DEBUG_MISC) { + printf("pf: %s: multiple TS??", __func__); + pf_print_state(state); + printf("\n"); } - /* FALLTHROUGH */ - default: - hlen -= MAX(opt[1], 2); - opt += MAX(opt[1], 2); - break; + REASON_SET(reason, PFRES_TS); + return (PF_DROP); } + + memcpy(&tsval, ts, sizeof(u_int32_t)); + memcpy(&tsecr, tsr, sizeof(u_int32_t)); + + /* modulate TS */ + if (tsval && src->scrub && + (src->scrub->pfss_flags & PFSS_TIMESTAMP)) { + /* tsval used further on */ + tsval = ntohl(tsval); + pf_patch_32(pd, + ts, htonl(tsval + src->scrub->pfss_ts_mod), + PF_ALGNMNT(ts - opts)); + copyback = 1; + } + + /* modulate TS reply if any (!0) */ + if (tsecr && dst->scrub && + (dst->scrub->pfss_flags & PFSS_TIMESTAMP)) { + /* tsecr used further on */ + tsecr = ntohl(tsecr) - dst->scrub->pfss_ts_mod; + pf_patch_32(pd, tsr, htonl(tsecr), + PF_ALGNMNT(tsr - opts)); + copyback = 1; + } + + got_ts = 1; + opt += opt[1]; } + if (copyback) { /* Copyback the options, caller copys back header */ *writeback = 1; - m_copyback(pd->m, pd->off + sizeof(struct tcphdr), - (th->th_off << 2) - sizeof(struct tcphdr), hdr + - sizeof(struct tcphdr)); + m_copyback(pd->m, pd->off + sizeof(*th), olen, opts); } } @@ -1999,50 +1964,32 @@ pf_normalize_tcp_stateful(struct pf_pdesc *pd, int pf_normalize_mss(struct pf_pdesc *pd) { - struct tcphdr *th = &pd->hdr.tcp; - u_int16_t *mss; - int thoff; - int opt, cnt, optlen = 0; - u_char opts[TCP_MAXOLEN]; - u_char *optp = opts; - size_t startoff; - - thoff = th->th_off << 2; - cnt = thoff - sizeof(struct tcphdr); - - if (cnt <= 0 || cnt > MAX_TCPOPTLEN || !pf_pull_hdr(pd->m, - pd->off + sizeof(*th), opts, cnt, NULL, NULL, pd->af)) + int olen, optsoff; + uint8_t opts[MAX_TCPOPTLEN], *opt; + + olen = (pd->hdr.tcp.th_off << 2) - sizeof(struct tcphdr); + optsoff = pd->off + sizeof(struct tcphdr); + if (olen < TCPOLEN_MAXSEG || + !pf_pull_hdr(pd->m, optsoff, opts, olen, NULL, NULL, pd->af)) return (0); - for (; cnt > 0; cnt -= optlen, optp += optlen) { - startoff = optp - opts; - opt = optp[0]; - if (opt == TCPOPT_EOL) - break; - if (opt == TCPOPT_NOP) - optlen = 1; - else { - if (cnt < 2) - break; - optlen = optp[1]; - if (optlen < 2 || optlen > cnt) - break; - } - switch (opt) { - case TCPOPT_MAXSEG: - mss = (u_int16_t *)(optp + 2); - if ((ntohs(*mss)) > pd->act.max_mss) { - pf_patch_16(pd, - mss, htons(pd->act.max_mss), - PF_ALGNMNT(startoff)); - m_copyback(pd->m, pd->off + sizeof(*th), - thoff - sizeof(*th), opts); - m_copyback(pd->m, pd->off, sizeof(*th), (caddr_t)th); - } - break; - default: - break; + opt = opts; + while ((opt = pf_find_tcpopt(opt, opts, olen, + TCPOPT_MAXSEG, TCPOLEN_MAXSEG)) != NULL) { + uint16_t mss; + uint8_t *mssp = opt + 2; + memcpy(&mss, mssp, sizeof(mss)); + if (ntohs(mss) > pd->act.max_mss) { + size_t mssoffopts = mssp - opts; + pf_patch_16(pd, &mss, + htons(pd->act.max_mss), PF_ALGNMNT(mssoffopts)); + m_copyback(pd->m, optsoff + mssoffopts, + sizeof(mss), (caddr_t)&mss); + m_copyback(pd->m, pd->off, + sizeof(struct tcphdr), (caddr_t)&pd->hdr.tcp); } + + opt += opt[1]; } return (0);