{"record":{"id":"06b2b56237bedce0","repo":"shadow1ng/fscan","slug":"short-fragment-header-w","errorCode":null,"errorMessage":"short fragment header: %w","messagePattern":"short fragment header: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"plugins/services/nfs.go","lineNumber":228,"sourceCode":"\t\t\t}\n\t\t\toffset += int(groupLen)\n\t\t\tif pad := (4 - groupLen%4) % 4; pad > 0 {\n\t\t\t\tif int(pad) > len(data)-offset {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t\toffset += int(pad)\n\t\t\t}\n\t\t}\n\t}\n\treturn exports\n}\n\nfunc readRPCFragment(conn interface {\n\tRead([]byte) (int, error)\n}, maxPayload int) ([]byte, error) {\n\tvar header [4]byte\n\tif _, err := io.ReadFull(conn, header[:]); err != nil {\n\t\treturn nil, fmt.Errorf(\"short fragment header: %w\", err)\n\t}\n\tsize := int(binary.BigEndian.Uint32(header[:]) & 0x7fffffff)\n\tif size <= 0 || size > maxPayload {\n\t\treturn nil, fmt.Errorf(\"invalid fragment size: %d\", size)\n\t}\n\tpayload := make([]byte, size)\n\tif _, err := io.ReadFull(conn, payload); err != nil {\n\t\treturn nil, fmt.Errorf(\"short fragment payload: %w\", err)\n\t}\n\treturn payload, nil\n}\n\nfunc (p *NFSPlugin) buildRPCCall(xid, program, version, procedure uint32, data []byte) []byte {\n\tauthNone := []byte{0, 0, 0, 0, 0, 0, 0, 0} // AUTH_NONE flavor=0, len=0\n\n\tbuf := make([]byte, 0, 40+len(data))\n\tbuf = binary.BigEndian.AppendUint32(buf, xid)\n\tbuf = binary.BigEndian.AppendUint32(buf, 0) // CALL","sourceCodeStart":210,"sourceCodeEnd":246,"githubUrl":"https://github.com/shadow1ng/fscan/blob/95cc12e753bf43de7004e5aef42a9ffba3934303/plugins/services/nfs.go#L210-L246","documentation":"RPC-over-TCP frames every message with a 4-byte record-mark header. readRPCFragment uses io.ReadFull to read those 4 bytes; if the connection delivers fewer (EOF, reset, timeout), it wraps the underlying error as \"short fragment header: %w\". This is the transport-level guard that keeps the parser from interpreting garbage as a frame size.","triggerScenarios":"Calling rpcNullCall, getExports, or TestNFSReadRPCFragmentRejectsInvalidSize when the peer closes the connection before sending a full 4-byte header — connection refused/reset, immediate EOF on a non-RPC port, or a read timeout.","commonSituations":"Scanning a host that has no NFS service (connection closed instantly); port filtered by a firewall that accepts then resets; TLS-wrapped port responding with binary handshake data then closing; network blip mid-scan.","solutions":["Unwrap the cause (%w) — EOF/reset/timeout tells you which fix applies","Check the host actually runs NFS/mountd (`rpcinfo -p <host>`) before scanning","Retry with backoff; transient resets are common on busy networks","Verify the port number: mountd's port is dynamic — query the portmapper instead of hardcoding"],"exampleFix":"// before\nconn, err := net.DialTimeout(\"tcp\", host+\":2049\", 2*time.Second)\n// after\nmountdPort, err := portmapperGetPort(host, 100005)\nif err != nil {\n    return fmt.Errorf(\"no mountd on %s: %w\", host, err)\n}\nconn, err := net.DialTimeout(\"tcp\", fmt.Sprintf(\"%s:%d\", host, mountdPort), 2*time.Second)","handlingStrategy":"retry","validationCode":"conn, err := net.DialTimeout(\"tcp\", addr, timeout)\nif err != nil { return fmt.Errorf(\"target unreachable: %w\", err) }","typeGuard":"null","tryCatchPattern":"payload, err := readRPCFragment(conn, 4096)\nif err != nil {\n    var nerr net.Error\n    if errors.As(err, &nerr) && nerr.Timeout() {\n        return retryWithBackoff(addr, 3)\n    }\n    return fmt.Errorf(\"rpc transport failed: %w\", err)\n}","preventionTips":["Dial with a timeout and check connectivity before RPC calls","Retry transient short reads with exponential backoff","Verify the port with rpcinfo/portmapper before sending RPC frames"],"tags":["network","rpc","tcp","io"],"backgroundTag":"connection-refused","analyzedSha":"95cc12e753bf43de7004e5aef42a9ffba3934303","analyzedAt":"2026-09-06T17:07:30.094Z","contentChangedAt":"2026-09-06T17:07:30.094Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}