From 6fb10c1c1a839b42b00b4982b07f89b06b302c3b Mon Sep 17 00:00:00 2001 From: JackDoan Date: Mon, 27 Jul 2026 15:41:57 -0500 Subject: [PATCH] udp: make parseRecvCmsg's length check overflow-safe --- udp/udp_linux.go | 3 ++- udp/udp_linux_fixes_test.go | 42 +++++++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/udp/udp_linux.go b/udp/udp_linux.go index d47e4b23..782becd9 100644 --- a/udp/udp_linux.go +++ b/udp/udp_linux.go @@ -371,7 +371,8 @@ func parseRecvCmsg(hdr *msghdr, wantGRO, wantECN bool) (gso int, ecn byte) { for off+unix.SizeofCmsghdr <= len(ctrl) { ch := (*unix.Cmsghdr)(unsafe.Pointer(&ctrl[off])) clen := int(ch.Len) - if clen < unix.SizeofCmsghdr || off+clen > len(ctrl) { + // Compare against the remaining bytes rather than off+clen + if clen < unix.SizeofCmsghdr || clen > len(ctrl)-off { return gso, ecn } dataOff := off + unix.CmsgLen(0) diff --git a/udp/udp_linux_fixes_test.go b/udp/udp_linux_fixes_test.go index 8fc5f92a..690da4f9 100644 --- a/udp/udp_linux_fixes_test.go +++ b/udp/udp_linux_fixes_test.go @@ -282,3 +282,45 @@ func TestWriteBatchUnreachableDestDeliversOthers(t *testing.T) { } } } + +// TestParseRecvCmsgCorruptLenNoPanic: a cmsg Len near max-int used to wrap +// off+clen negative, slip past the bounds check, and drive the walk offset +// negative -- a panic on the next ctrl[off]. The guard must compare Len +// against the remaining bytes instead. Also pins the plain truncated-Len +// cases (too small, larger than the buffer) to a clean early return. +func TestParseRecvCmsgCorruptLenNoPanic(t *testing.T) { + // First cmsg: a valid empty one so the walk advances past off=0 + // (off+clen can't overflow while off is still zero). + valid := buildCmsg(int32(unix.SOL_UDP), int32(unix.UDP_GRO), make([]byte, 4)) + + corrupt := func(lenVal int) []byte { + buf := make([]byte, len(valid)+unix.CmsgSpace(4)) + copy(buf, valid) + h := (*unix.Cmsghdr)(unsafe.Pointer(&buf[len(valid)])) + h.Level = int32(unix.IPPROTO_IP) + h.Type = int32(unix.IP_TOS) + setCmsgLen(h, lenVal) + return buf + } + + cases := []struct { + name string + ctrl []byte + }{ + {"len_near_max_int", corrupt(int(^uint(0)>>1) - 8)}, + {"len_too_small", corrupt(unix.SizeofCmsghdr - 1)}, + {"len_past_buffer", corrupt(1 << 20)}, + } + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + hdr := &msghdr{Control: &c.ctrl[0]} + setMsgControllen(hdr, len(c.ctrl)) + gso, ecn := parseRecvCmsg(hdr, true, true) + // The valid leading UDP_GRO cmsg (payload 0) must still parse; + // the corrupt trailer just ends the walk. + if gso != 0 || ecn != 0 { + t.Errorf("parseRecvCmsg = (%d, %#x), want (0, 0)", gso, ecn) + } + }) + } +}