shadow1ng/fscan · error
invalid reply
Error message
invalid reply
What it means
getExports performs an NFS EXPORTS (mount protocol) RPC over a record-marked TCP connection. After reading one reply fragment with readRPCFragment, it checks that at least 24 bytes came back — the minimum for an RPC reply header (XID, message type, reply status, auth verifier fields). A shorter response means the server (or something else on the wire) sent a non-conformant or partial reply, so the library rejects it rather than parsing garbage.
Source
Thrown at plugins/services/nfs.go:126
func (p *NFSPlugin) getExports(conn interface {
Read([]byte) (int, error)
Write([]byte) (int, error)
}) ([]string, error) {
// Sun RPC call: program=MOUNT(100005), version=3, procedure=EXPORT(5)
xid := uint32(0x12345678)
rpcCall := p.buildRPCCall(xid, 100005, 3, 5, nil)
rpcFragment := p.wrapRPCFragment(rpcCall)
if _, err := conn.Write(rpcFragment); err != nil {
return nil, err
}
reply, err := readRPCFragment(conn, 4096)
if err != nil {
return nil, err
}
if len(reply) < 24 {
return nil, fmt.Errorf("invalid reply")
}
replyXID := binary.BigEndian.Uint32(reply[0:4])
if replyXID != xid {
return nil, fmt.Errorf("xid mismatch")
}
msgType := binary.BigEndian.Uint32(reply[4:8])
if msgType != 1 { // REPLY
return nil, fmt.Errorf("not a reply")
}
replyStatus := binary.BigEndian.Uint32(reply[8:12])
if replyStatus != 0 { // MSG_ACCEPTED
return nil, fmt.Errorf("reply rejected")
}
// Skip auth verifier
offset := 12
if offset+8 > len(reply) {View on GitHub (pinned to 95cc12e753)
Solutions
- Verify the target host:port is actually an NFS/mountd server (rpcinfo -p <host>)
- Confirm no proxy or firewall is truncating the TCP stream on port 2049/111
- Re-run the scan; a transient short read may succeed on retry
- Check the server is not replying over a different protocol (e.g. TLS) that breaks the record-mark framing
Example fix
// before
reply, err := readRPCFragment(conn, 4096) // reply came from wrong service
// after
// ensure the connection targets the mountd/NFS port before calling getExports
conn, err := net.DialTimeout("tcp", net.JoinHostPort(host, "111"), timeout) Defensive patterns
Strategy: validation
Validate before calling
if err := rpcinfoCheck(host, 100005); err != nil { return fmt.Errorf("%s is not an NFS/mountd host: %w", host, err) } Type guard
func isPlausibleRPCReply(reply []byte) bool { return len(reply) >= 24 && binary.BigEndian.Uint32(reply[4:8]) == 1 } Try / catch
exports, err := getExports(conn, xid)
if err != nil {
if strings.Contains(err.Error(), "invalid reply") {
log.Printf("host did not return a valid RPC reply; skipping")
return nil
}
return err
} Prevention
- Validate the target runs NFS (rpcinfo) before scanning
- Use fresh connections per request to avoid stale bytes
- Check for intercepting proxies on the scan path
When it happens
Trigger: Calling Scan or TestNFSGetExportsHandlesVerifierPadding against an endpoint whose reply fragment is shorter than 24 bytes — e.g. an HTTP server on port 2049/111, a plaintext banner, or a truncated TCP response.
Common situations: Pointing the NFS scanner at a non-NFS service that echoes short responses; a firewall/proxy that truncates packets; a server that closes the connection mid-reply; a misconfigured target port.
Understand the failure class
Background: "invalid response format", "malformed payload", "missing data field": when an API returns 200 but the response shape is wrong — this error's family across 23 libraries.
Related errors
AI-assisted analysis of shadow1ng/fscan@95cc12e753 (2026-09-06).
Data as JSON: /api/errors/19b53c5e09a4b2c7.
Report an issue: GitHub.