{"record":{"id":"fa0b52090ae9d88b","repo":"fatedier/frp","slug":"zone-is-not-valid-utf-8","errorCode":null,"errorMessage":"zone is not valid UTF-8","messagePattern":"zone is not valid UTF-8","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/msg/udp_binary.go","lineNumber":215,"sourceCode":"\t}\n\tif len(body)-offset < ipLen+3 {\n\t\treturn nil, offset, fmt.Errorf(\"truncated address\")\n\t}\n\tip := append(net.IP(nil), body[offset:offset+ipLen]...)\n\toffset += ipLen\n\tport := binary.BigEndian.Uint16(body[offset : offset+2])\n\toffset += 2\n\tzoneLen := int(body[offset])\n\toffset++\n\tif len(body)-offset < zoneLen {\n\t\treturn nil, offset, fmt.Errorf(\"truncated zone\")\n\t}\n\tzoneBytes := body[offset : offset+zoneLen]\n\tif family == 4 && zoneLen != 0 {\n\t\treturn nil, offset, fmt.Errorf(\"IPv4 zone is forbidden\")\n\t}\n\tif !utf8.Valid(zoneBytes) {\n\t\treturn nil, offset, fmt.Errorf(\"zone is not valid UTF-8\")\n\t}\n\toffset += zoneLen\n\treturn &net.UDPAddr{IP: ip, Port: int(port), Zone: string(zoneBytes)}, offset, nil\n}\n\ntype V2BinaryUDPPacketReadWriter struct {\n\tconn *wire.Conn\n}\n\nfunc NewV2BinaryUDPPacketReadWriter(rw io.ReadWriter) *V2BinaryUDPPacketReadWriter {\n\treturn &V2BinaryUDPPacketReadWriter{conn: wire.NewConn(rw)}\n}\n\nfunc (rw *V2BinaryUDPPacketReadWriter) ReadMsg() (Message, error) {\n\tframe, err := rw.conn.ReadFrame()\n\tif err != nil {\n\t\treturn nil, err\n\t}","sourceCodeStart":197,"sourceCodeEnd":233,"githubUrl":"https://github.com/fatedier/frp/blob/6c8a8d0a97d03b44e9528d30b30c70cb9d61b405/pkg/msg/udp_binary.go#L197-L233","documentation":"The v2 binary UDP address decoder validates the zone bytes with utf8.Valid before converting them into net.UDPAddr.Zone. IPv6 zone identifiers are conventionally interface names, and Go's net package expects a valid string, so invalid UTF-8 is rejected as malformed input rather than propagated. This guards downstream string handling from binary garbage.","triggerScenarios":"DecodeUDPPacketBinary receives an address whose zone bytes fail utf8.Valid — fuzzed input, a desynchronized stream being decoded as an address, or a custom encoder stuffing raw bytes into the zone field.","commonSituations":"Stream desynchronization after an earlier truncation error (error 264) makes random bytes land in the zone field; fuzzing corpora; hostile peers sending garbage frames.","solutions":["Close and re-establish the connection — the frame stream is likely desynchronized or hostile.","Use the library's EncodeUDPPacketBinary for sending so zones are always valid UTF-8.","Verify both peers run matching frp versions.","When fuzzing, treat this as a handled rejection."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"if _, err := rw.ReadMsg(); err != nil {\n\tif strings.Contains(err.Error(), \"not valid UTF-8\") {\n\t\tconn.Close() // garbage or desynchronized stream\n\t}\n}","preventionTips":["Only send zones that are plain interface names.","Close the connection on decode validation failures; do not attempt resync."],"tags":["protocol","binary","udp","utf-8","validation"],"backgroundTag":null,"analyzedSha":"6c8a8d0a97d03b44e9528d30b30c70cb9d61b405","analyzedAt":"2026-08-15T06:53:27.215Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}