shadow1ng/fscan · error
short fragment payload: %w
Error message
short fragment payload: %w
What it means
After validating the fragment size, readRPCFragment reads exactly `size` payload bytes with io.ReadFull. This error wraps any short read: the connection closed or delivered fewer bytes than the fragment header promised, so the RPC fragment is incomplete and cannot be parsed.
Source
Thrown at plugins/services/nfs.go:236
}
}
return exports
}
func readRPCFragment(conn interface {
Read([]byte) (int, error)
}, maxPayload int) ([]byte, error) {
var header [4]byte
if _, err := io.ReadFull(conn, header[:]); err != nil {
return nil, fmt.Errorf("short fragment header: %w", err)
}
size := int(binary.BigEndian.Uint32(header[:]) & 0x7fffffff)
if size <= 0 || size > maxPayload {
return nil, fmt.Errorf("invalid fragment size: %d", size)
}
payload := make([]byte, size)
if _, err := io.ReadFull(conn, payload); err != nil {
return nil, fmt.Errorf("short fragment payload: %w", err)
}
return payload, nil
}
func (p *NFSPlugin) buildRPCCall(xid, program, version, procedure uint32, data []byte) []byte {
authNone := []byte{0, 0, 0, 0, 0, 0, 0, 0} // AUTH_NONE flavor=0, len=0
buf := make([]byte, 0, 40+len(data))
buf = binary.BigEndian.AppendUint32(buf, xid)
buf = binary.BigEndian.AppendUint32(buf, 0) // CALL
buf = binary.BigEndian.AppendUint32(buf, 2) // RPC version
buf = binary.BigEndian.AppendUint32(buf, program)
buf = binary.BigEndian.AppendUint32(buf, version)
buf = binary.BigEndian.AppendUint32(buf, procedure)
buf = append(buf, authNone...) // credentials
buf = append(buf, authNone...) // verifier
buf = append(buf, data...)
return bufView on GitHub (pinned to 95cc12e753)
Solutions
- Retry the scan — transient connection resets are common on loaded servers
- Confirm the target actually speaks the RPC record-marking protocol on this port
- Check firewall/keepalive settings that may cut idle or slow TCP connections
- Add or increase the connection read deadline if slow servers truncate responses
Example fix
// before
if _, err := io.ReadFull(conn, payload); err != nil {
return nil, fmt.Errorf("short fragment payload: %w", err)
}
// after
if _, err := io.ReadFull(conn, payload); err != nil {
return nil, fmt.Errorf("short fragment payload: expected %d bytes: %w", size, err)
} Defensive patterns
Strategy: retry
Validate before calling
// pre-check: ensure connection is alive before reading
if deadline, ok := conn.(interface{ SetReadDeadline(time.Time) error }); ok {
_ = deadline.SetReadDeadline(time.Now().Add(5 * time.Second))
} Try / catch
payload, err := readRPCFragment(conn)
if err != nil {
if errors.Is(err, io.ErrUnexpectedEOF) || errors.Is(err, io.EOF) {
return retryWithNewConnection() // truncated stream: reconnect, do not reuse
}
return err
} Prevention
- Set reasonable read deadlines to fail fast on stalled servers
- Always retry fragmented RPC reads on a fresh connection — partial fragments poison the stream
- Check network path (firewalls, NAT timeouts) if truncation is recurring
When it happens
Trigger: rpcNullCall or getExports receives a header declaring N bytes but the peer closes the connection or stalls mid-fragment; TestNFSReadRPCFragmentRejectsInvalidSize also exercises this path indirectly.
Common situations: Server crashed or reset the connection mid-response; network timeout/firewall dropped the stream; target is not a real NFS server and closed after the first bytes; read deadline expired.
Related errors
AI-assisted analysis of shadow1ng/fscan@95cc12e753 (2026-09-06).
Data as JSON: /api/errors/25a0a58168595048.
Report an issue: GitHub.