fix: review fixes for WsReadInfo and WsUpgrade

- WsReadInfo: validate 64-bit frame payload length against maxPayload
  before casting to int (prevents overflow/memory exhaustion)
- WsReadInfo: always send close response per RFC 6455 Section 5.5.1,
  including for empty close frames
- WsUpgrade: restrict no-masking to leaf node connections only (browser
  clients must always mask frames)
This commit is contained in:
Joseph Doherty
2026-02-23 04:55:53 -05:00
parent 1c948b5b0f
commit fe304dfe01
2 changed files with 13 additions and 3 deletions

View File

@@ -176,7 +176,10 @@ public struct WsReadInfo
{
var (lenBuf, p2) = WsGet(stream, buf, pos, max, 8);
pos = p2;
r.Remaining = (int)BinaryPrimitives.ReadUInt64BigEndian(lenBuf);
var len64 = BinaryPrimitives.ReadUInt64BigEndian(lenBuf);
if (len64 > (ulong)maxPayload)
throw new InvalidOperationException($"frame payload length {len64} exceeds max payload {maxPayload}");
r.Remaining = (int)len64;
break;
}
}
@@ -264,11 +267,17 @@ public struct WsReadInfo
if (payload.Length > WsConstants.CloseStatusSize)
r.CloseBody = Encoding.UTF8.GetString(payload.AsSpan(WsConstants.CloseStatusSize));
}
// Per RFC 6455 Section 5.5.1, always send a close response
if (r.CloseStatus != WsConstants.CloseStatusNoStatusReceived)
{
var closeMsg = WsFrameWriter.CreateCloseMessage(r.CloseStatus, r.CloseBody ?? "");
r.PendingControlFrames.Add(new ControlFrameAction(WsConstants.CloseMessage, closeMsg));
}
else
{
// Empty close frame — respond with empty close
r.PendingControlFrames.Add(new ControlFrameAction(WsConstants.CloseMessage, []));
}
break;
case WsConstants.PingMessage:

View File

@@ -62,8 +62,9 @@ public static class WsUpgrade
ext.Contains(WsConstants.PmcExtension, StringComparison.OrdinalIgnoreCase);
}
// No-masking support
bool noMasking = headers.TryGetValue(WsConstants.NoMaskingHeader, out var nmVal) &&
// No-masking support (leaf nodes only — browser clients must always mask)
bool noMasking = kind == WsClientKind.Leaf &&
headers.TryGetValue(WsConstants.NoMaskingHeader, out var nmVal) &&
string.Equals(nmVal.Trim(), WsConstants.NoMaskingValue, StringComparison.OrdinalIgnoreCase);
// Browser detection