git: c114db294d5d - main - pf: Refactor the six ways to find TCP options into one new function.
- Go to: [ bottom of page ] [ top of archives ] [ this month ]
Date: Fri, 27 Jun 2025 15:16:13 UTC
The branch main has been updated by kp:
URL: https://cgit.FreeBSD.org/src/commit/?id=c114db294d5d0cf82eb010c09061330aa0fdc925
commit c114db294d5d0cf82eb010c09061330aa0fdc925
Author: Kristof Provost <kp@FreeBSD.org>
AuthorDate: 2025-06-24 11:27:56 +0000
Commit: Kristof Provost <kp@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 <procter@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);