shadow1ng/fscan · error
NODE_RDP_PROTOCOL_T125_MCS_INVALID_USER_ID
Error message
NODE_RDP_PROTOCOL_T125_MCS_INVALID_USER_ID
What it means
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.
Source
Thrown at libs/grdp/protocol/t125/mcs.go:506
glog.Debug("mcs recvChannelJoinConfirm", hex.EncodeToString(s))
r := bytes.NewReader(s)
option, err := core.ReadUInt8(r)
if err != nil {
c.Emit("error", err)
return
}
if !readMCSPDUHeader(option, CHANNEL_JOIN_CONFIRM) {
c.Emit("error", errors.New("NODE_RDP_PROTOCOL_T125_MCS_WAIT_CHANNEL_JOIN_CONFIRM"))
return
}
confirm, _ := per.ReadEnumerates(r)
userId, _ := per.ReadInteger16(r)
userId += MCS_USERCHANNEL_BASE
if c.userId != userId {
c.Emit("error", errors.New("NODE_RDP_PROTOCOL_T125_MCS_INVALID_USER_ID"))
return
}
channelId, _ := per.ReadInteger16(r)
if (confirm != 0) && (channelId == uint16(MCS_GLOBAL_CHANNEL_ID) || channelId == c.userId) {
c.Emit("error", errors.New("NODE_RDP_PROTOCOL_T125_MCS_SERVER_MUST_CONFIRM_STATIC_CHANNEL"))
return
}
glog.Debug("Confirm channelId:", channelId)
if confirm == 0 && c.serverNetworkData != nil {
for i := 0; i < int(c.serverNetworkData.ChannelCount); i++ {
if channelId == c.serverNetworkData.ChannelIdArray[i] {
var t MCSChannelInfo
t.ID = channelId
t.Name = string(c.clientNetworkData.ChannelDefArray[i].Name[:])
c.channels = append(c.channels, t)
}
}View on GitHub (pinned to 95cc12e753)
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.
Defensive patterns
Strategy: type-guard
Type guard
func validUserIdEcho(sent, received uint16) bool {
return sent == received // attach-user id must match confirm
} Try / catch
c.On("error", func(err error) {
if strings.Contains(err.Error(), "INVALID_USER_ID") {
log.Printf("user id mismatch: client=%d", c.userId)
}
}) Prevention
- 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
When it happens
Trigger: recvChannelJoinConfirm parses confirm/userId via per.ReadEnumerates and per.ReadInteger16, adds MCS_USERCHANNEL_BASE, and the result does not equal c.userId.
Common situations: 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.
Related errors
- NODE_RDP_PROTOCOL_T125_MCS_WAIT_CHANNEL_JOIN_CONFIRM
- NODE_RDP_PROTOCOL_T125_MCS_SERVER_MUST_CONFIRM_STATIC_CHANNE
- failed to get server public key
- Not a valid license packet
- bad BER tags
AI-assisted analysis of shadow1ng/fscan@95cc12e753 (2026-09-06).
Data as JSON: /api/errors/28abc4b564ab3a07.
Report an issue: GitHub.