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 buf

View on GitHub (pinned to 95cc12e753)

Solutions

  1. Retry the scan — transient connection resets are common on loaded servers
  2. Confirm the target actually speaks the RPC record-marking protocol on this port
  3. Check firewall/keepalive settings that may cut idle or slow TCP connections
  4. 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

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.