shadow1ng/fscan · error

invalid fragment size: %d

Error message

invalid fragment size: %d

What it means

readRPCFragment reads one Record Marking fragment from an NFS/RPC TCP stream. The 4-byte header's high bit is stripped (last-fragment flag) and the remaining 31 bits must yield a size in (0, maxPayload]. This error is thrown when the peer announces a fragment that is zero or larger than the maximum allowed payload, i.e. a malformed or hostile stream header.

Source

Thrown at plugins/services/nfs.go:232

					break
				}
				offset += int(pad)
			}
		}
	}
	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)

View on GitHub (pinned to 95cc12e753)

Solutions

  1. Verify the target is actually an NFS server exposing the RPC protocol on the expected port
  2. Check that no proxy/NAT device is mangling the TCP stream between scanner and server
  3. Capture traffic and compare the first 4 bytes against RFC 5531 record-marking framing
  4. If intentional, raise maxPayload only after confirming the peer is trusted

Example fix

// before
size := int(binary.BigEndian.Uint32(header[:]) & 0x7fffffff)
// after
size := int(binary.BigEndian.Uint32(header[:]) & 0x7fffffff)
if size <= 0 || size > maxPayload {
    return nil, fmt.Errorf("invalid fragment size: %d (max %d)", size, maxPayload) // clearer diagnostics
}
Defensive patterns

Strategy: validation

Validate before calling

func validFragmentSize(hdr [4]byte, maxPayload int) bool {
    size := int(binary.BigEndian.Uint32(hdr[:]) & 0x7fffffff)
    return size > 0 && size <= maxPayload
}

Type guard

func isValidFragment(size, maxPayload int) bool { return size > 0 && size <= maxPayload }

Try / catch

payload, err := readRPCFragment(conn)
if err != nil {
    if strings.Contains(err.Error(), "invalid fragment size") {
        // mark target as non-NFS or hostile; do not retry same connection
        return ErrNotNFS
    }
    return err
}

Prevention

When it happens

Trigger: Calling rpcNullCall or getExports against a server that sends a fragment header with size 0 (after masking) or exceeding maxPayload; test TestNFSReadRPCFragmentRejectsInvalidSize exercises this directly.

Common situations: Scanning a port that is not really NFS/mountd so random bytes are parsed as a fragment header; a middlebox/proxy corrupting the record-marking protocol; a malicious honeypot service returning oversized sizes.

Understand the failure class

Background: "value must be between 0 and 1" / "out of range" / "must not be negative" errors: fixing range-validation failures across open-source libraries — this error's family across 42 libraries.

Related errors


AI-assisted analysis of shadow1ng/fscan@95cc12e753 (2026-09-06). Data as JSON: /api/errors/7cc373bddf31725a. Report an issue: GitHub.