{"record":{"id":"c0b32d4aabf84d5a","repo":"slackhq/nebula","slug":"gso-size-is-zero","errorCode":null,"errorMessage":"gso_size is zero","messagePattern":"gso_size is zero","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"overlay/tio/virtio/segment_linux.go","lineNumber":216,"sourceCode":"\tflags := uint16(pkt[csumStart+tcpFlagsOff])\n\n\tsum := uint32(checksum.Checksum(pkt[csumStart:headerLen], 0))\n\tsum += uint32(^uint16(seq >> 16))\n\tsum += uint32(^uint16(seq))\n\tsum += uint32(^flags)\n\tsum += uint32(^binary.BigEndian.Uint16(pkt[csumStart+tcpChecksumOff : csumStart+tcpChecksumOff+2]))\n\tsum = (sum & 0xffff) + (sum >> 16)\n\tsum = (sum & 0xffff) + (sum >> 16)\n\treturn sum\n}\n\n// SegmentTCP walks a TSO superpacket pkt, yielding each segment as a slice into pkt.\n// Per-segment plaintext is laid out by stamping a copy of the original L3+L4 header into pkt at offset i*gsoSize,\n// where it sits immediately before that segment's payload chunk in the original buffer.\n// pkt is consumed by this call and must not be inspected by the caller after the final yield.\nfunc SegmentTCP(pkt []byte, hdrLenU, csumStartU, gsoSizeU uint16, yield func(seg []byte) error) error {\n\tif gsoSizeU == 0 {\n\t\treturn fmt.Errorf(\"gso_size is zero\")\n\t}\n\tif csumStartU == 0 {\n\t\treturn fmt.Errorf(\"csum_start is zero\")\n\t}\n\n\theaderLen := int(hdrLenU)\n\tcsumStart := int(csumStartU)\n\tif headerLen > maxSegHdrLen {\n\t\treturn fmt.Errorf(\"header len %d exceeds max %d\", headerLen, maxSegHdrLen)\n\t}\n\tisV4 := pkt[0]>>4 == 4\n\n\ttcpHdrLen := int(pkt[csumStart+tcpDataOffOff]>>4) * 4\n\tpayLen := len(pkt) - headerLen\n\tgsoSize := int(gsoSizeU)\n\tnumSeg := segCount(payLen, gsoSize)\n\n\torigSeq := binary.BigEndian.Uint32(pkt[csumStart+tcpSeqOff : csumStart+tcpSeqOff+4])","sourceCodeStart":198,"sourceCodeEnd":234,"githubUrl":"https://github.com/slackhq/nebula/blob/dd8f660c0ac37903ec4080ca4d3c861ba9342ceb/overlay/tio/virtio/segment_linux.go#L198-L234","documentation":"SegmentTCP walks a TSO/GSO superpacket and needs a nonzero gsoSize to compute each segment boundary (offset i*gsoSize). A zero gso_size would produce infinite/invalid segmentation, so the function rejects it up front with this error.","triggerScenarios":"Calling SegmentTCP with gsoSizeU == 0 — typically because the virtio-net header's gso_size field was not populated (packet not actually GSO), or the caller read the field from the wrong struct offset and got 0.","commonSituations":"Passing a non-GSO (ordinary) packet to the segmentation path; reading gso_size from an unparsed or zeroed virtio-net header; a driver/kernel path that skipped TSO so gso_size stayed 0; copying only part of the virtio-net header.","solutions":["Check the virtio-net header's gso_size before calling; if it is 0, send the packet through unsegmented instead.","Verify you are reading gso_size at the correct offset for the header version (with/without num_buffers, little-endian).","Ensure the packet really is a GSO superpacket (mss/gso info present) before invoking SegmentTCP.","Guard the call: if gsoSizeU == 0 { return pkt unchanged }."],"exampleFix":"// before\nerr := seg.SegmentTCP(pkt, hdrLen, csumStart, gsoHdr.GsoSize, yield) // GsoSize may be 0\n// after\nif gsoHdr.GsoSize == 0 {\n    return yield(pkt) // not a GSO packet; handle as single segment\n}\nerr := seg.SegmentTCP(pkt, hdrLen, csumStart, gsoHdr.GsoSize, yield)","handlingStrategy":"validation","validationCode":"func canSegmentTCP(gsoSize, csumStart, hdrLen uint16) bool {\n    return gsoSize > 0 && csumStart > 0 && hdrLen <= 128\n}\n// if !canSegmentTCP(gsoHdr.GsoSize, gsoHdr.CsumStart, hdrLen) { handle as plain packet }","typeGuard":"func isGsoTCP(vhdr VirtioNetHdr) bool { return vhdr.GsoSize > 0 && vhdr.CsumStart > 0 }","tryCatchPattern":"err := segmenter.SegmentTCP(pkt, hdrLen, csumStart, gsoSize, yield)\nif err != nil {\n    if strings.Contains(err.Error(), \"gso_size is zero\") {\n        return yield(pkt) // fall back to unsegmented delivery\n    }\n    return err\n}","preventionTips":["Only call SegmentTCP when the virtio-net header reports GSO (gso_size > 0).","Validate gso_size right after parsing the virtio-net header, before any segmentation work.","Use the same struct/offsets as the kernel (check VIRTIO_F_VERSION_1 / endian) when reading gso_size.","Fall back to delivering the superpacket unsegmented when GSO metadata is absent."],"tags":["network","tso","gso","tcp","segmentation"],"backgroundTag":"gso-size-zero","analyzedSha":"dd8f660c0ac37903ec4080ca4d3c861ba9342ceb","analyzedAt":"2026-09-03T11:13:55.444Z","contentChangedAt":"2026-09-03T11:13:55.444Z","schemaVersion":2},"datasetVersion":"2026-09-10T17:17:09.494Z"}