{"record":{"id":"6b1fbc7ec731c280","repo":"shadow1ng/fscan","slug":"mssql-invalid-prelogin-option-table","errorCode":null,"errorMessage":"mssql: invalid prelogin option table","messagePattern":"mssql: invalid prelogin option table","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"plugins/services/mssql_raw.go","lineNumber":174,"sourceCode":"\tif len(payload) == 0 {\n\t\treturn fmt.Errorf(\"mssql: empty prelogin response\")\n\t}\n\n\tfields, err := mssqlParsePreloginFields(payload)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif _, ok := fields[tdsPreloginEncryption]; !ok {\n\t\treturn fmt.Errorf(\"mssql: prelogin response missing encryption field\")\n\t}\n\treturn nil\n}\n\nfunc mssqlParsePreloginFields(payload []byte) (map[byte][]byte, error) {\n\tfields := make(map[byte][]byte)\n\tfor pos := 0; ; pos += 5 {\n\t\tif pos >= len(payload) {\n\t\t\treturn nil, fmt.Errorf(\"mssql: invalid prelogin option table\")\n\t\t}\n\t\ttoken := payload[pos]\n\t\tif token == tdsPreloginTerminator {\n\t\t\treturn fields, nil\n\t\t}\n\t\tif pos+5 > len(payload) {\n\t\t\treturn nil, fmt.Errorf(\"mssql: truncated prelogin option\")\n\t\t}\n\t\toffset := int(binary.BigEndian.Uint16(payload[pos+1 : pos+3]))\n\t\tlength := int(binary.BigEndian.Uint16(payload[pos+3 : pos+5]))\n\t\tif offset < 0 || length < 0 || offset+length > len(payload) {\n\t\t\treturn nil, fmt.Errorf(\"mssql: invalid prelogin option bounds\")\n\t\t}\n\t\tfields[token] = payload[offset : offset+length]\n\t}\n}\n\nfunc mssqlSendLogin7(w io.Writer, host, username, password string) error {","sourceCodeStart":156,"sourceCodeEnd":192,"githubUrl":"https://github.com/shadow1ng/fscan/blob/95cc12e753bf43de7004e5aef42a9ffba3934303/plugins/services/mssql_raw.go#L156-L192","documentation":"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.","triggerScenarios":"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.","commonSituations":"Truncated packets from flaky networks/middleboxes; a non-TDS service emitting garbage; bug in a TDS proxy reassembling prelogin responses.","solutions":["Re-run the connection — transient truncation may disappear; if persistent, capture packets to find where bytes are lost.","Verify the endpoint speaks TDS (test with sqlcmd).","Check MTU/fragmentation issues or TLS termination proxies that mangle payloads.","If parsing third-party TDS traffic, validate the option table length equals number-of-options*5+1."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"// Sanity-check the response shape client-side after mssqlReadMessage:\nif len(payload) < 6 || (len(payload)-1)%5 != 0 {\n    // option table cannot be well-formed; skip parsing\n}","typeGuard":"func looksLikeOptionTable(payload []byte) bool {\n    return len(payload) > 0 && (len(payload)-1)%5 == 0 && payload[len(payload)-1] != 0xff || len(payload) > 0\n}","tryCatchPattern":"_, err := mssqlRawLogin(ctx, host, port, user, pass, timeout)\nif err != nil && strings.Contains(err.Error(), \"invalid prelogin option table\") {\n    // treat as protocol corruption: log payload hex, reconnect or mark host unhealthy\n}","preventionTips":["Log the hex payload on parse failure to detect truncation patterns.","Check MTU/proxy settings when many hosts fail the same way.","Reconnect-and-retry once before flagging the server as broken."],"tags":["mssql","tds","prelogin","malformed-data"],"backgroundTag":"invalid-argument-format","analyzedSha":"95cc12e753bf43de7004e5aef42a9ffba3934303","analyzedAt":"2026-09-06T17:07:30.094Z","contentChangedAt":"2026-09-06T17:07:30.094Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}