{"record":{"id":"28abc4b564ab3a07","repo":"shadow1ng/fscan","slug":"node-rdp-protocol-t125-mcs-invalid-user-id","errorCode":null,"errorMessage":"NODE_RDP_PROTOCOL_T125_MCS_INVALID_USER_ID","messagePattern":"NODE_RDP_PROTOCOL_T125_MCS_INVALID_USER_ID","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"libs/grdp/protocol/t125/mcs.go","lineNumber":506,"sourceCode":"\tglog.Debug(\"mcs recvChannelJoinConfirm\", hex.EncodeToString(s))\n\tr := bytes.NewReader(s)\n\toption, err := core.ReadUInt8(r)\n\tif err != nil {\n\t\tc.Emit(\"error\", err)\n\t\treturn\n\t}\n\n\tif !readMCSPDUHeader(option, CHANNEL_JOIN_CONFIRM) {\n\t\tc.Emit(\"error\", errors.New(\"NODE_RDP_PROTOCOL_T125_MCS_WAIT_CHANNEL_JOIN_CONFIRM\"))\n\t\treturn\n\t}\n\n\tconfirm, _ := per.ReadEnumerates(r)\n\tuserId, _ := per.ReadInteger16(r)\n\tuserId += MCS_USERCHANNEL_BASE\n\n\tif c.userId != userId {\n\t\tc.Emit(\"error\", errors.New(\"NODE_RDP_PROTOCOL_T125_MCS_INVALID_USER_ID\"))\n\t\treturn\n\t}\n\n\tchannelId, _ := per.ReadInteger16(r)\n\tif (confirm != 0) && (channelId == uint16(MCS_GLOBAL_CHANNEL_ID) || channelId == c.userId) {\n\t\tc.Emit(\"error\", errors.New(\"NODE_RDP_PROTOCOL_T125_MCS_SERVER_MUST_CONFIRM_STATIC_CHANNEL\"))\n\t\treturn\n\t}\n\tglog.Debug(\"Confirm channelId:\", channelId)\n\tif confirm == 0 && c.serverNetworkData != nil {\n\t\tfor i := 0; i < int(c.serverNetworkData.ChannelCount); i++ {\n\t\t\tif channelId == c.serverNetworkData.ChannelIdArray[i] {\n\t\t\t\tvar t MCSChannelInfo\n\t\t\t\tt.ID = channelId\n\t\t\t\tt.Name = string(c.clientNetworkData.ChannelDefArray[i].Name[:])\n\t\t\t\tc.channels = append(c.channels, t)\n\t\t\t}\n\t\t}","sourceCodeStart":488,"sourceCodeEnd":524,"githubUrl":"https://github.com/shadow1ng/fscan/blob/95cc12e753bf43de7004e5aef42a9ffba3934303/libs/grdp/protocol/t125/mcs.go#L488-L524","documentation":"In the MCS Channel Join Confirm handler, the client compares the userId echoed by the server against the user id it obtained during MCS Attach. If they differ, the T.125 handshake is out of sync or the server misbehaved, so the library emits this sentinel error. It indicates the server confirmed a join for a different client identity than expected.","triggerScenarios":"recvChannelJoinConfirm parses confirm/userId via per.ReadEnumerates and per.ReadInteger16, adds MCS_USERCHANNEL_BASE, and the result does not equal c.userId.","commonSituations":"Multiple concurrent connections racing on shared state; server implementations that number user channels differently; stream desync from an earlier malformed PDU shifting PER-decoded fields.","solutions":["Ensure one MCS connection object is not shared across concurrent sessions; recreate the client for each connection.","Log c.userId vs received userId to confirm whether the server is renumbering or the stream is desynced.","Re-check the attach-user flow that set c.userId (recvConnectResponse) for parsing errors that shifted subsequent reads.","Retry against a known-good RDP server (e.g. standard Windows) to rule out a nonconformant peer."],"exampleFix":null,"handlingStrategy":"type-guard","validationCode":null,"typeGuard":"func validUserIdEcho(sent, received uint16) bool {\n    return sent == received // attach-user id must match confirm\n}","tryCatchPattern":"c.On(\"error\", func(err error) {\n    if strings.Contains(err.Error(), \"INVALID_USER_ID\") {\n        log.Printf(\"user id mismatch: client=%d\", c.userId)\n    }\n})","preventionTips":["Never share an MCS client across goroutines or sessions","Verify the attach-user response parsing when adding protocol features","Compare echoed userId against expected value before proceeding to channel joins"],"tags":["rdp","mcs","protocol-violation","handshake"],"backgroundTag":"unexpected-response-shape","analyzedSha":"95cc12e753bf43de7004e5aef42a9ffba3934303","analyzedAt":"2026-09-06T17:07:30.094Z","contentChangedAt":"2026-09-06T17:07:30.094Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}