{"record":{"id":"bd7c2b2115955085","repo":"shadow1ng/fscan","slug":"truncated-verifier","errorCode":null,"errorMessage":"truncated verifier","messagePattern":"truncated verifier","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"plugins/services/nfs.go","lineNumber":150,"sourceCode":"\t}\n\tmsgType := binary.BigEndian.Uint32(reply[4:8])\n\tif msgType != 1 { // REPLY\n\t\treturn nil, fmt.Errorf(\"not a reply\")\n\t}\n\treplyStatus := binary.BigEndian.Uint32(reply[8:12])\n\tif replyStatus != 0 { // MSG_ACCEPTED\n\t\treturn nil, fmt.Errorf(\"reply rejected\")\n\t}\n\n\t// Skip auth verifier\n\toffset := 12\n\tif offset+8 > len(reply) {\n\t\treturn nil, fmt.Errorf(\"truncated\")\n\t}\n\t// verifier flavor + length\n\tverifierLen := binary.BigEndian.Uint32(reply[offset+4 : offset+8])\n\tif verifierLen > uint32(len(reply)-offset-8) {\n\t\treturn nil, fmt.Errorf(\"truncated verifier\")\n\t}\n\toffset += 8 + int(verifierLen)\n\tif pad := (4 - verifierLen%4) % 4; pad > 0 {\n\t\tif int(pad) > len(reply)-offset {\n\t\t\treturn nil, fmt.Errorf(\"truncated verifier padding\")\n\t\t}\n\t\toffset += int(pad)\n\t}\n\n\t// Accept status\n\tif offset+4 > len(reply) {\n\t\treturn nil, fmt.Errorf(\"truncated\")\n\t}\n\tacceptStatus := binary.BigEndian.Uint32(reply[offset : offset+4])\n\tif acceptStatus != 0 { // SUCCESS\n\t\treturn nil, fmt.Errorf(\"accept status: %d\", acceptStatus)\n\t}\n\toffset += 4","sourceCodeStart":132,"sourceCodeEnd":168,"githubUrl":"https://github.com/shadow1ng/fscan/blob/95cc12e753bf43de7004e5aef42a9ffba3934303/plugins/services/nfs.go#L132-L168","documentation":"After reading the verifier length, getExports checks that verifierLen bytes of verifier data actually fit in the remaining reply. If the declared verifier length exceeds the bytes present, the reply is truncated and cannot be parsed. This protects the parser from out-of-bounds reads on malformed input.","triggerScenarios":"Calling Scan or TestNFSGetExportsHandlesVerifierPadding when a malicious/broken server declares a verifier longer than the data it sent, or the reply was cut off inside the verifier.","commonSituations":"Fuzzed or hostile target crafting an oversized verifier length; truncated transfer from a buggy middlebox; a non-RPC service returning arbitrary bytes that coincidentally pass earlier checks.","solutions":["Treat the target as untrusted/non-conformant and skip or flag it in scan output","Retry once to rule out transient truncation","Capture raw reply bytes to confirm whether the server is genuinely malformed","Keep maxPayload-sized bounds and report the server as failing the mount protocol"],"exampleFix":"// before\n// server declares verifierLen = 9000 but sends only 20 bytes\n// after\n// guard the caller: scan continues to next host and records the failure\nexports, err := getExports(conn, ...)\nif err != nil {\n    log.Printf(\"host %s: bad mountd reply: %v\", host, err)\n    return\n}","handlingStrategy":"validation","validationCode":"if len(reply) < 12 { return fmt.Errorf(\"reply too short to contain a verifier\") }","typeGuard":"func verifierFits(reply []byte) bool {\n    if len(reply) < 16 { return false }\n    vlen := binary.BigEndian.Uint32(reply[16:20])\n    return int(vlen) <= len(reply)-20\n}","tryCatchPattern":"if err != nil && strings.Contains(err.Error(), \"truncated verifier\") {\n    log.Printf(\"host sent malformed mountd reply; marking non-conformant\")\n    return nil\n}","preventionTips":["Treat untrusted scan targets as adversarial — never trust declared lengths","Flag hosts failing bounds checks instead of retrying indefinitely","Capture raw bytes when a host repeatedly fails to diagnose its stack"],"tags":["network","rpc","malformed","security"],"backgroundTag":"invalid-json-response","analyzedSha":"95cc12e753bf43de7004e5aef42a9ffba3934303","analyzedAt":"2026-09-06T17:07:30.094Z","contentChangedAt":"2026-09-06T17:07:30.094Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}