{"record":{"id":"b1e917076e0180ff","repo":"slackhq/nebula","slug":"encoded-ips-should-be-in-pairs-an-odd-number-was","errorCode":null,"errorMessage":"encoded IPs should be in pairs, an odd number was found","messagePattern":"encoded IPs should be in pairs, an odd number was found","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cert/cert_v1.go","lineNumber":417,"sourceCode":"\n// unmarshalCertificateV1 will unmarshal a protobuf byte representation of a nebula cert\n// if the publicKey is provided here then it is not required to be present in `b`\nfunc unmarshalCertificateV1(b []byte, publicKey []byte) (*certificateV1, error) {\n\tif len(b) == 0 {\n\t\treturn nil, fmt.Errorf(\"nil byte array\")\n\t}\n\tvar rc RawNebulaCertificate\n\terr := proto.Unmarshal(b, &rc)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif rc.Details == nil {\n\t\treturn nil, fmt.Errorf(\"encoded Details was nil\")\n\t}\n\n\tif len(rc.Details.Ips)%2 != 0 {\n\t\treturn nil, fmt.Errorf(\"encoded IPs should be in pairs, an odd number was found\")\n\t}\n\n\tif len(rc.Details.Subnets)%2 != 0 {\n\t\treturn nil, fmt.Errorf(\"encoded Subnets should be in pairs, an odd number was found\")\n\t}\n\n\tnc := certificateV1{\n\t\tdetails: detailsV1{\n\t\t\tname:           rc.Details.Name,\n\t\t\tgroups:         make([]string, len(rc.Details.Groups)),\n\t\t\tnetworks:       make([]netip.Prefix, len(rc.Details.Ips)/2),\n\t\t\tunsafeNetworks: make([]netip.Prefix, len(rc.Details.Subnets)/2),\n\t\t\tnotBefore:      time.Unix(rc.Details.NotBefore, 0),\n\t\t\tnotAfter:       time.Unix(rc.Details.NotAfter, 0),\n\t\t\tpublicKey:      nil,\n\t\t\tisCA:           rc.Details.IsCA,\n\t\t\tcurve:          rc.Details.Curve,\n\t\t},","sourceCodeStart":399,"sourceCodeEnd":435,"githubUrl":"https://github.com/slackhq/nebula/blob/dd8f660c0ac37903ec4080ca4d3c861ba9342ceb/cert/cert_v1.go#L399-L435","documentation":"In v1 certificates, IPs are stored as a flat []uint32 where each IP is a (network, mask) pair. An odd number of entries means one half of a pair is missing, which the unmarshaler rejects rather than silently mis-pairing the data.","triggerScenarios":"unmarshalCertificateV1 receives a certificate whose rc.Details.Ips has odd length — produced by appending a lone IP without its mask, corrupting the array, or writing pairs incorrectly with a custom marshaller.","commonSituations":"Hand-editing or post-processing RawNebulaCertificate.Details.Ips; a bug in tooling that packs IPs; data corruption during storage; building test certificates incorrectly.","solutions":["Ensure Details.Ips always contains ip,mask pairs (even length) before marshaling","Regenerate or re-issue the certificate with the library's standard marshaling path","Check any custom tooling that manipulates the Ips array for off-by-one/dropped-element bugs","Compare the failing certificate's Ips length against a known-good certificate"],"exampleFix":"// before\nrc.Details.Ips = []uint32{ipToUint32(ip)} // odd length\n// after\nrc.Details.Ips = []uint32{ipToUint32(ip), maskToUint32(mask)} // paired","handlingStrategy":"validation","validationCode":"func ipsArePaired(ips []uint32) bool { return len(ips)%2 == 0 }\nif !ipsArePaired(rc.Details.Ips) {\n    return fmt.Errorf(\"Ips must be (ip,mask) pairs before marshaling\")\n}\nb, err := proto.Marshal(rc)","typeGuard":"func validIPPairCount(n int) bool { return n > 0 && n%2 == 0 }","tryCatchPattern":"c, err := unmarshalCertificateV1(b, nil)\nif err != nil {\n    if strings.Contains(err.Error(), \"IPs should be in pairs\") {\n        return nil, fmt.Errorf(\"corrupt certificate: odd Ips length; regenerate cert\")\n    }\n    return nil, err\n}","preventionTips":["Always append IP and mask together when building Details.Ips","Use helper functions that enforce pair semantics instead of raw uint32 slices","Round-trip (marshal/unmarshal) certificates in tests after any mutation","Treat odd-length arrays as corruption and re-issue rather than patching"],"tags":["certificate","protobuf","ips","validation"],"backgroundTag":"malformed-certificate","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"}