shadow1ng/fscan · error

mssql: invalid prelogin option table

Error message

mssql: invalid prelogin option table

What it means

The prelogin option table walk ran past the end of the payload without ever encountering the 0xFF terminator. Every 5-byte option entry is examined; if the buffer ends before a terminator byte appears, the table is malformed.

Source

Thrown at plugins/services/mssql_raw.go:174

	if len(payload) == 0 {
		return fmt.Errorf("mssql: empty prelogin response")
	}

	fields, err := mssqlParsePreloginFields(payload)
	if err != nil {
		return err
	}
	if _, ok := fields[tdsPreloginEncryption]; !ok {
		return fmt.Errorf("mssql: prelogin response missing encryption field")
	}
	return nil
}

func mssqlParsePreloginFields(payload []byte) (map[byte][]byte, error) {
	fields := make(map[byte][]byte)
	for pos := 0; ; pos += 5 {
		if pos >= len(payload) {
			return nil, fmt.Errorf("mssql: invalid prelogin option table")
		}
		token := payload[pos]
		if token == tdsPreloginTerminator {
			return fields, nil
		}
		if pos+5 > len(payload) {
			return nil, fmt.Errorf("mssql: truncated prelogin option")
		}
		offset := int(binary.BigEndian.Uint16(payload[pos+1 : pos+3]))
		length := int(binary.BigEndian.Uint16(payload[pos+3 : pos+5]))
		if offset < 0 || length < 0 || offset+length > len(payload) {
			return nil, fmt.Errorf("mssql: invalid prelogin option bounds")
		}
		fields[token] = payload[offset : offset+length]
	}
}

func mssqlSendLogin7(w io.Writer, host, username, password string) error {

View on GitHub (pinned to 95cc12e753)

Solutions

  1. Re-run the connection — transient truncation may disappear; if persistent, capture packets to find where bytes are lost.
  2. Verify the endpoint speaks TDS (test with sqlcmd).
  3. Check MTU/fragmentation issues or TLS termination proxies that mangle payloads.
  4. If parsing third-party TDS traffic, validate the option table length equals number-of-options*5+1.
Defensive patterns

Strategy: try-catch

Validate before calling

// Sanity-check the response shape client-side after mssqlReadMessage:
if len(payload) < 6 || (len(payload)-1)%5 != 0 {
    // option table cannot be well-formed; skip parsing
}

Type guard

func looksLikeOptionTable(payload []byte) bool {
    return len(payload) > 0 && (len(payload)-1)%5 == 0 && payload[len(payload)-1] != 0xff || len(payload) > 0
}

Try / catch

_, err := mssqlRawLogin(ctx, host, port, user, pass, timeout)
if err != nil && strings.Contains(err.Error(), "invalid prelogin option table") {
    // treat as protocol corruption: log payload hex, reconnect or mark host unhealthy
}

Prevention

When it happens

Trigger: mssqlParsePreloginFields iterates pos in steps of 5 over the payload and pos >= len(payload) before a 0xFF byte is read — payload length is not compatible with a valid (5*k+1)-byte option table.

Common situations: Truncated packets from flaky networks/middleboxes; a non-TDS service emitting garbage; bug in a TDS proxy reassembling prelogin responses.

Understand the failure class

Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.

Related errors


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