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); var (lenBuf, p2) = WsGet(stream, buf, pos, max, 8);
pos = p2; 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; break;
} }
} }
@@ -264,11 +267,17 @@ public struct WsReadInfo
if (payload.Length > WsConstants.CloseStatusSize) if (payload.Length > WsConstants.CloseStatusSize)
r.CloseBody = Encoding.UTF8.GetString(payload.AsSpan(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) if (r.CloseStatus != WsConstants.CloseStatusNoStatusReceived)
{ {
var closeMsg = WsFrameWriter.CreateCloseMessage(r.CloseStatus, r.CloseBody ?? ""); var closeMsg = WsFrameWriter.CreateCloseMessage(r.CloseStatus, r.CloseBody ?? "");
r.PendingControlFrames.Add(new ControlFrameAction(WsConstants.CloseMessage, closeMsg)); r.PendingControlFrames.Add(new ControlFrameAction(WsConstants.CloseMessage, closeMsg));
} }
else
{
// Empty close frame — respond with empty close
r.PendingControlFrames.Add(new ControlFrameAction(WsConstants.CloseMessage, []));
}
break; break;
case WsConstants.PingMessage: case WsConstants.PingMessage:

View File

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