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

  1. Verify the target host:port is actually an NFS/mountd server (rpcinfo -p <host>)
  2. Confirm no proxy or firewall is truncating the TCP stream on port 2049/111
  3. Re-run the scan; a transient short read may succeed on retry
  4. 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

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.