diff --git a/docs/CHANGELOG b/docs/CHANGELOG index 8f57a29a..adcdaacf 100644 --- a/docs/CHANGELOG +++ b/docs/CHANGELOG @@ -1,4 +1,14 @@ 07/23/2026 Version 4.5.5 + - SECURITY: fix heap buffer over-read in get_layer4_v6() (GHSA-jj65-mrgg-f5fx, CWE-125). + get_ipv6_next() (src/common/get.c) validates the header it is given, but the pointer it + returns is only checked with "ptr > end_ptr" - so it may be exactly end_ptr, or leave + fewer bytes than a tcpr_ipv6_ext_hdr_base needs. get_layer4_v6() then read ip_nh straight + off that pointer, one byte past the packet buffer, for a crafted IPv6 packet whose + extension header chain runs to the end of the captured data. Reachable from tcpprep, + tcprewrite and tcpreplay when reading an untrusted pcap. A bounds check is now applied at + the three sites that dereference such a pointer. This was reported in 2024 against 4.4.4 + and marked patched, but the reporter's proof-of-concept still reproduced on 4.5.4. + Reported by msxfXF. - SECURITY: fix stack buffer overflow in the fragroute rules-file parser (GHSA-777w-9599-w8g4, CWE-787). On a successful parse, mod_open() (src/fragroute/mod.c) accumulated a " -> -> ..." diagnostic of every parsed diff --git a/docs/CREDIT b/docs/CREDIT index 9dd1ea5f..6020b180 100644 --- a/docs/CREDIT +++ b/docs/CREDIT @@ -162,6 +162,10 @@ Gabriel Ganne - tcprewrite: fix vlan tag add silently truncating output when --enet-vlan-pri/-cfi omitted (#994) - tcpprep: fix buffer overflow in check_dst_port() on truncated L4 headers (#995) +msxfXF + - common: reported heap buffer over-read in get_layer4_v6() when walking IPv6 + extension headers (GHSA-jj65-mrgg-f5fx) + tinyb0y - fragroute: reported heap buffer overflow in tcp_opt module (GHSA-pfqg-243f-8q25) - fragroute: reported heap buffer overflow in ip6_opt module (GHSA-2wmf-4p77-784q) diff --git a/src/common/get.c b/src/common/get.c index bd248fc7..d625a125 100644 --- a/src/common/get.c +++ b/src/common/get.c @@ -624,6 +624,20 @@ get_layer4_v4(const ipv4_hdr_t *ip_hdr, const u_char *end_ptr) return ((void *)ptr); } +/* + * Is a complete extension-header base (ip_nh + ip_len) readable at hdr? + * + * get_ipv6_next() only guarantees that the pointer it returns is not past + * end_ptr - it may be exactly end_ptr, or leave fewer bytes than the base + * header needs. Any caller that dereferences that pointer has to check first, + * or it reads off the end of the packet buffer. + */ +static inline bool +ipv6_exthdr_fits(const struct tcpr_ipv6_ext_hdr_base *hdr, const u_char *end_ptr) +{ + return hdr != NULL && (const u_char *)hdr + sizeof(*hdr) <= end_ptr; +} + /** * returns a pointer to the layer 4 header which is just beyond the IPv6 header * and any extension headers or NULL when there is none as in the case of @@ -662,7 +676,7 @@ get_layer4_v6(const ipv6_hdr_t *ip6_hdr, const u_char *end_ptr) case TCPR_IPV6_NH_HBH: dbgx(3, "Going deeper due to extension header 0x%02X", proto); exthdr = get_ipv6_next(next, end_ptr); - if (exthdr == NULL) { + if (!ipv6_exthdr_fits(exthdr, end_ptr)) { next = NULL; done = true; break; @@ -678,7 +692,7 @@ get_layer4_v6(const ipv6_hdr_t *ip6_hdr, const u_char *end_ptr) // next points to l4 data dbgx(3, "Go deeper due to fragment extension header 0x%02X", proto); exthdr = get_ipv6_next(next, end_ptr); - if ((exthdr == NULL) || ((u_char *)exthdr > end_ptr)) { + if (!ipv6_exthdr_fits(exthdr, end_ptr)) { next = NULL; done = true; break; @@ -701,6 +715,11 @@ get_layer4_v6(const ipv6_hdr_t *ip6_hdr, const u_char *end_ptr) */ default: if (proto != ip6_hdr->ip_nh && next) { + /* reading next->ip_len needs the base header to be present */ + if (!ipv6_exthdr_fits(next, end_ptr)) { + return NULL; + } + dbgx(3, "Returning byte offset of this ext header: %u", IPV6_EXTLEN_TO_BYTES(next->ip_len)); next = (void *)((u_char *)next + IPV6_EXTLEN_TO_BYTES(next->ip_len)); if ((u_char*)next > end_ptr)