From c567883ff5915da6fc8f215b0f1625ba470c7fa1 Mon Sep 17 00:00:00 2001 From: Nils Weiss Date: Tue, 20 Oct 2026 08:42:18 +0200 Subject: [PATCH 1/4] automaton: raise on Windows WAIT_FAILED ctypes may return WAIT_FAILED as -1, which select_objects() treats as an input index and can report the last object ready. Raise the Windows error and close temporary Winsock events on failed waits. AI-Assisted: yes (GPT-6) --- scapy/automaton.py | 13 ++++++++++++- test/windows.uts | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 1 deletion(-) diff --git a/scapy/automaton.py b/scapy/automaton.py index 303db73a38d..9ef4a0aa325 100644 --- a/scapy/automaton.py +++ b/scapy/automaton.py @@ -139,7 +139,13 @@ def select_objects(inputs, remain): False, remainms ) - if res != 0xFFFFFFFF and res != 0x00000102: # Failed or Timeout + # ctypes' default signed c_int restype returns WAIT_FAILED as -1. + if res == -1 or res == 0xFFFFFFFF: + error = ctypes.WinError() + for evt in created: + ctypes.windll.ws2_32.WSACloseEvent(evt) + raise error + if res != 0x00000102: # Timeout results.add(inputs[res]) if len(events) > 1: # Now poll the others, if any @@ -148,6 +154,11 @@ def select_objects(inputs, remain): ctypes.c_void_p(evt), 0 # poll: don't wait ) + if res == -1 or res == 0xFFFFFFFF: + error = ctypes.WinError() + for created_evt in created: + ctypes.windll.ws2_32.WSACloseEvent(created_evt) + raise error if res == 0: results.add(inputs[i]) # Cleanup created events, if any diff --git a/test/windows.uts b/test/windows.uts index 394376be0c2..56e2f6f1eba 100644 --- a/test/windows.uts +++ b/test/windows.uts @@ -5,6 +5,7 @@ + Configuration = Imports +~ automaton from unittest import mock @@ -21,6 +22,51 @@ a = ObjectPipe() a.send("test") assert select_objects([a], 0) == [a] += Automaton - WaitFor* errors are raised +~ automaton + +import ctypes as _ctypes +import scapy.automaton as _automaton + +for failure, input_count, use_native_socket in [ + (-1, 1, True), + (0xFFFFFFFF, 2, False), + (0, 2, True)]: + inputs = [mock.Mock() for _ in range(input_count)] + for index, obj in enumerate(inputs): + obj.fileno.return_value = index + 10 + obj.__selectable_force_select__ = False + if use_native_socket: + inputs[0].__selectable_force_select__ = True + kernel32 = mock.Mock() + if input_count == 1: + kernel32.WaitForSingleObject.return_value = failure + elif use_native_socket: + kernel32.WaitForMultipleObjects.return_value = 0 + kernel32.WaitForSingleObject.return_value = -1 + else: + kernel32.WaitForMultipleObjects.return_value = failure + ws2_32 = mock.Mock() + ws2_32.WSACreateEvent.return_value = 100 + ws2_32.WSAEventSelect.return_value = 0 + windll = mock.Mock(kernel32=kernel32, ws2_32=ws2_32) + with mock.patch.object(_automaton, "WINDOWS", True): + with mock.patch.object(_automaton, "ctypes", _ctypes, create=True): + with mock.patch.object( + _ctypes, "windll", windll, create=True): + with mock.patch.object( + _ctypes, "WinError", + return_value=OSError("wait failed"), create=True + ) as win_error: + try: + select_objects(inputs, 0) + except OSError: + pass + else: + assert False, "WAIT_FAILED must raise an OSError" + win_error.assert_called_once_with() + if use_native_socket: + assert ws2_32.WSACloseEvent.call_count == 1 ############ ############ + Windows arch unit tests From 13837ede8bb40cb909ad436f7a8acd3851ffe03d Mon Sep 17 00:00:00 2001 From: Nils Weiss Date: Thu, 12 Nov 2026 20:56:21 +0100 Subject: [PATCH 2/4] automaton: centralize Windows event cleanup Close temporary Winsock events in a finally block, including when input setup fails. Review: https://github.com/secdev/scapy/pull/5238#issuecomment-6521096292 AI-Assisted: yes (GPT-6) --- scapy/automaton.py | 140 ++++++++++++++++++++++----------------------- test/windows.uts | 26 +++++++++ 2 files changed, 94 insertions(+), 72 deletions(-) diff --git a/scapy/automaton.py b/scapy/automaton.py index a702a0368a2..0cdd92cf3ba 100644 --- a/scapy/automaton.py +++ b/scapy/automaton.py @@ -95,80 +95,76 @@ def select_objects(inputs, remain): events_to_inputs = [] created = [] results = set() - for i in inputs: - fno = i.fileno() - if fno < 0: - # Special case: On Windows, we consider that an object that returns - # a negative fileno (impossible), is always readable. This is used - # in very few places but important (e.g. PcapReader), where we have - # no valid fileno (and will stop on EOFError). - results.add(i) - remain = 0 - continue - if getattr(i, "__selectable_force_select__", False): - # Native socket.socket object. We would normally use select.select. - evt = ctypes.windll.ws2_32.WSACreateEvent() - created.append(evt) - res = ctypes.windll.ws2_32.WSAEventSelect( - ctypes.c_void_p(fno), - evt, - FD_READ - ) - if res == 0: - # Was a socket - event = evt + try: + for i in inputs: + fno = i.fileno() + if fno < 0: + # Special case: On Windows, we consider that an object that returns + # a negative fileno (impossible), is always readable. This is used + # in very few places but important (e.g. PcapReader), where we have + # no valid fileno (and will stop on EOFError). + results.add(i) + remain = 0 + continue + if getattr(i, "__selectable_force_select__", False): + # Native socket.socket object. We would normally use select.select. + evt = ctypes.windll.ws2_32.WSACreateEvent() + created.append(evt) + res = ctypes.windll.ws2_32.WSAEventSelect( + ctypes.c_void_p(fno), + evt, + FD_READ + ) + if res == 0: + # Was a socket + event = evt + else: + # Fallback to normal event + event = fno else: - # Fallback to normal event event = fno - else: - event = fno - events.append(event) - events_to_inputs.append(i) - if events: - # 0xFFFFFFFF = INFINITE - remainms = int(remain * 1000 if remain is not None else 0xFFFFFFFF) - if len(events) == 1: - res = ctypes.windll.kernel32.WaitForSingleObject( - ctypes.c_void_p(events[0]), - remainms - ) - else: - # Sadly, the only way to emulate select() is to first check - # if any object is available using WaitForMultipleObjects - # then poll the others. - res = ctypes.windll.kernel32.WaitForMultipleObjects( - len(events), - (ctypes.c_void_p * len(events))( - *events - ), - False, - remainms - ) - # ctypes' default signed c_int restype returns WAIT_FAILED as -1. - if res == -1 or res == 0xFFFFFFFF: - error = ctypes.WinError() - for evt in created: - ctypes.windll.ws2_32.WSACloseEvent(evt) - raise error - if res != 0x00000102: # Timeout - results.add(events_to_inputs[res]) - if len(events) > 1: - # Now poll the others, if any - for i, evt in enumerate(events): - res = ctypes.windll.kernel32.WaitForSingleObject( - ctypes.c_void_p(evt), - 0 # poll: don't wait - ) - if res == -1 or res == 0xFFFFFFFF: - error = ctypes.WinError() - for created_evt in created: - ctypes.windll.ws2_32.WSACloseEvent(created_evt) - raise error - if res == 0: - results.add(events_to_inputs[i]) - # Cleanup created events, if any - for evt in created: - ctypes.windll.ws2_32.WSACloseEvent(evt) + events.append(event) + events_to_inputs.append(i) + if events: + # 0xFFFFFFFF = INFINITE + remainms = int(remain * 1000 if remain is not None else 0xFFFFFFFF) + if len(events) == 1: + res = ctypes.windll.kernel32.WaitForSingleObject( + ctypes.c_void_p(events[0]), + remainms + ) + else: + # Sadly, the only way to emulate select() is to first check + # if any object is available using WaitForMultipleObjects + # then poll the others. + res = ctypes.windll.kernel32.WaitForMultipleObjects( + len(events), + (ctypes.c_void_p * len(events))( + *events + ), + False, + remainms + ) + # ctypes' default signed c_int restype returns WAIT_FAILED as -1. + if res == -1 or res == 0xFFFFFFFF: + raise ctypes.WinError() + if res != 0x00000102: # Timeout + results.add(events_to_inputs[res]) + if len(events) > 1: + # Now poll the others, if any + for i, evt in enumerate(events): + res = ctypes.windll.kernel32.WaitForSingleObject( + ctypes.c_void_p(evt), + 0 # poll: don't wait + ) + if res == -1 or res == 0xFFFFFFFF: + raise ctypes.WinError() + if res == 0: + results.add(events_to_inputs[i]) + finally: + # Cleanup created events, if any + for evt in created: + ctypes.windll.ws2_32.WSACloseEvent(evt) return list(results) diff --git a/test/windows.uts b/test/windows.uts index 04af091c5e2..3e050db96c3 100644 --- a/test/windows.uts +++ b/test/windows.uts @@ -95,6 +95,32 @@ for failure, input_count, use_native_socket in [ win_error.assert_called_once_with() if use_native_socket: assert ws2_32.WSACloseEvent.call_count == 1 + += Automaton - Created events are closed if input setup fails +~ automaton + +native_socket = mock.Mock() +native_socket.fileno.return_value = 10 +native_socket.__selectable_force_select__ = True +bad_input = mock.Mock() +setup_error = OSError("fileno failed") +bad_input.fileno.side_effect = setup_error +ws2_32 = mock.Mock() +ws2_32.WSACreateEvent.return_value = 100 +ws2_32.WSAEventSelect.return_value = 0 +windll = mock.Mock(ws2_32=ws2_32) +with mock.patch.object(_automaton, "WINDOWS", True): + with mock.patch.object(_automaton, "ctypes", _ctypes, create=True): + with mock.patch.object(_ctypes, "windll", windll, create=True): + try: + select_objects([native_socket, bad_input], 0) + except OSError as error: + assert error is setup_error + else: + assert False, "Input setup errors must propagate" + +ws2_32.WSACloseEvent.assert_called_once_with(100) + ############ ############ + Windows arch unit tests From d2ca5e3463d671125a37ce63698af3326f4c687e Mon Sep 17 00:00:00 2001 From: Gabriel <10530980+gpotter2@users.noreply.github.com> Date: Thu, 12 Nov 2026 22:18:17 +0100 Subject: [PATCH 3/4] Update test/windows.uts --- test/windows.uts | 71 ------------------------------------------------ 1 file changed, 71 deletions(-) diff --git a/test/windows.uts b/test/windows.uts index 3e050db96c3..0bbf2089ed8 100644 --- a/test/windows.uts +++ b/test/windows.uts @@ -50,77 +50,6 @@ if WINDOWS: readable.close() b.close() -= Automaton - WaitFor* errors are raised -~ automaton - -import ctypes as _ctypes -import scapy.automaton as _automaton - -for failure, input_count, use_native_socket in [ - (-1, 1, True), - (0xFFFFFFFF, 2, False), - (0, 2, True)]: - inputs = [mock.Mock() for _ in range(input_count)] - for index, obj in enumerate(inputs): - obj.fileno.return_value = index + 10 - obj.__selectable_force_select__ = False - if use_native_socket: - inputs[0].__selectable_force_select__ = True - kernel32 = mock.Mock() - if input_count == 1: - kernel32.WaitForSingleObject.return_value = failure - elif use_native_socket: - kernel32.WaitForMultipleObjects.return_value = 0 - kernel32.WaitForSingleObject.return_value = -1 - else: - kernel32.WaitForMultipleObjects.return_value = failure - ws2_32 = mock.Mock() - ws2_32.WSACreateEvent.return_value = 100 - ws2_32.WSAEventSelect.return_value = 0 - windll = mock.Mock(kernel32=kernel32, ws2_32=ws2_32) - with mock.patch.object(_automaton, "WINDOWS", True): - with mock.patch.object(_automaton, "ctypes", _ctypes, create=True): - with mock.patch.object( - _ctypes, "windll", windll, create=True): - with mock.patch.object( - _ctypes, "WinError", - return_value=OSError("wait failed"), create=True - ) as win_error: - try: - select_objects(inputs, 0) - except OSError: - pass - else: - assert False, "WAIT_FAILED must raise an OSError" - win_error.assert_called_once_with() - if use_native_socket: - assert ws2_32.WSACloseEvent.call_count == 1 - -= Automaton - Created events are closed if input setup fails -~ automaton - -native_socket = mock.Mock() -native_socket.fileno.return_value = 10 -native_socket.__selectable_force_select__ = True -bad_input = mock.Mock() -setup_error = OSError("fileno failed") -bad_input.fileno.side_effect = setup_error -ws2_32 = mock.Mock() -ws2_32.WSACreateEvent.return_value = 100 -ws2_32.WSAEventSelect.return_value = 0 -windll = mock.Mock(ws2_32=ws2_32) -with mock.patch.object(_automaton, "WINDOWS", True): - with mock.patch.object(_automaton, "ctypes", _ctypes, create=True): - with mock.patch.object(_ctypes, "windll", windll, create=True): - try: - select_objects([native_socket, bad_input], 0) - except OSError as error: - assert error is setup_error - else: - assert False, "Input setup errors must propagate" - -ws2_32.WSACloseEvent.assert_called_once_with(100) - ############ ############ + Windows arch unit tests From a238b08cc456ed898c1dad0fccd3d94a38409128 Mon Sep 17 00:00:00 2001 From: Gabriel <10530980+gpotter2@users.noreply.github.com> Date: Thu, 12 Nov 2026 22:18:46 +0100 Subject: [PATCH 4/4] Apply suggestions from code review --- test/windows.uts | 1 - 1 file changed, 1 deletion(-) diff --git a/test/windows.uts b/test/windows.uts index 0bbf2089ed8..4d8a232e65b 100644 --- a/test/windows.uts +++ b/test/windows.uts @@ -5,7 +5,6 @@ + Configuration = Imports -~ automaton from unittest import mock