From 6f9ddb329b07099e392c78b6e4fef1c6252de0dc Mon Sep 17 00:00:00 2001 From: Kristof Provost Date: Sun, 26 Apr 2026 11:34:55 +0200 Subject: [PATCH] pf: improve SCTP validation As per RFC5061 "4.2. New Parameter Types" the add/delete IP address parameters (0xc001, 0xc002) may not be present in an INIT or INIT-ACK chunk. They are only allowed to be present in an ASCONF chunk. This also prevents unbounded recursion while parsing an SCTP packet. Approved by: so Security: FreeBSD-SA-26:14.pf Security: CVE-2026-7164 PR: 294799 Reported by: Igor Gabriel Sousa e Souza Sponsored by: Orange Business Services --- sys/netpfil/pf/pf.c | 16 +++++++++++----- tests/sys/netpfil/pf/sctp.py | 23 +++++++++++++++++++++++ 2 files changed, 34 insertions(+), 5 deletions(-) diff --git a/sys/netpfil/pf/pf.c b/sys/netpfil/pf/pf.c index 53f74271e26..27d03b68893 100644 --- a/sys/netpfil/pf/pf.c +++ b/sys/netpfil/pf/pf.c @@ -8353,7 +8353,7 @@ pf_sctp_multihome_delayed(struct pf_pdesc *pd, struct pfi_kkif *kif, } static int -pf_multihome_scan(int start, int len, struct pf_pdesc *pd, int op) +pf_multihome_scan(int start, int len, struct pf_pdesc *pd, int op, bool asconf) { int off = 0; struct pf_sctp_multihome_job *job; @@ -8458,6 +8458,9 @@ pf_multihome_scan(int start, int len, struct pf_pdesc *pd, int op) int ret; struct sctp_asconf_paramhdr ah; + if (asconf) + return (PF_DROP); + if (!pf_pull_hdr(pd->m, start + off, &ah, sizeof(ah), NULL, pd->af)) return (PF_DROP); @@ -8467,7 +8470,7 @@ pf_multihome_scan(int start, int len, struct pf_pdesc *pd, int op) ret = pf_multihome_scan(start + off + sizeof(ah), ntohs(ah.ph.param_length) - sizeof(ah), pd, - SCTP_ADD_IP_ADDRESS); + SCTP_ADD_IP_ADDRESS, true); if (ret != PF_PASS) return (ret); break; @@ -8476,6 +8479,9 @@ pf_multihome_scan(int start, int len, struct pf_pdesc *pd, int op) int ret; struct sctp_asconf_paramhdr ah; + if (asconf) + return (PF_DROP); + if (!pf_pull_hdr(pd->m, start + off, &ah, sizeof(ah), NULL, pd->af)) return (PF_DROP); @@ -8485,7 +8491,7 @@ pf_multihome_scan(int start, int len, struct pf_pdesc *pd, int op) ret = pf_multihome_scan(start + off + sizeof(ah), ntohs(ah.ph.param_length) - sizeof(ah), pd, - SCTP_DEL_IP_ADDRESS); + SCTP_DEL_IP_ADDRESS, true); if (ret != PF_PASS) return (ret); break; @@ -8506,7 +8512,7 @@ pf_multihome_scan_init(int start, int len, struct pf_pdesc *pd) start += sizeof(struct sctp_init_chunk); len -= sizeof(struct sctp_init_chunk); - return (pf_multihome_scan(start, len, pd, SCTP_ADD_IP_ADDRESS)); + return (pf_multihome_scan(start, len, pd, SCTP_ADD_IP_ADDRESS, false)); } int @@ -8515,7 +8521,7 @@ pf_multihome_scan_asconf(int start, int len, struct pf_pdesc *pd) start += sizeof(struct sctp_asconf_chunk); len -= sizeof(struct sctp_asconf_chunk); - return (pf_multihome_scan(start, len, pd, SCTP_ADD_IP_ADDRESS)); + return (pf_multihome_scan(start, len, pd, SCTP_ADD_IP_ADDRESS, false)); } int diff --git a/tests/sys/netpfil/pf/sctp.py b/tests/sys/netpfil/pf/sctp.py index 9f1d7dea4ef..722f7777a30 100644 --- a/tests/sys/netpfil/pf/sctp.py +++ b/tests/sys/netpfil/pf/sctp.py @@ -618,6 +618,29 @@ def test_initiate_tag_check(self): assert r.getlayer(sp.SCTPChunkInitAck) assert r.getlayer(sp.SCTP).tag == 42 + @pytest.mark.require_user("root") + @pytest.mark.require_progs(["scapy"]) + def test_too_many_add_ip(self): + import scapy.all as sp + DEPTH=90 + params=[] + for i in range(0, DEPTH): + ch = sp.SCTPChunkParamAddIPAddr(len=(DEPTH - i) * 8) + params.append(ch) + packet = sp.IP(src="192.0.2.1", dst="192.0.2.2") \ + / sp.SCTP(sport=4321, dport=1234) \ + / sp.SCTPChunkInit(init_tag=1, n_in_streams=1, n_out_streams=1, a_rwnd=1500, + params=params) + packet.show() + sp.hexdump(packet) + print("len %d" % len(packet)) + + r = sp.sr1(packet, timeout=3) + # We should not get a reply to this + if r: + r.show() + assert not r + class TestSCTPv6(VnetTestTemplate): REQUIRED_MODULES = ["sctp", "pf"] TOPOLOGY = {