{"record":{"id":"547dea020f412f1b","repo":"XTLS/Xray-core","slug":"negative-first-turn-prefix-length-d","errorCode":null,"errorMessage":"negative first turn prefix length: %d","messagePattern":"negative first turn prefix length: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"transport/internet/finalmask/xmc/padding.go","lineNumber":78,"sourceCode":"\t\tif localSends {\n\t\t\tif err := writePaddingTurnWithBuffer(writer, turn, prefixLength, time.Sleep, &writeBuffer); err != nil {\n\t\t\t\treturn fmt.Errorf(\"write padding turn %d: %w\", i, err)\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\t\tif err := readPaddingTurn(reader, turn, prefixLength); err != nil {\n\t\t\treturn fmt.Errorf(\"read padding turn %d: %w\", i, err)\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc validatePaddingSchedule(schedule []paddingTurn, firstTurnPrefixLength int) error {\n\tif len(schedule) == 0 {\n\t\treturn fmt.Errorf(\"empty padding schedule\")\n\t}\n\tif firstTurnPrefixLength < 0 {\n\t\treturn fmt.Errorf(\"negative first turn prefix length: %d\", firstTurnPrefixLength)\n\t}\n\tif firstTurnPrefixLength > 0 && schedule[0].direction != paddingClientToServer {\n\t\treturn fmt.Errorf(\"first prefixed padding turn is not client-to-server\")\n\t}\n\n\tfor i, turn := range schedule {\n\t\tif turn.direction != paddingClientToServer && turn.direction != paddingServerToClient {\n\t\t\treturn fmt.Errorf(\"padding turn %d has invalid direction: %d\", i, turn.direction)\n\t\t}\n\t\tif err := validatePaddingDelayRange(turn.startDelay); err != nil {\n\t\t\treturn fmt.Errorf(\"padding turn %d has an invalid start delay: %w\", i, err)\n\t\t}\n\t\tif err := validatePaddingDelayRange(turn.chunkDelay); err != nil {\n\t\t\treturn fmt.Errorf(\"padding turn %d has an invalid chunk delay: %w\", i, err)\n\t\t}\n\t\tif turn.writeChunkMinLength < 0 || turn.writeChunkLength < turn.writeChunkMinLength || turn.writeChunkLength > maxPaddingChunkLength {\n\t\t\treturn fmt.Errorf(\"padding turn %d has an invalid write chunk range: %d-%d\", i, turn.writeChunkMinLength, turn.writeChunkLength)\n\t\t}","sourceCodeStart":60,"sourceCodeEnd":96,"githubUrl":"https://github.com/XTLS/Xray-core/blob/7d214f8b094f75322fa3990f8aadad1c912f24f5/transport/internet/finalmask/xmc/padding.go#L60-L96","documentation":"validatePaddingSchedule rejected a negative firstTurnPrefixLength. The prefix length represents bytes of the real protocol record already sent before padding begins; it can be zero or positive but never negative. Only a caller bug can produce a negative value.","triggerScenarios":"Passing a negative prefix length to runPaddingSchedule; computing the prefix as 'bytes remaining' minus 'bytes sent' with the operands swapped so it goes negative.","commonSituations":"Forks that derive firstTurnPrefixLength arithmetically instead of tracking bytes actually written; test code passing -1 as a sentinel.","solutions":["Audit the call site that computes firstTurnPrefixLength and clamp/assert it is >= 0","Track bytes written with a counter rather than computing by subtraction","Add a unit test covering the zero-prefix and positive-prefix cases"],"exampleFix":"// before\nprefix := int(writtenHeader) - int(sentBytes)\n\n// after\nprefix := sentBytes - writtenHeader\nif prefix < 0 {\n    return fmt.Errorf(\"negative prefix: %d\", prefix)\n}","handlingStrategy":"validation","validationCode":"if prefix < 0 {\n    return fmt.Errorf(\"invalid prefix length %d\", prefix)\n}","typeGuard":"func validPrefixLength(n int) bool { return n >= 0 }","tryCatchPattern":null,"preventionTips":["Compute the prefix from a bytes-written counter, never by subtraction","Reject negative prefix values at the API boundary"],"tags":["go","padding","validation","internal","sanity-check"],"backgroundTag":null,"analyzedSha":"7d214f8b094f75322fa3990f8aadad1c912f24f5","analyzedAt":"2026-08-15T14:26:24.325Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}