Skip to content

Reject invalid control frames when building - #305

Merged
auvipy merged 3 commits into
Lawouach:masterfrom
dajiaohuang:fix-control-frame-output-validation
Oct 4, 2026
Merged

auvipy merged 3 commits into
Lawouach:masterfrom
dajiaohuang:fix-control-frame-output-validation

Conversation

@dajiaohuang

@dajiaohuang dajiaohuang commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #304.

Reject fragmented or oversized outgoing Close, Ping and Pong frames. Incoming-invalid-frame tests now construct malformed peer bytes directly, so they continue testing the parser without relying on the sender to generate protocol violations. Add output-boundary regressions for fragmentation, 126-byte rejection and valid empty/125-byte masked and unmasked controls.

Validation: the two rejection regressions fail against unchanged upstream framing and pass with this fix; all 62 frame/stream tests pass on Windows Python 3.13. The full local suite has 128 passes and one unrelated Unix-socket test failure because this Windows interpreter has no AF_UNIX. The supported Ubuntu/Python CI matrix must be checked on the updated head.

Additional validation: the complete suite passes on Linux/Python 3.12 (129 passed). The current-head upstream Actions run requires maintainer approval: https://github.com/Lawouach/WebSocket-for-Python/actions/runs/37199966249.

@auvipy
auvipy self-requested a review October 3, 2026 03:40
@auvipy

auvipy commented Oct 3, 2026

Copy link
Copy Markdown
Collaborator

lets see if the CI turn green

@auvipy auvipy left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

test/test_websocket.py .................. [100%]

=================================== FAILURES ===================================
_________ WSFrameParserTest.test_fragmented_control_frame_is_too_large _________

self = <test.test_frame.WSFrameParserTest testMethod=test_fragmented_control_frame_is_too_large>

  def test_fragmented_control_frame_is_too_large(self):
  bytes = Frame(opcode=OPCODE_PING, body=b'*'*65536, fin=1).build()
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

test/test_frame.py:202:


self = <ws4py.framing.Frame object at 0x7fc1acf349e0>

  def build(self):
      """
      Builds a frame from the instance's attributes and returns
      its bytes representation.
      """
      header = b''
  
      if self.fin > 0x1:
          raise ValueError('FIN bit parameter must be 0 or 1')
  
      if 0x3 <= self.opcode <= 0x7 or 0xB <= self.opcode:
          raise ValueError('Opcode cannot be a reserved opcode')
  
      if self.opcode in (OPCODE_CLOSE, OPCODE_PING, OPCODE_PONG):
          if self.fin != 1:
              raise ValueError('Control frames cannot be fragmented')
          if self.payload_length > 125:
          raise FrameTooLargeException()

E ws4py.exc.FrameTooLargeException

ws4py/framing.py:82: FrameTooLargeException
__________________ WSStreamTest.test_too_large_close_message ___________________

self = <test.test_stream.WSStreamTest testMethod=test_too_large_close_message>

  def test_too_large_close_message(self):
      payload = struct.pack("!H", 1000) + b'*' * 330
      f = Frame(opcode=OPCODE_CLOSE, body=payload,
            fin=1, masking_key=os.urandom(4)).build()
                                                  ^^^^^^^

test/test_stream.py:132:


self = <ws4py.framing.Frame object at 0x7fc1a3daa0c0>

  def build(self):
      """
      Builds a frame from the instance's attributes and returns
      its bytes representation.
      """
      header = b''
  
      if self.fin > 0x1:
          raise ValueError('FIN bit parameter must be 0 or 1')
  
      if 0x3 <= self.opcode <= 0x7 or 0xB <= self.opcode:
          raise ValueError('Opcode cannot be a reserved opcode')
  
      if self.opcode in (OPCODE_CLOSE, OPCODE_PING, OPCODE_PONG):
          if self.fin != 1:
              raise ValueError('Control frames cannot be fragmented')
          if self.payload_length > 125:
          raise FrameTooLargeException()

E ws4py.exc.FrameTooLargeException

ws4py/framing.py:82: FrameTooLargeException
=========================== short test summary info ============================
FAILED test/test_frame.py::WSFrameParserTest::test_fragmented_control_frame_is_too_large
FAILED test/test_stream.py::WSStreamTest::test_too_large_close_message - ws4p...
======================== 2 failed, 124 passed in 4.41s =========================
py312: exit 1 (4.63 seconds) /home/runner/work/WebSocket-for-Python/WebSocket-for-Python> pytest pid=2132
py312: FAIL code 1 (12.57=setup[7.94]+cmd[4.63] seconds)
evaluation failed :( (12.58 seconds)

@dajiaohuang

Copy link
Copy Markdown
Contributor Author

The two failing fixtures now build malformed peer bytes directly, preserving their parser/stream checks without asking the validating sender to emit invalid controls. Added sender regressions for fragmentation, the 125/126-byte boundary, and masked/unmasked controls. The rejection cases fail against unchanged upstream framing; all 62 frame/stream tests pass with the fix. Full Windows validation has one separate AF_UNIX availability failure, recorded in the PR description; the updated Ubuntu matrix remains the acceptance check.

@auvipy
auvipy merged commit ab1221c into Lawouach:master Oct 4, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Reject invalid control frames when building output

2 participants